func TestMultiServiceGeneratorGenerate(t *testing.T) { type response struct { success *api.GenerateServiceResponse failure error } tests := []struct { desc string // list of responses from different service generators responses []response // final expected response or errors wantResponse *api.GenerateServiceResponse wantErrors []string wantOneOfErrors []string // both, wantErrors and wantOneOfErrors may be set. All errors in // wantErrors must be present, but only one or more of the errors in // wantOneOfErrors must be present. }{ { desc: "no conflicts; no errors", responses: []response{ {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/a.go": {1, 2, 3}, "foo/b.go": {4, 5, 6}, }}}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/c.go": {7, 8, 9}, "foo/d.go": {1, 2, 3}, }}}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ // no files }}}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/keyvalue/e.go": {4, 5, 6}, }}}, }, wantResponse: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/a.go": {1, 2, 3}, "foo/b.go": {4, 5, 6}, "foo/c.go": {7, 8, 9}, "foo/d.go": {1, 2, 3}, "foo/keyvalue/e.go": {4, 5, 6}, }}, }, { desc: "no conflicts; with errors", responses: []response{ {failure: errors.New("foo: great sadness")}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/a.go": {1, 2, 3}, }}}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/b.go": {4, 5, 6}, }}}, {failure: errors.New("bar: great sadness")}, }, wantErrors: []string{ `foo: great sadness`, `bar: great sadness`, }, }, { desc: "conflicts", responses: []response{ {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/a.go": {1, 2, 3}, "foo/b.go": {4, 5, 6}, }}}, {success: &api.GenerateServiceResponse{Files: map[string][]byte{ "foo/c.go": {7, 8, 9}, "foo/b.go": {1, 2, 3}, }}}, }, wantErrors: []string{`plugin conflict: cannot write file "foo/b.go" for plugin`}, wantOneOfErrors: []string{ `plugin "plugin-1" already wrote to that file`, `plugin "plugin-0" already wrote to that file`, }, }, } req := &api.GenerateServiceRequest{ RootServices: []api.ServiceID{1}, Services: map[api.ServiceID]*api.Service{ 1: { Name: "KeyValue", ThriftName: "KeyValue", Functions: []*api.Function{}, ModuleID: api.ModuleID(1), }, }, Modules: map[api.ModuleID]*api.Module{ 1: { ImportPath: "go.uber.org/thriftrw/foo", Directory: "foo", }, }, } for _, tt := range tests { func() { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() var msg MultiServiceGenerator for i, res := range tt.responses { handle := handletest.NewMockHandle(mockCtrl) handle.EXPECT().Name().Return(fmt.Sprintf("plugin-%d", i)).AnyTimes() sg := handletest.NewMockServiceGenerator(mockCtrl) msg = append(msg, sg) sg.EXPECT().Generate(req).Return(res.success, res.failure) sg.EXPECT().Handle().Return(handle).AnyTimes() } res, err := msg.Generate(req) if len(tt.wantErrors) > 0 || len(tt.wantOneOfErrors) > 0 { if !assert.Error(t, err, tt.desc) { return } for _, errMsg := range tt.wantErrors { assert.Contains(t, err.Error(), errMsg, tt.desc) } matches := len(tt.wantOneOfErrors) == 0 for _, errMsg := range tt.wantOneOfErrors { if strings.Contains(err.Error(), errMsg) { matches = true break } } assert.True(t, matches, "expected %v to contain one of %v", err, tt.wantOneOfErrors) } else { assert.Equal(t, tt.wantResponse, res, tt.desc) } }() } }
func TestServiceGeneratorGenerate(t *testing.T) { tests := []struct { desc string generateResponse *api.GenerateServiceResponse generateError error wantError string }{ { desc: "success", generateResponse: &api.GenerateServiceResponse{ Files: map[string][]byte{"foo/bar.go": []byte("package foo")}, }, }, { desc: "parent directory", generateResponse: &api.GenerateServiceResponse{ Files: map[string][]byte{"../foo/bar.go": []byte("package foo")}, }, wantError: `plugin "foo" is attempting to write to a parent directory: ` + `path "../foo/bar.go" contains ".."`, }, { desc: "call error", generateError: errors.New("great sadness"), wantError: `plugin "foo" failed to generate service code: ` + "TApplicationException{Message: great sadness, Type: INTERNAL_ERROR}", }, } for _, tt := range tests { func() { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() server := newFakePluginServer(mockCtrl) defer server.Close() handle := server.Handshake(t, "foo", []api.Feature{api.FeatureServiceGenerator}) defer func() { server.ExpectGoodbye() require.NoError(t, handle.Close(), tt.desc) }() req := &api.GenerateServiceRequest{ RootServices: []api.ServiceID{1}, Services: map[api.ServiceID]*api.Service{ 1: { Name: "KeyValue", ThriftName: "KeyValue", Functions: []*api.Function{}, ModuleID: api.ModuleID(1), }, }, Modules: map[api.ModuleID]*api.Module{ 1: { ImportPath: "go.uber.org/thriftrw/foo", Directory: "foo", }, }, } server.ServiceGenerator.EXPECT().Generate(req). Return(tt.generateResponse, tt.generateError) res, err := handle.ServiceGenerator().Generate(req) if tt.wantError != "" { if assert.Error(t, err, tt.desc) { assert.Equal(t, tt.wantError, err.Error(), tt.desc) } } else { assert.NoError(t, err, tt.desc) assert.Equal(t, tt.generateResponse, res, tt.desc) } }() } }