1
2
3
4
5 package protogen
6
7 import (
8 "flag"
9 "fmt"
10 "testing"
11
12 "github.com/google/go-cmp/cmp"
13
14 "google.golang.org/protobuf/internal/genid"
15 "google.golang.org/protobuf/proto"
16 "google.golang.org/protobuf/reflect/protoreflect"
17 "google.golang.org/protobuf/testing/protocmp"
18
19 "google.golang.org/protobuf/types/descriptorpb"
20 "google.golang.org/protobuf/types/pluginpb"
21 )
22
23 func TestPluginParameters(t *testing.T) {
24 var flags flag.FlagSet
25 value := flags.Int("integer", 0, "")
26 const params = "integer=2"
27 _, err := Options{
28 ParamFunc: flags.Set,
29 }.New(&pluginpb.CodeGeneratorRequest{
30 Parameter: proto.String(params),
31 })
32 if err != nil {
33 t.Errorf("New(generator parameters %q): %v", params, err)
34 }
35 if *value != 2 {
36 t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
37 }
38 }
39
40 func TestPluginParameterErrors(t *testing.T) {
41 for _, parameter := range []string{
42 "unknown=1",
43 "boolean=error",
44 } {
45 var flags flag.FlagSet
46 flags.Bool("boolean", false, "")
47 _, err := Options{
48 ParamFunc: flags.Set,
49 }.New(&pluginpb.CodeGeneratorRequest{
50 Parameter: proto.String(parameter),
51 })
52 if err == nil {
53 t.Errorf("New(generator parameters %q): want error, got nil", parameter)
54 }
55 }
56 }
57
58 func TestNoGoPackage(t *testing.T) {
59 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
60 ProtoFile: []*descriptorpb.FileDescriptorProto{
61 {
62 Name: proto.String("testdata/go_package/no_go_package.proto"),
63 Syntax: proto.String(protoreflect.Proto3.String()),
64 Package: proto.String("goproto.testdata"),
65 },
66 },
67 })
68 if err == nil {
69 t.Fatalf("missing go_package option: New(req) = nil, want error")
70 }
71 }
72
73 func TestInvalidImportPath(t *testing.T) {
74 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
75 ProtoFile: []*descriptorpb.FileDescriptorProto{
76 {
77 Name: proto.String("testdata/go_package/no_go_package.proto"),
78 Syntax: proto.String(protoreflect.Proto3.String()),
79 Package: proto.String("goproto.testdata"),
80 Options: &descriptorpb.FileOptions{
81 GoPackage: proto.String("foo"),
82 },
83 },
84 },
85 })
86 if err == nil {
87 t.Fatalf("missing go_package option: New(req) = nil, want error")
88 }
89 }
90
91 func TestPackageNamesAndPaths(t *testing.T) {
92 const (
93 filename = "dir/filename.proto"
94 protoPackageName = "proto.package"
95 )
96 for _, test := range []struct {
97 desc string
98 parameter string
99 goPackageOption string
100 generate bool
101 wantPackageName GoPackageName
102 wantImportPath GoImportPath
103 wantFilename string
104 }{
105 {
106 desc: "go_package option sets import path",
107 goPackageOption: "golang.org/x/foo",
108 generate: true,
109 wantPackageName: "foo",
110 wantImportPath: "golang.org/x/foo",
111 wantFilename: "golang.org/x/foo/filename",
112 },
113 {
114 desc: "go_package option sets import path without slashes",
115 goPackageOption: "golang.org;foo",
116 generate: true,
117 wantPackageName: "foo",
118 wantImportPath: "golang.org",
119 wantFilename: "golang.org/filename",
120 },
121 {
122 desc: "go_package option sets import path and package",
123 goPackageOption: "golang.org/x/foo;bar",
124 generate: true,
125 wantPackageName: "bar",
126 wantImportPath: "golang.org/x/foo",
127 wantFilename: "golang.org/x/foo/filename",
128 },
129 {
130 desc: "command line sets import path for a file",
131 parameter: "Mdir/filename.proto=golang.org/x/bar",
132 goPackageOption: "golang.org/x/foo",
133 generate: true,
134 wantPackageName: "foo",
135 wantImportPath: "golang.org/x/bar",
136 wantFilename: "golang.org/x/bar/filename",
137 },
138 {
139 desc: "command line sets import path for a file with package name specified",
140 parameter: "Mdir/filename.proto=golang.org/x/bar;bar",
141 goPackageOption: "golang.org/x/foo",
142 generate: true,
143 wantPackageName: "bar",
144 wantImportPath: "golang.org/x/bar",
145 wantFilename: "golang.org/x/bar/filename",
146 },
147 {
148 desc: "module option set",
149 parameter: "module=golang.org/x",
150 goPackageOption: "golang.org/x/foo",
151 generate: false,
152 wantPackageName: "foo",
153 wantImportPath: "golang.org/x/foo",
154 wantFilename: "foo/filename",
155 },
156 {
157 desc: "paths=import uses import path from command line",
158 parameter: "paths=import,Mdir/filename.proto=golang.org/x/bar",
159 goPackageOption: "golang.org/x/foo",
160 generate: true,
161 wantPackageName: "foo",
162 wantImportPath: "golang.org/x/bar",
163 wantFilename: "golang.org/x/bar/filename",
164 },
165 {
166 desc: "module option implies paths=import",
167 parameter: "module=golang.org/x,Mdir/filename.proto=golang.org/x/foo",
168 generate: false,
169 wantPackageName: "foo",
170 wantImportPath: "golang.org/x/foo",
171 wantFilename: "foo/filename",
172 },
173 } {
174 context := fmt.Sprintf(`
175 TEST: %v
176 --go_out=%v:.
177 file %q: generate=%v
178 option go_package = %q;
179
180 `,
181 test.desc, test.parameter, filename, test.generate, test.goPackageOption)
182
183 req := &pluginpb.CodeGeneratorRequest{
184 Parameter: proto.String(test.parameter),
185 ProtoFile: []*descriptorpb.FileDescriptorProto{
186 {
187 Name: proto.String(filename),
188 Package: proto.String(protoPackageName),
189 Options: &descriptorpb.FileOptions{
190 GoPackage: proto.String(test.goPackageOption),
191 },
192 },
193 },
194 }
195 if test.generate {
196 req.FileToGenerate = []string{filename}
197 }
198 gen, err := Options{}.New(req)
199 if err != nil {
200 t.Errorf("%vNew(req) = %v", context, err)
201 continue
202 }
203 gotFile, ok := gen.FilesByPath[filename]
204 if !ok {
205 t.Errorf("%v%v: missing file info", context, filename)
206 continue
207 }
208 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
209 t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
210 }
211 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
212 t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
213 }
214 gen.NewGeneratedFile(gotFile.GeneratedFilenamePrefix, "")
215 resp := gen.Response()
216 if got, want := resp.File[0].GetName(), test.wantFilename; got != want {
217 t.Errorf("%vgenerated filename=%v, want %v", context, got, want)
218 }
219 }
220 }
221
222 func TestPackageNameInference(t *testing.T) {
223 gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
224 Parameter: proto.String("Mdir/file1.proto=path/to/file1"),
225 ProtoFile: []*descriptorpb.FileDescriptorProto{
226 {
227 Name: proto.String("dir/file1.proto"),
228 Package: proto.String("proto.package"),
229 },
230 {
231 Name: proto.String("dir/file2.proto"),
232 Package: proto.String("proto.package"),
233 Options: &descriptorpb.FileOptions{
234 GoPackage: proto.String("path/to/file2"),
235 },
236 },
237 },
238 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
239 })
240 if err != nil {
241 t.Fatalf("New(req) = %v", err)
242 }
243 if f1, ok := gen.FilesByPath["dir/file1.proto"]; !ok {
244 t.Errorf("missing file info for dir/file1.proto")
245 } else if f1.GoPackageName != "file1" {
246 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
247 }
248 }
249
250 func TestInconsistentPackageNames(t *testing.T) {
251 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
252 ProtoFile: []*descriptorpb.FileDescriptorProto{
253 {
254 Name: proto.String("dir/file1.proto"),
255 Package: proto.String("proto.package"),
256 Options: &descriptorpb.FileOptions{
257 GoPackage: proto.String("golang.org/x/foo"),
258 },
259 },
260 {
261 Name: proto.String("dir/file2.proto"),
262 Package: proto.String("proto.package"),
263 Options: &descriptorpb.FileOptions{
264 GoPackage: proto.String("golang.org/x/foo;bar"),
265 },
266 },
267 },
268 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
269 })
270 if err == nil {
271 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
272 }
273 }
274
275 func TestImports(t *testing.T) {
276 gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
277 if err != nil {
278 t.Fatal(err)
279 }
280 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
281 g.P("package foo")
282 g.P()
283 for _, importPath := range []GoImportPath{
284 "golang.org/x/foo",
285
286 "golang.org/x/bar",
287 "golang.org/x/bar",
288
289 "golang.org/y/bar",
290 "golang.org/x/baz",
291
292 "golang.org/z/string",
293 } {
294 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
295 }
296 want := `package foo
297
298 import (
299 bar "golang.org/x/bar"
300 baz "golang.org/x/baz"
301 bar1 "golang.org/y/bar"
302 string1 "golang.org/z/string"
303 )
304
305 var _ = X // "golang.org/x/foo"
306 var _ = bar.X // "golang.org/x/bar"
307 var _ = bar.X // "golang.org/x/bar"
308 var _ = bar1.X // "golang.org/y/bar"
309 var _ = baz.X // "golang.org/x/baz"
310 var _ = string1.X // "golang.org/z/string"
311 `
312 got, err := g.Content()
313 if err != nil {
314 t.Fatalf("g.Content() = %v", err)
315 }
316 if diff := cmp.Diff(string(want), string(got)); diff != "" {
317 t.Fatalf("content mismatch (-want +got):\n%s", diff)
318 }
319 }
320
321 func TestImportRewrites(t *testing.T) {
322 gen, err := Options{
323 ImportRewriteFunc: func(i GoImportPath) GoImportPath {
324 return "prefix/" + i
325 },
326 }.New(&pluginpb.CodeGeneratorRequest{})
327 if err != nil {
328 t.Fatal(err)
329 }
330 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
331 g.P("package foo")
332 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
333 want := `package foo
334
335 import (
336 bar "prefix/golang.org/x/bar"
337 )
338
339 var _ = bar.X
340 `
341 got, err := g.Content()
342 if err != nil {
343 t.Fatalf("g.Content() = %v", err)
344 }
345 if diff := cmp.Diff(string(want), string(got)); diff != "" {
346 t.Fatalf("content mismatch (-want +got):\n%s", diff)
347 }
348 }
349
350 func TestAnnotations(t *testing.T) {
351 gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
352 if err != nil {
353 t.Fatal(err)
354 }
355 loc := Location{SourceFile: "foo.go"}
356 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
357
358 g.P("package foo")
359 g.P()
360
361 structName := "S"
362 fieldName := "Field"
363
364 messageLoc := loc.appendPath(genid.FileDescriptorProto_MessageType_field_number, 1)
365 fieldLoc := messageLoc.appendPath(genid.DescriptorProto_Field_field_number, 1)
366
367 g.Annotate(structName, messageLoc)
368 g.P("type ", structName, " struct {")
369 g.AnnotateSymbol(structName+"."+fieldName, Annotation{Location: fieldLoc})
370 g.P(fieldName, " string")
371 g.P("}")
372 g.P()
373
374 g.AnnotateSymbol(fmt.Sprintf("%s.Set%s", structName, fieldName), Annotation{
375 Location: fieldLoc,
376 Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
377 })
378 g.P("func (m *", structName, ") Set", fieldName, "(x string) {")
379 g.P("m.", fieldName, " = x")
380 g.P("}")
381 g.P()
382
383 want := &descriptorpb.GeneratedCodeInfo{
384 Annotation: []*descriptorpb.GeneratedCodeInfo_Annotation{
385 {
386 SourceFile: proto.String("foo.go"),
387 Path: []int32{4, 1},
388 Begin: proto.Int32(18),
389 End: proto.Int32(19),
390 },
391 {
392 SourceFile: proto.String("foo.go"),
393 Path: []int32{4, 1, 2, 1},
394 Begin: proto.Int32(30),
395 End: proto.Int32(35),
396 },
397 {
398 SourceFile: proto.String("foo.go"),
399 Path: []int32{4, 1, 2, 1},
400 Begin: proto.Int32(58),
401 End: proto.Int32(66),
402 Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
403 },
404 },
405 }
406
407 content, err := g.Content()
408 if err != nil {
409 t.Fatalf("g.Content() = %v", err)
410 }
411 got, err := g.generatedCodeInfo(content)
412 if err != nil {
413 t.Fatalf("g.generatedCodeInfo(...) = %v", err)
414 }
415 if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
416 t.Fatalf("GeneratedCodeInfo mismatch (-want +got):\n%s", diff)
417 }
418 }
419
View as plain text