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