Files
2026-10-15 11:41:19 +08:00

87 lines
2.3 KiB
Go

package storage
import (
"bytes"
"image"
"image/color"
"image/gif"
"testing"
)
// buildGIF 生成指定帧数的动画 GIF。
func buildGIF(t *testing.T, frames int) []byte {
t.Helper()
g := &gif.GIF{}
for i := 0; i < frames; i++ {
pal := color.Palette{color.Black, color.White}
img := image.NewPaletted(image.Rect(0, 0, 4, 4), pal)
if i%2 == 1 {
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
img.SetColorIndex(x, y, 1)
}
}
}
g.Image = append(g.Image, img)
g.Delay = append(g.Delay, 10)
}
var b bytes.Buffer
if err := gif.EncodeAll(&b, g); err != nil {
t.Fatalf("encode gif: %v", err)
}
return b.Bytes()
}
func TestGIFFrameCount(t *testing.T) {
cases := []struct {
name string
frames int
}{
{"单帧", 1},
{"多帧", 12},
{"恰好等于上限", maxGIFFrames},
{"超过上限", maxGIFFrames + 1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
data := buildGIF(t, c.frames)
if got := gifFrameCount(data); got != c.frames {
t.Fatalf("gifFrameCount = %d, want %d", got, c.frames)
}
})
}
}
// 帧数统计必须能承受构造超大 GIF 的场景而不过度消耗内存。
func TestGIFFrameCount_RejectsGarbage(t *testing.T) {
cases := map[string][]byte{
"空数据": nil,
"非 GIF": []byte("not a gif at all, just some bytes"),
"仅有文件头": []byte("GIF89a"),
"截断的 LSD": []byte("GIF89a\x01\x00"),
"截断的色表": append([]byte("GIF89a"), bytes.Repeat([]byte{0xFF}, 8)...),
}
for name, data := range cases {
t.Run(name, func(t *testing.T) {
if n := gifFrameCount(data); n < 0 || n > 3 {
t.Fatalf("gifFrameCount = %d, out of sane range", n)
}
})
}
}
// 转换入口在帧数超限时必须返回错误(而不是把超大 GIF 丢给 cwebp)。
// 注意本机无 cwebp 时该测试只能覆盖「帧数检查发生在 LookPath 之后」这条路径,
// 故直接针对判定逻辑断言,不依赖外部二进制。
func TestGIFFrameLimitConstant(t *testing.T) {
if maxGIFFrames <= 0 {
t.Fatalf("maxGIFFrames = %d, must be positive", maxGIFFrames)
}
if gifFrameCount(buildGIF(t, maxGIFFrames+1)) <= maxGIFFrames {
t.Fatal("oversized GIF should be detected as over limit")
}
if gifFrameCount(buildGIF(t, 3)) > maxGIFFrames {
t.Fatal("small GIF should not be flagged as over limit")
}
}