diff --git a/pkg/agent/core/ngt/handler/grpc/index_test.go b/pkg/agent/core/ngt/handler/grpc/index_test.go index c39037e4a2..9587f8d0c1 100644 --- a/pkg/agent/core/ngt/handler/grpc/index_test.go +++ b/pkg/agent/core/ngt/handler/grpc/index_test.go @@ -1029,106 +1029,877 @@ func Test_server_SaveIndex(t *testing.T) { func Test_server_CreateAndSaveIndex(t *testing.T) { t.Parallel() type args struct { - ctx context.Context - c *payload.Control_CreateIndexRequest + c *payload.Control_CreateIndexRequest } type fields struct { - name string - ip string - ngt service.NGT - eg errgroup.Group - streamConcurrency int + srvOpts []Option + svcCfg *config.NGT + svcOpts []service.Option + indexPath string // index path for svcOpts } type want struct { wantRes *payload.Empty - err error + errCode codes.Code } type test struct { name string args args fields fields want want - checkFunc func(want, *payload.Empty, error) error - beforeFunc func(args) - afterFunc func(args) + checkFunc func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error + beforeFunc func(*testing.T, context.Context, Server, service.NGT, test) + afterFunc func(*testing.T, test) } - defaultCheckFunc := func(w want, gotRes *payload.Empty, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + + // common variables for test + const ( + name = "vald-agent-ngt-1" // agent name + dim = 3 // vector dimension + id = "uuid-1" // id for getObject request + ) + var ( + // agent ip address + ip = net.LoadLocalIP() + + // default NGT configuration for test + defaultSvcCfg = &config.NGT{ + Dimension: dim, + DistanceType: ngt.Angle.String(), + ObjectType: ngt.Float.String(), + KVSDB: &config.KVSDB{}, + VQueue: &config.VQueue{}, } - if !reflect.DeepEqual(gotRes, w.wantRes) { + defaultSrvOpts = []Option{ + WithName(name), + WithIP(ip), + } + defaultSvcOpts = []service.Option{ + service.WithEnableInMemoryMode(false), + } + defaultInsertConfig = &payload.Insert_Config{} + emptyPayload = &payload.Empty{} + ) + + defaultCheckFunc := func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if (err == nil && w.errCode != 0) || (err != nil && w.errCode == 0) { + return errors.Errorf("got error is %v, but want error code is %v", err, w.errCode) + } + if err != nil { + st, ok := status.FromError(err) + if !ok { + return errors.Errorf("got error cannot convert to Status: \"%#v\"", err) + } + if st.Code() != w.errCode { + return errors.Errorf("got code: \"%#v\",\n\t\t\t\twant code: \"%#v\"", st.Code(), w.errCode) + } + } + + if diff := comparator.Diff(gotRes, w.wantRes, comparator.IgnoreUnexported(payload.Empty{})); diff != "" { return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) } return nil } - tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - ctx: nil, - c: nil, - }, - fields: fields { - name: "", - ip: "", - ngt: nil, - eg: nil, - streamConcurrency: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - ctx: nil, - c: nil, - }, - fields: fields { - name: "", - ip: "", - ngt: nil, - eg: nil, - streamConcurrency: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + + mkdirTemp := func() string { + d, err := os.MkdirTemp("", "") + if err != nil { + t.Error(err) + } + return d + } + defaultAfterFunc := func(t *testing.T, test test) { + t.Helper() + os.RemoveAll(test.fields.indexPath) } - for _, tc := range tests { - test := tc - t.Run(test.name, func(tt *testing.T) { - tt.Parallel() - if test.beforeFunc != nil { - test.beforeFunc(test.args) + // this function checks the backup file can be loaded and check if it contains the wantVecs indexes. + // it creates a new ngt and server instance with the backup file, and checks if we can retrieve all of wantVecs indexes + // and check the total index count matches with wantVecs count. + checkBackupFolder := func(fields fields, ctx context.Context, wantVecs []*payload.Insert_Request) error { + // create another server instance to check if any vector is inserted and saved to the backup dir + eg, _ := errgroup.New(ctx) + ngt, err := service.New(fields.svcCfg, append(fields.svcOpts, + service.WithErrGroup(eg), + service.WithIndexPath(fields.indexPath))...) + if err != nil { + return errors.Errorf("failed to init ngt service, error = %v", err) + } + srv, err := New(append(fields.srvOpts, WithNGT(ngt), WithErrGroup(eg))...) + if err != nil { + return errors.Errorf("failed to init server, error= %v", err) + } + + // get object and check if the vector is equals to inserted one + for _, ir := range wantVecs { + obj, err := srv.GetObject(ctx, &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: ir.GetVector().GetId(), + }, + }) + if err != nil { + return err } - if test.afterFunc != nil { - defer test.afterFunc(test.args) + if !reflect.DeepEqual(obj, ir.GetVector()) { + return errors.Errorf("vector is not match, got: %v, want: %v", obj, ir) } - checkFunc := test.checkFunc - if test.checkFunc == nil { - checkFunc = defaultCheckFunc + } + + // check total index count is same + ii, err := srv.IndexInfo(ctx, emptyPayload) + if err != nil { + return err + } + + wantIndexInfo := &payload.Info_Index_Count{ + Stored: uint32(len(wantVecs)), + } + if !reflect.DeepEqual(ii, wantIndexInfo) { + return errors.Errorf("stored index count not correct, got: %v, want: %v", ii, wantIndexInfo) + } + + return nil + } + + /* + - Equivalence Class Testing (with copy on write disable) + - case 1.1: success to create and save 1 uncommitted insert index + - case 1.2: success to create and save 100 uncommitted insert index + - case 2.1: success to create and save 1 uncommitted delete index + - case 2.2: success to create and save 100 uncommitted delete index + - case 3.1: success to create and save 1 uncommitted update index + - case 3.2: success to create and save 100 uncommitted update index + - Boundary Value Testing + - case 1.1: fail to create and save 0 index + - case 2.1: success to create and save index with invalid dimension + - the invalid index will be removed from NGT and the index file + - Decision Table Testing + - case 1.1: success to create and save 100 index with in-memory mode + - do nothing and no file will be created + - case 2.1: success to create and save 1 inserted index with copy-on-write enabled + - case 2.2: success to create and save 100 inserted index with copy-on-write enabled + + // with uncommitted index count 100 + - case 3.1: success to create and save index with poolSize > uncommitted index count + - case 3.2: success to create and save index with poolSize < uncommitted index count + - case 3.3: success to create and save index with poolSize = uncommitted index count + - case 3.4: success to create and save index with poolSize = 0 + */ + tests := []test{ + func() test { + insertCnt := 1 + var ir *payload.Insert_MultiRequest + + return test{ + name: "Equivalence Class Testing case 1.1: success to create and save 1 uncommitted insert index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, ir.GetRequests()) + }, } - s := &server{ - name: test.fields.name, - ip: test.fields.ip, - ngt: test.fields.ngt, - eg: test.fields.eg, - streamConcurrency: test.fields.streamConcurrency, + }(), + func() test { + insertCnt := 100 + var ir *payload.Insert_MultiRequest + + return test{ + name: "Equivalence Class Testing case 1.2: success to create and save 100 uncommitted insert index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, ir.GetRequests()) + }, } + }(), + func() test { + insertCnt := 100 + var ir *payload.Insert_MultiRequest - gotRes, err := s.CreateAndSaveIndex(test.args.ctx, test.args.c) - if err := checkFunc(test.want, gotRes, err); err != nil { + return test{ + name: "Equivalence Class Testing case 2.1: success to create and save 1 uncommitted delete index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + + // insert 100 request + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + if _, err := s.CreateAndSaveIndex(ctx, &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }); err != nil { + t.Error(err) + } + + // delete 1 request + if _, err := s.Remove(ctx, &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: ir.GetRequests()[0].GetVector().GetId(), + }, + }); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + // we expect the request[0] is removed and shouldn't be exists in backup files + expectedVecs := ir.GetRequests()[1:] + return checkBackupFolder(test.fields, ctx, expectedVecs) + }, + } + }(), + func() test { + insertCnt := 200 + var ir *payload.Insert_MultiRequest + + return test{ + name: "Equivalence Class Testing case 2.2: success to create and save 100 uncommitted delete index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + + // remove requests + rr := make([]*payload.Remove_Request, 100) + for i := 0; i < 100; i++ { + rr[i] = &payload.Remove_Request{ + Id: &payload.Object_ID{ + Id: ir.GetRequests()[i].GetVector().GetId(), + }, + } + } + + // insert 200 request + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + if _, err := s.CreateAndSaveIndex(ctx, &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }); err != nil { + t.Error(err) + } + + // delete 100 request + if _, err := s.MultiRemove(ctx, &payload.Remove_MultiRequest{ + Requests: rr, + }); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + // we expect the request[0-99] is removed and shouldn't be exists in backup files + expectedVecs := ir.GetRequests()[100:] + return checkBackupFolder(test.fields, ctx, expectedVecs) + }, + } + }(), + func() test { + insertCnt := 1 + var ir *payload.Insert_MultiRequest + var updateVec []float32 // the updated vector + + return test{ + name: "Equivalence Class Testing case 3.1: success to create and save 1 uncommitted update index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + + updateID := ir.GetRequests()[0].GetVector().GetId() + updateVecs, err := vector.GenF32Vec(vector.Gaussian, 1, dim) + if err != nil { + t.Error(err) + } + updateVec = updateVecs[0] + + // insert 1 request + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + if _, err := s.CreateAndSaveIndex(ctx, &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }); err != nil { + t.Error(err) + } + + // update vector request + if _, err := s.Update(ctx, &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: updateID, + Vector: updateVec, + }, + }); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + // we expect vector is the update vec + expectedVecs := ir.GetRequests() + expectedVecs[0].Vector.Vector = updateVec + return checkBackupFolder(test.fields, ctx, expectedVecs) + }, + } + }(), + func() test { + insertCnt := 100 + var ir *payload.Insert_MultiRequest + var updateVecs [][]float32 + updateReqs := make([]*payload.Update_Request, insertCnt) + + return test{ + name: "Equivalence Class Testing case 3.2: success to create and save 100 uncommitted update index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + + // generate another set of vectors for update + updateVecs, err = vector.GenF32Vec(vector.Gaussian, insertCnt, dim) + if err != nil { + t.Error(err) + } + + // generate update requests for insert + for i := range ir.GetRequests() { + updateReqs[i] = &payload.Update_Request{ + Vector: &payload.Object_Vector{ + Id: ir.GetRequests()[i].GetVector().GetId(), + Vector: updateVecs[i], + }, + } + } + + // insert 100 request + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + if _, err := s.CreateAndSaveIndex(ctx, &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }); err != nil { + t.Error(err) + } + + // update vector request + if _, err := s.MultiUpdate(ctx, &payload.Update_MultiRequest{ + Requests: updateReqs, + }); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + // we expect vector is the update vec + expectedVecs := ir.GetRequests() + for i := range expectedVecs { + expectedVecs[i].GetVector().Vector = updateVecs[i] + } + + return checkBackupFolder(test.fields, ctx, expectedVecs) + }, + } + }(), + func() test { + return test{ + name: "Boundary Value Testing case 1.1: fail to create and save 0 index", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: 0, + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + want: want{ + wantRes: &payload.Empty{}, + }, + } + }(), + func() test { + return test{ + name: "Boundary Value Testing case 2.1: success to create and save index with invalid dimension", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: 1, + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + invalidDim := dim + 1 + vecs, err := vector.GenF32Vec(vector.Gaussian, 1, invalidDim) + if err != nil { + t.Error(err) + } + + // insert invalid vector to ngt directly + if err := n.Insert("uuid-1", vecs[0]); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + // the invalid index will be removed inside ngt + return checkBackupFolder(test.fields, ctx, nil) + }, + } + }(), + func() test { + insertCnt := 100 + var ir *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 1.1: success to create and save 100 index with in-memory mode", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: []service.Option{ + service.WithEnableInMemoryMode(true), + }, + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if ir, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, ir); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: &payload.Empty{}, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, gotRes *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, gotRes, err); err != nil { + return err + } + files, err := file.ListInDir(test.fields.indexPath) + if err != nil { + return err + } + + // check any file is generated in backup directory + if len(files) > 0 { + return errors.New("no file should be created when in memory mode is enabled") + } + return nil + }, + } + }(), + func() test { + insertCnt := 1 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 2.1: success to create and save 1 inserted index with copy-on-write enabled", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.Insert(ctx, irs.Requests[0]); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + func() test { + insertCnt := 100 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 2.2: success to create and save 100 inserted index with copy-on-write enabled", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, irs); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + func() test { + insertCnt := 100 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 3.1: success to create and save index with poolSize > uncommitted index count", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt + 1), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, irs); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + func() test { + insertCnt := 100 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 3.2: success to create and save index with poolSize < uncommitted index count", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt - 1), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, irs); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + func() test { + insertCnt := 100 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 3.3: success to create and save index with poolSize = uncommitted index count", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: uint32(insertCnt), + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, irs); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + func() test { + insertCnt := 100 + var irs *payload.Insert_MultiRequest + + return test{ + name: "Decision Table Testing case 3.4: success to create and save index with poolSize = 0", + args: args{ + c: &payload.Control_CreateIndexRequest{ + PoolSize: 0, + }, + }, + fields: fields{ + srvOpts: defaultSrvOpts, + svcCfg: defaultSvcCfg, + svcOpts: append(defaultSvcOpts, service.WithCopyOnWrite(true)), + indexPath: mkdirTemp(), + }, + beforeFunc: func(t *testing.T, ctx context.Context, s Server, n service.NGT, test test) { + t.Helper() + var err error + if irs, err = request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig); err != nil { + t.Error(err) + } + if _, err := s.MultiInsert(ctx, irs); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: emptyPayload, + }, + checkFunc: func(test test, ctx context.Context, s Server, n service.NGT, w want, e *payload.Empty, err error) error { + if err := defaultCheckFunc(test, ctx, s, n, w, e, err); err != nil { + return err + } + return checkBackupFolder(test.fields, ctx, irs.Requests) + }, + } + }(), + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + afterFunc := test.afterFunc + if test.afterFunc == nil { + afterFunc = defaultAfterFunc + } + defer afterFunc(tt, test) + + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + + eg, _ := errgroup.New(ctx) + ngt, err := service.New( + test.fields.svcCfg, + append(test.fields.svcOpts, + service.WithErrGroup(eg), + service.WithIndexPath(test.fields.indexPath), + )...) + if err != nil { + tt.Errorf("failed to init ngt service, error = %v", err) + } + + s, err := New(append(test.fields.srvOpts, WithNGT(ngt), WithErrGroup(eg))...) + if err != nil { + tt.Errorf("failed to init server, error= %v", err) + } + + if test.beforeFunc != nil { + test.beforeFunc(tt, ctx, s, ngt, test) + } + + gotRes, err := s.CreateAndSaveIndex(ctx, test.args.c) + if err := checkFunc(test, ctx, s, ngt, test.want, gotRes, err); err != nil { tt.Errorf("error = %v", err) } })