Пример #1
0
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)
				}
			}
		}()
	}
}
Пример #2
0
func TestAddRootService(t *testing.T) {
	tests := []struct {
		desc string
		spec *compile.ServiceSpec
		want *api.GenerateServiceRequest
	}{
		{
			desc: "empty service",
			spec: &compile.ServiceSpec{
				Name: "EmptyService",
				File: "idl/empty.thrift",
			},
			want: &api.GenerateServiceRequest{
				RootServices: []api.ServiceID{1},
				Services: map[api.ServiceID]*api.Service{
					1: {
						Name:       "EmptyService",
						ThriftName: "EmptyService",
						Functions:  []*api.Function{}, // must be non-nil
						ModuleID:   1,
					},
				},
				Modules: map[api.ModuleID]*api.Module{
					1: {
						ImportPath: "go.uber.org/thriftrw/gen/testdata/empty",
						Directory:  "empty",
					},
				},
			},
		},
		{
			desc: "Non standard names",
			spec: &compile.ServiceSpec{
				Name: "non_standard_service_name",
				File: "idl/service.thrift",
			},
			want: &api.GenerateServiceRequest{
				RootServices: []api.ServiceID{1},
				Services: map[api.ServiceID]*api.Service{
					1: {
						Name:       "NonStandardServiceName",
						ThriftName: "non_standard_service_name",
						Functions:  []*api.Function{}, // must be non-nil
						ModuleID:   1,
					},
				},
				Modules: map[api.ModuleID]*api.Module{
					1: {
						ImportPath: "go.uber.org/thriftrw/gen/testdata/service",
						Directory:  "service",
					},
				},
			},
		},
		{
			desc: "service with a parent",
			spec: &compile.ServiceSpec{
				Name: "KeyValue",
				File: "idl/kv.thrift",
				Parent: &compile.ServiceSpec{
					Name: "AbstractService",
					File: "idl/common/abstract.thrift",
				},
			},
			want: &api.GenerateServiceRequest{
				RootServices: []api.ServiceID{2},
				Services: map[api.ServiceID]*api.Service{
					1: {
						Name:       "AbstractService",
						ThriftName: "AbstractService",
						Functions:  []*api.Function{}, // must be non-nil
						ModuleID:   1,
					},
					2: {
						Name:       "KeyValue",
						ThriftName: "KeyValue",
						ParentID:   (*api.ServiceID)(ptr.Int32(1)),
						Functions:  []*api.Function{}, // must be non-nil
						ModuleID:   2,
					},
				},
				Modules: map[api.ModuleID]*api.Module{
					1: {
						ImportPath: "go.uber.org/thriftrw/gen/testdata/common/abstract",
						Directory:  "common/abstract",
					},
					2: {
						ImportPath: "go.uber.org/thriftrw/gen/testdata/kv",
						Directory:  "kv",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		spec := tt.spec
		err := spec.Link(compile.EmptyScope("foo"))
		if !assert.NoError(t, err, "%v: invalid test: scope must link", tt.desc) {
			continue
		}

		importer := thriftPackageImporter{
			ImportPrefix: "go.uber.org/thriftrw/gen/testdata",
			ThriftRoot:   "idl",
		}

		g := newGenerateServiceBuilder(importer)
		if _, err := g.AddRootService(spec); assert.NoError(t, err, tt.desc) {
			assert.Equal(t, tt.want, g.Build(), tt.desc)
		}
	}
}
Пример #3
0
func TestBuildType(t *testing.T) {
	tests := []struct {
		desc     string
		spec     compile.TypeSpec
		required bool

		want *api.Type
	}{
		// required primitives
		{
			desc:     "bool",
			spec:     &compile.BoolSpec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeBool)},
		},
		{
			desc:     "int8",
			spec:     &compile.I8Spec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeInt8)},
		},
		{
			desc:     "int16",
			spec:     &compile.I16Spec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeInt16)},
		},
		{
			desc:     "int32",
			spec:     &compile.I32Spec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeInt32)},
		},
		{
			desc:     "int64",
			spec:     &compile.I64Spec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeInt64)},
		},
		{
			desc:     "float64",
			spec:     &compile.DoubleSpec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeFloat64)},
		},
		{
			desc:     "string",
			spec:     &compile.StringSpec{},
			required: true,
			want:     &api.Type{SimpleType: simpleType(api.SimpleTypeString)},
		},
		{
			desc:     "[]byte",
			spec:     &compile.BinarySpec{},
			required: true,
			want:     &api.Type{SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)}},
		},

		// optional primitives
		{
			desc: "*bool",
			spec: &compile.BoolSpec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeBool)}},
		},
		{
			desc: "*int8",
			spec: &compile.I8Spec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeInt8)}},
		},
		{
			desc: "*int16",
			spec: &compile.I16Spec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeInt16)}},
		},
		{
			desc: "*int32",
			spec: &compile.I32Spec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeInt32)}},
		},
		{
			desc: "*int64",
			spec: &compile.I64Spec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeInt64)}},
		},
		{
			desc: "*float64",
			spec: &compile.DoubleSpec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeFloat64)}},
		},
		{
			desc: "*string",
			spec: &compile.StringSpec{},
			want: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeString)}},
		},
		{
			desc: "[]byte",
			spec: &compile.BinarySpec{},
			want: &api.Type{SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)}},
		},

		// containers
		{
			// hashable map key
			desc: "map[string]int32",
			spec: &compile.MapSpec{
				KeySpec:   &compile.StringSpec{},
				ValueSpec: &compile.I32Spec{},
			},
			want: &api.Type{MapType: &api.TypePair{
				Left:  &api.Type{SimpleType: simpleType(api.SimpleTypeString)},
				Right: &api.Type{SimpleType: simpleType(api.SimpleTypeInt32)},
			}},
		},
		{
			// unhashable map key
			desc: "[]struct{Key []byte; Value int32}",
			spec: &compile.MapSpec{
				KeySpec:   &compile.BinarySpec{},
				ValueSpec: &compile.I32Spec{},
			},
			want: &api.Type{KeyValueSliceType: &api.TypePair{
				Left: &api.Type{
					SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)},
				},
				Right: &api.Type{SimpleType: simpleType(api.SimpleTypeInt32)},
			}},
		},
		{
			// hashable set item
			desc: "map[float64]struct{}",
			spec: &compile.SetSpec{ValueSpec: &compile.DoubleSpec{}},
			want: &api.Type{MapType: &api.TypePair{
				Left:  &api.Type{SimpleType: simpleType(api.SimpleTypeFloat64)},
				Right: &api.Type{SimpleType: simpleType(api.SimpleTypeStructEmpty)},
			}},
		},
		{
			// unhashable set item
			desc: "[]*foo.Foo",
			spec: &compile.SetSpec{
				ValueSpec: &compile.StructSpec{
					Name: "Foo",
					File: "idl/foo.thrift",
					Type: ast.StructType,
					Fields: compile.FieldGroup{
						{
							ID:       1,
							Name:     "value",
							Type:     &compile.StringSpec{},
							Required: true,
						},
					},
				},
			},
			want: &api.Type{
				SliceType: &api.Type{
					PointerType: &api.Type{
						ReferenceType: &api.TypeReference{
							Name:       "Foo",
							ImportPath: "go.uber.org/thriftrw/gen/testdata/foo",
						},
					},
				},
			},
		},
		{
			// list
			desc: "[]map[string][]byte",
			spec: &compile.ListSpec{
				ValueSpec: &compile.MapSpec{
					KeySpec:   &compile.StringSpec{},
					ValueSpec: &compile.BinarySpec{},
				},
			},
			want: &api.Type{
				SliceType: &api.Type{
					MapType: &api.TypePair{
						Left: &api.Type{SimpleType: simpleType(api.SimpleTypeString)},
						Right: &api.Type{
							SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)},
						},
					},
				},
			},
		},
		{
			// required enum
			desc: "required enum",
			spec: &compile.EnumSpec{
				Name: "Foo",
				File: "idl/bar.thrift",
				Items: []compile.EnumItem{
					{Name: "A", Value: 0},
					{Name: "B", Value: 2},
				},
			},
			required: true,
			want: &api.Type{
				ReferenceType: &api.TypeReference{
					Name:       "Foo",
					ImportPath: "go.uber.org/thriftrw/gen/testdata/bar",
				},
			},
		},
		{
			// optional enum
			desc: "optional enum",
			spec: &compile.EnumSpec{
				Name: "Foo",
				File: "idl/bar.thrift",
				Items: []compile.EnumItem{
					{Name: "A", Value: 0},
					{Name: "B", Value: 2},
				},
			},
			want: &api.Type{
				PointerType: &api.Type{
					ReferenceType: &api.TypeReference{
						Name:       "Foo",
						ImportPath: "go.uber.org/thriftrw/gen/testdata/bar",
					},
				},
			},
		},
		{
			// struct
			desc: "struct",
			spec: &compile.StructSpec{
				Name: "Foo",
				File: "idl/foo.thrift",
				Type: ast.StructType,
				Fields: compile.FieldGroup{
					{
						ID:       1,
						Name:     "value",
						Type:     &compile.StringSpec{},
						Required: true,
					},
				},
			},
			want: &api.Type{
				PointerType: &api.Type{
					ReferenceType: &api.TypeReference{
						Name:       "Foo",
						ImportPath: "go.uber.org/thriftrw/gen/testdata/foo",
					},
				},
			},
		},
		{
			desc: "required typedef with a primitive",
			spec: &compile.TypedefSpec{
				Name:   "Foo",
				File:   "idl/foo/bar.thrift",
				Target: &compile.I64Spec{},
			},
			required: true,
			want: &api.Type{
				ReferenceType: &api.TypeReference{
					Name:       "Foo",
					ImportPath: "go.uber.org/thriftrw/gen/testdata/foo/bar",
				},
			},
		},
		{
			desc: "optional typedef with a primitive",
			spec: &compile.TypedefSpec{
				Name:   "Foo",
				File:   "idl/foo/bar.thrift",
				Target: &compile.I64Spec{},
			},
			want: &api.Type{
				PointerType: &api.Type{
					ReferenceType: &api.TypeReference{
						Name:       "Foo",
						ImportPath: "go.uber.org/thriftrw/gen/testdata/foo/bar",
					},
				},
			},
		},
		{
			desc: "required typedef with non-primitive",
			spec: &compile.TypedefSpec{
				Name:   "Foo",
				File:   "idl/foo/bar.thrift",
				Target: &compile.BinarySpec{},
			},
			required: true,
			want: &api.Type{
				ReferenceType: &api.TypeReference{
					Name:       "Foo",
					ImportPath: "go.uber.org/thriftrw/gen/testdata/foo/bar",
				},
			},
		},
		{
			desc: "optional typedef with non-primitive",
			spec: &compile.TypedefSpec{
				Name:   "Foo",
				File:   "idl/foo/bar.thrift",
				Target: &compile.ListSpec{ValueSpec: &compile.StringSpec{}},
			},
			want: &api.Type{
				ReferenceType: &api.TypeReference{
					Name:       "Foo",
					ImportPath: "go.uber.org/thriftrw/gen/testdata/foo/bar",
				},
			},
		},
	}

	for _, tt := range tests {
		spec, err := tt.spec.Link(compile.EmptyScope("foo"))
		if !assert.NoError(t, err, "%v: invalid test: scope must link", tt.desc) {
			continue
		}

		importer := thriftPackageImporter{
			ImportPrefix: "go.uber.org/thriftrw/gen/testdata",
			ThriftRoot:   "idl",
		}

		g := newGenerateServiceBuilder(importer)
		got, err := g.buildType(spec, tt.required)
		if assert.NoError(t, err, tt.desc) {
			assert.Equal(t, tt.want, got, tt.desc)
		}
	}
}
Пример #4
0
func TestBuildFunction(t *testing.T) {
	tests := []struct {
		desc string
		spec *compile.FunctionSpec
		want *api.Function
	}{
		{
			desc: "returns and throws",
			spec: &compile.FunctionSpec{
				Name: "getValue",
				ArgsSpec: compile.ArgsSpec{
					{
						ID:   1,
						Name: "key",
						Type: &compile.StringSpec{},
					},
				},
				ResultSpec: &compile.ResultSpec{
					ReturnType: &compile.BinarySpec{},
					Exceptions: compile.FieldGroup{
						{
							ID:   1,
							Name: "doesNotExist",
							Type: &compile.StructSpec{
								Name: "KeyDoesNotExist",
								File: "idl/keyvalue.thrift",
								Type: ast.ExceptionType,
								Fields: compile.FieldGroup{
									{
										ID:   1,
										Name: "message",
										Type: &compile.StringSpec{},
									},
								},
							},
						},
					},
				},
			},
			want: &api.Function{
				Name:       "GetValue",
				ThriftName: "getValue",
				Arguments: []*api.Argument{
					{
						Name: "Key",
						Type: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeString)}},
					},
				},
				ReturnType: &api.Type{SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)}},
				Exceptions: []*api.Argument{
					{
						Name: "DoesNotExist",
						Type: &api.Type{
							PointerType: &api.Type{
								ReferenceType: &api.TypeReference{
									Name:       "KeyDoesNotExist",
									ImportPath: "go.uber.org/thriftrw/gen/testdata/keyvalue",
								},
							},
						},
					},
				},
			},
		},
		{
			desc: "no return, no throw",
			spec: &compile.FunctionSpec{
				Name: "setValue",
				ArgsSpec: compile.ArgsSpec{
					{
						ID:   1,
						Name: "key",
						Type: &compile.StringSpec{},
					},
					{
						ID:   2,
						Name: "value",
						Type: &compile.BinarySpec{},
					},
				},
				ResultSpec: &compile.ResultSpec{},
			},
			want: &api.Function{
				Name:       "SetValue",
				ThriftName: "setValue",
				Arguments: []*api.Argument{
					{
						Name: "Key",
						Type: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeString)}},
					},
					{
						Name: "Value",
						Type: &api.Type{SliceType: &api.Type{SimpleType: simpleType(api.SimpleTypeByte)}},
					},
				},
			},
		},
		{
			desc: "oneway",
			spec: &compile.FunctionSpec{
				Name:   "clearCache",
				OneWay: true,
				ArgsSpec: compile.ArgsSpec{
					{
						ID:   1,
						Name: "delayMS",
						Type: &compile.I64Spec{},
					},
				},
			},
			want: &api.Function{
				Name:       "ClearCache",
				ThriftName: "clearCache",
				OneWay:     ptr.Bool(true),
				Arguments: []*api.Argument{
					{
						Name: "DelayMS",
						Type: &api.Type{PointerType: &api.Type{SimpleType: simpleType(api.SimpleTypeInt64)}},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		spec := tt.spec
		err := spec.Link(compile.EmptyScope("foo"))
		if !assert.NoError(t, err, "%v: invalid test: scope must link", tt.desc) {
			continue
		}

		importer := thriftPackageImporter{
			ImportPrefix: "go.uber.org/thriftrw/gen/testdata",
			ThriftRoot:   "idl",
		}

		g := newGenerateServiceBuilder(importer)
		got, err := g.buildFunction(spec)
		if assert.NoError(t, err, tt.desc) {
			assert.Equal(t, tt.want, got, tt.desc)
		}
	}
}