From 35f78d269b9f275aecf1bd26eaec8c4f5c3ef17e Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 15 Sep 2026 17:51:24 +0200 Subject: test(notify): actually exercise rowstride, 3-channel and short-data paths The stride test was Height 1, so the second row and the padding beyond the used pixels were never reached and rowstride was untested despite the comment. It is now 2x2 with RowStride 12 and asserts a pixel in row 1, which fails if the stride is ignored (verified by mutation). Add the 3-channel opaque encode and the short-data rejection, both explicit requirements of the image-data contract. --- internal/notify/image_test.go | 53 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) (limited to 'internal/notify') diff --git a/internal/notify/image_test.go b/internal/notify/image_test.go index 2db4c75..7121f32 100644 --- a/internal/notify/image_test.go +++ b/internal/notify/image_test.go @@ -13,6 +13,8 @@ package notify import ( "bytes" + "image" + "image/color" "image/png" "testing" @@ -70,10 +72,23 @@ func TestImagePathFromHints(t *testing.T) { } } +// checkPixel asserts the RGBA of img at (x,y) matches want. +func checkPixel(t *testing.T, img image.Image, x, y int, want color.RGBA) { + t.Helper() + r, g, b, a := img.At(x, y).RGBA() + if r>>8 != uint32(want.R) || g>>8 != uint32(want.G) || b>>8 != uint32(want.B) || a>>8 != uint32(want.A) { + t.Fatalf("pixel (%d,%d) = %d %d %d %d, want %v", x, y, r>>8, g>>8, b>>8, a>>8, want) + } +} + func TestRawImagePNG(t *testing.T) { - // 2x1 RGBA on a rowstride wider than the data, to prove stride is honoured. - r := &RawImage{Width: 2, Height: 1, RowStride: 12, HasAlpha: true, BitsPerSample: 8, Channels: 4, - Data: []byte{255, 0, 0, 255, 0, 255, 0, 255, 9, 9, 9, 9}} + // 2x2 RGBA with a rowstride wider than Width*Channels. The second row is + // the point: if stride were ignored, row 1 would read the row-0 padding. + r := &RawImage{Width: 2, Height: 2, RowStride: 12, HasAlpha: true, BitsPerSample: 8, Channels: 4, + Data: []byte{ + 255, 0, 0, 255, 0, 255, 0, 255, 9, 9, 9, 9, // row 0: red, green, pad + 0, 0, 255, 255, 255, 255, 255, 255, 9, 9, 9, 9, // row 1: blue, white, pad + }} data, err := r.PNG() if err != nil { t.Fatalf("PNG: %v", err) @@ -82,13 +97,32 @@ func TestRawImagePNG(t *testing.T) { if err != nil { t.Fatalf("decode: %v", err) } - if img.Bounds().Dx() != 2 || img.Bounds().Dy() != 1 { + if img.Bounds().Dx() != 2 || img.Bounds().Dy() != 2 { t.Fatalf("bounds = %v", img.Bounds()) } - r0, g0, b0, a0 := img.At(0, 0).RGBA() - if r0>>8 != 255 || g0>>8 != 0 || b0>>8 != 0 || a0>>8 != 255 { - t.Fatalf("pixel 0 = %d %d %d %d", r0>>8, g0>>8, b0>>8, a0>>8) + checkPixel(t, img, 0, 0, color.RGBA{255, 0, 0, 255}) + checkPixel(t, img, 1, 0, color.RGBA{0, 255, 0, 255}) + checkPixel(t, img, 0, 1, color.RGBA{0, 0, 255, 255}) + checkPixel(t, img, 1, 1, color.RGBA{255, 255, 255, 255}) +} + +func TestRawImagePNGThreeChannel(t *testing.T) { + // 2x1 RGB, no alpha byte: PNG() must force opaque. + r := &RawImage{Width: 2, Height: 1, RowStride: 6, HasAlpha: false, BitsPerSample: 8, Channels: 3, + Data: []byte{255, 0, 0, 0, 255, 0}} + data, err := r.PNG() + if err != nil { + t.Fatalf("PNG: %v", err) } + img, err := png.Decode(bytes.NewReader(data)) + if err != nil { + t.Fatalf("decode: %v", err) + } + if img.Bounds().Dx() != 2 || img.Bounds().Dy() != 1 { + t.Fatalf("bounds = %v", img.Bounds()) + } + checkPixel(t, img, 0, 0, color.RGBA{255, 0, 0, 255}) + checkPixel(t, img, 1, 0, color.RGBA{0, 255, 0, 255}) } func TestRawImagePNGRejectsBadData(t *testing.T) { @@ -98,4 +132,9 @@ func TestRawImagePNGRejectsBadData(t *testing.T) { if _, err := (&RawImage{Width: 1, Height: 1, Channels: 2, BitsPerSample: 8}).PNG(); err == nil { t.Fatal("channels 2 must error") } + // 2x2 RGBA needs 16 bytes; the slice holds one row. + if _, err := (&RawImage{Width: 2, Height: 2, RowStride: 8, BitsPerSample: 8, Channels: 4, + Data: []byte{0, 0, 0, 255, 255, 255, 255, 255}}).PNG(); err == nil { + t.Fatal("short data must error") + } } -- cgit v1.2.3