func TestMultiHandleServiceGenerator(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() handles := make([]*handletest.MockHandle, 1000) var mh MultiHandle for i := range handles { handle := handletest.NewMockHandle(mockCtrl) handles[i] = handle mh = append(mh, handle) // only odd handles have a ServiceGenerator if i%2 == 0 { handle.EXPECT().ServiceGenerator().Return(nil) continue } handle.EXPECT().ServiceGenerator().Return( handletest.NewMockServiceGenerator(mockCtrl)) } assert.NotNil(t, mh.ServiceGenerator()) }
func TestGenerate(t *testing.T) { var ( ts compile.TypeSpec = &compile.TypedefSpec{ Name: "Timestamp", File: testdata(t, "thrift/common/bar.thrift"), Target: &compile.I64Spec{}, } ts2 compile.TypeSpec = &compile.TypedefSpec{ Name: "Timestamp", File: testdata(t, "thrift/foo.thrift"), Target: ts, } ) ts2, err := ts2.Link(compile.EmptyScope("bar")) require.NoError(t, err) ts, err = ts.Link(compile.EmptyScope("bar")) require.NoError(t, err) module := &compile.Module{ Name: "foo", ThriftPath: testdata(t, "thrift/foo.thrift"), Includes: map[string]*compile.IncludedModule{ "bar": { Name: "bar", Module: &compile.Module{ Name: "bar", ThriftPath: testdata(t, "thrift/common/bar.thrift"), Types: map[string]compile.TypeSpec{"Timestamp": ts}, }, }, }, Types: map[string]compile.TypeSpec{"Timestamp": ts2}, } tests := []struct { desc string noRecurse bool getPlugin func(*gomock.Controller) plugin.Handle wantFiles []string wantError string }{ { desc: "nil plugin; no recurse", noRecurse: true, wantFiles: []string{"foo/types.go"}, }, { desc: "nil plugin; recurse", wantFiles: []string{ "foo/types.go", "common/bar/types.go", }, }, { desc: "no service generator", getPlugin: func(mockCtrl *gomock.Controller) plugin.Handle { handle := handletest.NewMockHandle(mockCtrl) handle.EXPECT().ServiceGenerator().Return(nil) return handle }, wantFiles: []string{ "foo/types.go", "common/bar/types.go", }, }, { desc: "empty plugin", getPlugin: func(mockCtrl *gomock.Controller) plugin.Handle { return plugin.EmptyHandle }, wantFiles: []string{ "foo/types.go", "common/bar/types.go", }, }, { desc: "ServiceGenerator plugin", getPlugin: func(mockCtrl *gomock.Controller) plugin.Handle { sgen := handletest.NewMockServiceGenerator(mockCtrl) sgen.EXPECT().Generate(gomock.Any()). Return(&api.GenerateServiceResponse{ Files: map[string][]byte{ "foo.txt": []byte("hello world\n"), "bar/baz.go": []byte("package bar\n"), }, }, nil) handle := handletest.NewMockHandle(mockCtrl) handle.EXPECT().ServiceGenerator().Return(sgen) return handle }, wantFiles: []string{ "foo/types.go", "common/bar/types.go", "foo.txt", "bar/baz.go", }, }, { desc: "ServiceGenerator plugin conflict", getPlugin: func(mockCtrl *gomock.Controller) plugin.Handle { sgen := handletest.NewMockServiceGenerator(mockCtrl) sgen.EXPECT().Generate(gomock.Any()). Return(&api.GenerateServiceResponse{ Files: map[string][]byte{ "common/bar/types.go": []byte("hulk smash"), }, }, nil) handle := handletest.NewMockHandle(mockCtrl) handle.EXPECT().ServiceGenerator().Return(sgen) return handle }, wantError: `file generation conflict: multiple sources are trying to write to "common/bar/types.go"`, }, { desc: "ServiceGenerator plugin error", getPlugin: func(mockCtrl *gomock.Controller) plugin.Handle { sgen := handletest.NewMockServiceGenerator(mockCtrl) sgen.EXPECT().Generate(gomock.Any()).Return(nil, errors.New("great sadness")) handle := handletest.NewMockHandle(mockCtrl) handle.EXPECT().ServiceGenerator().Return(sgen) return handle }, wantError: `great sadness`, }, } for _, tt := range tests { func() { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() outputDir, err := ioutil.TempDir(os.TempDir(), "test-generate-recurse") require.NoError(t, err) defer os.RemoveAll(outputDir) var p plugin.Handle if tt.getPlugin != nil { p = tt.getPlugin(mockCtrl) } err = Generate(module, &Options{ OutputDir: outputDir, PackagePrefix: "go.uber.org/thriftrw/gen/testdata", ThriftRoot: testdata(t, "thrift"), Plugin: p, NoRecurse: tt.noRecurse, }) if tt.wantError != "" { assert.Contains(t, err.Error(), tt.wantError) return } if assert.NoError(t, err, tt.desc) { for _, f := range tt.wantFiles { _, err = os.Stat(filepath.Join(outputDir, f)) assert.NoError(t, err, tt.desc) } } }() } }
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) } }() } }