1// 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 ( 8 "flag" 9 "fmt" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 14 "google.golang.org/protobuf/proto" 15 "google.golang.org/protobuf/reflect/protoreflect" 16 17 "google.golang.org/protobuf/types/descriptorpb" 18 "google.golang.org/protobuf/types/pluginpb" 19) 20 21func TestPluginParameters(t *testing.T) { 22 var flags flag.FlagSet 23 value := flags.Int("integer", 0, "") 24 const params = "integer=2" 25 _, err := Options{ 26 ParamFunc: flags.Set, 27 }.New(&pluginpb.CodeGeneratorRequest{ 28 Parameter: proto.String(params), 29 }) 30 if err != nil { 31 t.Errorf("New(generator parameters %q): %v", params, err) 32 } 33 if *value != 2 { 34 t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value) 35 } 36} 37 38func TestPluginParameterErrors(t *testing.T) { 39 for _, parameter := range []string{ 40 "unknown=1", 41 "boolean=error", 42 } { 43 var flags flag.FlagSet 44 flags.Bool("boolean", false, "") 45 _, err := Options{ 46 ParamFunc: flags.Set, 47 }.New(&pluginpb.CodeGeneratorRequest{ 48 Parameter: proto.String(parameter), 49 }) 50 if err == nil { 51 t.Errorf("New(generator parameters %q): want error, got nil", parameter) 52 } 53 } 54} 55 56func TestNoGoPackage(t *testing.T) { 57 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{ 58 ProtoFile: []*descriptorpb.FileDescriptorProto{ 59 { 60 Name: proto.String("testdata/go_package/no_go_package.proto"), 61 Syntax: proto.String(protoreflect.Proto3.String()), 62 Package: proto.String("goproto.testdata"), 63 }, 64 }, 65 }) 66 if err == nil { 67 t.Fatalf("missing go_package option: New(req) = nil, want error") 68 } 69} 70 71func TestInvalidImportPath(t *testing.T) { 72 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{ 73 ProtoFile: []*descriptorpb.FileDescriptorProto{ 74 { 75 Name: proto.String("testdata/go_package/no_go_package.proto"), 76 Syntax: proto.String(protoreflect.Proto3.String()), 77 Package: proto.String("goproto.testdata"), 78 Options: &descriptorpb.FileOptions{ 79 GoPackage: proto.String("foo"), 80 }, 81 }, 82 }, 83 }) 84 if err == nil { 85 t.Fatalf("missing go_package option: New(req) = nil, want error") 86 } 87} 88 89func TestPackageNamesAndPaths(t *testing.T) { 90 const ( 91 filename = "dir/filename.proto" 92 protoPackageName = "proto.package" 93 ) 94 for _, test := range []struct { 95 desc string 96 parameter string 97 goPackageOption string 98 generate bool 99 wantPackageName GoPackageName 100 wantImportPath GoImportPath 101 wantFilename string 102 }{ 103 { 104 desc: "go_package option sets import path", 105 goPackageOption: "golang.org/x/foo", 106 generate: true, 107 wantPackageName: "foo", 108 wantImportPath: "golang.org/x/foo", 109 wantFilename: "golang.org/x/foo/filename", 110 }, 111 { 112 desc: "go_package option sets import path without slashes", 113 goPackageOption: "golang.org;foo", 114 generate: true, 115 wantPackageName: "foo", 116 wantImportPath: "golang.org", 117 wantFilename: "golang.org/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 wantFilename: "golang.org/x/foo/filename", 126 }, 127 { 128 desc: "command line sets import path for a file", 129 parameter: "Mdir/filename.proto=golang.org/x/bar", 130 goPackageOption: "golang.org/x/foo", 131 generate: true, 132 wantPackageName: "foo", 133 wantImportPath: "golang.org/x/bar", 134 wantFilename: "golang.org/x/bar/filename", 135 }, 136 { 137 desc: "command line sets import path for a file with package name specified", 138 parameter: "Mdir/filename.proto=golang.org/x/bar;bar", 139 goPackageOption: "golang.org/x/foo", 140 generate: true, 141 wantPackageName: "bar", 142 wantImportPath: "golang.org/x/bar", 143 wantFilename: "golang.org/x/bar/filename", 144 }, 145 { 146 desc: "module option set", 147 parameter: "module=golang.org/x", 148 goPackageOption: "golang.org/x/foo", 149 generate: false, 150 wantPackageName: "foo", 151 wantImportPath: "golang.org/x/foo", 152 wantFilename: "foo/filename", 153 }, 154 { 155 desc: "paths=import uses import path from command line", 156 parameter: "paths=import,Mdir/filename.proto=golang.org/x/bar", 157 goPackageOption: "golang.org/x/foo", 158 generate: true, 159 wantPackageName: "foo", 160 wantImportPath: "golang.org/x/bar", 161 wantFilename: "golang.org/x/bar/filename", 162 }, 163 { 164 desc: "module option implies paths=import", 165 parameter: "module=golang.org/x,Mdir/filename.proto=golang.org/x/foo", 166 generate: false, 167 wantPackageName: "foo", 168 wantImportPath: "golang.org/x/foo", 169 wantFilename: "foo/filename", 170 }, 171 } { 172 context := fmt.Sprintf(` 173TEST: %v 174 --go_out=%v:. 175 file %q: generate=%v 176 option go_package = %q; 177 178 `, 179 test.desc, test.parameter, filename, test.generate, test.goPackageOption) 180 181 req := &pluginpb.CodeGeneratorRequest{ 182 Parameter: proto.String(test.parameter), 183 ProtoFile: []*descriptorpb.FileDescriptorProto{ 184 { 185 Name: proto.String(filename), 186 Package: proto.String(protoPackageName), 187 Options: &descriptorpb.FileOptions{ 188 GoPackage: proto.String(test.goPackageOption), 189 }, 190 }, 191 }, 192 } 193 if test.generate { 194 req.FileToGenerate = []string{filename} 195 } 196 gen, err := Options{}.New(req) 197 if err != nil { 198 t.Errorf("%vNew(req) = %v", context, err) 199 continue 200 } 201 gotFile, ok := gen.FilesByPath[filename] 202 if !ok { 203 t.Errorf("%v%v: missing file info", context, filename) 204 continue 205 } 206 if got, want := gotFile.GoPackageName, test.wantPackageName; got != want { 207 t.Errorf("%vGoPackageName=%v, want %v", context, got, want) 208 } 209 if got, want := gotFile.GoImportPath, test.wantImportPath; got != want { 210 t.Errorf("%vGoImportPath=%v, want %v", context, got, want) 211 } 212 gen.NewGeneratedFile(gotFile.GeneratedFilenamePrefix, "") 213 resp := gen.Response() 214 if got, want := resp.File[0].GetName(), test.wantFilename; got != want { 215 t.Errorf("%vgenerated filename=%v, want %v", context, got, want) 216 } 217 } 218} 219 220func TestPackageNameInference(t *testing.T) { 221 gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{ 222 Parameter: proto.String("Mdir/file1.proto=path/to/file1"), 223 ProtoFile: []*descriptorpb.FileDescriptorProto{ 224 { 225 Name: proto.String("dir/file1.proto"), 226 Package: proto.String("proto.package"), 227 }, 228 { 229 Name: proto.String("dir/file2.proto"), 230 Package: proto.String("proto.package"), 231 Options: &descriptorpb.FileOptions{ 232 GoPackage: proto.String("path/to/file2"), 233 }, 234 }, 235 }, 236 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"}, 237 }) 238 if err != nil { 239 t.Fatalf("New(req) = %v", err) 240 } 241 if f1, ok := gen.FilesByPath["dir/file1.proto"]; !ok { 242 t.Errorf("missing file info for dir/file1.proto") 243 } else if f1.GoPackageName != "file1" { 244 t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName) 245 } 246} 247 248func TestInconsistentPackageNames(t *testing.T) { 249 _, err := Options{}.New(&pluginpb.CodeGeneratorRequest{ 250 ProtoFile: []*descriptorpb.FileDescriptorProto{ 251 { 252 Name: proto.String("dir/file1.proto"), 253 Package: proto.String("proto.package"), 254 Options: &descriptorpb.FileOptions{ 255 GoPackage: proto.String("golang.org/x/foo"), 256 }, 257 }, 258 { 259 Name: proto.String("dir/file2.proto"), 260 Package: proto.String("proto.package"), 261 Options: &descriptorpb.FileOptions{ 262 GoPackage: proto.String("golang.org/x/foo;bar"), 263 }, 264 }, 265 }, 266 FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"}, 267 }) 268 if err == nil { 269 t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error") 270 } 271} 272 273func TestImports(t *testing.T) { 274 gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{}) 275 if err != nil { 276 t.Fatal(err) 277 } 278 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo") 279 g.P("package foo") 280 g.P() 281 for _, importPath := range []GoImportPath{ 282 "golang.org/x/foo", 283 // Multiple references to the same package. 284 "golang.org/x/bar", 285 "golang.org/x/bar", 286 // Reference to a different package with the same basename. 287 "golang.org/y/bar", 288 "golang.org/x/baz", 289 // Reference to a package conflicting with a predeclared identifier. 290 "golang.org/z/string", 291 } { 292 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath) 293 } 294 want := `package foo 295 296import ( 297 bar "golang.org/x/bar" 298 baz "golang.org/x/baz" 299 bar1 "golang.org/y/bar" 300 string1 "golang.org/z/string" 301) 302 303var _ = X // "golang.org/x/foo" 304var _ = bar.X // "golang.org/x/bar" 305var _ = bar.X // "golang.org/x/bar" 306var _ = bar1.X // "golang.org/y/bar" 307var _ = baz.X // "golang.org/x/baz" 308var _ = string1.X // "golang.org/z/string" 309` 310 got, err := g.Content() 311 if err != nil { 312 t.Fatalf("g.Content() = %v", err) 313 } 314 if diff := cmp.Diff(string(want), string(got)); diff != "" { 315 t.Fatalf("content mismatch (-want +got):\n%s", diff) 316 } 317} 318 319func TestImportRewrites(t *testing.T) { 320 gen, err := Options{ 321 ImportRewriteFunc: func(i GoImportPath) GoImportPath { 322 return "prefix/" + i 323 }, 324 }.New(&pluginpb.CodeGeneratorRequest{}) 325 if err != nil { 326 t.Fatal(err) 327 } 328 g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo") 329 g.P("package foo") 330 g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"}) 331 want := `package foo 332 333import ( 334 bar "prefix/golang.org/x/bar" 335) 336 337var _ = bar.X 338` 339 got, err := g.Content() 340 if err != nil { 341 t.Fatalf("g.Content() = %v", err) 342 } 343 if diff := cmp.Diff(string(want), string(got)); diff != "" { 344 t.Fatalf("content mismatch (-want +got):\n%s", diff) 345 } 346} 347