blob: b0988454cacd1c1a12136f8cb56646a9b60ae5d4 [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"
Joe Tsai009e0672018-11-27 18:45:07 -080022 "github.com/golang/protobuf/v2/internal/scalar"
Damien Neil220c2022018-08-15 11:24:18 -070023)
24
Damien Neil3cf6e622018-09-11 13:53:14 -070025func TestPluginParameters(t *testing.T) {
26 var flags flag.FlagSet
27 value := flags.Int("integer", 0, "")
28 opts := &Options{
29 ParamFunc: flags.Set,
30 }
31 const params = "integer=2"
32 _, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -080033 Parameter: scalar.String(params),
Damien Neil3cf6e622018-09-11 13:53:14 -070034 }, opts)
35 if err != nil {
36 t.Errorf("New(generator parameters %q): %v", params, err)
37 }
38 if *value != 2 {
39 t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
40 }
41}
42
43func TestPluginParameterErrors(t *testing.T) {
44 for _, parameter := range []string{
45 "unknown=1",
46 "boolean=error",
47 } {
48 var flags flag.FlagSet
49 flags.Bool("boolean", false, "")
50 opts := &Options{
51 ParamFunc: flags.Set,
52 }
53 _, err := New(&pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -080054 Parameter: scalar.String(parameter),
Damien Neil3cf6e622018-09-11 13:53:14 -070055 }, opts)
56 if err == nil {
57 t.Errorf("New(generator parameters %q): want error, got nil", parameter)
58 }
59 }
60}
61
Damien Neil220c2022018-08-15 11:24:18 -070062func TestFiles(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -070063 gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
Damien Neil220c2022018-08-15 11:24:18 -070064 if err != nil {
65 t.Fatal(err)
66 }
67 for _, test := range []struct {
68 path string
69 wantGenerate bool
70 }{
71 {
72 path: "go_package/no_go_package_import.proto",
73 wantGenerate: true,
74 },
75 {
76 path: "go_package/no_go_package.proto",
77 wantGenerate: false,
78 },
79 } {
80 f, ok := gen.FileByName(test.path)
81 if !ok {
82 t.Errorf("%q: not found by gen.FileByName", test.path)
83 continue
84 }
85 if f.Generate != test.wantGenerate {
86 t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
87 }
88 }
89}
90
Damien Neil082ce922018-09-06 10:23:53 -070091func 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 wantFilenamePrefix string
104 }{
105 {
106 desc: "no parameters, no go_package option",
107 generate: true,
108 wantPackageName: "proto_package",
109 wantImportPath: "dir",
110 wantFilenamePrefix: "dir/filename",
111 },
112 {
113 desc: "go_package option sets import path",
114 goPackageOption: "golang.org/x/foo",
115 generate: true,
116 wantPackageName: "foo",
117 wantImportPath: "golang.org/x/foo",
118 wantFilenamePrefix: "golang.org/x/foo/filename",
119 },
120 {
121 desc: "go_package option sets import path and package",
122 goPackageOption: "golang.org/x/foo;bar",
123 generate: true,
124 wantPackageName: "bar",
125 wantImportPath: "golang.org/x/foo",
126 wantFilenamePrefix: "golang.org/x/foo/filename",
127 },
128 {
129 desc: "go_package option sets package",
130 goPackageOption: "foo",
131 generate: true,
132 wantPackageName: "foo",
133 wantImportPath: "dir",
134 wantFilenamePrefix: "dir/filename",
135 },
136 {
137 desc: "command line sets import path for a file",
138 parameter: "Mdir/filename.proto=golang.org/x/bar",
139 goPackageOption: "golang.org/x/foo",
140 generate: true,
141 wantPackageName: "foo",
142 wantImportPath: "golang.org/x/bar",
143 wantFilenamePrefix: "golang.org/x/foo/filename",
144 },
145 {
146 desc: "import_path parameter sets import path of generated files",
147 parameter: "import_path=golang.org/x/bar",
148 goPackageOption: "golang.org/x/foo",
149 generate: true,
150 wantPackageName: "foo",
151 wantImportPath: "golang.org/x/bar",
152 wantFilenamePrefix: "golang.org/x/foo/filename",
153 },
154 {
155 desc: "import_path parameter does not set import path of dependencies",
156 parameter: "import_path=golang.org/x/bar",
157 goPackageOption: "golang.org/x/foo",
158 generate: false,
159 wantPackageName: "foo",
160 wantImportPath: "golang.org/x/foo",
161 wantFilenamePrefix: "golang.org/x/foo/filename",
162 },
163 } {
164 context := fmt.Sprintf(`
165TEST: %v
166 --go_out=%v:.
167 file %q: generate=%v
168 option go_package = %q;
169
170 `,
171 test.desc, test.parameter, filename, test.generate, test.goPackageOption)
172
173 req := &pluginpb.CodeGeneratorRequest{
Joe Tsai009e0672018-11-27 18:45:07 -0800174 Parameter: scalar.String(test.parameter),
Damien Neil082ce922018-09-06 10:23:53 -0700175 ProtoFile: []*descpb.FileDescriptorProto{
176 {
Joe Tsai009e0672018-11-27 18:45:07 -0800177 Name: scalar.String(filename),
178 Package: scalar.String(protoPackageName),
Damien Neil082ce922018-09-06 10:23:53 -0700179 Options: &descpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800180 GoPackage: scalar.String(test.goPackageOption),
Damien Neil082ce922018-09-06 10:23:53 -0700181 },
182 },
183 },
184 }
185 if test.generate {
186 req.FileToGenerate = []string{filename}
187 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700188 gen, err := New(req, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700189 if err != nil {
190 t.Errorf("%vNew(req) = %v", context, err)
191 continue
192 }
193 gotFile, ok := gen.FileByName(filename)
194 if !ok {
195 t.Errorf("%v%v: missing file info", context, filename)
196 continue
197 }
198 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
199 t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
200 }
201 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
202 t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
203 }
204 if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
205 t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
206 }
207 }
208}
209
210func TestPackageNameInference(t *testing.T) {
211 gen, err := New(&pluginpb.CodeGeneratorRequest{
212 ProtoFile: []*descpb.FileDescriptorProto{
213 {
Joe Tsai009e0672018-11-27 18:45:07 -0800214 Name: scalar.String("dir/file1.proto"),
215 Package: scalar.String("proto.package"),
Damien Neil082ce922018-09-06 10:23:53 -0700216 },
217 {
Joe Tsai009e0672018-11-27 18:45:07 -0800218 Name: scalar.String("dir/file2.proto"),
219 Package: scalar.String("proto.package"),
Damien Neil082ce922018-09-06 10:23:53 -0700220 Options: &descpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800221 GoPackage: scalar.String("foo"),
Damien Neil082ce922018-09-06 10:23:53 -0700222 },
223 },
224 },
225 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700226 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700227 if err != nil {
228 t.Fatalf("New(req) = %v", err)
229 }
230 if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
231 t.Errorf("missing file info for dir/file1.proto")
232 } else if f1.GoPackageName != "foo" {
233 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
234 }
235}
236
237func TestInconsistentPackageNames(t *testing.T) {
238 _, err := New(&pluginpb.CodeGeneratorRequest{
239 ProtoFile: []*descpb.FileDescriptorProto{
240 {
Joe Tsai009e0672018-11-27 18:45:07 -0800241 Name: scalar.String("dir/file1.proto"),
242 Package: scalar.String("proto.package"),
Damien Neil082ce922018-09-06 10:23:53 -0700243 Options: &descpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800244 GoPackage: scalar.String("golang.org/x/foo"),
Damien Neil082ce922018-09-06 10:23:53 -0700245 },
246 },
247 {
Joe Tsai009e0672018-11-27 18:45:07 -0800248 Name: scalar.String("dir/file2.proto"),
249 Package: scalar.String("proto.package"),
Damien Neil082ce922018-09-06 10:23:53 -0700250 Options: &descpb.FileOptions{
Joe Tsai009e0672018-11-27 18:45:07 -0800251 GoPackage: scalar.String("golang.org/x/foo;bar"),
Damien Neil082ce922018-09-06 10:23:53 -0700252 },
253 },
254 },
255 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700256 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700257 if err == nil {
258 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
259 }
260}
261
Damien Neild9016772018-08-23 14:39:30 -0700262func TestImports(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -0700263 gen, err := New(&pluginpb.CodeGeneratorRequest{}, nil)
Damien Neild9016772018-08-23 14:39:30 -0700264 if err != nil {
265 t.Fatal(err)
266 }
267 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
268 g.P("package foo")
269 g.P()
270 for _, importPath := range []GoImportPath{
271 "golang.org/x/foo",
272 // Multiple references to the same package.
273 "golang.org/x/bar",
274 "golang.org/x/bar",
275 // Reference to a different package with the same basename.
276 "golang.org/y/bar",
277 "golang.org/x/baz",
Damien Neil87214662018-10-05 11:23:35 -0700278 // Reference to a package conflicting with a predeclared identifier.
279 "golang.org/z/string",
Damien Neild9016772018-08-23 14:39:30 -0700280 } {
281 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
282 }
283 want := `package foo
284
285import (
286 bar "golang.org/x/bar"
Damien Neild9016772018-08-23 14:39:30 -0700287 baz "golang.org/x/baz"
Damien Neil1ec33152018-09-13 13:12:36 -0700288 bar1 "golang.org/y/bar"
Damien Neil87214662018-10-05 11:23:35 -0700289 string1 "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700290)
291
Damien Neil87214662018-10-05 11:23:35 -0700292var _ = X // "golang.org/x/foo"
293var _ = bar.X // "golang.org/x/bar"
294var _ = bar.X // "golang.org/x/bar"
295var _ = bar1.X // "golang.org/y/bar"
296var _ = baz.X // "golang.org/x/baz"
297var _ = string1.X // "golang.org/z/string"
Damien Neild9016772018-08-23 14:39:30 -0700298`
Damien Neil162c1272018-10-04 12:42:37 -0700299 got, err := g.content()
Damien Neild9016772018-08-23 14:39:30 -0700300 if err != nil {
Damien Neil162c1272018-10-04 12:42:37 -0700301 t.Fatalf("g.content() = %v", err)
Damien Neild9016772018-08-23 14:39:30 -0700302 }
303 if want != string(got) {
304 t.Fatalf(`want:
305==========
306%v
307==========
308
309got:
310==========
311%v
312==========`,
313 want, string(got))
314 }
315}
316
Damien Neil1fa8ab02018-09-27 15:51:05 -0700317func TestImportRewrites(t *testing.T) {
318 gen, err := New(&pluginpb.CodeGeneratorRequest{}, &Options{
319 ImportRewriteFunc: func(i GoImportPath) GoImportPath {
320 return "prefix/" + i
321 },
322 })
323 if err != nil {
324 t.Fatal(err)
325 }
326 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
327 g.P("package foo")
328 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
329 want := `package foo
330
331import bar "prefix/golang.org/x/bar"
332
333var _ = bar.X
334`
Damien Neil162c1272018-10-04 12:42:37 -0700335 got, err := g.content()
Damien Neil1fa8ab02018-09-27 15:51:05 -0700336 if err != nil {
Damien Neil162c1272018-10-04 12:42:37 -0700337 t.Fatalf("g.content() = %v", err)
Damien Neil1fa8ab02018-09-27 15:51:05 -0700338 }
339 if want != string(got) {
340 t.Fatalf(`want:
341==========
342%v
343==========
344
345got:
346==========
347%v
348==========`,
349 want, string(got))
350 }
351}
352
Damien Neil220c2022018-08-15 11:24:18 -0700353// makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
354//
355// It does this by running protoc with the current binary as the protoc-gen-go
356// plugin. This "plugin" produces a single file, named 'request', which contains
357// the code generator request.
358func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
359 workdir, err := ioutil.TempDir("", "test")
360 if err != nil {
361 t.Fatal(err)
362 }
363 defer os.RemoveAll(workdir)
364
365 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
366 cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
367 cmd.Args = append(cmd.Args, args...)
368 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
369 out, err := cmd.CombinedOutput()
370 if len(out) > 0 || err != nil {
371 t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
372 }
373 if len(out) > 0 {
374 t.Log(string(out))
375 }
376 if err != nil {
377 t.Fatalf("protoc: %v", err)
378 }
379
380 b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
381 if err != nil {
382 t.Fatal(err)
383 }
384 req := &pluginpb.CodeGeneratorRequest{}
385 if err := proto.UnmarshalText(string(b), req); err != nil {
386 t.Fatal(err)
387 }
388 return req
389}
390
391func init() {
392 if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
Damien Neil3cf6e622018-09-11 13:53:14 -0700393 Run(nil, func(p *Plugin) error {
Damien Neild9016772018-08-23 14:39:30 -0700394 g := p.NewGeneratedFile("request", "")
Damien Neil220c2022018-08-15 11:24:18 -0700395 return proto.MarshalText(g, p.Request)
396 })
397 os.Exit(0)
398 }
399}