1package ports
2
3import (
4 "errors"
5 "strings"
6 "testing"
7)
8
9func TestFrameValidation(t *testing.T) {
10 valid := Frame{Pixels: make([]byte, 24), Width: 2, Height: 2, Stride: 12, Format: XRGB8888}
11 if err := valid.Validate(); err != nil {
12 t.Fatal(err)
13 }
14 cases := []struct {
15 name string
16 frame Frame
17 wantMessage string
18 wantIs error
19 }{
20 {"zero width", Frame{Width: 0, Height: 1, Stride: 4, Pixels: make([]byte, 4)}, "invalid frame dimensions", nil},
21 {"negative height", Frame{Width: 1, Height: -1, Stride: 4, Pixels: make([]byte, 4)}, "invalid frame dimensions", nil},
22 {"oversized width", Frame{Width: MaxDimension + 1, Height: 1, Stride: (MaxDimension + 1) * 4, Pixels: make([]byte, (MaxDimension+1)*4)}, "invalid frame dimensions", nil},
23 {"oversized height", Frame{Width: 1, Height: MaxDimension + 1, Stride: 4, Pixels: make([]byte, (MaxDimension+1)*4)}, "invalid frame dimensions", nil},
24 {"short aligned stride", Frame{Width: 2, Height: 1, Stride: 4, Pixels: make([]byte, 4)}, "invalid frame stride", nil},
25 {"unaligned stride", Frame{Width: 1, Height: 1, Stride: 6, Pixels: make([]byte, 6)}, "invalid frame stride", nil},
26 // The error family proves storage bounds run before the short-slice check.
27 {"excessive stride", Frame{Width: 1, Height: 2, Stride: MaxFrameBytes/2 + 4}, "invalid frame stride", nil},
28 {"overflow stride", Frame{Width: 1, Height: 2, Stride: int(^uint(0)>>1) &^ 3}, "invalid frame stride", nil},
29 {"unsupported format", Frame{Width: 1, Height: 1, Stride: 4, Pixels: make([]byte, 4), Format: 42}, "", ErrUnsupportedFormat},
30 {"short storage", Frame{Width: 2, Height: 2, Stride: 12, Pixels: make([]byte, 19)}, "short frame storage", nil},
31 }
32 for _, tc := range cases {
33 t.Run(tc.name, func(t *testing.T) {
34 err := tc.frame.Validate()
35 if err == nil {
36 t.Fatal("accepted invalid frame")
37 }
38 if tc.wantIs != nil && !errors.Is(err, tc.wantIs) {
39 t.Fatalf("got %v, want %v", err, tc.wantIs)
40 }
41 if tc.wantMessage != "" && !strings.Contains(err.Error(), tc.wantMessage) {
42 t.Fatalf("got %v, want %s", err, tc.wantMessage)
43 }
44 })
45 }
46}
47
48func TestFrameRow(t *testing.T) {
49 f := Frame{Pixels: []byte{1, 2, 3, 4, 99, 99, 99, 99, 5, 6, 7, 8, 99, 99, 99, 99}, Width: 1, Height: 2, Stride: 8, Format: ARGB8888}
50 if err := f.Validate(); err != nil {
51 t.Fatal(err)
52 }
53 if got := f.Row(0); len(got) != 4 || got[0] != 1 {
54 t.Fatalf("row: %v", got)
55 }
56 if got := f.Row(1); len(got) != 4 || got[0] != 5 {
57 t.Fatalf("row: %v", got)
58 }
59}
60
61func TestFrameAllocations(t *testing.T) {
62 f := Frame{Pixels: make([]byte, 16), Width: 2, Height: 2, Stride: 8, Format: XRGB8888}
63 if allocs := testing.AllocsPerRun(1000, func() {
64 if f.Validate() != nil {
65 panic("invalid test frame")
66 }
67 _ = f.Row(0)
68 }); allocs != 0 {
69 t.Fatalf("frame view: %g allocations", allocs)
70 }
71}
72
73func BenchmarkFrameRows(b *testing.B) {
74 f := Frame{Pixels: make([]byte, 1920*1080*4), Width: 1920, Height: 1080, Stride: 1920 * 4, Format: XRGB8888}
75 b.ReportAllocs()
76 b.ResetTimer()
77 for b.Loop() {
78 if err := f.Validate(); err != nil {
79 b.Fatal(err)
80 }
81 for y := 0; y < f.Height; y++ {
82 _ = f.Row(y)
83 }
84 }
85}