mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-10-04 18:33:36 +02:00
Return reset func for testutil.LazyObj; Add testutil.LazySuite
This commit is contained in:
@@ -2,11 +2,9 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -32,8 +30,7 @@ type LoadTestSuite struct {
|
|||||||
testData *testutil.TestDataProvider
|
testData *testutil.TestDataProvider
|
||||||
testImagesPath string
|
testImagesPath string
|
||||||
|
|
||||||
addr net.Addr
|
server *TestServer
|
||||||
stopImgproxy context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupSuite starts imgproxy instance server
|
// SetupSuite starts imgproxy instance server
|
||||||
@@ -49,12 +46,12 @@ func (s *LoadTestSuite) SetupSuite() {
|
|||||||
config.DevelopmentErrorsMode = true
|
config.DevelopmentErrorsMode = true
|
||||||
|
|
||||||
// In this test we start the single imgproxy server for all test cases
|
// In this test we start the single imgproxy server for all test cases
|
||||||
s.addr, s.stopImgproxy = s.StartImgproxy(c)
|
s.server = s.StartImgproxy(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TearDownSuite stops imgproxy instance server
|
// TearDownSuite stops imgproxy instance server
|
||||||
func (s *LoadTestSuite) TearDownSuite() {
|
func (s *LoadTestSuite) TearDownSuite() {
|
||||||
s.stopImgproxy()
|
s.server.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// testLoadFolder fetches images iterates over images in the specified folder,
|
// testLoadFolder fetches images iterates over images in the specified folder,
|
||||||
@@ -117,7 +114,7 @@ func (s *LoadTestSuite) testLoadFolder(folder string) {
|
|||||||
|
|
||||||
// fetchImage fetches an image from the imgproxy server
|
// fetchImage fetches an image from the imgproxy server
|
||||||
func (s *LoadTestSuite) fetchImage(path string) []byte {
|
func (s *LoadTestSuite) fetchImage(path string) []byte {
|
||||||
url := fmt.Sprintf("http://%s/%s", s.addr.String(), path)
|
url := fmt.Sprintf("http://%s/%s", s.server.Addr, path)
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
s.Require().NoError(err, "Failed to fetch image from %s", url)
|
s.Require().NoError(err, "Failed to fetch image from %s", url)
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -36,6 +35,7 @@ type ProcessingHandlerTestSuite struct {
|
|||||||
// happen afterwards. It is done via lazy obj. When all config values will be moved
|
// happen afterwards. It is done via lazy obj. When all config values will be moved
|
||||||
// to imgproxy.Config struct, this can be removed.
|
// to imgproxy.Config struct, this can be removed.
|
||||||
config testutil.LazyObj[*imgproxy.Config]
|
config testutil.LazyObj[*imgproxy.Config]
|
||||||
|
server testutil.LazyObj[*TestServer]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) SetupSuite() {
|
func (s *ProcessingHandlerTestSuite) SetupSuite() {
|
||||||
@@ -44,15 +44,8 @@ func (s *ProcessingHandlerTestSuite) SetupSuite() {
|
|||||||
|
|
||||||
// Initialize test data provider (local test files)
|
// Initialize test data provider (local test files)
|
||||||
s.testData = testutil.NewTestDataProvider(s.T())
|
s.testData = testutil.NewTestDataProvider(s.T())
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) TearDownSuite() {
|
s.config, _ = testutil.NewLazySuiteObj(s, func() (*imgproxy.Config, error) {
|
||||||
logrus.SetOutput(os.Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupObjs initializes lazy objects
|
|
||||||
func (s *ProcessingHandlerTestSuite) setupObjs() {
|
|
||||||
s.config = testutil.NewLazyObj(s.T(), func() (*imgproxy.Config, error) {
|
|
||||||
c, err := imgproxy.LoadConfigFromEnv(nil)
|
c, err := imgproxy.LoadConfigFromEnv(nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
@@ -61,6 +54,21 @@ func (s *ProcessingHandlerTestSuite) setupObjs() {
|
|||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
s.server, _ = testutil.NewLazySuiteObj(
|
||||||
|
s,
|
||||||
|
func() (*TestServer, error) {
|
||||||
|
return s.StartImgproxy(s.config()), nil
|
||||||
|
},
|
||||||
|
func(s *TestServer) error {
|
||||||
|
s.Shutdown()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProcessingHandlerTestSuite) TearDownSuite() {
|
||||||
|
logrus.SetOutput(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) SetupTest() {
|
func (s *ProcessingHandlerTestSuite) SetupTest() {
|
||||||
@@ -69,23 +77,17 @@ func (s *ProcessingHandlerTestSuite) SetupTest() {
|
|||||||
// NOTE: This must be moved to security config
|
// NOTE: This must be moved to security config
|
||||||
config.AllowLoopbackSourceAddresses = true
|
config.AllowLoopbackSourceAddresses = true
|
||||||
// NOTE: end note
|
// NOTE: end note
|
||||||
|
|
||||||
s.setupObjs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) SetupSubTest() {
|
func (s *ProcessingHandlerTestSuite) SetupSubTest() {
|
||||||
// We use t.Run() a lot, so we need to reset lazy objects at the beginning of each subtest
|
// We use t.Run() a lot, so we need to reset lazy objects at the beginning of each subtest
|
||||||
s.setupObjs()
|
s.ResetLazyObjects()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET performs a GET request to the imageproxy real server
|
// GET performs a GET request to the imageproxy real server
|
||||||
// NOTE: Do not forget to move this to Suite in case of need in other future test suites
|
// NOTE: Do not forget to move this to Suite in case of need in other future test suites
|
||||||
func (s *ProcessingHandlerTestSuite) GET(path string, header ...http.Header) *http.Response {
|
func (s *ProcessingHandlerTestSuite) GET(path string, header ...http.Header) *http.Response {
|
||||||
// In this test we start the imgproxy server instance per request
|
url := fmt.Sprintf("http://%s%s", s.server().Addr, path)
|
||||||
addr, stopServer := s.StartImgproxy(s.config())
|
|
||||||
defer stopServer()
|
|
||||||
|
|
||||||
url := fmt.Sprintf("http://%s%s", addr.String(), path)
|
|
||||||
|
|
||||||
// Perform GET request to an url
|
// Perform GET request to an url
|
||||||
req, _ := http.NewRequest("GET", url, nil)
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
@@ -99,13 +101,6 @@ func (s *ProcessingHandlerTestSuite) GET(path string, header ...http.Header) *ht
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
// Read the entire body into memory and replace the original body with memory reader
|
|
||||||
// to avoid the defer
|
|
||||||
bodyBytes, err := io.ReadAll(resp.Body)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
resp.Body.Close()
|
|
||||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,16 +5,21 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v3"
|
"github.com/imgproxy/imgproxy/v3"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/imgproxy/imgproxy/v3/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TestServer struct {
|
||||||
|
Addr net.Addr
|
||||||
|
Shutdown context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
type Suite struct {
|
type Suite struct {
|
||||||
suite.Suite
|
testutil.LazySuite
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartImgproxy starts imgproxy instance for the tests
|
// StartImgproxy starts imgproxy instance for the tests
|
||||||
// Returns instance, instance address and stop function
|
// Returns instance, instance address and stop function
|
||||||
func (s *Suite) StartImgproxy(c *imgproxy.Config) (net.Addr, context.CancelFunc) {
|
func (s *Suite) StartImgproxy(c *imgproxy.Config) *TestServer {
|
||||||
ctx, cancel := context.WithCancel(s.T().Context())
|
ctx, cancel := context.WithCancel(s.T().Context())
|
||||||
|
|
||||||
c.Server.Bind = ":0"
|
c.Server.Bind = ":0"
|
||||||
@@ -32,5 +37,8 @@ func (s *Suite) StartImgproxy(c *imgproxy.Config) (net.Addr, context.CancelFunc)
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return <-addrCh, cancel
|
return &TestServer{
|
||||||
|
Addr: <-addrCh,
|
||||||
|
Shutdown: cancel,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
59
testutil/lasy_suite.go
Normal file
59
testutil/lasy_suite.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LazySuite is a test suite that automatically resets [LazyObj] instances.
|
||||||
|
// It uses [LazySuite.AfterTest] to perform the reset after each test,
|
||||||
|
// so if you also use this function in your test suite, don't forget to call
|
||||||
|
// [LazySuite.AfterTest] or [LazySuite.ResetLazyObjects] explicitly.
|
||||||
|
type LazySuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
resets []context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazy returns the LazySuite instance itself.
|
||||||
|
// Needed to implement [LazySuiteFrom].
|
||||||
|
func (s *LazySuite) Lazy() *LazySuite {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterTest is called by testify after each test.
|
||||||
|
// If you also use this function in your test suite, don't forget to call
|
||||||
|
// [LazySuite.AfterTest] or [LazySuite.ResetLazyObjects] explicitly.
|
||||||
|
func (s *LazySuite) AfterTest(_, _ string) {
|
||||||
|
// Reset lazy objects after each test
|
||||||
|
s.ResetLazyObjects()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetLazyObjects resets all lazy objects created with [NewLazySuiteObj]
|
||||||
|
func (s *LazySuite) ResetLazyObjects() {
|
||||||
|
for _, reset := range s.resets {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LazySuiteFrom interface {
|
||||||
|
Lazy() *LazySuite
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLazySuiteObj creates a new [LazyObj] instance and registers its cleanup function
|
||||||
|
// to a provided [LazySuite].
|
||||||
|
func NewLazySuiteObj[T any](
|
||||||
|
s LazySuiteFrom,
|
||||||
|
newFn LazyObjNew[T],
|
||||||
|
dropFn ...LazyObjDrop[T],
|
||||||
|
) (LazyObj[T], context.CancelFunc) {
|
||||||
|
// Get the [LazySuite] instance
|
||||||
|
lazy := s.Lazy()
|
||||||
|
// Create the [LazyObj] instance
|
||||||
|
obj, cancel := NewLazyObj(lazy, newFn, dropFn...)
|
||||||
|
// Add cleanup function to the resets list
|
||||||
|
lazy.resets = append(lazy.resets, cancel)
|
||||||
|
|
||||||
|
return obj, cancel
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -9,24 +10,58 @@ import (
|
|||||||
// LazyObj is a function that returns an object of type T.
|
// LazyObj is a function that returns an object of type T.
|
||||||
type LazyObj[T any] func() T
|
type LazyObj[T any] func() T
|
||||||
|
|
||||||
// LazyObjInit is a function that initializes and returns an object of type T and an error if any.
|
// LazyObjT is an interface that provides access to the [testing.T] object.
|
||||||
type LazyObjInit[T any] func() (T, error)
|
type LazyObjT interface {
|
||||||
|
T() *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
// NewLazyObj creates a new LazyObj that initializes the object on the first call.
|
// LazyObjNew is a function that creates and returns an object of type T and an error if any.
|
||||||
func NewLazyObj[T any](t *testing.T, init LazyObjInit[T]) LazyObj[T] {
|
type LazyObjNew[T any] func() (T, error)
|
||||||
t.Helper()
|
|
||||||
|
// LazyObjDrop is a callback that is called when [LazyObj] is reset.
|
||||||
|
// It receives a pointer to the object being dropped.
|
||||||
|
// If the object was not yet initialized, the callback is not called.
|
||||||
|
type LazyObjDrop[T any] func(T) error
|
||||||
|
|
||||||
|
// NewLazyObj creates a new [LazyObj] that initializes the object on the first call.
|
||||||
|
// It returns a function that can be called to get the object and a cancel function
|
||||||
|
// that can be called to reset the object.
|
||||||
|
func NewLazyObj[T any](
|
||||||
|
s LazyObjT,
|
||||||
|
newFn LazyObjNew[T],
|
||||||
|
dropFn ...LazyObjDrop[T],
|
||||||
|
) (LazyObj[T], context.CancelFunc) {
|
||||||
|
s.T().Helper()
|
||||||
|
|
||||||
var obj *T
|
var obj *T
|
||||||
|
|
||||||
return func() T {
|
init := func() T {
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
return *obj
|
return *obj
|
||||||
}
|
}
|
||||||
|
|
||||||
o, err := init()
|
o, err := newFn()
|
||||||
require.NoError(t, err)
|
require.NoError(s.T(), err, "Failed to initialize lazy object")
|
||||||
|
|
||||||
obj = &o
|
obj = &o
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel := func() {
|
||||||
|
if obj == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range dropFn {
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(s.T(), fn(*obj), "Failed to reset lazy object")
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return init, cancel
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/imgproxy/imgproxy/v3/ioutil"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,8 +21,8 @@ func ReadersEqual(t *testing.T, expected, actual io.Reader) bool {
|
|||||||
buf2 := make([]byte, bufSize)
|
buf2 := make([]byte, bufSize)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n1, err1 := expected.Read(buf1)
|
n1, err1 := ioutil.TryReadFull(expected, buf1)
|
||||||
n2, err2 := actual.Read(buf2)
|
n2, err2 := ioutil.TryReadFull(actual, buf2)
|
||||||
|
|
||||||
if n1 != n2 {
|
if n1 != n2 {
|
||||||
return false
|
return false
|
||||||
|
Reference in New Issue
Block a user