From 30d8249249e8474ffab4012bb6331fc3a3482c4f Mon Sep 17 00:00:00 2001 From: vankichi Date: Thu, 19 Oct 2023 11:41:01 +0900 Subject: [PATCH] :white_check_mark: add benchmark operator reconcile test Signed-off-by: vankichi --- internal/k8s/job/job.go | 3 + internal/test/mock/controller_runtime.go | 67 + pkg/tools/benchmark/job/service/insert.go | 7 + pkg/tools/benchmark/job/service/job.go | 2 + pkg/tools/benchmark/job/service/object.go | 6 + pkg/tools/benchmark/job/service/remove.go | 3 + pkg/tools/benchmark/job/service/search.go | 14 + pkg/tools/benchmark/job/service/update.go | 7 + pkg/tools/benchmark/job/service/upsert.go | 7 + .../benchmark/operator/service/operator.go | 25 +- .../operator/service/operator_test.go | 4509 +++++++++++++++++ 11 files changed, 4644 insertions(+), 6 deletions(-) create mode 100644 internal/test/mock/controller_runtime.go create mode 100644 pkg/tools/benchmark/operator/service/operator_test.go diff --git a/internal/k8s/job/job.go b/internal/k8s/job/job.go index 76fda87522..5c00db89a0 100644 --- a/internal/k8s/job/job.go +++ b/internal/k8s/job/job.go @@ -48,6 +48,9 @@ type reconciler struct { // Job is a type alias for the k8s job definition. type Job = batchv1.Job +// JobStatus is a type alias for the k8s job status definition. +type JobStatus = batchv1.JobStatus + // New returns the JobWatcher that implements reconciliation loop, or any errors occurred. func New(opts ...Option) (JobWatcher, error) { r := &reconciler{ diff --git a/internal/test/mock/controller_runtime.go b/internal/test/mock/controller_runtime.go new file mode 100644 index 0000000000..30a3835416 --- /dev/null +++ b/internal/test/mock/controller_runtime.go @@ -0,0 +1,67 @@ +// Copyright (C) 2019-2023 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 mock + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type MockSubResourceWriter struct { + client.SubResourceWriter +} + +func (s *MockSubResourceWriter) Update(context.Context, client.Object, ...client.SubResourceUpdateOption) error { + return nil +} + +type MockClient struct { + client.Client +} + +func (*MockClient) Status() client.SubResourceWriter { + s := MockSubResourceWriter{ + SubResourceWriter: &MockSubResourceWriter{}, + } + return s.SubResourceWriter +} + +func (*MockClient) Get(context.Context, client.ObjectKey, client.Object, ...client.GetOption) error { + return nil +} + +func (*MockClient) Create(context.Context, client.Object, ...client.CreateOption) error { + return nil +} + +func (*MockClient) Delete(context.Context, client.Object, ...client.DeleteOption) error { + return nil +} + +func (*MockClient) DeleteAllOf(context.Context, client.Object, ...client.DeleteAllOfOption) error { + return nil +} + +type MockManager struct { + manager.Manager +} + +func (m *MockManager) GetClient() client.Client { + c := &MockClient{ + Client: &MockClient{}, + } + return c.Client +} diff --git a/pkg/tools/benchmark/job/service/insert.go b/pkg/tools/benchmark/job/service/insert.go index b38c3622b2..d654ecc1a4 100644 --- a/pkg/tools/benchmark/job/service/insert.go +++ b/pkg/tools/benchmark/job/service/insert.go @@ -56,6 +56,10 @@ func (j *job) insert(ctx context.Context, ech chan error) error { case ech <- err: } } + // loopCnt represents the quotient of iter divided by the len(vecs). + // This is to account for when iter exceeds len(vecs). + // It is used to calculate idx to determine which index of vecs to access. + // idx takes between <0, len(vecs)-1>. loopCnt := math.Floor(float64(iter-1) / float64(len(vecs))) idx := iter - 1 - (len(vecs) * int(loopCnt)) res, err := j.client.Insert(egctx, &payload.Insert_Request{ @@ -71,6 +75,9 @@ func (j *job) insert(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return errors.Join(err, egctx.Err()) default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } // TODO: send metrics to the Prometeus diff --git a/pkg/tools/benchmark/job/service/job.go b/pkg/tools/benchmark/job/service/job.go index b047051ea1..299b114172 100644 --- a/pkg/tools/benchmark/job/service/job.go +++ b/pkg/tools/benchmark/job/service/job.go @@ -254,6 +254,7 @@ func (j *job) Start(ctx context.Context) (<-chan error, error) { cech, err := j.client.Start(ctx) if err != nil { log.Error("[benchmark job] failed to start connection monitor") + close(ech) return nil, err } j.eg.Go(func() error { @@ -280,6 +281,7 @@ func (j *job) Start(ctx context.Context) (<-chan error, error) { case ech <- err: } } + close(ech) if err := p.Signal(syscall.SIGTERM); err != nil { log.Error(err) } diff --git a/pkg/tools/benchmark/job/service/object.go b/pkg/tools/benchmark/job/service/object.go index 9284b50f95..91732f78c2 100644 --- a/pkg/tools/benchmark/job/service/object.go +++ b/pkg/tools/benchmark/job/service/object.go @@ -56,6 +56,9 @@ func (j *job) exists(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return nil default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } log.Debugf("[benchmark job] Finish exists: iter= %d \n%v\n", idx, res) @@ -115,6 +118,9 @@ func (j *job) getObject(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return nil default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } if res != nil { diff --git a/pkg/tools/benchmark/job/service/remove.go b/pkg/tools/benchmark/job/service/remove.go index c8a6650bc2..dbf58664aa 100644 --- a/pkg/tools/benchmark/job/service/remove.go +++ b/pkg/tools/benchmark/job/service/remove.go @@ -65,6 +65,9 @@ func (j *job) remove(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return errors.Join(err, egctx.Err()) default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } log.Debugf("[benchmark job] Finish remove: iter= %d \n%v", idx, res) diff --git a/pkg/tools/benchmark/job/service/search.go b/pkg/tools/benchmark/job/service/search.go index 715b8f5356..fe5f81f9f4 100644 --- a/pkg/tools/benchmark/job/service/search.go +++ b/pkg/tools/benchmark/job/service/search.go @@ -65,6 +65,10 @@ func (j *job) search(ctx context.Context, ech chan error) error { case ech <- err: } } + // loopCnt represents the quotient of iter divided by the len(vecs). + // This is to account for when iter exceeds len(vecs). + // It is used to calculate idx to determine which index of vecs to access. + // idx takes between <0, len(vecs)-1>. loopCnt := math.Floor(float64(iter-1) / float64(len(vecs))) idx := iter - 1 - (len(vecs) * int(loopCnt)) if len(vecs[idx]) != j.dimension { @@ -81,6 +85,9 @@ func (j *job) search(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return nil default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } if res != nil && j.searchConfig.EnableLinearSearch { @@ -116,6 +123,10 @@ func (j *job) search(ctx context.Context, ech chan error) error { } } log.Debugf("[benchmark job] Start linear search: iter = %d", iter) + // loopCnt represents the quotient of iter divided by the len(vecs). + // This is to account for when iter exceeds len(vecs). + // It is used to calculate idx to determine which index of vecs to access. + // idx takes between <0, len(vecs)-1>. loopCnt := math.Floor(float64(i-1) / float64(len(vecs))) idx := iter - 1 - (len(vecs) * int(loopCnt)) if len(vecs[idx]) != j.dimension { @@ -132,6 +143,9 @@ func (j *job) search(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return errors.Join(err, egctx.Err()) default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } if res != nil { diff --git a/pkg/tools/benchmark/job/service/update.go b/pkg/tools/benchmark/job/service/update.go index f641f84f57..06a95ebaa3 100644 --- a/pkg/tools/benchmark/job/service/update.go +++ b/pkg/tools/benchmark/job/service/update.go @@ -57,6 +57,10 @@ func (j *job) update(ctx context.Context, ech chan error) error { case ech <- err: } } + // loopCnt represents the quotient of iter divided by the len(vecs). + // This is to account for when iter exceeds len(vecs). + // It is used to calculate idx to determine which index of vecs to access. + // idx takes between <0, len(vecs)-1>. loopCnt := math.Floor(float64(iter-1) / float64(len(vecs))) idx := iter - 1 - (len(vecs) * int(loopCnt)) res, err := j.client.Update(egctx, &payload.Update_Request{ @@ -72,6 +76,9 @@ func (j *job) update(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return errors.Join(err, egctx.Err()) default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } if res != nil { diff --git a/pkg/tools/benchmark/job/service/upsert.go b/pkg/tools/benchmark/job/service/upsert.go index 71a88cfda7..cf77479a79 100644 --- a/pkg/tools/benchmark/job/service/upsert.go +++ b/pkg/tools/benchmark/job/service/upsert.go @@ -57,6 +57,10 @@ func (j *job) upsert(ctx context.Context, ech chan error) error { case ech <- err: } } + // loopCnt represents the quotient of iter divided by the len(vecs). + // This is to account for when iter exceeds len(vecs). + // It is used to calculate idx to determine which index of vecs to access. + // idx takes between <0, len(vecs)-1>. loopCnt := math.Floor(float64(iter-1) / float64(len(vecs))) idx := iter - 1 - (len(vecs) * int(loopCnt)) res, err := j.client.Upsert(egctx, &payload.Upsert_Request{ @@ -72,6 +76,9 @@ func (j *job) upsert(ctx context.Context, ech chan error) error { log.Errorf("[benchmark job] context error is detected: %s\t%s", err.Error(), egctx.Err()) return errors.Join(err, egctx.Err()) default: + // TODO: count up error for observe benchmark job + // We should wait for refactoring internal/o11y. + log.Errorf("[benchmark job] err: %s", err.Error()) } } if res != nil { diff --git a/pkg/tools/benchmark/operator/service/operator.go b/pkg/tools/benchmark/operator/service/operator.go index ef377b6227..867541fe21 100644 --- a/pkg/tools/benchmark/operator/service/operator.go +++ b/pkg/tools/benchmark/operator/service/operator.go @@ -57,9 +57,9 @@ type operator struct { jobNamespace string jobImage string jobImagePullPolicy string - scenarios atomic.Pointer[map[string]*scenario] - benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] - jobs atomic.Pointer[map[string]string] + scenarios *atomic.Pointer[map[string]*scenario] + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + jobs *atomic.Pointer[map[string]string] rcd time.Duration // reconcile check duration eg errgroup.Group ctrl k8s.Controller @@ -137,6 +137,10 @@ func (o *operator) initCtrl() error { } func (o *operator) getAtomicScenario() map[string]*scenario { + if o.scenarios == nil { + o.scenarios = &atomic.Pointer[map[string]*scenario]{} + return nil + } if v := o.scenarios.Load(); v != nil { return *(v) } @@ -144,6 +148,10 @@ func (o *operator) getAtomicScenario() map[string]*scenario { } func (o *operator) getAtomicBenchJob() map[string]*v1.ValdBenchmarkJob { + if o.benchjobs == nil { + o.benchjobs = &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + return nil + } if v := o.benchjobs.Load(); v != nil { return *(v) } @@ -151,6 +159,10 @@ func (o *operator) getAtomicBenchJob() map[string]*v1.ValdBenchmarkJob { } func (o *operator) getAtomicJob() map[string]string { + if o.jobs == nil { + o.jobs = &atomic.Pointer[map[string]string]{} + return nil + } if v := o.jobs.Load(); v != nil { return *(v) } @@ -171,7 +183,7 @@ func (o *operator) jobReconcile(ctx context.Context, jobList map[string][]job.Jo if cjobs == nil { cjobs = map[string]string{} } - // jobStatus is used for update benchmark job resource status + // benchmarkJobStatus is used for update benchmark job resource status benchmarkJobStatus := make(map[string]v1.BenchmarkJobStatus) // jobNames is used for check whether cjobs has delted job. // If cjobs has the delted job, it will be remove the end of jobReconcile function. @@ -215,7 +227,7 @@ func (o *operator) jobReconcile(ctx context.Context, jobList map[string][]job.Jo log.Debug("[reconcile job] finish") } -// benchmarkJobReconcile gets the vald benchmark job resource list and create Job for running benchmark job. +// benchJobReconcile gets the vald benchmark job resource list and create Job for running benchmark job. func (o *operator) benchJobReconcile(ctx context.Context, benchJobList map[string]v1.ValdBenchmarkJob) { log.Debugf("[reconcile benchmark job resource] job list: %#v", benchJobList) if len(benchJobList) == 0 { @@ -228,6 +240,7 @@ func (o *operator) benchJobReconcile(ctx context.Context, benchJobList map[strin if cbjl == nil { cbjl = make(map[string]*v1.ValdBenchmarkJob, 0) } + // jobStatus is used for update benchmarkJob CR status if updating is needed. jobStatus := make(map[string]v1.BenchmarkJobStatus) for k := range benchJobList { // update scenario status @@ -475,7 +488,7 @@ func (o *operator) createJob(ctx context.Context, bjr v1.ValdBenchmarkJob) error benchjob.WithTTLSecondsAfterFinished(int32(bjr.Spec.TTLSecondsAfterFinished)), ) if err != nil { - return nil + return err } // create job c := o.ctrl.GetManager().GetClient() diff --git a/pkg/tools/benchmark/operator/service/operator_test.go b/pkg/tools/benchmark/operator/service/operator_test.go new file mode 100644 index 0000000000..f7b49866d4 --- /dev/null +++ b/pkg/tools/benchmark/operator/service/operator_test.go @@ -0,0 +1,4509 @@ +// Copyright (C) 2019-2023 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 service + +import ( + "context" + "reflect" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/vdaas/vald/internal/config" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/k8s" + "github.com/vdaas/vald/internal/k8s/job" + v1 "github.com/vdaas/vald/internal/k8s/vald/benchmark/api/v1" + "github.com/vdaas/vald/internal/test/goleak" + "github.com/vdaas/vald/internal/test/mock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// mockCtrl is used for mock the request to the Kubernetes API. +type mockCtrl struct { + StartFunc func(ctx context.Context) (<-chan error, error) + GetManagerFunc func() k8s.Manager +} + +func (m *mockCtrl) Start(ctx context.Context) (<-chan error, error) { + return m.StartFunc(ctx) +} + +func (m *mockCtrl) GetManager() k8s.Manager { + return m.GetManagerFunc() +} + +func Test_operator_getAtomicScenario(t *testing.T) { + type fields struct { + scenarios *atomic.Pointer[map[string]*scenario] + } + type want struct { + want map[string]*scenario + } + type test struct { + name string + fields fields + want want + checkFunc func(want, map[string]*scenario) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, got map[string]*scenario) 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{ + { + name: "get nil when atomic has no resource", + fields: fields{}, + want: want{ + want: nil, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + }, + { + name: "get scenarios when scenario list is stored", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scneario-insert": v1.BenchmarkJobAvailable, + "scneario-search": v1.BenchmarkJobAvailable, + }, + }, + }) + return &ap + }(), + }, + want: want{ + want: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scneario-insert": v1.BenchmarkJobAvailable, + "scneario-search": v1.BenchmarkJobAvailable, + }, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + 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) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + o := &operator{ + scenarios: test.fields.scenarios, + } + + got := o.getAtomicScenario() + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_getAtomicBenchJob(t *testing.T) { + type fields struct { + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + } + type want struct { + want map[string]*v1.ValdBenchmarkJob + } + type test struct { + name string + fields fields + want want + checkFunc func(want, map[string]*v1.ValdBenchmarkJob) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, got map[string]*v1.ValdBenchmarkJob) 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{ + { + name: "get nil when atomic has no resource", + fields: fields{}, + want: want{ + want: nil, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + }, + { + name: "get benchjobs when job list is stored", + fields: fields{ + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + "scenario-search": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + } + ap.Store(&m) + return &ap + }(), + }, + want: want{ + want: map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + "scenario-search": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + 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) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + o := &operator{ + benchjobs: test.fields.benchjobs, + } + + got := o.getAtomicBenchJob() + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_getAtomicJob(t *testing.T) { + type fields struct { + jobs *atomic.Pointer[map[string]string] + } + type want struct { + want map[string]string + } + type test struct { + name string + fields fields + want want + checkFunc func(want, map[string]string) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, got map[string]string) 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{ + { + name: "get nil when atomic has no resource", + fields: fields{}, + want: want{ + want: nil, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + }, + { + name: "get jobs when jobs has resource", + fields: fields{ + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + m := map[string]string{ + "scenario-insert": "default", + "scenario-search": "default", + } + ap.Store(&m) + return &ap + }(), + }, + want: want{ + want: map[string]string{ + "scenario-insert": "default", + "scenario-search": "default", + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + 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) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + o := &operator{ + jobs: test.fields.jobs, + } + + got := o.getAtomicJob() + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_jobReconcile(t *testing.T) { + type args struct { + ctx context.Context + jobList map[string][]job.Job + } + type fields struct { + jobNamespace string + jobImage string + jobImagePullPolicy string + scenarios *atomic.Pointer[map[string]*scenario] + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + jobs *atomic.Pointer[map[string]string] + ctrl k8s.Controller + } + type want struct { + want map[string]string + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, map[string]string) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got map[string]string) 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 { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when the length of jobList is 0.", + args: args{ + ctx: ctx, + jobList: map[string][]job.Job{}, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + }, + want: want{ + want: map[string]string{}, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with new job whose namespace is same as jobNamespace and deleted job by etcd", + args: args{ + ctx: ctx, + jobList: map[string][]job.Job{ + "scenario-insert": { + { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + }, + Status: job.JobStatus{ + Active: 1, + }, + }, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + m := map[string]string{ + "scenario-completed-insert": "default", + } + ap.Store(&m) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + } + ap.Store(&m) + return &ap + }(), + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]string{ + "scenario-insert": "default", + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with completed job whose namespace is same as jobNamespace", + args: args{ + ctx: ctx, + jobList: map[string][]job.Job{ + "scenario-insert": { + { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-completed-insert", + Namespace: "default", + }, + Status: job.JobStatus{ + Active: 0, + Succeeded: 1, + CompletionTime: func() *metav1.Time { + t := &metav1.Time{ + Time: time.Now(), + } + return t + }(), + }, + }, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + m := map[string]string{ + "scenario-completed-insert": "default", + } + ap.Store(&m) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + } + ap.Store(&m) + return &ap + }(), + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]string{ + "scenario-completed-insert": "default", + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with job whose namespace is not same as jobNamespace", + args: args{ + ctx: ctx, + jobList: map[string][]job.Job{ + "scenario-insert": { + { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "benchmark", + }, + Status: job.JobStatus{ + Active: 1, + }, + }, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: nil, + }, + want: want{ + want: map[string]string{}, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + 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 + } + o := &operator{ + jobNamespace: test.fields.jobNamespace, + jobImage: test.fields.jobImage, + jobImagePullPolicy: test.fields.jobImagePullPolicy, + benchjobs: test.fields.benchjobs, + jobs: test.fields.jobs, + ctrl: test.fields.ctrl, + } + + o.jobReconcile(test.args.ctx, test.args.jobList) + got := o.getAtomicJob() + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_benchJobReconcile(t *testing.T) { + type args struct { + ctx context.Context + benchJobList map[string]v1.ValdBenchmarkJob + } + type fields struct { + jobNamespace string + jobImage string + jobImagePullPolicy string + scenarios *atomic.Pointer[map[string]*scenario] + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + jobs *atomic.Pointer[map[string]string] + ctrl k8s.Controller + } + type want struct { + scenarios map[string]*scenario + benchjobs map[string]*v1.ValdBenchmarkJob + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, map[string]*scenario, map[string]*v1.ValdBenchmarkJob) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, gotS map[string]*scenario, gotJ map[string]*v1.ValdBenchmarkJob) error { + if !reflect.DeepEqual(w.scenarios, gotS) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotS, w.scenarios) + } + if !reflect.DeepEqual(w.benchjobs, gotJ) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotJ, w.benchjobs) + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when benchJobList is empty", + args: args{ + ctx: ctx, + benchJobList: map[string]v1.ValdBenchmarkJob{}, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: nil, + }, + want: want{ + benchjobs: map[string]*v1.ValdBenchmarkJob{}, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when benchJobList has new benchmark Job with owner reference (reconcile after submitted scenario)", + args: args{ + ctx: ctx, + benchJobList: map[string]v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + scenarios: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": "", + }, + }, + }, + benchjobs: map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when benchJobList has updated benchmark Job with owner reference (reconcile after updated scenario)", + args: args{ + ctx: ctx, + benchJobList: map[string]v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 2, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 2, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + } + ap.Store(&m) + return &ap + }(), + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + scenarios: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 2, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + }, + }, + }, + benchjobs: map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 2, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when benchJobList has updated benchmark Job with owner reference (reconcile after updated job status)", + args: args{ + ctx: ctx, + benchJobList: map[string]v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": "", + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: "", + }, + } + ap.Store(&m) + return &ap + }(), + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + scenarios: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + }, + }, + }, + benchjobs: map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success when benchJobList has new benchmark Job with owner reference and benchJob has deleted job", + args: args{ + ctx: ctx, + benchJobList: map[string]v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": "", + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + m := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: "", + }, + "scenario-deleted-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-deleted-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario-deleted", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobCompleted, + }, + } + ap.Store(&m) + return &ap + }(), + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + scenarios: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + }, + }, + }, + benchjobs: map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Name: "scenario", + }, + }, + Generation: 1, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + 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 + } + o := &operator{ + jobNamespace: test.fields.jobNamespace, + jobImage: test.fields.jobImage, + jobImagePullPolicy: test.fields.jobImagePullPolicy, + scenarios: test.fields.scenarios, + benchjobs: test.fields.benchjobs, + jobs: test.fields.jobs, + ctrl: test.fields.ctrl, + } + + o.benchJobReconcile(test.args.ctx, test.args.benchJobList) + gotS := o.getAtomicScenario() + gotJ := o.getAtomicBenchJob() + if err := checkFunc(test.want, gotS, gotJ); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_benchScenarioReconcile(t *testing.T) { + type args struct { + ctx context.Context + scenarioList map[string]v1.ValdBenchmarkScenario + } + type fields struct { + jobNamespace string + jobImage string + jobImagePullPolicy string + scenarios *atomic.Pointer[map[string]*scenario] + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + jobs *atomic.Pointer[map[string]string] + ctrl k8s.Controller + } + type want struct { + want map[string]*scenario + } + type test struct { + name string + args args + fields fields + want want + checkFunc func(want, map[string]*scenario) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) + } + defaultCheckFunc := func(w want, got map[string]*scenario) error { + if len(w.want) != len(got) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + for k, ws := range w.want { + gs, ok := got[k] + if !ok { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + // check CRD + if !reflect.DeepEqual(ws.Crd, gs.Crd) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + // check benchJobStatus + if len(ws.BenchJobStatus) != len(gs.BenchJobStatus) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + for k, v := range gs.BenchJobStatus { + sk := strings.Split(k, "-") + wk := strings.Join(sk[:len(sk)-1], "-") + if v != ws.BenchJobStatus[wk] { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } + } + } + return nil + } + tests := []test{ + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with scenarioList is empty", + args: args{ + ctx: ctx, + scenarioList: map[string]v1.ValdBenchmarkScenario{}, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: nil, + }, + want: want{ + want: map[string]*scenario{}, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with scenarioList has new scenario with no scenario has been applied yet.", + args: args{ + ctx: ctx, + scenarioList: map[string]v1.ValdBenchmarkScenario{ + "scenario": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: &atomic.Pointer[map[string]*scenario]{}, + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobNotReady, + "scenario-search": v1.BenchmarkJobNotReady, + }, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with scenarioList has only status updated scenario.", + args: args{ + ctx: ctx, + scenarioList: map[string]v1.ValdBenchmarkScenario{ + "scenario": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert-1234567890": v1.BenchmarkJobNotReady, + "scenario-search-1234567891": v1.BenchmarkJobNotReady, + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobNotReady, + "scenario-search": v1.BenchmarkJobNotReady, + }, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with scenarioList has updated scenario when job is already running", + args: args{ + ctx: ctx, + scenarioList: map[string]v1.ValdBenchmarkScenario{ + "scenario": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 2, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + "scenario-search": v1.BenchmarkJobAvailable, + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 2, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 20000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 20000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobNotReady, + "scenario-search": v1.BenchmarkJobNotReady, + }, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + func() test { + ctx, cancel := context.WithCancel(context.Background()) + return test{ + name: "success with scenarioList has another scenario when scenario is already running", + args: args{ + ctx: ctx, + scenarioList: map[string]v1.ValdBenchmarkScenario{ + "scenario-v2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-v2", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + }, + }, + fields: fields{ + jobNamespace: "default", + jobImage: "vdaas/vald-benchmark-job", + jobImagePullPolicy: "Always", + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + m := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioAvailable, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobAvailable, + "scenario-search": v1.BenchmarkJobAvailable, + }, + }, + } + ap.Store(&m) + return &ap + }(), + benchjobs: &atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{}, + jobs: &atomic.Pointer[map[string]string]{}, + ctrl: &mockCtrl{ + StartFunc: func(ctx context.Context) (<-chan error, error) { + return nil, nil + }, + GetManagerFunc: func() k8s.Manager { + m := &mock.MockManager{ + Manager: &mock.MockManager{}, + } + return m + }, + }, + }, + want: want{ + want: map[string]*scenario{ + "scenario-v2": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-v2", + Namespace: "default", + Generation: 1, + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 5, + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-v2-insert": v1.BenchmarkJobNotReady, + "scenario-v2-search": v1.BenchmarkJobNotReady, + }, + }, + }, + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T, args args) { + t.Helper() + }, + afterFunc: func(t *testing.T, args args) { + t.Helper() + cancel() + }, + } + }(), + } + + 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 + } + o := &operator{ + jobNamespace: test.fields.jobNamespace, + jobImage: test.fields.jobImage, + jobImagePullPolicy: test.fields.jobImagePullPolicy, + scenarios: test.fields.scenarios, + benchjobs: test.fields.benchjobs, + jobs: test.fields.jobs, + ctrl: test.fields.ctrl, + } + + o.benchScenarioReconcile(test.args.ctx, test.args.scenarioList) + got := o.getAtomicScenario() + t.Log(got["scenario"]) + if err := checkFunc(test.want, got); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_operator_checkAtomics(t *testing.T) { + type fields struct { + scenarios *atomic.Pointer[map[string]*scenario] + benchjobs *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] + jobs *atomic.Pointer[map[string]string] + } + type want struct { + err error + } + type test struct { + name string + fields fields + want want + checkFunc func(want, error) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + 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 + } + defaultScenarioMap := map[string]*scenario{ + "scenario": { + Crd: &v1.ValdBenchmarkScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario", + Namespace: "default", + }, + Spec: v1.ValdBenchmarkScenarioSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + Jobs: []*v1.BenchmarkJobSpec{ + { + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + { + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + { + JobType: "update", + UpdateConfig: &config.UpdateConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + }, + }, + Status: v1.BenchmarkScenarioHealthy, + }, + BenchJobStatus: map[string]v1.BenchmarkJobStatus{ + "scenario-insert": v1.BenchmarkJobCompleted, + "scenario-search": v1.BenchmarkJobAvailable, + "scenario-update": v1.BenchmarkJobAvailable, + }, + }, + } + defaultBenchJobMap := map[string]*v1.ValdBenchmarkJob{ + "scenario-insert": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-insert", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: ScenarioKind, + Name: "scenario", + }, + }, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "insert", + InsertConfig: &config.InsertConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobCompleted, + }, + "scenario-search": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-search", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: ScenarioKind, + Name: "scenario", + }, + }, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "search", + SearchConfig: &config.SearchConfig{ + Epsilon: 0.1, + Radius: -1, + Num: 10, + MinNum: 10, + Timeout: "10s", + EnableLinearSearch: false, + AggregationAlgorithm: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + "scenario-update": { + ObjectMeta: metav1.ObjectMeta{ + Name: "scenario-update", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: ScenarioKind, + Name: "scenario", + }, + }, + }, + Spec: v1.BenchmarkJobSpec{ + Target: &v1.BenchmarkTarget{ + Host: "localhost", + Port: 8080, + }, + Dataset: &v1.BenchmarkDataset{ + Name: "fashion-minsit", + Group: "train", + Indexes: 10000, + Range: &config.BenchmarkDatasetRange{ + Start: 0, + End: 10000, + }, + URL: "", + }, + JobType: "update", + UpdateConfig: &config.UpdateConfig{ + SkipStrictExistCheck: false, + Timestamp: "", + }, + }, + Status: v1.BenchmarkJobAvailable, + }, + } + defaultJobMap := map[string]string{ + "scenario-insert": "default", + "scenario-search": "default", + "scenario-update": "default", + } + tests := []test{ + func() test { + return test{ + name: "return nil with no mismatch atmoics", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&defaultScenarioMap) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + ap.Store(&defaultBenchJobMap) + return &ap + }(), + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + ap.Store(&defaultJobMap) + return &ap + }(), + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + } + }(), + func() test { + return test{ + name: "return mismatch error when scneario and job has atomic and benchJob has no atomic", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&defaultScenarioMap) + return &ap + }(), + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + ap.Store(&defaultJobMap) + return &ap + }(), + }, + want: want{ + err: errors.ErrMismatchBenchmarkAtomics(defaultJobMap, map[string]*v1.ValdBenchmarkJob{}, defaultScenarioMap), + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + } + }(), + func() test { + benchJobMap := map[string]*v1.ValdBenchmarkJob{} + for k, v := range defaultBenchJobMap { + val := v1.ValdBenchmarkJob{} + val = *v + benchJobMap[k] = &val + } + benchJobMap["scenario-search"].SetNamespace("benchmark") + return test{ + name: "return mismatch error when benchJob with different namespace", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&defaultScenarioMap) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + ap.Store(&benchJobMap) + return &ap + }(), + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + ap.Store(&defaultJobMap) + return &ap + }(), + }, + want: want{ + err: errors.ErrMismatchBenchmarkAtomics(defaultJobMap, benchJobMap, defaultScenarioMap), + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + } + }(), + func() test { + benchJobMap := map[string]*v1.ValdBenchmarkJob{} + for k, v := range defaultBenchJobMap { + val := v1.ValdBenchmarkJob{} + val = *v + benchJobMap[k] = &val + } + benchJobMap["scenario-search"].Status = v1.BenchmarkJobNotReady + return test{ + name: "return mismatch error when status is not same between benchJob and scenario.BenchJobStatus", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&defaultScenarioMap) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + ap.Store(&benchJobMap) + return &ap + }(), + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + ap.Store(&defaultJobMap) + return &ap + }(), + }, + want: want{ + err: errors.ErrMismatchBenchmarkAtomics(defaultJobMap, benchJobMap, defaultScenarioMap), + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + t.Helper() + }, + } + }(), + func() test { + benchJobMap := map[string]*v1.ValdBenchmarkJob{} + for k, v := range defaultBenchJobMap { + val := v1.ValdBenchmarkJob{} + val = *v + benchJobMap[k] = &val + } + ors := []metav1.OwnerReference{} + for _, v := range benchJobMap["scenario-search"].OwnerReferences { + or := v.DeepCopy() + or.Name = "incorrectName" + ors = append(ors, *or) + } + benchJobMap["scenario-search"].OwnerReferences = ors + return test{ + name: "return mismatch error when scenario does not have key of bench job owners scenario", + fields: fields{ + scenarios: func() *atomic.Pointer[map[string]*scenario] { + ap := atomic.Pointer[map[string]*scenario]{} + ap.Store(&defaultScenarioMap) + return &ap + }(), + benchjobs: func() *atomic.Pointer[map[string]*v1.ValdBenchmarkJob] { + ap := atomic.Pointer[map[string]*v1.ValdBenchmarkJob]{} + ap.Store(&benchJobMap) + return &ap + }(), + jobs: func() *atomic.Pointer[map[string]string] { + ap := atomic.Pointer[map[string]string]{} + ap.Store(&defaultJobMap) + return &ap + }(), + }, + want: want{ + err: errors.ErrMismatchBenchmarkAtomics(defaultJobMap, benchJobMap, defaultScenarioMap), + }, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T) { + t.Helper() + }, + afterFunc: func(t *testing.T) { + 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) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + o := &operator{ + scenarios: test.fields.scenarios, + benchjobs: test.fields.benchjobs, + jobs: test.fields.jobs, + } + + err := o.checkAtomics() + if err := checkFunc(test.want, err); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +// NOT IMPLEMENTED BELOW +// func TestNew(t *testing.T) { +// type args struct { +// opts []Option +// } +// type want struct { +// want Operator +// err error +// } +// type test struct { +// name string +// args args +// want want +// checkFunc func(want, Operator, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got Operator, 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) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// // { +// // name: "test_case_1", +// // args: args{ +// // opts: nil, +// // }, +// // want: want{ +// // want: func() Operator { +// // o := &operator{ +// // jobNamespace: "default", +// // jobImage: "vdaas/vald-benchmark-job", +// // jobImagePullPolicy: "Always", +// // rcd: 10 * time.Second, +// // } +// // return o +// // }(), +// // }, +// // 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 { +// opts:nil, +// }, +// 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 +// } +// +// got, err := New(test.args.opts...) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } + +// +// func Test_operator_PreStart(t *testing.T) { +// type args struct { +// ctx context.Context +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// 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 { +// ctx:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.PreStart(test.args.ctx) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } +// +// func Test_operator_Start(t *testing.T) { +// type args struct { +// ctx context.Context +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// type want struct { +// want <-chan error +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, <-chan error, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got <-chan error, 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) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// got, err := o.Start(test.args.ctx) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// +// }) +// } +// } + +// func Test_operator_initCtrl(t *testing.T) { +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// type want struct { +// err error +// } +// type test struct { +// name string +// fields fields +// want want +// checkFunc func(want, error) error +// beforeFunc func(*testing.T) +// afterFunc func(*testing.T) +// } +// 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{ +// // { +// // name: "test_case_1", +// // fields: fields{ +// // jobNamespace: "default", +// // jobImage: "vdaas/vald-benchmark-job", +// // jobImagePullPolicy: "Always", +// // // scenarios:nil, +// // // benchjobs:nil, +// // // jobs:nil, +// // // rcd:nil, +// // eg: nil, +// // ctrl: nil, +// // }, +// // want: want{ +// // err: errors.New("hoge"), +// // }, +// // checkFunc: defaultCheckFunc, +// // beforeFunc: func(t *testing.T) { +// // t.Helper() +// // }, +// // afterFunc: func(t *testing.T) { +// // t.Helper() +// // }, +// // }, +// +// // TODO test cases +// /* +// func() test { +// return test { +// name: "test_case_2", +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// want: want{}, +// checkFunc: defaultCheckFunc, +// beforeFunc: func(t *testing.T,) { +// t.Helper() +// }, +// afterFunc: func(t *testing.T,) { +// 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) +// } +// if test.afterFunc != nil { +// defer test.afterFunc(tt) +// } +// checkFunc := test.checkFunc +// if test.checkFunc == nil { +// checkFunc = defaultCheckFunc +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.initCtrl() +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } + +// func Test_operator_deleteBenchmarkJob(t *testing.T) { +// type args struct { +// ctx context.Context +// name string +// generation int64 +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// 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 { +// ctx:nil, +// name:"", +// generation:0, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// name:"", +// generation:0, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.deleteBenchmarkJob(test.args.ctx, test.args.name, test.args.generation) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_deleteJob(t *testing.T) { +// type args struct { +// ctx context.Context +// name string +// generation int64 +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// 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 { +// ctx:nil, +// name:"", +// generation:0, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// name:"", +// generation:0, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.deleteJob(test.args.ctx, test.args.name, test.args.generation) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_createBenchmarkJob(t *testing.T) { +// type args struct { +// ctx context.Context +// scenario v1.ValdBenchmarkScenario +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// type want struct { +// want []string +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, []string, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got []string, 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) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// scenario:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// scenario:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// got, err := o.createBenchmarkJob(test.args.ctx, test.args.scenario) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_createJob(t *testing.T) { +// type args struct { +// ctx context.Context +// bjr v1.ValdBenchmarkJob +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// 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 { +// ctx:nil, +// bjr:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// bjr:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.createJob(test.args.ctx, test.args.bjr) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_updateBenchmarkScenarioStatus(t *testing.T) { +// type args struct { +// ctx context.Context +// ss map[string]v1.ValdBenchmarkScenarioStatus +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// type want struct { +// want []string +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, []string, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got []string, 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) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// ss:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// ss:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// got, err := o.updateBenchmarkScenarioStatus(test.args.ctx, test.args.ss) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_updateBenchmarkJobStatus(t *testing.T) { +// type args struct { +// ctx context.Context +// js map[string]v1.BenchmarkJobStatus +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// type want struct { +// want []string +// err error +// } +// type test struct { +// name string +// args args +// fields fields +// want want +// checkFunc func(want, []string, error) error +// beforeFunc func(*testing.T, args) +// afterFunc func(*testing.T, args) +// } +// defaultCheckFunc := func(w want, got []string, 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) +// } +// return nil +// } +// tests := []test{ +// // TODO test cases +// /* +// { +// name: "test_case_1", +// args: args { +// ctx:nil, +// js:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 { +// ctx:nil, +// js:nil, +// }, +// fields: fields { +// jobNamespace:"", +// jobImage:"", +// jobImagePullPolicy:"", +// scenarios:nil, +// benchjobs:nil, +// jobs:nil, +// rcd:nil, +// eg:nil, +// ctrl:nil, +// }, +// 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// got, err := o.updateBenchmarkJobStatus(test.args.ctx, test.args.js) +// if err := checkFunc(test.want, got, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// } +// +// func Test_operator_checkJobsStatus(t *testing.T) { +// type args struct { +// ctx context.Context +// jobs map[string]string +// } +// type fields struct { +// jobNamespace string +// jobImage string +// jobImagePullPolicy string +// scenarios atomic.Pointer[map[string]*scenario] +// benchjobs atomic.Pointer[map[string]*v1.ValdBenchmarkJob] +// jobs atomic.Pointer[map[string]string] +// rcd time.Duration +// eg errgroup.Group +// ctrl k8s.Controller +// } +// 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{ +// // func() test { +// // return test{ +// // name: "test_case_2", +// // args: args{ +// // ctx: nil, +// // jobs: nil, +// // }, +// // fields: fields{ +// // jobNamespace: "", +// // jobImage: "", +// // jobImagePullPolicy: "", +// // scenarios: nil, +// // benchjobs: nil, +// // jobs: nil, +// // rcd: nil, +// // eg: nil, +// // ctrl: nil, +// // }, +// // 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 +// } +// o := &operator{ +// jobNamespace: test.fields.jobNamespace, +// jobImage: test.fields.jobImage, +// jobImagePullPolicy: test.fields.jobImagePullPolicy, +// scenarios: test.fields.scenarios, +// benchjobs: test.fields.benchjobs, +// jobs: test.fields.jobs, +// rcd: test.fields.rcd, +// eg: test.fields.eg, +// ctrl: test.fields.ctrl, +// } +// +// err := o.checkJobsStatus(test.args.ctx, test.args.jobs) +// if err := checkFunc(test.want, err); err != nil { +// tt.Errorf("error = %v", err) +// } +// }) +// } +// }