package generator import ( "errors" "strings" "testing" ) func fillFrame(value byte) FrameRenderer { return func(dst []byte, _, _, _ int) error { for i := range dst { dst[i] = value } return nil } } func TestNewCPUGeneratorValidation(t *testing.T) { renderer := fillFrame(0) tests := []struct { name string width uint height uint renderer FrameRenderer wantErrSub string }{ {name: "zero width", height: 1, renderer: renderer, wantErrSub: "greater than zero"}, {name: "zero height", width: 6, renderer: renderer, wantErrSub: "greater than zero"}, {name: "odd width", width: 7, height: 1, renderer: renderer, wantErrSub: "even"}, {name: "nil renderer", width: 6, height: 1, wantErrSub: "renderer is nil"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := NewCPUGenerator(tc.width, tc.height, tc.renderer, nil) if err == nil || !strings.Contains(err.Error(), tc.wantErrSub) { t.Fatalf("error = %v, want substring %q", err, tc.wantErrSub) } }) } } func TestCPUGeneratorCopiesBaseAndRestoresBeforePatch(t *testing.T) { patch := func(dst []byte, _, _, frameIndex int) error { switch frameIndex { case 1: dst[0] = 0x22 case 2: dst[16] = 0x33 } return nil } g, err := NewCPUGenerator(12, 1, fillFrame(0x11), patch) if err != nil { t.Fatalf("NewCPUGenerator: %v", err) } frame := make([]byte, V210FrameSize(12, 1)) if err := g.GenerateFrame(frame, 1); err != nil { t.Fatalf("GenerateFrame(1): %v", err) } if frame[0] != 0x22 { t.Fatalf("frame 1 patch byte = %#x, want 0x22", frame[0]) } if err := g.GenerateFrame(frame, 2); err != nil { t.Fatalf("GenerateFrame(2): %v", err) } if frame[0] != 0x11 { t.Fatalf("old patch byte = %#x, want restored base 0x11", frame[0]) } if frame[16] != 0x33 { t.Fatalf("frame 2 patch byte = %#x, want 0x33", frame[16]) } } func TestPackV210Block(t *testing.T) { var pixels [6]YCbCr10 for i := range pixels { pixels[i] = YCbCr10{ Y: uint32(100 + i), Cb: uint32(200 + i), Cr: uint32(300 + i), } } frame := make([]byte, 16) packV210Block(frame, pixels) for x := range pixels { y, cb, cr := sampleV210(frame, 6, x, 0) chromaX := x - x%2 if y != pixels[x].Y || cb != pixels[chromaX].Cb || cr != pixels[chromaX].Cr { t.Fatalf("pixel %d: got %d/%d/%d, want %d/%d/%d", x, y, cb, cr, pixels[x].Y, pixels[chromaX].Cb, pixels[chromaX].Cr) } } } func TestPackV210BlockMasksComponents(t *testing.T) { pixel := YCbCr10{Y: 0xC01, Cb: 0xC02, Cr: 0xC03} frame := make([]byte, 16) packV210Block(frame, [6]YCbCr10{pixel, pixel, pixel, pixel, pixel, pixel}) y, cb, cr := sampleV210(frame, 6, 0, 0) if y != 1 || cb != 2 || cr != 3 { t.Fatalf("masked components = %d/%d/%d, want 1/2/3", y, cb, cr) } } func TestCPUGeneratorErrors(t *testing.T) { wantErr := errors.New("patch failed") g, err := NewCPUGenerator(6, 1, fillFrame(0), func([]byte, int, int, int) error { return wantErr }) if err != nil { t.Fatalf("NewCPUGenerator: %v", err) } if err := g.GenerateFrame(make([]byte, V210FrameSize(6, 1)-1), 0); err == nil || !strings.Contains(err.Error(), "too small") { t.Fatalf("small destination error = %v", err) } if err := g.GenerateFrame(make([]byte, V210FrameSize(6, 1)), 4); !errors.Is(err, wantErr) { t.Fatalf("patch error = %v, want wrapped %v", err, wantErr) } } func TestCPUGeneratorUsesPaddedV210Rows(t *testing.T) { const width, height = 100, 2 g, err := NewCPUPatternGenerator(width, height, "gray-ramp") if err != nil { t.Fatalf("NewCPUPatternGenerator: %v", err) } frame := make([]byte, V210FrameSize(width, height)) if err := g.GenerateFrame(frame, 0); err != nil { t.Fatalf("GenerateFrame: %v", err) } for y := 0; y < height; y++ { gotY, gotCb, gotCr := sampleV210(frame, width, 0, y) if gotY != 64 || gotCb != 512 || gotCr != 512 { t.Fatalf("row %d first pixel = %d/%d/%d, want 64/512/512", y, gotY, gotCb, gotCr) } padding := frame[y*V210LineSize(width)+V210ActiveLineSize(width) : (y+1)*V210LineSize(width)] for i, b := range padding { if b != 0 { t.Fatalf("row %d padding byte %d = %#x, want 0", y, i, b) } } } }