chore: add resize tests
parent
aa78e3ab1f
commit
cb8ac5ebf1
2
go.mod
2
go.mod
|
@ -25,11 +25,13 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.6.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
go.etcd.io/bbolt v1.3.3
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -206,6 +206,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
|
@ -318,4 +320,6 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
|||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"github.com/filebrowser/filebrowser/v2/img"
|
||||
|
@ -21,7 +20,7 @@ const (
|
|||
|
||||
type ImgService interface {
|
||||
FormatFromExtension(ext string) (img.Format, error)
|
||||
Resize(ctx context.Context, file afero.File, width, height int, out io.Writer, options ...img.Option) error
|
||||
Resize(ctx context.Context, in io.Reader, width, height int, out io.Writer, options ...img.Option) error
|
||||
}
|
||||
|
||||
func previewHandler(imgSvc ImgService, enableThumbnails, resizePreview bool) handleFunc {
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
package img
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/marusama/semaphore/v2"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// ErrUnsupportedFormat means the given image format is not supported.
|
||||
|
@ -17,14 +18,12 @@ var ErrUnsupportedFormat = errors.New("unsupported image format")
|
|||
|
||||
// Service
|
||||
type Service struct {
|
||||
lowPrioritySem semaphore.Semaphore
|
||||
highPrioritySem semaphore.Semaphore
|
||||
sem semaphore.Semaphore
|
||||
}
|
||||
|
||||
func New(workers int) *Service {
|
||||
return &Service{
|
||||
lowPrioritySem: semaphore.New(workers),
|
||||
highPrioritySem: semaphore.New(workers),
|
||||
sem: semaphore.New(workers),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +74,7 @@ func (x Quality) resampleFilter() imaging.ResampleFilter {
|
|||
case QualityLow:
|
||||
return imaging.NearestNeighbor
|
||||
default:
|
||||
return imaging.Linear
|
||||
return imaging.Box
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,13 +107,19 @@ func (s *Service) FormatFromExtension(ext string) (Format, error) {
|
|||
}
|
||||
|
||||
type resizeConfig struct {
|
||||
prioritized bool
|
||||
format Format
|
||||
resizeMode ResizeMode
|
||||
quality Quality
|
||||
}
|
||||
|
||||
type Option func(*resizeConfig)
|
||||
|
||||
func WithFormat(format Format) Option {
|
||||
return func(config *resizeConfig) {
|
||||
config.format = format
|
||||
}
|
||||
}
|
||||
|
||||
func WithMode(mode ResizeMode) Option {
|
||||
return func(config *resizeConfig) {
|
||||
config.resizeMode = mode
|
||||
|
@ -127,14 +132,19 @@ func WithQuality(quality Quality) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func WithHighPriority() Option {
|
||||
return func(config *resizeConfig) {
|
||||
config.prioritized = true
|
||||
func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, out io.Writer, options ...Option) error {
|
||||
if err := s.sem.Acquire(ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.sem.Release(1)
|
||||
|
||||
format, wrappedReader, err := s.detectFormat(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Resize(ctx context.Context, file afero.File, width, height int, out io.Writer, options ...Option) error {
|
||||
config := resizeConfig{
|
||||
format: format,
|
||||
resizeMode: ResizeModeFit,
|
||||
quality: QualityMedium,
|
||||
}
|
||||
|
@ -142,21 +152,7 @@ func (s *Service) Resize(ctx context.Context, file afero.File, width, height int
|
|||
option(&config)
|
||||
}
|
||||
|
||||
sem := s.lowPrioritySem
|
||||
if config.prioritized {
|
||||
sem = s.highPrioritySem
|
||||
}
|
||||
|
||||
if err := sem.Acquire(ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
defer sem.Release(1)
|
||||
|
||||
format, err := s.FormatFromExtension(filepath.Ext(file.Name()))
|
||||
if err != nil {
|
||||
return ErrUnsupportedFormat
|
||||
}
|
||||
img, err := imaging.Decode(file, imaging.AutoOrientation(true))
|
||||
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -168,5 +164,22 @@ func (s *Service) Resize(ctx context.Context, file afero.File, width, height int
|
|||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||
}
|
||||
|
||||
return imaging.Encode(out, img, format.toImaging())
|
||||
return imaging.Encode(out, img, config.format.toImaging())
|
||||
}
|
||||
|
||||
func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
r := io.TeeReader(in, buf)
|
||||
|
||||
_, imgFormat, err := image.DecodeConfig(r)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("%s: %w", err.Error(), ErrUnsupportedFormat)
|
||||
}
|
||||
|
||||
format, err := ParseFormat(imgFormat)
|
||||
if err != nil {
|
||||
return 0, nil, ErrUnsupportedFormat
|
||||
}
|
||||
|
||||
return format, io.MultiReader(buf, in), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,398 @@
|
|||
package img
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/image/bmp"
|
||||
"golang.org/x/image/tiff"
|
||||
)
|
||||
|
||||
func TestService_Resize(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
options []Option
|
||||
width int
|
||||
height int
|
||||
source func(t *testing.T) afero.File
|
||||
matcher func(t *testing.T, reader io.Reader)
|
||||
wantErr bool
|
||||
}{
|
||||
"fill upscale": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 50, 20)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"fill downscale": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"fit upscale": {
|
||||
options: []Option{WithMode(ResizeModeFit)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 50, 20)
|
||||
},
|
||||
matcher: sizeMatcher(50, 20),
|
||||
},
|
||||
"fit downscale": {
|
||||
options: []Option{WithMode(ResizeModeFit)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 75),
|
||||
},
|
||||
"keep original format": {
|
||||
options: []Option{},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayPng(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatPng),
|
||||
},
|
||||
"convert to jpeg": {
|
||||
options: []Option{WithFormat(FormatJpeg)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatJpeg),
|
||||
},
|
||||
"convert to png": {
|
||||
options: []Option{WithFormat(FormatPng)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatPng),
|
||||
},
|
||||
"convert to gif": {
|
||||
options: []Option{WithFormat(FormatGif)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatGif),
|
||||
},
|
||||
"convert to tiff": {
|
||||
options: []Option{WithFormat(FormatTiff)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatTiff),
|
||||
},
|
||||
"convert to bmp": {
|
||||
options: []Option{WithFormat(FormatBmp)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatBmp),
|
||||
},
|
||||
"convert to unknown": {
|
||||
options: []Option{WithFormat(Format(-1))},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: formatMatcher(FormatJpeg),
|
||||
},
|
||||
"resize png": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayPng(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize gif": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayGif(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize tiff": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayTiff(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize bmp": {
|
||||
options: []Option{WithMode(ResizeModeFill)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayBmp(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize with high quality": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityHigh)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize with medium quality": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize with low quality": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize with unknown quality": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(Quality(-1))},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return newGrayJpeg(t, 200, 150)
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"broken file": {
|
||||
options: []Option{WithMode(ResizeModeFit)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.jpg")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.WriteString("this is not an image")
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
svc := New(1)
|
||||
source := test.source(t)
|
||||
defer source.Close()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := svc.Resize(context.Background(), source, test.width, test.height, buf, test.options...)
|
||||
if (err != nil) != test.wantErr {
|
||||
t.Fatalf("GetMarketSpecs() error = %v, wantErr %v", err, test.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
test.matcher(t, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sizeMatcher(width, height int) func(t *testing.T, reader io.Reader) {
|
||||
return func(t *testing.T, reader io.Reader) {
|
||||
resizedImg, _, err := image.Decode(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, width, resizedImg.Bounds().Dx())
|
||||
require.Equal(t, height, resizedImg.Bounds().Dy())
|
||||
}
|
||||
}
|
||||
|
||||
func formatMatcher(format Format) func(t *testing.T, reader io.Reader) {
|
||||
return func(t *testing.T, reader io.Reader) {
|
||||
_, decodedFormat, err := image.DecodeConfig(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, format.String(), decodedFormat)
|
||||
}
|
||||
}
|
||||
|
||||
func newGrayJpeg(t *testing.T, width, height int) afero.File {
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.jpg")
|
||||
require.NoError(t, err)
|
||||
|
||||
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 90})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func newGrayPng(t *testing.T, width, height int) afero.File {
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||
err = png.Encode(file, img)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func newGrayGif(t *testing.T, width, height int) afero.File {
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.gif")
|
||||
require.NoError(t, err)
|
||||
|
||||
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||
err = gif.Encode(file, img, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func newGrayTiff(t *testing.T, width, height int) afero.File {
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.tiff")
|
||||
require.NoError(t, err)
|
||||
|
||||
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||
err = tiff.Encode(file, img, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func newGrayBmp(t *testing.T, width, height int) afero.File {
|
||||
fs := afero.NewMemMapFs()
|
||||
file, err := fs.Create("image.bmp")
|
||||
require.NoError(t, err)
|
||||
|
||||
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||
err = bmp.Encode(file, img)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func TestService_FormatFromExtension(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
ext string
|
||||
want Format
|
||||
wantErr error
|
||||
}{
|
||||
"jpg": {
|
||||
ext: ".jpg",
|
||||
want: FormatJpeg,
|
||||
},
|
||||
"jpeg": {
|
||||
ext: ".jpeg",
|
||||
want: FormatJpeg,
|
||||
},
|
||||
"png": {
|
||||
ext: ".png",
|
||||
want: FormatPng,
|
||||
},
|
||||
"gif": {
|
||||
ext: ".gif",
|
||||
want: FormatGif,
|
||||
},
|
||||
"tiff": {
|
||||
ext: ".tiff",
|
||||
want: FormatTiff,
|
||||
},
|
||||
"bmp": {
|
||||
ext: ".bmp",
|
||||
want: FormatBmp,
|
||||
},
|
||||
"unknown": {
|
||||
ext: ".mov",
|
||||
wantErr: ErrUnsupportedFormat,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
svc := New(1)
|
||||
got, err := svc.FormatFromExtension(test.ext)
|
||||
require.Truef(t, errors.Is(err, test.wantErr), "error = %v, wantErr %v", err, test.wantErr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
require.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue