diff --git a/.github/workflows/update-web-docs.yml b/.github/workflows/update-web-docs.yml new file mode 100644 index 00000000000..442ae85cf0a --- /dev/null +++ b/.github/workflows/update-web-docs.yml @@ -0,0 +1,43 @@ +# +# Copyright (C) 2019-2024 vdaas.org vald team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: "Update web contents" +on: + workflow_dispatch: + push: + branches: + - main + - "release/v*.*" + tags: + - "*.*.*" + - "v*.*.*" + - "*.*.*-*" + - "v*.*.*-*" + paths: + - "**.md" + - "assets/docs/**" +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - name: Dispatch + run: | + curl --fail -u "${USER}:${TOKEN}" \ + -X POST https://api.github.com/repos/vdaas/web/dispatches \ + -H 'Accept: application/vnd.github.everest-preview+json' \ + --data '{"event_type": "update-contents"}' + env: + USER: ${{ secrets.DISPATCH_USER }} + TOKEN: ${{ secrets.DISPATCH_TOKEN }} diff --git a/go.mod b/go.mod index 8fe8ea44a67..e0e6b1da422 100644 --- a/go.mod +++ b/go.mod @@ -423,6 +423,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/akrylysov/pogreb v0.10.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/campoy/embedmd v1.0.0 // indirect diff --git a/go.sum b/go.sum index 97a905200c7..94319ba434f 100644 --- a/go.sum +++ b/go.sum @@ -174,6 +174,8 @@ github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyR github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78= +github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= diff --git a/hack/go.mod.default b/hack/go.mod.default index b3f221bbc96..33efd46fae7 100644 --- a/hack/go.mod.default +++ b/hack/go.mod.default @@ -234,6 +234,7 @@ replace ( github.com/pkg/errors => github.com/pkg/errors upgrade github.com/pkg/sftp => github.com/pkg/sftp upgrade github.com/pmezard/go-difflib => github.com/pmezard/go-difflib upgrade + github.com/akrylysov/pogreb => github.com/akrylysov/pogreb upgrade github.com/prashantv/gostub => github.com/prashantv/gostub upgrade github.com/prometheus/client_golang => github.com/prometheus/client_golang upgrade github.com/prometheus/client_model => github.com/prometheus/client_model upgrade diff --git a/internal/db/kvs/pogreb/options.go b/internal/db/kvs/pogreb/options.go new file mode 100644 index 00000000000..58c62728a99 --- /dev/null +++ b/internal/db/kvs/pogreb/options.go @@ -0,0 +1,84 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package pogreb + +import ( + "time" + + "github.com/akrylysov/pogreb" +) + +var deafultOpts = []Option{ + WithPath("pogreb.db"), + WithBackgroundSyncInterval("5s"), +} + +// Option represents the functional option for database. +type Option func(*db) error + +// WithPath returns the option to set path. +func WithPath(path string) Option { + return func(d *db) error { + if path != "" { + d.path = path + } + return nil + } +} + +// WithBackgroundSyncInterval returns the option to sets the amount of time between background Sync() calls. +// Setting the value to 0 disables the automatic background synchronization. +// Setting the value to -1 or less makes the DB call Sync() after every write operation. +func WithBackgroundSyncInterval(s string) Option { + return func(d *db) error { + if s == "" { + return nil + } + dur, err := time.ParseDuration(s) + if err != nil { + return err + } + if d.opts == nil { + d.opts = new(pogreb.Options) + } + if dur < -1 { + dur = -1 + } + d.opts.BackgroundSyncInterval = dur + return nil + } +} + +// WithBackgroundCompactionInterval returns the option to sets the amount of time between background Compact() calls. +// Setting the value to 0 or less disables the automatic background compaction. +func WithBackgroundCompactionInterval(s string) Option { + return func(d *db) error { + if s == "" { + return nil + } + dur, err := time.ParseDuration(s) + if err != nil { + return err + } + if d.opts == nil { + d.opts = new(pogreb.Options) + } + + if dur < 0 { + dur = 0 + } + d.opts.BackgroundCompactionInterval = dur + return nil + } +} diff --git a/internal/db/kvs/pogreb/options_test.go b/internal/db/kvs/pogreb/options_test.go new file mode 100644 index 00000000000..58d0abf847e --- /dev/null +++ b/internal/db/kvs/pogreb/options_test.go @@ -0,0 +1,261 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package pogreb + +import ( + "reflect" + "testing" + "time" + + "github.com/akrylysov/pogreb" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/test/goleak" +) + +func TestWithPath(t *testing.T) { + t.Parallel() + type args struct { + path string + } + type want struct { + want Option + err error + path string + } + type test struct { + name string + args args + want want + checkFunc func(want, *db, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got *db, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if err == nil { + if !reflect.DeepEqual(got.path, w.path) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + } + return nil + } + tests := []test{ + func() test { + path := "db-dir" + return test{ + name: "Succeeds to apply option", + args: args{ + path: path, + }, + want: want{ + path: path, + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got := new(db) + err := WithPath(test.args.path)(got) + if err := checkFunc(test.want, got, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func TestWithBackgroundSyncInterval(t *testing.T) { + t.Parallel() + type args struct { + s string + } + type want struct { + want Option + err error + opts *pogreb.Options + } + type test struct { + name string + args args + want want + checkFunc func(want, *db, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got *db, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if err == nil { + if !reflect.DeepEqual(got.opts.BackgroundSyncInterval, w.opts.BackgroundSyncInterval) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + } + return nil + } + tests := []test{ + func() test { + dur := "100ms" + return test{ + name: "Succeeds to apply option", + args: args{ + s: dur, + }, + want: want{ + opts: &pogreb.Options{ + BackgroundSyncInterval: 100 * time.Millisecond, + }, + }, + } + }(), + func() test { + dur := "invalid" + return test{ + name: "Fails to apply option with invalid value", + args: args{ + s: dur, + }, + want: want{ + err: errors.New("time: invalid duration \"invalid\""), + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got := new(db) + err := WithBackgroundSyncInterval(test.args.s)(got) + if err := checkFunc(test.want, got, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func TestWithBackgroundCompactionInterval(t *testing.T) { + t.Parallel() + type args struct { + s string + } + type want struct { + want Option + err error + opts *pogreb.Options + } + type test struct { + name string + args args + want want + checkFunc func(want, *db, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got *db, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if err == nil { + if !reflect.DeepEqual(got.opts.BackgroundCompactionInterval, w.opts.BackgroundCompactionInterval) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + } + return nil + } + tests := []test{ + func() test { + dur := "100ms" + return test{ + name: "Succeeds to apply option", + args: args{ + s: dur, + }, + want: want{ + opts: &pogreb.Options{ + BackgroundCompactionInterval: 100 * time.Millisecond, + }, + }, + } + }(), + func() test { + dur := "invalid" + return test{ + name: "Fails to apply option with invalid value", + args: args{ + s: dur, + }, + want: want{ + err: errors.New("time: invalid duration \"invalid\""), + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got := new(db) + err := WithBackgroundCompactionInterval(test.args.s)(got) + if err := checkFunc(test.want, got, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOT IMPLEMENTED BELOW diff --git a/internal/db/kvs/pogreb/pogreb.go b/internal/db/kvs/pogreb/pogreb.go new file mode 100644 index 00000000000..d2dc8854bd8 --- /dev/null +++ b/internal/db/kvs/pogreb/pogreb.go @@ -0,0 +1,137 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package pogreb + +import ( + "context" + "os" + "reflect" + + "github.com/akrylysov/pogreb" + + "github.com/vdaas/vald/internal/conv" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" +) + +// Pogreb represents an interface for operating the pogreb database. +type Pogreb interface { + Set(key string, val []byte) error + Get(key string) ([]byte, bool, error) + Delete(key string) error + Range(ctx context.Context, f func(key string, val []byte) bool) error + Len() uint32 + Close(remove bool) error +} + +type db struct { + db *pogreb.DB + opts *pogreb.Options + path string +} + +// New returns a new pogreb instance. +// If the directory path does not exist, it creates a directory for database. +// If opts is nil, it uses default options. +func New(opts ...Option) (_ Pogreb, err error) { + db := new(db) + for _, opt := range append(deafultOpts, opts...) { + if err := opt(db); err != nil { + oerr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + e := &errors.ErrCriticalOption{} + if errors.As(oerr, &e) { + log.Error(err) + return nil, oerr + } + log.Warn(oerr) + } + } + + // If db.opts is nil, a default value is used. + db.db, err = pogreb.Open(db.path, db.opts) + if err != nil { + return nil, err + } + return db, nil +} + +// Set sets the value for a key. +func (d *db) Set(key string, val []byte) error { + return d.db.Put(conv.Atob(key), val) +} + +// Get returns the value stored in the database for a key. +// The ok result indicates whether value was found in the database. +func (d *db) Get(key string) ([]byte, bool, error) { + val, err := d.db.Get(conv.Atob(key)) + if err != nil { + return nil, false, err + } + // If val is nil, it means that there is no value associated with key, so false is returned. + if val == nil { + return nil, false, nil + } + return val, true, nil +} + +// Delete deletes the given key from the database. +func (d *db) Delete(key string) error { + // NOTE: Even if the key does not exist in the database, no error occurs. + // Depending on the future use case, it may be necessary to check for the existence of the key, in which case the `Has` method can be used. + return d.db.Delete(conv.Atob(key)) +} + +// Range calls f sequentially for each key and value present in the database. +// If f returns false, range stops the iteration. +func (d *db) Range(ctx context.Context, f func(key string, val []byte) bool) error { + it := d.db.Items() + for { + select { + case <-ctx.Done(): + return nil + default: + key, val, err := it.Next() + if err != nil { + if errors.Is(err, pogreb.ErrIterationDone) { + return nil + } + return err + } + if !f(conv.Btoa(key), val) { + return nil + } + } + } +} + +// Len returns the number of keys in the DB. +func (d *db) Len() uint32 { + return d.db.Count() +} + +// Close closes the database and removes the file if remove is true. +func (d *db) Close(remove bool) (err error) { + if serr := d.db.Sync(); serr != nil { + err = serr + } + if cerr := d.db.Close(); cerr != nil { + err = errors.Join(err, cerr) + } + if remove { + if rerr := os.RemoveAll(d.path); rerr != nil { + err = errors.Join(err, rerr) + } + } + return err +} diff --git a/internal/db/kvs/pogreb/pogreb_test.go b/internal/db/kvs/pogreb/pogreb_test.go new file mode 100644 index 00000000000..ac4dda8a826 --- /dev/null +++ b/internal/db/kvs/pogreb/pogreb_test.go @@ -0,0 +1,801 @@ +// Copyright (C) 2019-2024 vdaas.org vald team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package pogreb + +import ( + "context" + "reflect" + "testing" + + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/test/goleak" +) + +func TestNew(t *testing.T) { + t.Parallel() + type args struct { + opts []Option + } + type want struct { + err error + } + type test struct { + name string + args args + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + return test{ + name: "Succeeds to create pogreb instance with new path when the path does not exist", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + }, + } + }(), + func() test { + opts := []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + } + return test{ + name: " Succeeds to restart the pogres instance", + args: args{ + opts: opts, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + db, err := New(opts...) + if err != nil { + t.Fatal(err) + } + if err := db.Close(false); err != nil { + t.Fatal(err) + } + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + _, err := New(test.args.opts...) + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOTE: To test Get, data needs to be inserted beforehand, so we test Set and Get together. +// If the test function name is changed, it is regenerated by gotests, so the function name is kept the same. +func Test_db_Get(t *testing.T) { + t.Parallel() + type args struct { + opts []Option + key string + } + type want struct { + want []byte + want1 bool + err error + } + type test struct { + name string + args args + want want + checkFunc func(want, []byte, bool, error) error + beforeFunc func(*testing.T, Pogreb, args) + afterFunc func(*testing.T, Pogreb, args) + } + defaultCheckFunc := func(w want, got []byte, got1 bool, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + if !reflect.DeepEqual(got1, w.want1) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got1, w.want1) + } + return nil + } + tests := []test{ + func() test { + var ( + key = "key" + val = []byte("val") + ) + return test{ + name: "Succeeds to get the value associated with the key", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + key: key, + }, + want: want{ + want: val, + want1: true, + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + func() test { + var ( + key = "key" + val = []byte("val") + ) + return test{ + name: "Fails to get the value associated with the key if it does not exist", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + key: "not-exist", + }, + want: want{ + want1: false, + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := New(test.args.opts...) + if err != nil { + t.Fatal(err) + } + if test.beforeFunc != nil { + test.beforeFunc(tt, d, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, d, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got, got1, err := d.Get(test.args.key) + if err := checkFunc(test.want, got, got1, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOTE: To test Delete, data needs to be inserted beforehand, so we test Set and Delete together. +// If the test function name is changed, it is regenerated by gotests, so the function name is kept the same. +func Test_db_Delete(t *testing.T) { + t.Parallel() + type args struct { + opts []Option + key string + } + type want struct { + err error + } + type test struct { + name string + args args + want want + checkFunc func(want, Pogreb, error) error + beforeFunc func(*testing.T, Pogreb, args) + afterFunc func(*testing.T, Pogreb, args) + } + defaultCheckFunc := func(w want, _ Pogreb, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + var ( + key = "key" + val = []byte("val") + ) + return test{ + name: "Succeeds to delete the value associated with the key", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + key: key, + }, + checkFunc: func(w want, d Pogreb, err error) error { + if err := defaultCheckFunc(w, d, err); err != nil { + return err + } + _, ok, err := d.Get(key) + if err != nil { + return err + } + if ok { + return errors.New("key exists") + } + return nil + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := New(test.args.opts...) + if err != nil { + t.Fatal(err) + } + if test.beforeFunc != nil { + test.beforeFunc(tt, d, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, d, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + err = d.Delete(test.args.key) + if err := checkFunc(test.want, d, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// skipcq: GO-R1005 +func Test_db_Range(t *testing.T) { + t.Parallel() + type args struct { + opts []Option + ctx context.Context + f func(key string, val []byte) bool + } + type want struct { + err error + } + type test struct { + name string + args args + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T, Pogreb, args) + afterFunc func(*testing.T, Pogreb, args) + } + defaultCheckFunc := func(w want, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + } + return nil + } + tests := []test{ + func() test { + data := map[string][]byte{ + "key-1": []byte("val-1"), + "key-2": []byte("val-2"), + } + got := make(map[string][]byte) + return test{ + name: "Succeeds to get all keys", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + ctx: context.Background(), + f: func(key string, val []byte) bool { + got[key] = val + return true + }, + }, + checkFunc: func(w want, err error) error { + if err := defaultCheckFunc(w, err); err != nil { + return err + } + if !reflect.DeepEqual(got, data) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, data) + } + return nil + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + for key, val := range data { + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + func() test { + got := make(map[string][]byte) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return test{ + name: "Fails to get all keys when the context is canceled", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + ctx: ctx, + f: func(key string, val []byte) bool { + got[key] = val + return true + }, + }, + checkFunc: func(w want, err error) error { + if err := defaultCheckFunc(w, err); err != nil { + return err + } + if data := make(map[string][]byte); !reflect.DeepEqual(got, data) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, data) + } + return nil + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + data := map[string][]byte{ + "key-1": []byte("val-1"), + "key-2": []byte("val-2"), + } + for key, val := range data { + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := New(test.args.opts...) + if err != nil { + t.Fatal(err) + } + if test.beforeFunc != nil { + test.beforeFunc(tt, d, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, d, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + err = d.Range(test.args.ctx, test.args.f) + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_db_Len(t *testing.T) { + t.Parallel() + type args struct { + opts []Option + } + type want struct { + want uint32 + } + type test struct { + name string + args args + want want + checkFunc func(want, uint32) error + beforeFunc func(*testing.T, Pogreb, args) + afterFunc func(*testing.T, Pogreb, args) + } + defaultCheckFunc := func(w want, got uint32) error { + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + return nil + } + tests := []test{ + func() test { + data := map[string][]byte{ + "key-1": []byte("val-1"), + "key-2": []byte("val-2"), + } + return test{ + name: "Succeeds to get the number of keys", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + }, + want: want{ + want: uint32(len(data)), + }, + beforeFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + for key, val := range data { + if err := d.Set(key, val); err != nil { + t.Fatal(err) + } + } + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + func() test { + return test{ + name: "Succeeds to get the number of keys when key does not exist", + args: args{ + opts: []Option{ + WithPath(t.TempDir()), + WithBackgroundSyncInterval("0s"), + }, + }, + afterFunc: func(t *testing.T, d Pogreb, args args) { + t.Helper() + if err := d.Close(true); err != nil { + t.Fatal(err) + } + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + + d, err := New(test.args.opts...) + if err != nil { + t.Fatal(err) + } + if test.beforeFunc != nil { + test.beforeFunc(tt, d, test.args) + } + if test.afterFunc != nil { + defer test.afterFunc(tt, d, test.args) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + got := d.Len() + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOT IMPLEMENTED BELOW +// +// +// func Test_db_Set(t *testing.T) { +// type args struct { +// key string +// val []byte +// } +// type fields struct { +// db *pogreb.DB +// opts *pogreb.Options +// path string +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// key:"", +// val:nil, +// }, +// fields: fields { +// db:nil, +// opts:nil, +// path:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// key:"", +// val:nil, +// }, +// fields: fields { +// db:nil, +// opts:nil, +// path:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// d := &db{ +// db: test.fields.db, +// opts: test.fields.opts, +// path: test.fields.path, +// } +// +// err := d.Set(test.args.key, test.args.val) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_db_Close(t *testing.T) { +// type args struct { +// remove bool +// } +// type fields struct { +// db *pogreb.DB +// opts *pogreb.Options +// path string +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, err error) error { +// if !errors.Is(err, w.err) { +// return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// remove:false, +// }, +// fields: fields { +// db:nil, +// opts:nil, +// path:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// }, +// */ +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// args: args { +// remove:false, +// }, +// fields: fields { +// db:nil, +// opts:nil, +// path:"", +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T, args args) { +// t.Helper() +// }, +// } +// }(), +// */ +// } +// +// for _, tc := range tests { +// test := tc +// t.Run(test.name, func(tt *testing.T) { +// tt.Parallel() +// defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) +// if test.beforeFunc != nil { +// test.beforeFunc(tt, test.args) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt, test.args) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// d := &db{ +// db: test.fields.db, +// opts: test.fields.opts, +// path: test.fields.path, +// } +// +// err := d.Close(test.args.remove) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// }