You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/nrzled/nrzled_spi_test.go

666 lines
17 KiB
Go

// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package nrzled
import (
"bytes"
"errors"
"image"
"image/color"
"image/draw"
"io/ioutil"
"testing"
"periph.io/x/conn/v3/conntest"
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/spi"
"periph.io/x/conn/v3/spi/spitest"
)
// toRGB converts a slice of color.NRGBA to a byte stream of RGB pixels.
//
// Ignores alpha.
func toRGB(p []color.NRGBA) []byte {
b := make([]byte, 0, len(p)*3)
for _, c := range p {
b = append(b, c.R, c.G, c.B)
}
return b
}
func TestSPI_Empty(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 0, Channels: 3, Freq: 2500 * physic.KiloHertz}
s := spitest.Playback{
Playback: conntest.Playback{
Count: 1,
Ops: []conntest.IO{{W: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
},
}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
if got, expected := d.String(), "nrzled{recordraw}"; got != expected {
t.Fatalf("\nGot: %s\nWant: %s\n", got, expected)
}
if n, err := d.Write([]byte{}); n != 0 || err != nil {
t.Fatalf("%d %v", n, err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestSPI_fail(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 1, Channels: 3, Freq: 1 * physic.KiloHertz}
if _, err := NewSPI(spitest.NewRecordRaw(&buf), &o); err == nil {
t.Fatal("invalid Freq")
}
o = Opts{NumPixels: 1, Channels: 0, Freq: 2500 * physic.KiloHertz}
if _, err := NewSPI(spitest.NewRecordRaw(&buf), &o); err == nil {
t.Fatal("invalid Channels")
}
o = Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
if d, err := NewSPI(&configFail{}, &o); d != nil || err == nil {
t.Fatal("Connect() call have failed")
}
o = Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
if d, err := NewSPI(&limitLow{}, &o); d != nil || err == nil {
t.Fatal("MaxTxSize() is too small")
}
}
func TestSPI_Len(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 1, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
if n, err := d.Write([]byte{0}); n != 0 || err == nil {
t.Fatalf("%d %v", n, err)
}
if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected)
}
}
var writeTests = []struct {
name string
pixels []byte
want []byte
opts Opts
}{
{
name: "1 pixel to #FFFFFF",
pixels: toRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*FF*/ 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #FEFEFE",
pixels: toRGB([]color.NRGBA{
{0xFE, 0xFE, 0xFE, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*FE*/ 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #F0F0F0",
pixels: toRGB([]color.NRGBA{
{0xF0, 0xF0, 0xF0, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*F0*/ 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #808080",
pixels: toRGB([]color.NRGBA{
{0x80, 0x80, 0x80, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #80FF00",
pixels: toRGB([]color.NRGBA{
{0x80, 0xFF, 0x00, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*FF*/ 0xEE, 0xEE, 0xEE, 0xEE /*80*/, 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #800000",
pixels: toRGB([]color.NRGBA{
{0x80, 0x00, 0x00, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #008000",
pixels: toRGB([]color.NRGBA{
{0x00, 0x80, 0x00, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "1 pixel to #000080",
pixels: toRGB([]color.NRGBA{
{0x00, 0x00, 0x80, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "All at once",
pixels: toRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x00, 0x10, 0x00},
{0x00, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00},
}),
want: []byte{
/*NUL*/ 0x00, 0x00, 0x00,
/*FF*/ 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE,
/*FE*/ 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8,
/*F0*/ 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88,
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*10*/, 0x88, 0x8E, 0x88, 0x88,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*01*/, 0x88, 0x88, 0x88, 0x8E,
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
/*EOF*/ 0x00, 0x00, 0x00,
},
opts: Opts{
NumPixels: 10,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
}
func TestSPI_Writes(t *testing.T) {
for _, tt := range writeTests {
buf := bytes.Buffer{}
tt.opts.NumPixels = len(tt.pixels) / 3
d, err := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
if err != nil {
t.Fatal(err)
}
n, err := d.Write(tt.pixels)
if err != nil {
t.Fatal(err)
}
if n != len(tt.pixels) {
t.Fatalf("%s: Got %d bytes result, want %d", tt.name, n, len(tt.pixels)*3)
}
if got := buf.Bytes(); !bytes.Equal(got, tt.want) {
t.Logf("%s:\nGot: (%d)%#02v\nWant: (%d)%#02v\n", tt.name, len(got), got, len(tt.want), tt.want)
for i := range tt.want {
if got[i] != tt.want[i] {
t.Logf("(%d) Got: %#02v\tWant: %#02v\n", i, got[i], tt.want[i])
}
}
t.Fatal("expectation failure")
}
}
}
func TestSPI_Color(t *testing.T) {
if c := (&Dev{}).ColorModel(); c != color.NRGBAModel {
t.Fatal(c)
}
}
func TestSPI_Long(t *testing.T) {
buf := bytes.Buffer{}
colors := make([]color.NRGBA, 256)
o := Opts{NumPixels: len(colors), Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
if n, err := d.Write(toRGB(colors)); n != len(colors)*3 || err != nil {
t.Fatalf("%d %v", n, err)
}
expected := make([]byte, 3+12*o.NumPixels+3)
// leave three bytes of padding
for i := 3; i < 12*o.NumPixels; i += 12 {
//Each channel should be 0x00
for j := 0; j < 12; j++ {
expected[i+j] = 0x88
}
}
trailer := expected[3+12*o.NumPixels:]
for i := range trailer {
trailer[i] = 0x00
}
if !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected)
}
}
func TestSPI_Write_Long(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 1, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 0 || err == nil {
t.Fatal(n, err)
}
}
var drawTests = []struct {
name string
img image.Image
want []byte
opts Opts
}{
{
name: "Draw NRGBA",
img: func() image.Image {
im := image.NewNRGBA(image.Rect(0, 0, 4, 1))
for i := 0; i < 4; i++ {
im.Pix[4*i+0] = 0x00
im.Pix[4*i+1] = 0x80
im.Pix[4*i+2] = 0xFF
im.Pix[4*i+3] = 0
}
return im
}(),
want: func() []byte {
var b []byte
//padding
for i := 0; i < 3; i++ {
b = append(b, 0x00)
}
for i := 0; i < 4; i++ {
b = append(b, 0xE8, 0x88, 0x88, 0x88) //0x80
b = append(b, 0x88, 0x88, 0x88, 0x88) //0x00
b = append(b, 0xEE, 0xEE, 0xEE, 0xEE) //0xFF
}
//padding
for i := 0; i < 3; i++ {
b = append(b, 0x00)
}
return b
}(),
opts: Opts{
NumPixels: 4,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
{
name: "Draw Empty",
img: func() image.Image {
im := image.NewNRGBA(image.Rect(0, 0, 0, 0))
return im
}(),
want: func() []byte {
var b []byte
return b
}(),
opts: Opts{
NumPixels: 4,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
},
},
}
func TestSPI_Draws(t *testing.T) {
for _, tt := range drawTests {
buf := bytes.Buffer{}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
if err != nil {
t.Fatal(err)
}
if err := d.Draw(d.Bounds(), tt.img, image.Point{}); err != nil {
t.Fatalf("%s: %v", tt.name, err)
}
got := buf.Bytes()
if !bytes.Equal(got, tt.want) {
t.Logf("%s:\nGot: (%d)%#02v\nWant: (%d)%#02v\n", tt.name, len(got), got, len(tt.want), tt.want)
for i := range tt.want {
if got[i] != tt.want[i] {
t.Logf("(%d) Got: %#02v\tWant: %#02v\n", i, got[i], tt.want[i])
}
}
t.Fatal("expectation failure")
}
}
}
func TestSPI_Draw_DstEmpty(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
img := image.NewNRGBA(image.Rect(0, 0, 1, 1))
if err := d.Draw(image.Rect(0, 0, 0, 0), img, image.Point{}); err != nil {
t.Fatal(err)
}
}
func TestSPI_Halt(t *testing.T) {
s := spitest.Playback{
Playback: conntest.Playback{
Count: 1,
Ops: []conntest.IO{
{},
{W: []byte{
//Begin of frame
0x00, 0x00, 0x00,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
//End of frame
0x00, 0x00, 0x00,
}},
},
},
}
o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(&s, &o)
if err != nil {
t.Fatal(err)
}
if err := d.Halt(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestSPI_Halt_fail(t *testing.T) {
s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}}
o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(&s, &o)
if err != nil {
t.Fatal(err)
}
if d.Halt() == nil {
t.Fatal("expected failure")
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
type genColor func(int) [3]byte
func benchmarkSPIWrite(b *testing.B, o Opts, length int, f genColor) {
var pixels []byte
for i := 0; i < length; i++ {
c := f(i)
pixels = append(pixels, c[:]...)
}
o.NumPixels = length
b.ReportAllocs()
d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
if err != nil {
b.Fatal(err)
}
if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSPI_WriteWhite(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
}
func BenchmarkSPI_WriteDim(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x01, 0x01, 0x01} })
}
func BenchmarkSPI_WriteBlack(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x0, 0x0, 0x0} })
}
func genColorfulPixel(x int) [3]byte {
i := x * 3
return [3]byte{uint8(i) + uint8(i>>8),
uint8(i+1) + uint8(i+1>>8),
uint8(i+2) + uint8(i+2>>8),
}
}
func BenchmarkSPI_WriteColorful(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIWrite(b, o, 150, genColorfulPixel)
}
func BenchmarkSPI_WriteColorfulPassThru(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIWrite(b, o, 150, genColorfulPixel)
}
func BenchmarkSPI_WriteColorfulVariation(b *testing.B) {
// Continuously vary the lookup tables.
b.ReportAllocs()
pixels := [256 * 3]byte{}
for i := range pixels {
pixels[i] = uint8(i) + uint8(i>>8)
}
o := Opts{NumPixels: len(pixels) / 3, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
if err != nil {
b.Fatal(err)
}
if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
}
}
func benchmarkSPIDraw(b *testing.B, o Opts, img draw.Image, f genColor) {
for x := 0; x < img.Bounds().Dx(); x++ {
for y := 0; y < img.Bounds().Dy(); y++ {
pix := f(x)
c := color.NRGBA{R: pix[0], G: pix[1], B: pix[2], A: 255}
img.Set(x, y, c)
}
}
o.NumPixels = img.Bounds().Max.X
b.ReportAllocs()
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
r := d.Bounds()
p := image.Point{}
if err := d.Draw(r, img, p); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := d.Draw(r, img, p); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSPI_DrawNRGBAColorful(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
}
func BenchmarkSPI_DrawNRGBAWhite(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
}
func BenchmarkDrawRGBAColorful(b *testing.B) {
o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkSPIDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
}
func BenchmarkSPI_DrawSlowpath(b *testing.B) {
// Should be an image type that doesn't have a fast path
img := image.NewGray(image.Rect(0, 0, 150, 1))
for x := 0; x < img.Bounds().Dx(); x++ {
for y := 0; y < img.Bounds().Dy(); y++ {
pix := genColorfulPixel(x)
img.Set(x, y, color.Gray{pix[0]})
}
}
o := Opts{NumPixels: img.Bounds().Max.X, Channels: 3, Freq: 2500 * physic.KiloHertz}
b.ReportAllocs()
d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
if err != nil {
b.Fatal(err)
}
r := d.Bounds()
p := image.Point{}
if err := d.Draw(r, img, p); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := d.Draw(r, img, p); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSPI_Halt(b *testing.B) {
b.ReportAllocs()
o := &Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), o)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := d.Halt(); err != nil {
b.Fatal(err)
}
}
}
//
type configFail struct {
spitest.Record
}
func (c *configFail) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) {
return nil, errors.New("injected error")
}
type limitLow struct {
spitest.Record
}
func (c *limitLow) MaxTxSize() int {
return 1
}