blob: 9218acda6c77cf6b6be181f6d44e810e5c733fcd [file] [log] [blame]
Damien Neil220c2022018-08-15 11:24:18 -07001// 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
Joe Tsai42fa50d2018-10-15 17:34:43 -07005// +build golden
6
Damien Neil220c2022018-08-15 11:24:18 -07007package protogen
8
9import (
Damien Neil3cf6e622018-09-11 13:53:14 -070010 "flag"
Damien Neil082ce922018-09-06 10:23:53 -070011 "fmt"
Damien Neil220c2022018-08-15 11:24:18 -070012 "io/ioutil"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "strings"
17 "testing"
18
19 "github.com/golang/protobuf/proto"
Joe Tsai009e0672018-11-27 18:45:07 -080020 "github.com/golang/protobuf/v2/internal/scalar"
Joe Tsaie1f8d502018-11-26 18:55:29 -080021
22 descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
23 pluginpb "github.com/golang/protobuf/v2/types/plugin"
Damien Neil220c2022018-08-15 11:24:18 -070024)
25
Damien Neil3cf6e622018-09-11 13:53:14 -070026func TestPluginParameters(t *testing.T) {
27 var flags flag.FlagSet
28 value := flags.Int("integer", 0, "")
29 opts := &Options{
30 ParamFunc: flags.Set,
31 }
32 const params = "integer=2"
33 _, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -080034 Parameter: scalar.String(params),
Damien Neil3cf6e622018-09-11 13:53:14 -070035 }, opts)
36 if err != nil {
37 t.Errorf("New(generator parameters %q): %v", params, err)
38 }
39 if *value != 2 {
40 t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
41 }
42}
43
44func TestPluginParameterErrors(t *testing.T) {
45 for _, parameter := range []string{
46 "unknown=1",
47 "boolean=error",
48 } {
49 var flags flag.FlagSet
50 flags.Bool("boolean", false, "")
51 opts := &Options{
52 ParamFunc: flags.Set,
53 }
54 _, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -080055 Parameter: scalar.String(parameter),
Damien Neil3cf6e622018-09-11 13:53:14 -070056 }, opts)
57 if err == nil {
58 t.Errorf("New(generator parameters %q): want error, got nil", parameter)
59 }
60 }
61}
62
Damien Neil220c2022018-08-15 11:24:18 -070063func TestFiles(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -070064 gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
Damien Neil220c2022018-08-15 11:24:18 -070065 if err != nil {
66 t.Fatal(err)
67 }
68 for _, test := range []struct {
69 path string
70 wantGenerate bool
71 }{
72 {
73 path: "go_package/no_go_package_import.proto",
74 wantGenerate: true,
75 },
76 {
77 path: "go_package/no_go_package.proto",
78 wantGenerate: false,
79 },
80 } {
81 f, ok := gen.FileByName(test.path)
82 if !ok {
83 t.Errorf("%q: not found by gen.FileByName", test.path)
84 continue
85 }
86 if f.Generate != test.wantGenerate {
87 t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
88 }
89 }
90}
91
Damien Neil082ce922018-09-06 10:23:53 -070092func TestPackageNamesAndPaths(t *testing.T) {
93 const (
94 filename = "dir/filename.proto"
95 protoPackageName = "proto.package"
96 )
97 for _, test := range []struct {
98 desc string
99 parameter string
100 goPackageOption string
101 generate bool
102 wantPackageName GoPackageName
103 wantImportPath GoImportPath
104 wantFilenamePrefix string
105 }{
106 {
107 desc: "no parameters, no go_package option",
108 generate: true,
109 wantPackageName: "proto_package",
110 wantImportPath: "dir",
111 wantFilenamePrefix: "dir/filename",
112 },
113 {
114 desc: "go_package option sets import path",
115 goPackageOption: "golang.org/x/foo",
116 generate: true,
117 wantPackageName: "foo",
118 wantImportPath: "golang.org/x/foo",
119 wantFilenamePrefix: "golang.org/x/foo/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 wantFilenamePrefix: "golang.org/x/foo/filename",
128 },
129 {
130 desc: "go_package option sets package",
131 goPackageOption: "foo",
132 generate: true,
133 wantPackageName: "foo",
134 wantImportPath: "dir",
135 wantFilenamePrefix: "dir/filename",
136 },
137 {
138 desc: "command line sets import path for a file",
139 parameter: "Mdir/filename.proto=golang.org/x/bar",
140 goPackageOption: "golang.org/x/foo",
141 generate: true,
142 wantPackageName: "foo",
143 wantImportPath: "golang.org/x/bar",
144 wantFilenamePrefix: "golang.org/x/foo/filename",
145 },
146 {
147 desc: "import_path parameter sets import path of generated files",
148 parameter: "import_path=golang.org/x/bar",
149 goPackageOption: "golang.org/x/foo",
150 generate: true,
151 wantPackageName: "foo",
152 wantImportPath: "golang.org/x/bar",
153 wantFilenamePrefix: "golang.org/x/foo/filename",
154 },
155 {
156 desc: "import_path parameter does not set import path of dependencies",
157 parameter: "import_path=golang.org/x/bar",
158 goPackageOption: "golang.org/x/foo",
159 generate: false,
160 wantPackageName: "foo",
161 wantImportPath: "golang.org/x/foo",
162 wantFilenamePrefix: "golang.org/x/foo/filename",
163 },
164 } {
165 context := fmt.Sprintf(`
166TEST: %v
167 --go_out=%v:.
168 file %q: generate=%v
169 option go_package = %q;
170
171 `,
172 test.desc, test.parameter, filename, test.generate, test.goPackageOption)
173
174 req := &pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -0800175 Parameter: scalar.String(test.parameter),
Joe Tsaie1f8d502018-11-26 18:55:29 -0800176 ProtoFile: []*descriptorpb.FileDescriptorProto{
Damien Neil082ce922018-09-06 10:23:53 -0700177 {
Joe Tsai009e0672018-11-27 18:45:07 -0800178 Name: scalar.String(filename),
179 Package: scalar.String(protoPackageName),
Joe Tsaie1f8d502018-11-26 18:55:29 -0800180 Options: &descriptorpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800181 GoPackage: scalar.String(test.goPackageOption),
Damien Neil082ce922018-09-06 10:23:53 -0700182 },
183 },
184 },
185 }
186 if test.generate {
187 req.FileToGenerate = []string{filename}
188 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700189 gen, err := New(req, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700190 if err != nil {
191 t.Errorf("%vNew(req) = %v", context, err)
192 continue
193 }
194 gotFile, ok := gen.FileByName(filename)
195 if !ok {
196 t.Errorf("%v%v: missing file info", context, filename)
197 continue
198 }
199 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
200 t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
201 }
202 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
203 t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
204 }
205 if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
206 t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
207 }
208 }
209}
210
211func TestPackageNameInference(t *testing.T) {
212 gen, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsaie1f8d502018-11-26 18:55:29 -0800213 ProtoFile: []*descriptorpb.FileDescriptorProto{
Damien Neil082ce922018-09-06 10:23:53 -0700214 {
Joe Tsai009e0672018-11-27 18:45:07 -0800215 Name: scalar.String("dir/file1.proto"),
216 Package: scalar.String("proto.package"),
Damien Neil082ce922018-09-06 10:23:53 -0700217 },
218 {
Joe Tsai009e0672018-11-27 18:45:07 -0800219 Name: scalar.String("dir/file2.proto"),
220 Package: scalar.String("proto.package"),
Joe Tsaie1f8d502018-11-26 18:55:29 -0800221 Options: &descriptorpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800222 GoPackage: scalar.String("foo"),
Damien Neil082ce922018-09-06 10:23:53 -0700223 },
224 },
225 },
226 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700227 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700228 if err != nil {
229 t.Fatalf("New(req) = %v", err)
230 }
231 if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
232 t.Errorf("missing file info for dir/file1.proto")
233 } else if f1.GoPackageName != "foo" {
234 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
235 }
236}
237
238func TestInconsistentPackageNames(t *testing.T) {
239 _, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsaie1f8d502018-11-26 18:55:29 -0800240 ProtoFile: []*descriptorpb.FileDescriptorProto{
Damien Neil082ce922018-09-06 10:23:53 -0700241 {
Joe Tsai009e0672018-11-27 18:45:07 -0800242 Name: scalar.String("dir/file1.proto"),
243 Package: scalar.String("proto.package"),
Joe Tsaie1f8d502018-11-26 18:55:29 -0800244 Options: &descriptorpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800245 GoPackage: scalar.String("golang.org/x/foo"),
Damien Neil082ce922018-09-06 10:23:53 -0700246 },
247 },
248 {
Joe Tsai009e0672018-11-27 18:45:07 -0800249 Name: scalar.String("dir/file2.proto"),
250 Package: scalar.String("proto.package"),
Joe Tsaie1f8d502018-11-26 18:55:29 -0800251 Options: &descriptorpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800252 GoPackage: scalar.String("golang.org/x/foo;bar"),
Damien Neil082ce922018-09-06 10:23:53 -0700253 },
254 },
255 },
256 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700257 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700258 if err == nil {
259 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
260 }
261}
262
Damien Neild9016772018-08-23 14:39:30 -0700263func TestImports(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -0700264 gen, err := New(&pluginpb.CodeGeneratorRequest{}, nil)
Damien Neild9016772018-08-23 14:39:30 -0700265 if err != nil {
266 t.Fatal(err)
267 }
268 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
269 g.P("package foo")
270 g.P()
271 for _, importPath := range []GoImportPath{
272 "golang.org/x/foo",
273 // Multiple references to the same package.
274 "golang.org/x/bar",
275 "golang.org/x/bar",
276 // Reference to a different package with the same basename.
277 "golang.org/y/bar",
278 "golang.org/x/baz",
Damien Neil87214662018-10-05 11:23:35 -0700279 // Reference to a package conflicting with a predeclared identifier.
280 "golang.org/z/string",
Damien Neild9016772018-08-23 14:39:30 -0700281 } {
282 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
283 }
284 want := `package foo
285
286import (
287 bar "golang.org/x/bar"
Damien Neild9016772018-08-23 14:39:30 -0700288 baz "golang.org/x/baz"
Damien Neil1ec33152018-09-13 13:12:36 -0700289 bar1 "golang.org/y/bar"
Damien Neil87214662018-10-05 11:23:35 -0700290 string1 "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700291)
292
Damien Neil87214662018-10-05 11:23:35 -0700293var _ = X // "golang.org/x/foo"
294var _ = bar.X // "golang.org/x/bar"
295var _ = bar.X // "golang.org/x/bar"
296var _ = bar1.X // "golang.org/y/bar"
297var _ = baz.X // "golang.org/x/baz"
298var _ = string1.X // "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700299`
Damien Neil7bf3ce22018-12-21 15:54:06 -0800300 got, err := g.Content()
Damien Neild9016772018-08-23 14:39:30 -0700301 if err != nil {
Damien Neil7bf3ce22018-12-21 15:54:06 -0800302 t.Fatalf("g.Content() = %v", err)
Damien Neild9016772018-08-23 14:39:30 -0700303 }
304 if want != string(got) {
305 t.Fatalf(`want:
306==========
307%v
308==========
309
310got:
311==========
312%v
313==========`,
314 want, string(got))
315 }
316}
317
Damien Neil1fa8ab02018-09-27 15:51:05 -0700318func TestImportRewrites(t *testing.T) {
319 gen, err := New(&pluginpb.CodeGeneratorRequest{}, &Options{
320 ImportRewriteFunc: func(i GoImportPath) GoImportPath {
321 return "prefix/" + i
322 },
323 })
324 if err != nil {
325 t.Fatal(err)
326 }
327 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
328 g.P("package foo")
329 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
330 want := `package foo
331
332import bar "prefix/golang.org/x/bar"
333
334var _ = bar.X
335`
Damien Neil7bf3ce22018-12-21 15:54:06 -0800336 got, err := g.Content()
Damien Neil1fa8ab02018-09-27 15:51:05 -0700337 if err != nil {
Damien Neil7bf3ce22018-12-21 15:54:06 -0800338 t.Fatalf("g.Content() = %v", err)
Damien Neil1fa8ab02018-09-27 15:51:05 -0700339 }
340 if want != string(got) {
341 t.Fatalf(`want:
342==========
343%v
344==========
345
346got:
347==========
348%v
349==========`,
350 want, string(got))
351 }
352}
353
Damien Neil220c2022018-08-15 11:24:18 -0700354// makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
355//
356// It does this by running protoc with the current binary as the protoc-gen-go
357// plugin. This "plugin" produces a single file, named 'request', which contains
358// the code generator request.
359func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
360 workdir, err := ioutil.TempDir("", "test")
361 if err != nil {
362 t.Fatal(err)
363 }
364 defer os.RemoveAll(workdir)
365
366 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
367 cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
368 cmd.Args = append(cmd.Args, args...)
369 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
370 out, err := cmd.CombinedOutput()
371 if len(out) > 0 || err != nil {
372 t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
373 }
374 if len(out) > 0 {
375 t.Log(string(out))
376 }
377 if err != nil {
378 t.Fatalf("protoc: %v", err)
379 }
380
381 b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
382 if err != nil {
383 t.Fatal(err)
384 }
385 req := &pluginpb.CodeGeneratorRequest{}
386 if err := proto.UnmarshalText(string(b), req); err != nil {
387 t.Fatal(err)
388 }
389 return req
390}
391
392func init() {
393 if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
Damien Neil3cf6e622018-09-11 13:53:14 -0700394 Run(nil, func(p *Plugin) error {
Damien Neild9016772018-08-23 14:39:30 -0700395 g := p.NewGeneratedFile("request", "")
Damien Neil220c2022018-08-15 11:24:18 -0700396 return proto.MarshalText(g, p.Request)
397 })
398 os.Exit(0)
399 }
400}