xref: /aosp_15_r20/external/golang-protobuf/compiler/protogen/protogen_test.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package protogen
6
7import (
8	"flag"
9	"fmt"
10	"testing"
11
12	"github.com/google/go-cmp/cmp"
13
14	"google.golang.org/protobuf/proto"
15	"google.golang.org/protobuf/reflect/protoreflect"
16
17	"google.golang.org/protobuf/types/descriptorpb"
18	"google.golang.org/protobuf/types/pluginpb"
19)
20
21func TestPluginParameters(t *testing.T) {
22	var flags flag.FlagSet
23	value := flags.Int("integer", 0, "")
24	const params = "integer=2"
25	_, err := Options{
26		ParamFunc: flags.Set,
27	}.New(&pluginpb.CodeGeneratorRequest{
28		Parameter: proto.String(params),
29	})
30	if err != nil {
31		t.Errorf("New(generator parameters %q): %v", params, err)
32	}
33	if *value != 2 {
34		t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
35	}
36}
37
38func TestPluginParameterErrors(t *testing.T) {
39	for _, parameter := range []string{
40		"unknown=1",
41		"boolean=error",
42	} {
43		var flags flag.FlagSet
44		flags.Bool("boolean", false, "")
45		_, err := Options{
46			ParamFunc: flags.Set,
47		}.New(&pluginpb.CodeGeneratorRequest{
48			Parameter: proto.String(parameter),
49		})
50		if err == nil {
51			t.Errorf("New(generator parameters %q): want error, got nil", parameter)
52		}
53	}
54}
55
56func TestNoGoPackage(t *testing.T) {
57	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
58		ProtoFile: []*descriptorpb.FileDescriptorProto{
59			{
60				Name:    proto.String("testdata/go_package/no_go_package.proto"),
61				Syntax:  proto.String(protoreflect.Proto3.String()),
62				Package: proto.String("goproto.testdata"),
63			},
64		},
65	})
66	if err == nil {
67		t.Fatalf("missing go_package option: New(req) = nil, want error")
68	}
69}
70
71func TestInvalidImportPath(t *testing.T) {
72	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
73		ProtoFile: []*descriptorpb.FileDescriptorProto{
74			{
75				Name:    proto.String("testdata/go_package/no_go_package.proto"),
76				Syntax:  proto.String(protoreflect.Proto3.String()),
77				Package: proto.String("goproto.testdata"),
78				Options: &descriptorpb.FileOptions{
79					GoPackage: proto.String("foo"),
80				},
81			},
82		},
83	})
84	if err == nil {
85		t.Fatalf("missing go_package option: New(req) = nil, want error")
86	}
87}
88
89func TestPackageNamesAndPaths(t *testing.T) {
90	const (
91		filename         = "dir/filename.proto"
92		protoPackageName = "proto.package"
93	)
94	for _, test := range []struct {
95		desc            string
96		parameter       string
97		goPackageOption string
98		generate        bool
99		wantPackageName GoPackageName
100		wantImportPath  GoImportPath
101		wantFilename    string
102	}{
103		{
104			desc:            "go_package option sets import path",
105			goPackageOption: "golang.org/x/foo",
106			generate:        true,
107			wantPackageName: "foo",
108			wantImportPath:  "golang.org/x/foo",
109			wantFilename:    "golang.org/x/foo/filename",
110		},
111		{
112			desc:            "go_package option sets import path without slashes",
113			goPackageOption: "golang.org;foo",
114			generate:        true,
115			wantPackageName: "foo",
116			wantImportPath:  "golang.org",
117			wantFilename:    "golang.org/filename",
118		},
119		{
120			desc:            "go_package option sets import path and package",
121			goPackageOption: "golang.org/x/foo;bar",
122			generate:        true,
123			wantPackageName: "bar",
124			wantImportPath:  "golang.org/x/foo",
125			wantFilename:    "golang.org/x/foo/filename",
126		},
127		{
128			desc:            "command line sets import path for a file",
129			parameter:       "Mdir/filename.proto=golang.org/x/bar",
130			goPackageOption: "golang.org/x/foo",
131			generate:        true,
132			wantPackageName: "foo",
133			wantImportPath:  "golang.org/x/bar",
134			wantFilename:    "golang.org/x/bar/filename",
135		},
136		{
137			desc:            "command line sets import path for a file with package name specified",
138			parameter:       "Mdir/filename.proto=golang.org/x/bar;bar",
139			goPackageOption: "golang.org/x/foo",
140			generate:        true,
141			wantPackageName: "bar",
142			wantImportPath:  "golang.org/x/bar",
143			wantFilename:    "golang.org/x/bar/filename",
144		},
145		{
146			desc:            "module option set",
147			parameter:       "module=golang.org/x",
148			goPackageOption: "golang.org/x/foo",
149			generate:        false,
150			wantPackageName: "foo",
151			wantImportPath:  "golang.org/x/foo",
152			wantFilename:    "foo/filename",
153		},
154		{
155			desc:            "paths=import uses import path from command line",
156			parameter:       "paths=import,Mdir/filename.proto=golang.org/x/bar",
157			goPackageOption: "golang.org/x/foo",
158			generate:        true,
159			wantPackageName: "foo",
160			wantImportPath:  "golang.org/x/bar",
161			wantFilename:    "golang.org/x/bar/filename",
162		},
163		{
164			desc:            "module option implies paths=import",
165			parameter:       "module=golang.org/x,Mdir/filename.proto=golang.org/x/foo",
166			generate:        false,
167			wantPackageName: "foo",
168			wantImportPath:  "golang.org/x/foo",
169			wantFilename:    "foo/filename",
170		},
171	} {
172		context := fmt.Sprintf(`
173TEST: %v
174  --go_out=%v:.
175  file %q: generate=%v
176  option go_package = %q;
177
178  `,
179			test.desc, test.parameter, filename, test.generate, test.goPackageOption)
180
181		req := &pluginpb.CodeGeneratorRequest{
182			Parameter: proto.String(test.parameter),
183			ProtoFile: []*descriptorpb.FileDescriptorProto{
184				{
185					Name:    proto.String(filename),
186					Package: proto.String(protoPackageName),
187					Options: &descriptorpb.FileOptions{
188						GoPackage: proto.String(test.goPackageOption),
189					},
190				},
191			},
192		}
193		if test.generate {
194			req.FileToGenerate = []string{filename}
195		}
196		gen, err := Options{}.New(req)
197		if err != nil {
198			t.Errorf("%vNew(req) = %v", context, err)
199			continue
200		}
201		gotFile, ok := gen.FilesByPath[filename]
202		if !ok {
203			t.Errorf("%v%v: missing file info", context, filename)
204			continue
205		}
206		if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
207			t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
208		}
209		if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
210			t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
211		}
212		gen.NewGeneratedFile(gotFile.GeneratedFilenamePrefix, "")
213		resp := gen.Response()
214		if got, want := resp.File[0].GetName(), test.wantFilename; got != want {
215			t.Errorf("%vgenerated filename=%v, want %v", context, got, want)
216		}
217	}
218}
219
220func TestPackageNameInference(t *testing.T) {
221	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
222		Parameter: proto.String("Mdir/file1.proto=path/to/file1"),
223		ProtoFile: []*descriptorpb.FileDescriptorProto{
224			{
225				Name:    proto.String("dir/file1.proto"),
226				Package: proto.String("proto.package"),
227			},
228			{
229				Name:    proto.String("dir/file2.proto"),
230				Package: proto.String("proto.package"),
231				Options: &descriptorpb.FileOptions{
232					GoPackage: proto.String("path/to/file2"),
233				},
234			},
235		},
236		FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
237	})
238	if err != nil {
239		t.Fatalf("New(req) = %v", err)
240	}
241	if f1, ok := gen.FilesByPath["dir/file1.proto"]; !ok {
242		t.Errorf("missing file info for dir/file1.proto")
243	} else if f1.GoPackageName != "file1" {
244		t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
245	}
246}
247
248func TestInconsistentPackageNames(t *testing.T) {
249	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
250		ProtoFile: []*descriptorpb.FileDescriptorProto{
251			{
252				Name:    proto.String("dir/file1.proto"),
253				Package: proto.String("proto.package"),
254				Options: &descriptorpb.FileOptions{
255					GoPackage: proto.String("golang.org/x/foo"),
256				},
257			},
258			{
259				Name:    proto.String("dir/file2.proto"),
260				Package: proto.String("proto.package"),
261				Options: &descriptorpb.FileOptions{
262					GoPackage: proto.String("golang.org/x/foo;bar"),
263				},
264			},
265		},
266		FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
267	})
268	if err == nil {
269		t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
270	}
271}
272
273func TestImports(t *testing.T) {
274	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
275	if err != nil {
276		t.Fatal(err)
277	}
278	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
279	g.P("package foo")
280	g.P()
281	for _, importPath := range []GoImportPath{
282		"golang.org/x/foo",
283		// Multiple references to the same package.
284		"golang.org/x/bar",
285		"golang.org/x/bar",
286		// Reference to a different package with the same basename.
287		"golang.org/y/bar",
288		"golang.org/x/baz",
289		// Reference to a package conflicting with a predeclared identifier.
290		"golang.org/z/string",
291	} {
292		g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
293	}
294	want := `package foo
295
296import (
297	bar "golang.org/x/bar"
298	baz "golang.org/x/baz"
299	bar1 "golang.org/y/bar"
300	string1 "golang.org/z/string"
301)
302
303var _ = X         // "golang.org/x/foo"
304var _ = bar.X     // "golang.org/x/bar"
305var _ = bar.X     // "golang.org/x/bar"
306var _ = bar1.X    // "golang.org/y/bar"
307var _ = baz.X     // "golang.org/x/baz"
308var _ = string1.X // "golang.org/z/string"
309`
310	got, err := g.Content()
311	if err != nil {
312		t.Fatalf("g.Content() = %v", err)
313	}
314	if diff := cmp.Diff(string(want), string(got)); diff != "" {
315		t.Fatalf("content mismatch (-want +got):\n%s", diff)
316	}
317}
318
319func TestImportRewrites(t *testing.T) {
320	gen, err := Options{
321		ImportRewriteFunc: func(i GoImportPath) GoImportPath {
322			return "prefix/" + i
323		},
324	}.New(&pluginpb.CodeGeneratorRequest{})
325	if err != nil {
326		t.Fatal(err)
327	}
328	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
329	g.P("package foo")
330	g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
331	want := `package foo
332
333import (
334	bar "prefix/golang.org/x/bar"
335)
336
337var _ = bar.X
338`
339	got, err := g.Content()
340	if err != nil {
341		t.Fatalf("g.Content() = %v", err)
342	}
343	if diff := cmp.Diff(string(want), string(got)); diff != "" {
344		t.Fatalf("content mismatch (-want +got):\n%s", diff)
345	}
346}
347