diff --git a/internal/db/storage/blob/s3/reader/option.go b/internal/db/storage/blob/s3/reader/option.go index 92f910c85a..d65355c847 100644 --- a/internal/db/storage/blob/s3/reader/option.go +++ b/internal/db/storage/blob/s3/reader/option.go @@ -22,6 +22,7 @@ import ( "github.com/vdaas/vald/internal/errgroup" ) +// Option represents the functional option for reader. type Option func(r *reader) var ( @@ -32,6 +33,7 @@ var ( } ) +// WithErrGroup returns the option to set the eg. func WithErrGroup(eg errgroup.Group) Option { return func(r *reader) { if eg != nil { @@ -40,6 +42,7 @@ func WithErrGroup(eg errgroup.Group) Option { } } +// WithService returns the option to set the service. func WithService(s *s3.S3) Option { return func(r *reader) { if s != nil { @@ -48,34 +51,43 @@ func WithService(s *s3.S3) Option { } } +// WithBucket returns the option to set the bucket. func WithBucket(bucket string) Option { return func(r *reader) { r.bucket = bucket } } +// WithKey returns the option to set the key. func WithKey(key string) Option { return func(r *reader) { r.key = key } } +// WithMaxChunkSize retunrs the option to set the maxChunkSize. func WithMaxChunkSize(size int64) Option { return func(r *reader) { r.maxChunkSize = size } } +// WithBackoff returns the option to set the backoffEnabled. func WithBackoff(enabled bool) Option { return func(r *reader) { r.backoffEnabled = enabled } } +// WithBackoffOpts returns the option to set the backoffOpts. func WithBackoffOpts(opts ...backoff.Option) Option { return func(r *reader) { + if opts == nil { + return + } if r.backoffOpts == nil { r.backoffOpts = opts + return } r.backoffOpts = append(r.backoffOpts, opts...) diff --git a/internal/db/storage/blob/s3/reader/option_test.go b/internal/db/storage/blob/s3/reader/option_test.go index 0d7b9ab86e..fde22221f4 100644 --- a/internal/db/storage/blob/s3/reader/option_test.go +++ b/internal/db/storage/blob/s3/reader/option_test.go @@ -17,205 +17,137 @@ package reader import ( + "reflect" "testing" "github.com/aws/aws-sdk-go/service/s3" + "github.com/google/go-cmp/cmp" + "github.com/vdaas/vald/internal/backoff" "github.com/vdaas/vald/internal/errgroup" + "github.com/vdaas/vald/internal/errors" "go.uber.org/goleak" ) +var ( + // Goroutine leak is detected by `fastime`, but it should be ignored in the test because it is an external package. + goleakIgnoreOptions = []goleak.Option{ + goleak.IgnoreTopFunction("github.com/kpango/fastime.(*Fastime).StartTimerD.func1"), + } +) + func TestWithErrGroup(t *testing.T) { - // Change interface type to the type of object you are testing - type T = interface{} + type T = reader type args struct { eg errgroup.Group } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got error = %v, want %v", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T) error { + if !reflect.DeepEqual(obj, w.obj) { + return errors.Errorf("got = %v, want %v", obj, w.obj) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - eg: nil, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - eg: nil, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set success when eg is not nil", + args: args{ + eg: errgroup.Get(), + }, + want: want{ + obj: &T{ + eg: errgroup.Get(), + }, + }, + }, + { + name: "set success when eg is nil", + want: want{ + obj: new(T), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(tt) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithErrGroup(test.args.eg) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option do not return an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithErrGroup(test.args.eg) - obj := new(T) - got(obj) - if err := test.checkFunc(test.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + got := WithErrGroup(test.args.eg) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithService(t *testing.T) { - // Change interface type to the type of object you are testing - type T = interface{} + type T = reader type args struct { s *s3.S3 } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got error = %v, want %v", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T) error { + if !reflect.DeepEqual(obj, w.obj) { + return errors.Errorf("got = %v, want %v", obj, w.obj) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - s: nil, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - s: nil, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set success when service is not nil", + args: args{ + s: new(s3.S3), + }, + want: want{ + obj: &T{ + service: new(s3.S3), + }, + }, + }, + { + name: "set success when service is nil", + want: want{ + obj: new(T), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(tt) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } @@ -223,259 +155,393 @@ func TestWithService(t *testing.T) { defer test.afterFunc(test.args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithService(test.args.s) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option do not return an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithService(test.args.s) - obj := new(T) - got(obj) - if err := test.checkFunc(test.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + got := WithService(test.args.s) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithBucket(t *testing.T) { - // Change interface type to the type of object you are testing - type T = interface{} + type T = reader type args struct { bucket string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got error = %v, want %v", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T) error { + if !reflect.DeepEqual(obj, w.obj) { + return errors.Errorf("got = %v, want %v", obj, w.obj) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - bucket: "", - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - bucket: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set success when bucket is not nil", + args: args{ + bucket: "vald", + }, + want: want{ + obj: &T{ + bucket: "vald", + }, + }, + }, + { + name: "set success when bucket is nil", + want: want{ + obj: new(T), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(tt) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithBucket(test.args.bucket) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option do not return an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithBucket(test.args.bucket) - obj := new(T) - got(obj) - if err := test.checkFunc(test.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + got := WithBucket(test.args.bucket) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithKey(t *testing.T) { - // Change interface type to the type of object you are testing - type T = interface{} + type T = reader type args struct { key string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got error = %v, want %v", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T) error { + if !reflect.DeepEqual(obj, w.obj) { + return errors.Errorf("got = %v, want %v", obj, w.obj) + } + return nil + } - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got = %v, want %v", obj, w.obj) - } - return nil - } - */ + tests := []test{ + { + name: "set success when key is not empty string", + args: args{ + key: "vdaas", + }, + want: want{ + obj: &T{ + key: "vdaas", + }, + }, + }, + { + name: "set success when key is empty string", + want: want{ + obj: new(T), + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) + if test.beforeFunc != nil { + test.beforeFunc(test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(test.args) + } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + got := WithKey(test.args.key) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func TestWithMaxChunkSize(t *testing.T) { + type T = reader + type args struct { + size int64 + } + type want struct { + obj *T + } + type test struct { + name string + args args + want want + checkFunc func(want, *T) error + beforeFunc func(args) + afterFunc func(args) + } + defaultCheckFunc := func(w want, got *T) error { + if !reflect.DeepEqual(got, w.obj) { + return errors.Errorf("got = %v, want %v", got, w.obj) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - key: "", - }, - want: want { - obj: new(T), - }, - }, - */ + { + name: "set success when size is positive number", + args: args{ + size: 10, + }, + want: want{ + obj: &T{ + maxChunkSize: 10, + }, + }, + }, + { + name: "set success when size is negative number", + args: args{ + size: -10, + }, + want: want{ + obj: &T{ + maxChunkSize: -10, + }, + }, + }, + { + name: "set success when size is zero", + want: want{ + obj: new(T), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) + if test.beforeFunc != nil { + test.beforeFunc(test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(test.args) + } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + + got := WithMaxChunkSize(test.args.size) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - key: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ +func TestWithBackoff(t *testing.T) { + type T = reader + type args struct { + enabled bool + } + type want struct { + obj *T + } + type test struct { + name string + args args + want want + checkFunc func(want, *T) error + beforeFunc func(args) + afterFunc func(args) + } + defaultCheckFunc := func(w want, got *T) error { + if !reflect.DeepEqual(got, w.obj) { + return errors.Errorf("got = %v, want %v", got, w.obj) + } + return nil + } + tests := []test{ + { + name: "set success when backoff enabled is true", + args: args{ + enabled: true, + }, + want: want{ + obj: &T{ + backoffEnabled: true, + }, + }, + }, + { + name: "set success when backoff enabled is false", + want: want{ + obj: new(T), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(tt) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } + got := WithBackoff(test.args.enabled) + obj := new(T) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } - got := WithKey(test.args.key) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ + }) + } +} - // Uncomment this block if the option do not return an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithKey(test.args.key) - obj := new(T) - got(obj) - if err := test.checkFunc(test.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ +func TestWithBackoffOpts(t *testing.T) { + type T = reader + type args struct { + opts []backoff.Option + defaultOpts []backoff.Option + } + type want struct { + obj *T + } + type test struct { + name string + args args + want want + checkFunc func(want, *T) error + beforeFunc func(args, *T) + afterFunc func(args, *T) + } + defaultCheckFunc := func(w want, got *T) error { + opts := []cmp.Option{ + cmp.AllowUnexported(*got), + cmp.AllowUnexported(*w.obj), + cmp.Comparer(func(want, got []backoff.Option) bool { + return len(got) == len(want) + }), + cmp.Comparer(func(want, got backoff.Option) bool { + return reflect.ValueOf(got).Pointer() == reflect.ValueOf(want).Pointer() + }), + } + if diff := cmp.Diff(w.obj, got, opts...); diff != "" { + return errors.Errorf("got = %v, want %v", got, w.obj) + } + return nil + } + tests := []test{ + func() test { + opts := []backoff.Option{ + backoff.WithRetryCount(1), + } + return test{ + name: "set success when opts is not nil and backoffOpts is nil", + args: args{ + opts: opts, + }, + want: want{ + obj: &T{ + backoffOpts: opts, + }, + }, + } + }(), + func() test { + defaultOpts := []backoff.Option{} + opts := []backoff.Option{ + backoff.WithRetryCount(1), + } + return test{ + name: "set success when opts is not nil and backoffOpts is not nil", + args: args{ + opts: opts, + defaultOpts: defaultOpts, + }, + want: want{ + obj: &T{ + backoffOpts: append(defaultOpts, opts...), + }, + }, + beforeFunc: func(args args, r *T) { + r.backoffOpts = args.defaultOpts + }, + } + }(), + func() test { + return test{ + name: "set success when opts is nil", + want: want{ + obj: new(T), + }, + } + }(), + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) + obj := new(T) + if test.beforeFunc != nil { + test.beforeFunc(test.args, obj) + } + if test.afterFunc != nil { + defer test.afterFunc(test.args, obj) + } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + + got := WithBackoffOpts(test.args.opts...) + got(obj) + if err := test.checkFunc(test.want, obj); err != nil { + tt.Errorf("error = %v", err) + } }) } }