blob: a61c18bd8bc8cbc0f2009a57e439a9c3facbfcb2 [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"
Damien Neil082ce922018-09-06 10:23:53 -070020 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
Damien Neil220c2022018-08-15 11:24:18 -070021 pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
22)
23
Damien Neil3cf6e622018-09-11 13:53:14 -070024func TestPluginParameters(t *testing.T) {
25 var flags flag.FlagSet
26 value := flags.Int("integer", 0, "")
27 opts := &Options{
28 ParamFunc: flags.Set,
29 }
30 const params = "integer=2"
31 _, err := New(&pluginpb.CodeGeneratorRequest{
32 Parameter: proto.String(params),
33 }, opts)
34 if err != nil {
35 t.Errorf("New(generator parameters %q): %v", params, err)
36 }
37 if *value != 2 {
38 t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
39 }
40}
41
42func TestPluginParameterErrors(t *testing.T) {
43 for _, parameter := range []string{
44 "unknown=1",
45 "boolean=error",
46 } {
47 var flags flag.FlagSet
48 flags.Bool("boolean", false, "")
49 opts := &Options{
50 ParamFunc: flags.Set,
51 }
52 _, err := New(&pluginpb.CodeGeneratorRequest{
53 Parameter: proto.String(parameter),
54 }, opts)
55 if err == nil {
56 t.Errorf("New(generator parameters %q): want error, got nil", parameter)
57 }
58 }
59}
60
Damien Neil220c2022018-08-15 11:24:18 -070061func TestFiles(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -070062 gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
Damien Neil220c2022018-08-15 11:24:18 -070063 if err != nil {
64 t.Fatal(err)
65 }
66 for _, test := range []struct {
67 path string
68 wantGenerate bool
69 }{
70 {
71 path: "go_package/no_go_package_import.proto",
72 wantGenerate: true,
73 },
74 {
75 path: "go_package/no_go_package.proto",
76 wantGenerate: false,
77 },
78 } {
79 f, ok := gen.FileByName(test.path)
80 if !ok {
81 t.Errorf("%q: not found by gen.FileByName", test.path)
82 continue
83 }
84 if f.Generate != test.wantGenerate {
85 t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
86 }
87 }
88}
89
Damien Neil082ce922018-09-06 10:23:53 -070090func TestPackageNamesAndPaths(t *testing.T) {
91 const (
92 filename = "dir/filename.proto"
93 protoPackageName = "proto.package"
94 )
95 for _, test := range []struct {
96 desc string
97 parameter string
98 goPackageOption string
99 generate bool
100 wantPackageName GoPackageName
101 wantImportPath GoImportPath
102 wantFilenamePrefix string
103 }{
104 {
105 desc: "no parameters, no go_package option",
106 generate: true,
107 wantPackageName: "proto_package",
108 wantImportPath: "dir",
109 wantFilenamePrefix: "dir/filename",
110 },
111 {
112 desc: "go_package option sets import path",
113 goPackageOption: "golang.org/x/foo",
114 generate: true,
115 wantPackageName: "foo",
116 wantImportPath: "golang.org/x/foo",
117 wantFilenamePrefix: "golang.org/x/foo/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 wantFilenamePrefix: "golang.org/x/foo/filename",
126 },
127 {
128 desc: "go_package option sets package",
129 goPackageOption: "foo",
130 generate: true,
131 wantPackageName: "foo",
132 wantImportPath: "dir",
133 wantFilenamePrefix: "dir/filename",
134 },
135 {
136 desc: "command line sets import path for a file",
137 parameter: "Mdir/filename.proto=golang.org/x/bar",
138 goPackageOption: "golang.org/x/foo",
139 generate: true,
140 wantPackageName: "foo",
141 wantImportPath: "golang.org/x/bar",
142 wantFilenamePrefix: "golang.org/x/foo/filename",
143 },
144 {
145 desc: "import_path parameter sets import path of generated files",
146 parameter: "import_path=golang.org/x/bar",
147 goPackageOption: "golang.org/x/foo",
148 generate: true,
149 wantPackageName: "foo",
150 wantImportPath: "golang.org/x/bar",
151 wantFilenamePrefix: "golang.org/x/foo/filename",
152 },
153 {
154 desc: "import_path parameter does not set import path of dependencies",
155 parameter: "import_path=golang.org/x/bar",
156 goPackageOption: "golang.org/x/foo",
157 generate: false,
158 wantPackageName: "foo",
159 wantImportPath: "golang.org/x/foo",
160 wantFilenamePrefix: "golang.org/x/foo/filename",
161 },
162 } {
163 context := fmt.Sprintf(`
164TEST: %v
165 --go_out=%v:.
166 file %q: generate=%v
167 option go_package = %q;
168
169 `,
170 test.desc, test.parameter, filename, test.generate, test.goPackageOption)
171
172 req := &pluginpb.CodeGeneratorRequest{
173 Parameter: proto.String(test.parameter),
174 ProtoFile: []*descpb.FileDescriptorProto{
175 {
176 Name: proto.String(filename),
177 Package: proto.String(protoPackageName),
178 Options: &descpb.FileOptions{
179 GoPackage: proto.String(test.goPackageOption),
180 },
181 },
182 },
183 }
184 if test.generate {
185 req.FileToGenerate = []string{filename}
186 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700187 gen, err := New(req, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700188 if err != nil {
189 t.Errorf("%vNew(req) = %v", context, err)
190 continue
191 }
192 gotFile, ok := gen.FileByName(filename)
193 if !ok {
194 t.Errorf("%v%v: missing file info", context, filename)
195 continue
196 }
197 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
198 t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
199 }
200 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
201 t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
202 }
203 if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
204 t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
205 }
206 }
207}
208
209func TestPackageNameInference(t *testing.T) {
210 gen, err := New(&pluginpb.CodeGeneratorRequest{
211 ProtoFile: []*descpb.FileDescriptorProto{
212 {
213 Name: proto.String("dir/file1.proto"),
214 Package: proto.String("proto.package"),
215 },
216 {
217 Name: proto.String("dir/file2.proto"),
218 Package: proto.String("proto.package"),
219 Options: &descpb.FileOptions{
220 GoPackage: proto.String("foo"),
221 },
222 },
223 },
224 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700225 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700226 if err != nil {
227 t.Fatalf("New(req) = %v", err)
228 }
229 if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
230 t.Errorf("missing file info for dir/file1.proto")
231 } else if f1.GoPackageName != "foo" {
232 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
233 }
234}
235
236func TestInconsistentPackageNames(t *testing.T) {
237 _, err := New(&pluginpb.CodeGeneratorRequest{
238 ProtoFile: []*descpb.FileDescriptorProto{
239 {
240 Name: proto.String("dir/file1.proto"),
241 Package: proto.String("proto.package"),
242 Options: &descpb.FileOptions{
243 GoPackage: proto.String("golang.org/x/foo"),
244 },
245 },
246 {
247 Name: proto.String("dir/file2.proto"),
248 Package: proto.String("proto.package"),
249 Options: &descpb.FileOptions{
250 GoPackage: proto.String("golang.org/x/foo;bar"),
251 },
252 },
253 },
254 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700255 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700256 if err == nil {
257 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
258 }
259}
260
Damien Neild9016772018-08-23 14:39:30 -0700261func TestImports(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -0700262 gen, err := New(&pluginpb.CodeGeneratorRequest{}, nil)
Damien Neild9016772018-08-23 14:39:30 -0700263 if err != nil {
264 t.Fatal(err)
265 }
266 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
267 g.P("package foo")
268 g.P()
269 for _, importPath := range []GoImportPath{
270 "golang.org/x/foo",
271 // Multiple references to the same package.
272 "golang.org/x/bar",
273 "golang.org/x/bar",
274 // Reference to a different package with the same basename.
275 "golang.org/y/bar",
276 "golang.org/x/baz",
Damien Neil87214662018-10-05 11:23:35 -0700277 // Reference to a package conflicting with a predeclared identifier.
278 "golang.org/z/string",
Damien Neild9016772018-08-23 14:39:30 -0700279 } {
280 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
281 }
282 want := `package foo
283
284import (
285 bar "golang.org/x/bar"
Damien Neild9016772018-08-23 14:39:30 -0700286 baz "golang.org/x/baz"
Damien Neil1ec33152018-09-13 13:12:36 -0700287 bar1 "golang.org/y/bar"
Damien Neil87214662018-10-05 11:23:35 -0700288 string1 "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700289)
290
Damien Neil87214662018-10-05 11:23:35 -0700291var _ = X // "golang.org/x/foo"
292var _ = bar.X // "golang.org/x/bar"
293var _ = bar.X // "golang.org/x/bar"
294var _ = bar1.X // "golang.org/y/bar"
295var _ = baz.X // "golang.org/x/baz"
296var _ = string1.X // "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700297`
Damien Neil162c1272018-10-04 12:42:37 -0700298 got, err := g.content()
Damien Neild9016772018-08-23 14:39:30 -0700299 if err != nil {
Damien Neil162c1272018-10-04 12:42:37 -0700300 t.Fatalf("g.content() = %v", err)
Damien Neild9016772018-08-23 14:39:30 -0700301 }
302 if want != string(got) {
303 t.Fatalf(`want:
304==========
305%v
306==========
307
308got:
309==========
310%v
311==========`,
312 want, string(got))
313 }
314}
315
Damien Neil1fa8ab02018-09-27 15:51:05 -0700316func TestImportRewrites(t *testing.T) {
317 gen, err := New(&pluginpb.CodeGeneratorRequest{}, &Options{
318 ImportRewriteFunc: func(i GoImportPath) GoImportPath {
319 return "prefix/" + i
320 },
321 })
322 if err != nil {
323 t.Fatal(err)
324 }
325 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
326 g.P("package foo")
327 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
328 want := `package foo
329
330import bar "prefix/golang.org/x/bar"
331
332var _ = bar.X
333`
Damien Neil162c1272018-10-04 12:42:37 -0700334 got, err := g.content()
Damien Neil1fa8ab02018-09-27 15:51:05 -0700335 if err != nil {
Damien Neil162c1272018-10-04 12:42:37 -0700336 t.Fatalf("g.content() = %v", err)
Damien Neil1fa8ab02018-09-27 15:51:05 -0700337 }
338 if want != string(got) {
339 t.Fatalf(`want:
340==========
341%v
342==========
343
344got:
345==========
346%v
347==========`,
348 want, string(got))
349 }
350}
351
Damien Neil220c2022018-08-15 11:24:18 -0700352// makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
353//
354// It does this by running protoc with the current binary as the protoc-gen-go
355// plugin. This "plugin" produces a single file, named 'request', which contains
356// the code generator request.
357func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
358 workdir, err := ioutil.TempDir("", "test")
359 if err != nil {
360 t.Fatal(err)
361 }
362 defer os.RemoveAll(workdir)
363
364 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
365 cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
366 cmd.Args = append(cmd.Args, args...)
367 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
368 out, err := cmd.CombinedOutput()
369 if len(out) > 0 || err != nil {
370 t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
371 }
372 if len(out) > 0 {
373 t.Log(string(out))
374 }
375 if err != nil {
376 t.Fatalf("protoc: %v", err)
377 }
378
379 b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
380 if err != nil {
381 t.Fatal(err)
382 }
383 req := &pluginpb.CodeGeneratorRequest{}
384 if err := proto.UnmarshalText(string(b), req); err != nil {
385 t.Fatal(err)
386 }
387 return req
388}
389
390func init() {
391 if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
Damien Neil3cf6e622018-09-11 13:53:14 -0700392 Run(nil, func(p *Plugin) error {
Damien Neild9016772018-08-23 14:39:30 -0700393 g := p.NewGeneratedFile("request", "")
Damien Neil220c2022018-08-15 11:24:18 -0700394 return proto.MarshalText(g, p.Request)
395 })
396 os.Exit(0)
397 }
398}