From 9ea85a3e7bf585382e10301d7141523128e834d0 Mon Sep 17 00:00:00 2001 From: Kevin Diu Date: Thu, 30 Jun 2022 14:45:57 +0900 Subject: [PATCH] Implement agent handler getObject test case (#1707) * draft get object test case Signed-off-by: kevindiu * implement get object tests Signed-off-by: kevindiu * fix incorrect test impl Signed-off-by: kevindiu --- .../core/ngt/handler/grpc/object_test.go | 788 ++++++++++++++++-- 1 file changed, 734 insertions(+), 54 deletions(-) diff --git a/pkg/agent/core/ngt/handler/grpc/object_test.go b/pkg/agent/core/ngt/handler/grpc/object_test.go index 9f47da2563..3bd0f445e1 100644 --- a/pkg/agent/core/ngt/handler/grpc/object_test.go +++ b/pkg/agent/core/ngt/handler/grpc/object_test.go @@ -27,6 +27,7 @@ import ( "github.com/vdaas/vald/internal/core/algorithm/ngt" "github.com/vdaas/vald/internal/errgroup" "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/net" "github.com/vdaas/vald/internal/net/grpc/codes" "github.com/vdaas/vald/internal/net/grpc/status" "github.com/vdaas/vald/internal/test/data/request" @@ -356,19 +357,18 @@ func Test_server_Exists(t *testing.T) { func Test_server_GetObject(t *testing.T) { t.Parallel() type args struct { - ctx context.Context - id *payload.Object_VectorRequest + id *payload.Object_VectorRequest } type fields struct { name string ip string - ngt service.NGT - eg errgroup.Group streamConcurrency int + svcCfg *config.NGT + svcOpts []service.Option } type want struct { wantRes *payload.Object_Vector - err error + errCode codes.Code } type test struct { name string @@ -376,60 +376,729 @@ func Test_server_GetObject(t *testing.T) { fields fields want want checkFunc func(want, *payload.Object_Vector, error) error - beforeFunc func(args) + beforeFunc func(*testing.T, args, *server) afterFunc func(args) } + + // common variables for test + const ( + name = "vald-agent-ngt-1" // agent name + dim = 3 // vector dimension + id = "uuid-1" // id for getObject request + insertCnt = 1000 // default insert count + ) + var ( + ip = net.LoadLocalIP() // agent ip address + + // default NGT configuration for test + kvsdbCfg = &config.KVSDB{} + vqueueCfg = &config.VQueue{} + + defaultSvcCfg = &config.NGT{ + Dimension: dim, + DistanceType: ngt.Angle.String(), + ObjectType: ngt.Float.String(), + KVSDB: kvsdbCfg, + VQueue: vqueueCfg, + } + defaultSvcOpts = []service.Option{ + service.WithEnableInMemoryMode(true), + } + + defaultInsertConfig = &payload.Insert_Config{} + ) + + utf8Str := "こんにちは" + eucjpStr, err := conv.Utf8ToEucjp(utf8Str) + if err != nil { + t.Error(err) + } + sjisStr, err := conv.Utf8ToSjis(utf8Str) + if err != nil { + t.Error(err) + } + + insertAndCreateIndex := func(t *testing.T, s *server, req *payload.Insert_MultiRequest) { + ctx := context.Background() + + if _, err := s.MultiInsert(ctx, req); err != nil { + t.Fatal(err) + } + if _, err := s.CreateIndex(ctx, &payload.Control_CreateIndexRequest{ + PoolSize: uint32(len(req.Requests)), + }); err != nil { + t.Fatal(err) + } + } + defaultCheckFunc := func(w want, gotRes *payload.Object_Vector, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) + if err != nil { + st, ok := status.FromError(err) + if !ok { + 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 !reflect.DeepEqual(gotRes, w.wantRes) { return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotRes, w.wantRes) } return nil } + /* + - Equivalence Class Testing (1000 vectors inserted) + - case 1.1: success to get object (type: uint8) + - case 2.1: success to get object (type: float32) + - Boundary Value Testing (1000 float32 vectors inserted) + - case 1.1: fail to get object with "" + - case 2.1: success to get object with ^@ + - case 2.2: success to get object with ^I + - case 2.3: success to get object with ^J + - case 2.4: success to get object with ^M + - case 2.5: success to get object with ^[ + - case 2.6: success to get object with ^? + - case 2.7: success to get object with utf-8 ID from utf-8 index + - case 3.1: fail to get object with utf-8 ID from s-jis index + - case 3.2: fail to get object with utf-8 ID from euc-jp index + - case 3.3: fail to get object with s-jis ID from utf-8 index + - case 3.4: success to get object with s-jis ID from s-jis index + - case 4.1: fail to get object with s-jis ID from euc-jp index + - case 4.2: fail to get object with euc-jp ID from utf-8 index + - case 4.3: fail to get object with euc-jp ID from s-jis index + - case 4.4: success to get object with euc-jp ID from euc-jp index + - case 5.1: success to get object with 😀 + - Decision Table Testing + - NONE + */ tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - ctx: nil, - id: nil, - }, - fields: fields { - name: "", - ip: "", - ngt: nil, - eg: nil, - streamConcurrency: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ + func() test { + ir, err := request.GenMultiInsertReq(request.Uint8, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - ctx: nil, - id: nil, - }, - fields: fields { - name: "", - ip: "", - ngt: nil, - eg: nil, - streamConcurrency: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + return test{ + name: "Equivalence Class Testing case 1.1: success to get object (type: uint8)", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: &config.NGT{ + Dimension: dim, + DistanceType: ngt.Angle.String(), + ObjectType: ngt.Uint8.String(), + KVSDB: kvsdbCfg, + VQueue: vqueueCfg, + }, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + + return test{ + name: "Equivalence Class Testing case 2.1: success to get object (type: float32)", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + + return test{ + name: `Boundary Value Testing case 1.1: fail to get object with ""`, + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: "", + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.InvalidArgument, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = string([]byte{0}) + + return test{ + name: "Boundary Value Testing case 2.1: success to get object with ^@", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = "\t" + + return test{ + name: "Boundary Value Testing case 2.2: success to get object with ^I", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = "\n" + + return test{ + name: "Boundary Value Testing case 2.3: success to get object with ^J", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = "\r" + + return test{ + name: "Boundary Value Testing case 2.4: success to get object with ^M", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = string([]byte{27}) + + return test{ + name: "Boundary Value Testing case 2.5: success to get object with ^[", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = string([]byte{127}) + + return test{ + name: "Boundary Value Testing case 2.6: success to get object with ^?", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = utf8Str + + return test{ + name: "Boundary Value Testing case 2.7: success to get object with utf-8 ID from utf-8 index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = sjisStr + + return test{ + name: "Boundary Value Testing case 3.1: fail to get object with utf-8 ID from s-jis index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: utf8Str, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = eucjpStr + + return test{ + name: "Boundary Value Testing case 3.2: fail to get object with utf-8 ID from euc-jp index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: utf8Str, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = utf8Str + + return test{ + name: "Boundary Value Testing case 3.3: fail to get object with s-jis ID from utf-8 index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: sjisStr, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = sjisStr + + return test{ + name: "Boundary Value Testing case 3.4: success to get object with s-jis ID from s-jis index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = eucjpStr + + return test{ + name: "Boundary Value Testing case 4.1: fail to get object with s-jis ID from euc-jp index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: sjisStr, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = utf8Str + + return test{ + name: "Boundary Value Testing case 4.2: fail to get object with euc-jp ID from utf-8 index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: eucjpStr, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = sjisStr + + return test{ + name: "Boundary Value Testing case 4.3: fail to get object with euc-jp ID from s-jis index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: eucjpStr, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + errCode: codes.NotFound, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = eucjpStr + + return test{ + name: "Boundary Value Testing case 4.4: success to get object with euc-jp ID from euc-jp index", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), + func() test { + ir, err := request.GenMultiInsertReq(request.Float, vector.Gaussian, insertCnt, dim, defaultInsertConfig) + if err != nil { + t.Fatal(err) + } + reqVec := ir.Requests[0].Vector + reqVec.Id = "😀" + + return test{ + name: "Boundary Value Testing case 5.1: success to get object with 😀", + args: args{ + id: &payload.Object_VectorRequest{ + Id: &payload.Object_ID{ + Id: reqVec.Id, + }, + }, + }, + fields: fields{ + name: name, + ip: ip, + svcCfg: defaultSvcCfg, + svcOpts: defaultSvcOpts, + }, + beforeFunc: func(t *testing.T, a args, s *server) { + insertAndCreateIndex(t, s, ir) + }, + want: want{ + wantRes: &payload.Object_Vector{ + Id: reqVec.Id, + Vector: reqVec.Vector, + }, + }, + } + }(), } for _, tc := range tests { @@ -437,9 +1106,10 @@ func Test_server_GetObject(t *testing.T) { t.Run(test.name, func(tt *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) - if test.beforeFunc != nil { - test.beforeFunc(test.args) - } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if test.afterFunc != nil { defer test.afterFunc(test.args) } @@ -447,15 +1117,25 @@ func Test_server_GetObject(t *testing.T) { if test.checkFunc == nil { checkFunc = defaultCheckFunc } + + eg, _ := errgroup.New(ctx) + ngt, err := service.New(test.fields.svcCfg, append(test.fields.svcOpts, service.WithErrGroup(eg))...) + if err != nil { + tt.Errorf("failed to init ngt service, error = %v", err) + } + s := &server{ name: test.fields.name, ip: test.fields.ip, - ngt: test.fields.ngt, - eg: test.fields.eg, + ngt: ngt, + eg: eg, streamConcurrency: test.fields.streamConcurrency, } + if test.beforeFunc != nil { + test.beforeFunc(tt, test.args, s) + } - gotRes, err := s.GetObject(test.args.ctx, test.args.id) + gotRes, err := s.GetObject(ctx, test.args.id) if err := checkFunc(test.want, gotRes, err); err != nil { tt.Errorf("error = %v", err) }