blob: 237226c06ce032f4835c5ffd5f364b82341d22be [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
5package protogen
6
7import (
Damien Neil3cf6e622018-09-11 13:53:14 -07008 "flag"
Damien Neil082ce922018-09-06 10:23:53 -07009 "fmt"
Damien Neil220c2022018-08-15 11:24:18 -070010 "io/ioutil"
11 "os"
12 "os/exec"
13 "path/filepath"
14 "strings"
15 "testing"
16
17 "github.com/golang/protobuf/proto"
Damien Neil082ce922018-09-06 10:23:53 -070018 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
Damien Neil220c2022018-08-15 11:24:18 -070019 pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
20)
21
Damien Neil3cf6e622018-09-11 13:53:14 -070022func TestPluginParameters(t *testing.T) {
23 var flags flag.FlagSet
24 value := flags.Int("integer", 0, "")
25 opts := &Options{
26 ParamFunc: flags.Set,
27 }
28 const params = "integer=2"
29 _, err := New(&pluginpb.CodeGeneratorRequest{
30 Parameter: proto.String(params),
31 }, opts)
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
40func 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 opts := &Options{
48 ParamFunc: flags.Set,
49 }
50 _, err := New(&pluginpb.CodeGeneratorRequest{
51 Parameter: proto.String(parameter),
52 }, opts)
53 if err == nil {
54 t.Errorf("New(generator parameters %q): want error, got nil", parameter)
55 }
56 }
57}
58
Damien Neil220c2022018-08-15 11:24:18 -070059func TestFiles(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -070060 gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
Damien Neil220c2022018-08-15 11:24:18 -070061 if err != nil {
62 t.Fatal(err)
63 }
64 for _, test := range []struct {
65 path string
66 wantGenerate bool
67 }{
68 {
69 path: "go_package/no_go_package_import.proto",
70 wantGenerate: true,
71 },
72 {
73 path: "go_package/no_go_package.proto",
74 wantGenerate: false,
75 },
76 } {
77 f, ok := gen.FileByName(test.path)
78 if !ok {
79 t.Errorf("%q: not found by gen.FileByName", test.path)
80 continue
81 }
82 if f.Generate != test.wantGenerate {
83 t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
84 }
85 }
86}
87
Damien Neil082ce922018-09-06 10:23:53 -070088func TestPackageNamesAndPaths(t *testing.T) {
89 const (
90 filename = "dir/filename.proto"
91 protoPackageName = "proto.package"
92 )
93 for _, test := range []struct {
94 desc string
95 parameter string
96 goPackageOption string
97 generate bool
98 wantPackageName GoPackageName
99 wantImportPath GoImportPath
100 wantFilenamePrefix string
101 }{
102 {
103 desc: "no parameters, no go_package option",
104 generate: true,
105 wantPackageName: "proto_package",
106 wantImportPath: "dir",
107 wantFilenamePrefix: "dir/filename",
108 },
109 {
110 desc: "go_package option sets import path",
111 goPackageOption: "golang.org/x/foo",
112 generate: true,
113 wantPackageName: "foo",
114 wantImportPath: "golang.org/x/foo",
115 wantFilenamePrefix: "golang.org/x/foo/filename",
116 },
117 {
118 desc: "go_package option sets import path and package",
119 goPackageOption: "golang.org/x/foo;bar",
120 generate: true,
121 wantPackageName: "bar",
122 wantImportPath: "golang.org/x/foo",
123 wantFilenamePrefix: "golang.org/x/foo/filename",
124 },
125 {
126 desc: "go_package option sets package",
127 goPackageOption: "foo",
128 generate: true,
129 wantPackageName: "foo",
130 wantImportPath: "dir",
131 wantFilenamePrefix: "dir/filename",
132 },
133 {
134 desc: "command line sets import path for a file",
135 parameter: "Mdir/filename.proto=golang.org/x/bar",
136 goPackageOption: "golang.org/x/foo",
137 generate: true,
138 wantPackageName: "foo",
139 wantImportPath: "golang.org/x/bar",
140 wantFilenamePrefix: "golang.org/x/foo/filename",
141 },
142 {
143 desc: "import_path parameter sets import path of generated files",
144 parameter: "import_path=golang.org/x/bar",
145 goPackageOption: "golang.org/x/foo",
146 generate: true,
147 wantPackageName: "foo",
148 wantImportPath: "golang.org/x/bar",
149 wantFilenamePrefix: "golang.org/x/foo/filename",
150 },
151 {
152 desc: "import_path parameter does not set import path of dependencies",
153 parameter: "import_path=golang.org/x/bar",
154 goPackageOption: "golang.org/x/foo",
155 generate: false,
156 wantPackageName: "foo",
157 wantImportPath: "golang.org/x/foo",
158 wantFilenamePrefix: "golang.org/x/foo/filename",
159 },
160 } {
161 context := fmt.Sprintf(`
162TEST: %v
163 --go_out=%v:.
164 file %q: generate=%v
165 option go_package = %q;
166
167 `,
168 test.desc, test.parameter, filename, test.generate, test.goPackageOption)
169
170 req := &pluginpb.CodeGeneratorRequest{
171 Parameter: proto.String(test.parameter),
172 ProtoFile: []*descpb.FileDescriptorProto{
173 {
174 Name: proto.String(filename),
175 Package: proto.String(protoPackageName),
176 Options: &descpb.FileOptions{
177 GoPackage: proto.String(test.goPackageOption),
178 },
179 },
180 },
181 }
182 if test.generate {
183 req.FileToGenerate = []string{filename}
184 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700185 gen, err := New(req, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700186 if err != nil {
187 t.Errorf("%vNew(req) = %v", context, err)
188 continue
189 }
190 gotFile, ok := gen.FileByName(filename)
191 if !ok {
192 t.Errorf("%v%v: missing file info", context, filename)
193 continue
194 }
195 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
196 t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
197 }
198 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
199 t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
200 }
201 if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
202 t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
203 }
204 }
205}
206
207func TestPackageNameInference(t *testing.T) {
208 gen, err := New(&pluginpb.CodeGeneratorRequest{
209 ProtoFile: []*descpb.FileDescriptorProto{
210 {
211 Name: proto.String("dir/file1.proto"),
212 Package: proto.String("proto.package"),
213 },
214 {
215 Name: proto.String("dir/file2.proto"),
216 Package: proto.String("proto.package"),
217 Options: &descpb.FileOptions{
218 GoPackage: proto.String("foo"),
219 },
220 },
221 },
222 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700223 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700224 if err != nil {
225 t.Fatalf("New(req) = %v", err)
226 }
227 if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
228 t.Errorf("missing file info for dir/file1.proto")
229 } else if f1.GoPackageName != "foo" {
230 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
231 }
232}
233
234func TestInconsistentPackageNames(t *testing.T) {
235 _, err := New(&pluginpb.CodeGeneratorRequest{
236 ProtoFile: []*descpb.FileDescriptorProto{
237 {
238 Name: proto.String("dir/file1.proto"),
239 Package: proto.String("proto.package"),
240 Options: &descpb.FileOptions{
241 GoPackage: proto.String("golang.org/x/foo"),
242 },
243 },
244 {
245 Name: proto.String("dir/file2.proto"),
246 Package: proto.String("proto.package"),
247 Options: &descpb.FileOptions{
248 GoPackage: proto.String("golang.org/x/foo;bar"),
249 },
250 },
251 },
252 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
Damien Neil3cf6e622018-09-11 13:53:14 -0700253 }, nil)
Damien Neil082ce922018-09-06 10:23:53 -0700254 if err == nil {
255 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
256 }
257}
258
Damien Neild9016772018-08-23 14:39:30 -0700259func TestImports(t *testing.T) {
Damien Neil3cf6e622018-09-11 13:53:14 -0700260 gen, err := New(&pluginpb.CodeGeneratorRequest{}, nil)
Damien Neild9016772018-08-23 14:39:30 -0700261 if err != nil {
262 t.Fatal(err)
263 }
264 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
265 g.P("package foo")
266 g.P()
267 for _, importPath := range []GoImportPath{
268 "golang.org/x/foo",
269 // Multiple references to the same package.
270 "golang.org/x/bar",
271 "golang.org/x/bar",
272 // Reference to a different package with the same basename.
273 "golang.org/y/bar",
274 "golang.org/x/baz",
275 } {
276 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
277 }
278 want := `package foo
279
280import (
281 bar "golang.org/x/bar"
Damien Neild9016772018-08-23 14:39:30 -0700282 baz "golang.org/x/baz"
Damien Neil1ec33152018-09-13 13:12:36 -0700283 bar1 "golang.org/y/bar"
Damien Neild9016772018-08-23 14:39:30 -0700284)
285
286var _ = X // "golang.org/x/foo"
287var _ = bar.X // "golang.org/x/bar"
288var _ = bar.X // "golang.org/x/bar"
289var _ = bar1.X // "golang.org/y/bar"
290var _ = baz.X // "golang.org/x/baz"
291`
292 got, err := g.Content()
293 if err != nil {
294 t.Fatalf("g.Content() = %v", err)
295 }
296 if want != string(got) {
297 t.Fatalf(`want:
298==========
299%v
300==========
301
302got:
303==========
304%v
305==========`,
306 want, string(got))
307 }
308}
309
Damien Neil220c2022018-08-15 11:24:18 -0700310// makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
311//
312// It does this by running protoc with the current binary as the protoc-gen-go
313// plugin. This "plugin" produces a single file, named 'request', which contains
314// the code generator request.
315func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
316 workdir, err := ioutil.TempDir("", "test")
317 if err != nil {
318 t.Fatal(err)
319 }
320 defer os.RemoveAll(workdir)
321
322 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
323 cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
324 cmd.Args = append(cmd.Args, args...)
325 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
326 out, err := cmd.CombinedOutput()
327 if len(out) > 0 || err != nil {
328 t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
329 }
330 if len(out) > 0 {
331 t.Log(string(out))
332 }
333 if err != nil {
334 t.Fatalf("protoc: %v", err)
335 }
336
337 b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
338 if err != nil {
339 t.Fatal(err)
340 }
341 req := &pluginpb.CodeGeneratorRequest{}
342 if err := proto.UnmarshalText(string(b), req); err != nil {
343 t.Fatal(err)
344 }
345 return req
346}
347
348func init() {
349 if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
Damien Neil3cf6e622018-09-11 13:53:14 -0700350 Run(nil, func(p *Plugin) error {
Damien Neild9016772018-08-23 14:39:30 -0700351 g := p.NewGeneratedFile("request", "")
Damien Neil220c2022018-08-15 11:24:18 -0700352 return proto.MarshalText(g, p.Request)
353 })
354 os.Exit(0)
355 }
356}