load_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package integration
  2. import (
  3. "bytes"
  4. "fmt"
  5. "image/png"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "strings"
  12. "testing"
  13. "github.com/corona10/goimagehash"
  14. "github.com/imgproxy/imgproxy/v3/config"
  15. "github.com/imgproxy/imgproxy/v3/imagetype"
  16. "github.com/imgproxy/imgproxy/v3/vips"
  17. "github.com/stretchr/testify/suite"
  18. )
  19. const (
  20. similarityThreshold = 5 // Distance between images to be considered similar
  21. )
  22. type LoadTestSuite struct {
  23. Suite
  24. testImagesPath string
  25. }
  26. func (s *LoadTestSuite) SetupTest() {
  27. s.testImagesPath = s.TestData.Path("test-images")
  28. config.MaxAnimationFrames = 999
  29. config.DevelopmentErrorsMode = true
  30. s.Config().Fetcher.Transport.Local.Root = s.testImagesPath
  31. }
  32. // testLoadFolder fetches images iterates over images in the specified folder,
  33. // runs imgproxy on each image, and compares the result with the reference image
  34. // which is expected to be in the `integration` folder with the same name
  35. // but with `.png` extension.
  36. func (s *LoadTestSuite) testLoadFolder(folder string) {
  37. walkPath := path.Join(s.testImagesPath, folder)
  38. // Iterate over the files in the source folder
  39. err := filepath.Walk(walkPath, func(path string, info os.FileInfo, err error) error {
  40. s.Require().NoError(err)
  41. // Skip directories
  42. if info.IsDir() {
  43. return nil
  44. }
  45. // get the base name of the file (8-bpp.png)
  46. basePath := filepath.Base(path)
  47. // Replace the extension with .png
  48. referencePath := strings.TrimSuffix(basePath, filepath.Ext(basePath)) + ".png"
  49. // Construct the full path to the reference image (integration/ folder)
  50. referencePath = filepath.Join(s.testImagesPath, "integration", folder, referencePath)
  51. // Construct the source URL for imgproxy (no processing)
  52. sourceUrl := fmt.Sprintf("/insecure/plain/local:///%s/%s@png", folder, basePath)
  53. imgproxyImageBytes := s.fetchImage(sourceUrl)
  54. imgproxyImage, err := png.Decode(bytes.NewReader(imgproxyImageBytes))
  55. s.Require().NoError(err, "Failed to decode PNG image from imgproxy for %s", basePath)
  56. referenceFile, err := os.Open(referencePath)
  57. s.Require().NoError(err)
  58. defer referenceFile.Close()
  59. referenceImage, err := png.Decode(referenceFile)
  60. s.Require().NoError(err, "Failed to decode PNG reference image for %s", referencePath)
  61. hash1, err := goimagehash.DifferenceHash(imgproxyImage)
  62. s.Require().NoError(err)
  63. hash2, err := goimagehash.DifferenceHash(referenceImage)
  64. s.Require().NoError(err)
  65. distance, err := hash1.Distance(hash2)
  66. s.Require().NoError(err)
  67. s.Require().LessOrEqual(distance, similarityThreshold,
  68. "Image %s differs from reference image %s by %d, which is greater than the allowed threshold of %d",
  69. basePath, referencePath, distance, similarityThreshold)
  70. return nil
  71. })
  72. s.Require().NoError(err)
  73. }
  74. // fetchImage fetches an image from the imgproxy server
  75. func (s *LoadTestSuite) fetchImage(path string) []byte {
  76. resp := s.GET(path)
  77. defer resp.Body.Close()
  78. s.Require().Equal(http.StatusOK, resp.StatusCode, "Expected status code 200 OK, got %d, path: %s", resp.StatusCode, path)
  79. bytes, err := io.ReadAll(resp.Body)
  80. s.Require().NoError(err, "Failed to read response body from %s", path)
  81. return bytes
  82. }
  83. // TestLoadSaveToPng ensures that our load pipeline works,
  84. // including standard and custom loaders. For each source image
  85. // in the folder, it does the passthrough request through imgproxy:
  86. // no processing, just convert format of the source file to png.
  87. // Then, it compares the result with the reference image.
  88. func (s *LoadTestSuite) TestLoadSaveToPng() {
  89. testCases := []struct {
  90. name string
  91. imageType imagetype.Type
  92. folderName string
  93. }{
  94. {"GIF", imagetype.GIF, "gif"},
  95. {"JPEG", imagetype.JPEG, "jpg"},
  96. {"HEIC", imagetype.HEIC, "heif"},
  97. {"JXL", imagetype.JXL, "jxl"},
  98. {"SVG", imagetype.SVG, "svg"},
  99. {"TIFF", imagetype.TIFF, "tiff"},
  100. {"WEBP", imagetype.WEBP, "webp"},
  101. {"BMP", imagetype.BMP, "bmp"},
  102. {"ICO", imagetype.ICO, "ico"},
  103. }
  104. for _, tc := range testCases {
  105. s.T().Run(tc.name, func(t *testing.T) {
  106. if vips.SupportsLoad(tc.imageType) {
  107. s.testLoadFolder(tc.folderName)
  108. } else {
  109. t.Skipf("%s format not supported by VIPS", tc.name)
  110. }
  111. })
  112. }
  113. }
  114. func TestIntegration(t *testing.T) {
  115. suite.Run(t, new(LoadTestSuite))
  116. }