From d18c5b8ee3f2ca00544bc12709b5423ce28bd1c1 Mon Sep 17 00:00:00 2001 From: Mark Wolfe Date: Sat, 16 Sep 2023 12:06:57 +1000 Subject: [PATCH] feat(testing): remove mocking library and follow SDK This change standardises the mocking model for the tests based on https://aws.github.io/aws-sdk-go-v2/docs/unit-testing/ --- go.mod | 3 -- go.sum | 29 --------------- mocks/s3api.go | 96 -------------------------------------------------- s3file.go | 2 +- s3file_test.go | 95 +++++++++++++++++++++++++++++++++++++++---------- s3iofs_test.go | 79 +++++++++++++++++++++++------------------ tools.go | 8 ----- 7 files changed, 123 insertions(+), 189 deletions(-) delete mode 100644 mocks/s3api.go delete mode 100644 tools.go diff --git a/go.mod b/go.mod index def6dc3..ee8c423 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/aws/aws-sdk-go-v2 v1.18.1 github.com/aws/aws-sdk-go-v2/config v1.18.27 github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 - github.com/golang/mock v1.6.0 github.com/rs/zerolog v1.29.1 github.com/stretchr/testify v1.8.4 golang.org/x/net v0.11.0 @@ -32,8 +31,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/mod v0.11.0 // indirect golang.org/x/sys v0.9.0 // indirect - golang.org/x/tools v0.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e122f52..8fc5060 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -61,41 +59,14 @@ github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/mocks/s3api.go b/mocks/s3api.go deleted file mode 100644 index 3ea83fb..0000000 --- a/mocks/s3api.go +++ /dev/null @@ -1,96 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/wolfeidau/s3iofs (interfaces: S3API) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - s3 "github.com/aws/aws-sdk-go-v2/service/s3" - gomock "github.com/golang/mock/gomock" -) - -// MockS3API is a mock of S3API interface. -type MockS3API struct { - ctrl *gomock.Controller - recorder *MockS3APIMockRecorder -} - -// MockS3APIMockRecorder is the mock recorder for MockS3API. -type MockS3APIMockRecorder struct { - mock *MockS3API -} - -// NewMockS3API creates a new mock instance. -func NewMockS3API(ctrl *gomock.Controller) *MockS3API { - mock := &MockS3API{ctrl: ctrl} - mock.recorder = &MockS3APIMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockS3API) EXPECT() *MockS3APIMockRecorder { - return m.recorder -} - -// GetObject mocks base method. -func (m *MockS3API) GetObject(arg0 context.Context, arg1 *s3.GetObjectInput, arg2 ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetObject", varargs...) - ret0, _ := ret[0].(*s3.GetObjectOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetObject indicates an expected call of GetObject. -func (mr *MockS3APIMockRecorder) GetObject(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObject", reflect.TypeOf((*MockS3API)(nil).GetObject), varargs...) -} - -// HeadObject mocks base method. -func (m *MockS3API) HeadObject(arg0 context.Context, arg1 *s3.HeadObjectInput, arg2 ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "HeadObject", varargs...) - ret0, _ := ret[0].(*s3.HeadObjectOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// HeadObject indicates an expected call of HeadObject. -func (mr *MockS3APIMockRecorder) HeadObject(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadObject", reflect.TypeOf((*MockS3API)(nil).HeadObject), varargs...) -} - -// ListObjectsV2 mocks base method. -func (m *MockS3API) ListObjectsV2(arg0 context.Context, arg1 *s3.ListObjectsV2Input, arg2 ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "ListObjectsV2", varargs...) - ret0, _ := ret[0].(*s3.ListObjectsV2Output) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListObjectsV2 indicates an expected call of ListObjectsV2. -func (mr *MockS3APIMockRecorder) ListObjectsV2(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjectsV2", reflect.TypeOf((*MockS3API)(nil).ListObjectsV2), varargs...) -} diff --git a/s3file.go b/s3file.go index 0e2bf6b..a07a91b 100644 --- a/s3file.go +++ b/s3file.go @@ -181,7 +181,7 @@ func buildRange(offset, length int64) *string { } else if length == 0 { // AWS doesn't support a zero-length read; we'll read 1 byte and then // ignore it in favor of http.NoBody below. - byteRange = aws.String(fmt.Sprintf("bytes=%d-%d", offset, offset)) + byteRange = aws.String(fmt.Sprintf("bytes=%d-%d", offset, offset+1)) } else if length >= 0 { byteRange = aws.String(fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)) } diff --git a/s3file_test.go b/s3file_test.go index e042907..c9a0fde 100644 --- a/s3file_test.go +++ b/s3file_test.go @@ -11,7 +11,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,11 +33,16 @@ func (m mockGetObjectAPI) HeadObject(ctx context.Context, params *s3.HeadObjectI func TestReadAt(t *testing.T) { - cases := []struct { - client func(t *testing.T) mockGetObjectAPI + type args struct { bucket string key string - expect []byte + } + + cases := []struct { + client func(t *testing.T) mockGetObjectAPI + args args + expectData []byte + expectedLength int }{ { client: func(t *testing.T) mockGetObjectAPI { @@ -46,9 +50,9 @@ func TestReadAt(t *testing.T) { getObject: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { t.Helper() - assert.Equal(t, aws.String("fooBucket"), params.Bucket) - assert.Equal(t, aws.String("barKey"), params.Key) - assert.Equal(t, aws.String("bytes=0-1023"), params.Range) + require.Equal(t, aws.String("fooBucket"), params.Bucket) + require.Equal(t, aws.String("barKey"), params.Key) + require.Equal(t, aws.String("bytes=0-1023"), params.Range) return &s3.GetObjectOutput{ Body: io.NopCloser(bytes.NewReader(make([]byte, 1024))), @@ -57,8 +61,8 @@ func TestReadAt(t *testing.T) { headObject: func(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { t.Helper() - assert.Equal(t, aws.String("fooBucket"), params.Bucket) - assert.Equal(t, aws.String("barKey"), params.Key) + require.Equal(t, aws.String("fooBucket"), params.Bucket) + require.Equal(t, aws.String("barKey"), params.Key) return &s3.HeadObjectOutput{ ContentLength: 1024, @@ -66,9 +70,12 @@ func TestReadAt(t *testing.T) { }, } }, - bucket: "fooBucket", - key: "barKey", - expect: []byte("this is the body foo bar baz"), + args: args{ + bucket: "fooBucket", + key: "barKey", + }, + expectData: []byte("this is the body foo bar baz"), + expectedLength: 1024, }, } @@ -76,25 +83,77 @@ func TestReadAt(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { assert := require.New(t) - // ctx := context.TODO() - - sysfs := NewWithClient(tt.bucket, tt.client(t)) + sysfs := NewWithClient(tt.args.bucket, tt.client(t)) - f, err := sysfs.Open(tt.key) + f, err := sysfs.Open(tt.args.key) assert.NoError(err) - data := make([]byte, 1024) + data := make([]byte, tt.expectedLength) if rc, ok := f.(io.ReaderAt); ok { n, err := rc.ReadAt(data, 0) assert.NoError(err) - assert.Equal(1024, n) + assert.Equal(tt.expectedLength, n) } }) } } +func Test_buildRange(t *testing.T) { + type args struct { + offset int64 + length int64 + } + tests := []struct { + name string + args args + want *string + }{ + { + name: "should return 0-1023 for offset 0 and length 1024", + args: args{ + offset: 0, + length: 1024, + }, + want: aws.String("bytes=0-1023"), + }, + { + // read the remainder of the file + name: "should return nil for offset 1024 and length -1", + args: args{ + offset: 1024, + length: -1, + }, + want: aws.String("bytes=1024-"), + }, + { + // a nil range will just read the entire file + name: "should return nil for offset 0 and length -1", + args: args{ + offset: 0, + length: -1, + }, + want: nil, + }, + { + // zero read will just read one byte as AWS doesn't support a zero byte range + name: "should return nil for offset 0 and length -1", + args: args{ + offset: 1024, + length: 0, + }, + want: aws.String("bytes=1024-1025"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := buildRange(tt.args.offset, tt.args.length) + require.Equal(t, aws.ToString(tt.want), aws.ToString(got)) + }) + } +} + func TestReadAtInt(t *testing.T) { if os.Getenv("TEST_BUCKET_NAME") == "" { t.Skip() diff --git a/s3iofs_test.go b/s3iofs_test.go index 3f1f0e2..1add7a3 100644 --- a/s3iofs_test.go +++ b/s3iofs_test.go @@ -1,21 +1,19 @@ package s3iofs import ( + "context" "io/fs" + "strconv" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/wolfeidau/s3iofs/mocks" ) func TestS3FS_Stat(t *testing.T) { - assert := require.New(t) - type fields struct { bucket string s3client S3API @@ -43,6 +41,8 @@ func TestS3FS_Stat(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := require.New(t) + s3fs := &S3FS{ bucket: tt.fields.bucket, s3client: tt.fields.s3client, @@ -53,47 +53,58 @@ func TestS3FS_Stat(t *testing.T) { t.Errorf("S3FS.Stat() error = %v, wantErr %v", err, tt.wantErr) return } + assert.Equal(got, tt.want) }) } } -func TestS3FS_ReadDir(t *testing.T) { - assert := require.New(t) - - ctrl := gomock.NewController(t) - - s3client := mocks.NewMockS3API(ctrl) +func TestS3FS_ReadDirTable(t *testing.T) { + type args struct { + bucket string + } - modTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") - assert.NoError(err) + modTime, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") - s3client.EXPECT().ListObjectsV2(gomock.Any(), &s3.ListObjectsV2Input{ - Bucket: aws.String("test"), - Prefix: aws.String(""), - Delimiter: aws.String("/"), - }).Return(&s3.ListObjectsV2Output{ - Contents: []types.Object{ - { - Key: aws.String("file1"), - LastModified: aws.Time(modTime), + cases := []struct { + client func(t *testing.T) mockGetObjectAPI + args args + expect []fs.DirEntry + }{ + { + client: func(t *testing.T) mockGetObjectAPI { + return mockGetObjectAPI{ + listObjectsV2: func(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { + return &s3.ListObjectsV2Output{ + Contents: []types.Object{ + { + Key: aws.String("file1"), + LastModified: aws.Time(modTime), + }, + }, + }, nil + }, + } + }, + args: args{ + bucket: "fooBucket", }, + expect: []fs.DirEntry{(*s3File)(&s3File{ + name: "file1", + bucket: "", + modTime: modTime, + })}, }, - }, nil) - - s3fs := &S3FS{ - bucket: "test", - s3client: s3client, } - got, err := s3fs.ReadDir(".") - assert.NoError(err) + for i, tt := range cases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert := require.New(t) - val := &s3File{ - name: "file1", - bucket: "", - modTime: modTime, + sysfs := NewWithClient(tt.args.bucket, tt.client(t)) + got, err := sysfs.ReadDir(".") + assert.NoError(err) + assert.Equal(tt.expect, got) + }) } - - assert.Equal([]fs.DirEntry([]fs.DirEntry{(*s3File)(val)}), got) } diff --git a/tools.go b/tools.go deleted file mode 100644 index b5034a2..0000000 --- a/tools.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build tools -// +build tools - -package s3iofs - -import ( - _ "github.com/golang/mock/mockgen" -)