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

648 lines
16 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}}},
},
}
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{
/*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{
/*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{
/*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{
/*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{
/*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{
/*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{
/*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{
/*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{
/*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, 12*o.NumPixels+3)
for i := 0; i < 12*o.NumPixels; i += 12 {
//Each channel should be 0x00
for j := 0; j < 12; j++ {
expected[i+j] = 0x88
}
}
trailer := expected[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
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
}
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{
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
}