mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-10-10 04:02:29 +02:00
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"image/png"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/corona10/goimagehash"
|
|
"github.com/imgproxy/imgproxy/v3"
|
|
"github.com/imgproxy/imgproxy/v3/config"
|
|
"github.com/imgproxy/imgproxy/v3/imagetype"
|
|
"github.com/imgproxy/imgproxy/v3/testutil"
|
|
"github.com/imgproxy/imgproxy/v3/vips"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
const (
|
|
similarityThreshold = 5 // Distance between images to be considered similar
|
|
)
|
|
|
|
type LoadTestSuite struct {
|
|
suite.Suite
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
testData *testutil.TestDataProvider
|
|
testImagesPath string
|
|
}
|
|
|
|
// SetupSuite starts imgproxy instance server
|
|
func (s *LoadTestSuite) SetupSuite() {
|
|
s.testData = testutil.NewTestDataProvider(s.T())
|
|
s.testImagesPath = s.testData.Path("test-images")
|
|
s.ctx, s.cancel = context.WithCancel(s.T().Context())
|
|
|
|
s.startImgproxy(s.ctx)
|
|
}
|
|
|
|
// TearDownSuite stops imgproxy instance server
|
|
func (s *LoadTestSuite) TearDownSuite() {
|
|
s.cancel()
|
|
}
|
|
|
|
// testLoadFolder fetches images iterates over images in the specified folder,
|
|
// runs imgproxy on each image, and compares the result with the reference image
|
|
// which is expected to be in the `integration` folder with the same name
|
|
// but with `.png` extension.
|
|
func (s *LoadTestSuite) testLoadFolder(folder string) {
|
|
walkPath := path.Join(s.testImagesPath, folder)
|
|
|
|
// Iterate over the files in the source folder
|
|
err := filepath.Walk(walkPath, func(path string, info os.FileInfo, err error) error {
|
|
s.Require().NoError(err)
|
|
|
|
// Skip directories
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// get the base name of the file (8-bpp.png)
|
|
basePath := filepath.Base(path)
|
|
|
|
// Replace the extension with .png
|
|
referencePath := strings.TrimSuffix(basePath, filepath.Ext(basePath)) + ".png"
|
|
|
|
// Construct the full path to the reference image (integration/ folder)
|
|
referencePath = filepath.Join(s.testImagesPath, "integration", folder, referencePath)
|
|
|
|
// Construct the source URL for imgproxy (no processing)
|
|
sourceUrl := fmt.Sprintf("insecure/plain/local:///%s/%s@png", folder, basePath)
|
|
|
|
imgproxyImageBytes := s.fetchImage(sourceUrl)
|
|
imgproxyImage, err := png.Decode(bytes.NewReader(imgproxyImageBytes))
|
|
s.Require().NoError(err, "Failed to decode PNG image from imgproxy for %s", basePath)
|
|
|
|
referenceFile, err := os.Open(referencePath)
|
|
s.Require().NoError(err)
|
|
defer referenceFile.Close()
|
|
|
|
referenceImage, err := png.Decode(referenceFile)
|
|
s.Require().NoError(err, "Failed to decode PNG reference image for %s", referencePath)
|
|
|
|
hash1, err := goimagehash.DifferenceHash(imgproxyImage)
|
|
s.Require().NoError(err)
|
|
|
|
hash2, err := goimagehash.DifferenceHash(referenceImage)
|
|
s.Require().NoError(err)
|
|
|
|
distance, err := hash1.Distance(hash2)
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().LessOrEqual(distance, similarityThreshold,
|
|
"Image %s differs from reference image %s by %d, which is greater than the allowed threshold of %d",
|
|
basePath, referencePath, distance, similarityThreshold)
|
|
|
|
return nil
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
// fetchImage fetches an image from the imgproxy server
|
|
func (s *LoadTestSuite) fetchImage(path string) []byte {
|
|
url := fmt.Sprintf("http://%s:%d/%s", bindHost, bindPort, path)
|
|
|
|
resp, err := http.Get(url)
|
|
s.Require().NoError(err, "Failed to fetch image from %s", url)
|
|
defer resp.Body.Close()
|
|
|
|
s.Require().Equal(http.StatusOK, resp.StatusCode, "Expected status code 200 OK, got %d, url: %s", resp.StatusCode, url)
|
|
|
|
bytes, err := io.ReadAll(resp.Body)
|
|
s.Require().NoError(err, "Failed to read response body from %s", url)
|
|
|
|
return bytes
|
|
}
|
|
|
|
func (s *LoadTestSuite) startImgproxy(ctx context.Context) *imgproxy.Imgproxy {
|
|
c, err := imgproxy.LoadConfigFromEnv(nil)
|
|
s.Require().NoError(err)
|
|
|
|
c.Server.Bind = ":" + fmt.Sprintf("%d", bindPort)
|
|
c.Transport.Local.Root = s.testImagesPath
|
|
c.Server.LogMemStats = true
|
|
|
|
config.MaxAnimationFrames = 999
|
|
config.DevelopmentErrorsMode = true
|
|
|
|
i, err := imgproxy.New(ctx, c)
|
|
s.Require().NoError(err)
|
|
|
|
go func() {
|
|
err = i.StartServer(ctx)
|
|
if err != nil {
|
|
s.T().Errorf("Imgproxy server exited with error: %v", err)
|
|
}
|
|
}()
|
|
|
|
return i
|
|
}
|
|
|
|
// TestLoadSaveToPng ensures that our load pipeline works,
|
|
// including standard and custom loaders. For each source image
|
|
// in the folder, it does the passthrough request through imgproxy:
|
|
// no processing, just convert format of the source file to png.
|
|
// Then, it compares the result with the reference image.
|
|
func (s *LoadTestSuite) TestLoadSaveToPng() {
|
|
testCases := []struct {
|
|
name string
|
|
imageType imagetype.Type
|
|
folderName string
|
|
}{
|
|
{"GIF", imagetype.GIF, "gif"},
|
|
{"JPEG", imagetype.JPEG, "jpg"},
|
|
{"HEIC", imagetype.HEIC, "heif"},
|
|
{"JXL", imagetype.JXL, "jxl"},
|
|
{"SVG", imagetype.SVG, "svg"},
|
|
{"TIFF", imagetype.TIFF, "tiff"},
|
|
{"WEBP", imagetype.WEBP, "webp"},
|
|
{"BMP", imagetype.BMP, "bmp"},
|
|
{"ICO", imagetype.ICO, "ico"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.T().Run(tc.name, func(t *testing.T) {
|
|
if vips.SupportsLoad(tc.imageType) {
|
|
s.testLoadFolder(tc.folderName)
|
|
} else {
|
|
t.Skipf("%s format not supported by VIPS", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegration(t *testing.T) {
|
|
suite.Run(t, new(LoadTestSuite))
|
|
}
|