// fileread_realimage_test.go -- 用真实图片文件做集成测试. // // 测试数据来自 golang.org/x/image 的 testdata 目录(已在模块缓存中), // 覆盖多种真实压缩格式,比合成数据更能暴露兼容性问题. package builtin import ( "bytes" "image" "os" "path/filepath" "runtime" "testing" ) // xImageTestdata 返回 golang.org/x/image testdata 目录路径. func xImageTestdata(t *testing.T) string { t.Helper() // 从模块缓存中找 gopath := os.Getenv("GOPATH") if gopath == "" { // 默认 GOPATH home, _ := os.UserHomeDir() gopath = filepath.Join(home, "go") } dir := filepath.Join(gopath, "pkg/mod/golang.org/x/image@v0.38.0/testdata") if _, err := os.Stat(dir); err != nil { t.Skipf("x/image testdata 不可用: %s", dir) } return dir } // readTestFile 读取测试文件,文件不存在则 skip. func readTestFile(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Skipf("跳过(文件不可用): %s", path) } return data } // ───────────────────────────────────────────────────────────────────── // TIFF 真实文件测试 // ───────────────────────────────────────────────────────────────────── // TestRealTIFF_UncompressedRGB 用真实的无压缩 RGB TIFF 测试完整解码流程. func TestRealTIFF_UncompressedRGB(t *testing.T) { dir := xImageTestdata(t) files := []string{ "video-001-uncompressed.tiff", "no_compress.tiff", "bw-uncompressed.tiff", } for _, name := range files { t.Run(name, func(t *testing.T) { data := readTestFile(t, filepath.Join(dir, name)) result, mt := correctTIFFImage(data) if mt != "image/jpeg" { t.Errorf("期望 image/jpeg,got %s", mt) return } img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Errorf("输出 JPEG 无法解码: %v", err) return } b := img.Bounds() t.Logf("✓ %s → JPEG %dx%d", name, b.Max.X, b.Max.Y) }) } } // TestRealTIFF_LZWCompressed 用真实的 LZW 压缩 TIFF 测试解压路径. func TestRealTIFF_LZWCompressed(t *testing.T) { dir := xImageTestdata(t) data := readTestFile(t, filepath.Join(dir, "blue-purple-pink.lzwcompressed.tiff")) result, mt := correctTIFFImage(data) if mt != "image/jpeg" { t.Errorf("LZW TIFF 期望 image/jpeg,got %s", mt) return } img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Fatalf("LZW TIFF 输出 JPEG 无法解码: %v", err) } b := img.Bounds() t.Logf("✓ LZW TIFF → JPEG %dx%d", b.Max.X, b.Max.Y) } // TestRealTIFF_PackBitsCompressed 用真实的 PackBits 压缩 TIFF(黑白)测试. func TestRealTIFF_PackBitsCompressed(t *testing.T) { dir := xImageTestdata(t) data := readTestFile(t, filepath.Join(dir, "bw-packbits.tiff")) result, mt := correctTIFFImage(data) if mt != "image/jpeg" { t.Errorf("PackBits TIFF 期望 image/jpeg,got %s", mt) return } img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Fatalf("PackBits TIFF 输出 JPEG 无法解码: %v", err) } b := img.Bounds() t.Logf("✓ PackBits TIFF → JPEG %dx%d", b.Max.X, b.Max.Y) } // TestRealTIFF_MultiStrip 用多条带 TIFF 测试条带拼接逻辑. func TestRealTIFF_MultiStrip(t *testing.T) { dir := xImageTestdata(t) data := readTestFile(t, filepath.Join(dir, "video-001-strip-64.tiff")) result, mt := correctTIFFImage(data) if mt != "image/jpeg" { t.Errorf("多条带 TIFF 期望 image/jpeg,got %s", mt) return } img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Fatalf("多条带 TIFF 输出 JPEG 无法解码: %v", err) } b := img.Bounds() t.Logf("✓ 多条带 TIFF → JPEG %dx%d", b.Max.X, b.Max.Y) } // TestRealTIFF_UnsupportedFormats_FailOpen 验证不支持的格式 fail-open 不崩溃. func TestRealTIFF_UnsupportedFormats_FailOpen(t *testing.T) { dir := xImageTestdata(t) // CCITT 和 16-bit 不支持,应 fail-open 返回原始数据 unsupported := []string{ "bw-gopher_ccittGroup3.tiff", "bw-gopher_ccittGroup4.tiff", "video-001-16bit.tiff", "video-001-gray-16bit.tiff", } for _, name := range unsupported { t.Run(name, func(t *testing.T) { data := readTestFile(t, filepath.Join(dir, name)) result, mt := correctTIFFImage(data) // fail-open: 返回原始数据 + image/tiff if mt == "image/jpeg" { // 如果成功了也没关系(某些格式我们可能意外支持) img, _, err := image.Decode(bytes.NewReader(result)) if err == nil { b := img.Bounds() t.Logf("意外成功 %s → JPEG %dx%d(可接受)", name, b.Max.X, b.Max.Y) } } else if mt == "image/tiff" { // 预期的 fail-open 路径 t.Logf("✓ %s fail-open 正确返回 image/tiff", name) } else { t.Errorf("意外 media type: %s", mt) } // 最重要的:不崩溃,不 panic }) } } // ───────────────────────────────────────────────────────────────────── // WebP 真实文件测试 // ───────────────────────────────────────────────────────────────────── // TestRealWebP_NoEXIF_ReturnOriginal 验证无 EXIF 的真实 WebP 原样返回. func TestRealWebP_NoEXIF_ReturnOriginal(t *testing.T) { dir := xImageTestdata(t) files := []struct { name string desc string }{ {"blue-purple-pink.lossy.webp", "有损"}, {"blue-purple-pink.lossless.webp", "无损"}, {"yellow_rose.lossy.webp", "有损(花)"}, {"yellow_rose.lossless.webp", "无损(花)"}, {"tux.lossless.webp", "无损(Tux)"}, } for _, f := range files { t.Run(f.name, func(t *testing.T) { path := filepath.Join(dir, f.name) data, err := os.ReadFile(path) if err != nil { t.Skipf("文件不存在: %s", f.name) } // 解析方向 orient := parseWebPOrientation(data) t.Logf("%s(%s)orientation=%d", f.name, f.desc, orient) // 真实 WebP 无 EXIF → orientation=1 → 应原样返回 result, mt := correctWebPOrientation(data) if orient == exifOrientNormal { if !bytes.Equal(result, data) { t.Error("无 EXIF WebP 应原样返回") } if mt != "image/webp" { t.Errorf("期望 image/webp,got %s", mt) } t.Logf("✓ %s 原样返回", f.name) } else { // 有 EXIF 且需要旋转:验证输出是合法 JPEG if mt == "image/jpeg" { img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Errorf("旋转后 JPEG 无法解码: %v", err) } else { b := img.Bounds() t.Logf("✓ %s 旋转 → JPEG %dx%d", f.name, b.Max.X, b.Max.Y) } } } }) } } // TestRealWebP_DecodePixels 验证 x/image/webp 解码真实 WebP 后像素尺寸正确. func TestRealWebP_DecodePixels(t *testing.T) { dir := xImageTestdata(t) // 这些文件有对应的 PNG 可以验证尺寸 cases := []struct { webp string wantW int wantH int }{ {"blue-purple-pink.lossy.webp", 128, 128}, {"blue-purple-pink.lossless.webp", 128, 128}, } for _, c := range cases { t.Run(c.webp, func(t *testing.T) { data := readTestFile(t, filepath.Join(dir, c.webp)) // detectImageMediaType 应识别为 webp mt := detectImageMediaType(data) if mt != "image/webp" { t.Errorf("期望 image/webp,got %s", mt) } // 用 x/image/webp 解码验证像素访问正常 img, _, err := image.Decode(bytes.NewReader(data)) if err != nil { t.Fatalf("x/image/webp 解码失败: %v", err) } b := img.Bounds() t.Logf("✓ %s 解码成功 %dx%d", c.webp, b.Max.X, b.Max.Y) // 注意:x/image/webp 返回 YCbCr 格式,尺寸可能略有差异(色度子采样) // 只验证非零尺寸 if b.Max.X == 0 || b.Max.Y == 0 { t.Errorf("解码后尺寸为零") } }) } } // ───────────────────────────────────────────────────────────────────── // HEIC 真实文件测试(如果有) // ───────────────────────────────────────────────────────────────────── // TestRealHEIC_Decode 用真实 HEIC 文件测试 CGO + libheif 路径. // 如果系统上没有 HEIC 文件则跳过. func TestRealHEIC_Decode(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("CGO libheif 测试仅在 Linux 上运行") } // 搜索系统上的 HEIC 文件 heicPaths := []string{ "/usr/share/thumbnailers/heif.thumbnailer", // 不是图片,跳过 } // 尝试用 heif-convert 生成一个测试 HEIC // 如果没有工具,跳过此测试 var testFile string for _, p := range heicPaths { if _, err := os.Stat(p); err == nil { testFile = p break } } if testFile == "" { t.Skip("系统上无 HEIC 测试文件,跳过。提示:可用 iOS 手机照片测试") return } data, err := os.ReadFile(testFile) if err != nil { t.Skipf("读取 HEIC 文件失败: %v", err) } result, mt := correctHEICToJPEG(data) if mt != "image/jpeg" { t.Errorf("HEIC 解码期望 image/jpeg,got %s(可能 fail-open)", mt) return } img, _, err := image.Decode(bytes.NewReader(result)) if err != nil { t.Fatalf("HEIC→JPEG 输出无法解码: %v", err) } b := img.Bounds() t.Logf("✓ HEIC → JPEG %dx%d", b.Max.X, b.Max.Y) } // TestRealHEIC_DecodeLibheif_Smoke 用 libheif 库 API 做冒烟测试(无需真实 HEIC 文件). // 验证 CGO 绑定编译和链接正确,decodeHEIC 对垃圾数据 fail-open. func TestRealHEIC_DecodeLibheif_Smoke(t *testing.T) { // 垃圾数据应 fail-open 返回 error _, err := decodeHEIC([]byte{0x00, 0x01, 0x02, 0x03}) if err == nil { t.Error("垃圾数据应返回 error") } t.Logf("✓ libheif CGO 绑定正常,错误信息: %v", err) // 空数据应 fail-open _, err = decodeHEIC([]byte{}) if err == nil { t.Error("空数据应返回 error") } // correctHEICToJPEG 对垃圾数据 fail-open garbage := []byte{0x00, 0x01, 0x02} result, mt := correctHEICToJPEG(garbage) if !bytes.Equal(result, garbage) { t.Error("无效 HEIC fail-open 应原样返回") } if mt != "image/heic" { t.Errorf("fail-open 应返回 image/heic,got %s", mt) } t.Logf("✓ HEIC fail-open 正确") }