protogen_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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. // +build golden
  5. package protogen
  6. import (
  7. "flag"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "os/exec"
  12. "path/filepath"
  13. "strings"
  14. "testing"
  15. "github.com/golang/protobuf/proto"
  16. descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
  17. pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
  18. )
  19. func TestPluginParameters(t *testing.T) {
  20. var flags flag.FlagSet
  21. value := flags.Int("integer", 0, "")
  22. opts := &Options{
  23. ParamFunc: flags.Set,
  24. }
  25. const params = "integer=2"
  26. _, err := New(&pluginpb.CodeGeneratorRequest{
  27. Parameter: proto.String(params),
  28. }, opts)
  29. if err != nil {
  30. t.Errorf("New(generator parameters %q): %v", params, err)
  31. }
  32. if *value != 2 {
  33. t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
  34. }
  35. }
  36. func TestPluginParameterErrors(t *testing.T) {
  37. for _, parameter := range []string{
  38. "unknown=1",
  39. "boolean=error",
  40. } {
  41. var flags flag.FlagSet
  42. flags.Bool("boolean", false, "")
  43. opts := &Options{
  44. ParamFunc: flags.Set,
  45. }
  46. _, err := New(&pluginpb.CodeGeneratorRequest{
  47. Parameter: proto.String(parameter),
  48. }, opts)
  49. if err == nil {
  50. t.Errorf("New(generator parameters %q): want error, got nil", parameter)
  51. }
  52. }
  53. }
  54. func TestFiles(t *testing.T) {
  55. gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
  56. if err != nil {
  57. t.Fatal(err)
  58. }
  59. for _, test := range []struct {
  60. path string
  61. wantGenerate bool
  62. }{
  63. {
  64. path: "go_package/no_go_package_import.proto",
  65. wantGenerate: true,
  66. },
  67. {
  68. path: "go_package/no_go_package.proto",
  69. wantGenerate: false,
  70. },
  71. } {
  72. f, ok := gen.FileByName(test.path)
  73. if !ok {
  74. t.Errorf("%q: not found by gen.FileByName", test.path)
  75. continue
  76. }
  77. if f.Generate != test.wantGenerate {
  78. t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
  79. }
  80. }
  81. }
  82. func TestPackageNamesAndPaths(t *testing.T) {
  83. const (
  84. filename = "dir/filename.proto"
  85. protoPackageName = "proto.package"
  86. )
  87. for _, test := range []struct {
  88. desc string
  89. parameter string
  90. goPackageOption string
  91. generate bool
  92. wantPackageName GoPackageName
  93. wantImportPath GoImportPath
  94. wantFilenamePrefix string
  95. }{
  96. {
  97. desc: "no parameters, no go_package option",
  98. generate: true,
  99. wantPackageName: "proto_package",
  100. wantImportPath: "dir",
  101. wantFilenamePrefix: "dir/filename",
  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. wantFilenamePrefix: "golang.org/x/foo/filename",
  110. },
  111. {
  112. desc: "go_package option sets import path and package",
  113. goPackageOption: "golang.org/x/foo;bar",
  114. generate: true,
  115. wantPackageName: "bar",
  116. wantImportPath: "golang.org/x/foo",
  117. wantFilenamePrefix: "golang.org/x/foo/filename",
  118. },
  119. {
  120. desc: "go_package option sets package",
  121. goPackageOption: "foo",
  122. generate: true,
  123. wantPackageName: "foo",
  124. wantImportPath: "dir",
  125. wantFilenamePrefix: "dir/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. wantFilenamePrefix: "golang.org/x/foo/filename",
  135. },
  136. {
  137. desc: "import_path parameter sets import path of generated files",
  138. parameter: "import_path=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 does not set import path of dependencies",
  147. parameter: "import_path=golang.org/x/bar",
  148. goPackageOption: "golang.org/x/foo",
  149. generate: false,
  150. wantPackageName: "foo",
  151. wantImportPath: "golang.org/x/foo",
  152. wantFilenamePrefix: "golang.org/x/foo/filename",
  153. },
  154. } {
  155. context := fmt.Sprintf(`
  156. TEST: %v
  157. --go_out=%v:.
  158. file %q: generate=%v
  159. option go_package = %q;
  160. `,
  161. test.desc, test.parameter, filename, test.generate, test.goPackageOption)
  162. req := &pluginpb.CodeGeneratorRequest{
  163. Parameter: proto.String(test.parameter),
  164. ProtoFile: []*descpb.FileDescriptorProto{
  165. {
  166. Name: proto.String(filename),
  167. Package: proto.String(protoPackageName),
  168. Options: &descpb.FileOptions{
  169. GoPackage: proto.String(test.goPackageOption),
  170. },
  171. },
  172. },
  173. }
  174. if test.generate {
  175. req.FileToGenerate = []string{filename}
  176. }
  177. gen, err := New(req, nil)
  178. if err != nil {
  179. t.Errorf("%vNew(req) = %v", context, err)
  180. continue
  181. }
  182. gotFile, ok := gen.FileByName(filename)
  183. if !ok {
  184. t.Errorf("%v%v: missing file info", context, filename)
  185. continue
  186. }
  187. if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
  188. t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
  189. }
  190. if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
  191. t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
  192. }
  193. if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
  194. t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
  195. }
  196. }
  197. }
  198. func TestPackageNameInference(t *testing.T) {
  199. gen, err := New(&pluginpb.CodeGeneratorRequest{
  200. ProtoFile: []*descpb.FileDescriptorProto{
  201. {
  202. Name: proto.String("dir/file1.proto"),
  203. Package: proto.String("proto.package"),
  204. },
  205. {
  206. Name: proto.String("dir/file2.proto"),
  207. Package: proto.String("proto.package"),
  208. Options: &descpb.FileOptions{
  209. GoPackage: proto.String("foo"),
  210. },
  211. },
  212. },
  213. FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
  214. }, nil)
  215. if err != nil {
  216. t.Fatalf("New(req) = %v", err)
  217. }
  218. if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
  219. t.Errorf("missing file info for dir/file1.proto")
  220. } else if f1.GoPackageName != "foo" {
  221. t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
  222. }
  223. }
  224. func TestInconsistentPackageNames(t *testing.T) {
  225. _, err := New(&pluginpb.CodeGeneratorRequest{
  226. ProtoFile: []*descpb.FileDescriptorProto{
  227. {
  228. Name: proto.String("dir/file1.proto"),
  229. Package: proto.String("proto.package"),
  230. Options: &descpb.FileOptions{
  231. GoPackage: proto.String("golang.org/x/foo"),
  232. },
  233. },
  234. {
  235. Name: proto.String("dir/file2.proto"),
  236. Package: proto.String("proto.package"),
  237. Options: &descpb.FileOptions{
  238. GoPackage: proto.String("golang.org/x/foo;bar"),
  239. },
  240. },
  241. },
  242. FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
  243. }, nil)
  244. if err == nil {
  245. t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
  246. }
  247. }
  248. func TestImports(t *testing.T) {
  249. gen, err := New(&pluginpb.CodeGeneratorRequest{}, nil)
  250. if err != nil {
  251. t.Fatal(err)
  252. }
  253. g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
  254. g.P("package foo")
  255. g.P()
  256. for _, importPath := range []GoImportPath{
  257. "golang.org/x/foo",
  258. // Multiple references to the same package.
  259. "golang.org/x/bar",
  260. "golang.org/x/bar",
  261. // Reference to a different package with the same basename.
  262. "golang.org/y/bar",
  263. "golang.org/x/baz",
  264. // Reference to a package conflicting with a predeclared identifier.
  265. "golang.org/z/string",
  266. } {
  267. g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
  268. }
  269. want := `package foo
  270. import (
  271. bar "golang.org/x/bar"
  272. baz "golang.org/x/baz"
  273. bar1 "golang.org/y/bar"
  274. string1 "golang.org/z/string"
  275. )
  276. var _ = X // "golang.org/x/foo"
  277. var _ = bar.X // "golang.org/x/bar"
  278. var _ = bar.X // "golang.org/x/bar"
  279. var _ = bar1.X // "golang.org/y/bar"
  280. var _ = baz.X // "golang.org/x/baz"
  281. var _ = string1.X // "golang.org/z/string"
  282. `
  283. got, err := g.content()
  284. if err != nil {
  285. t.Fatalf("g.content() = %v", err)
  286. }
  287. if want != string(got) {
  288. t.Fatalf(`want:
  289. ==========
  290. %v
  291. ==========
  292. got:
  293. ==========
  294. %v
  295. ==========`,
  296. want, string(got))
  297. }
  298. }
  299. func TestImportRewrites(t *testing.T) {
  300. gen, err := New(&pluginpb.CodeGeneratorRequest{}, &Options{
  301. ImportRewriteFunc: func(i GoImportPath) GoImportPath {
  302. return "prefix/" + i
  303. },
  304. })
  305. if err != nil {
  306. t.Fatal(err)
  307. }
  308. g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
  309. g.P("package foo")
  310. g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
  311. want := `package foo
  312. import bar "prefix/golang.org/x/bar"
  313. var _ = bar.X
  314. `
  315. got, err := g.content()
  316. if err != nil {
  317. t.Fatalf("g.content() = %v", err)
  318. }
  319. if want != string(got) {
  320. t.Fatalf(`want:
  321. ==========
  322. %v
  323. ==========
  324. got:
  325. ==========
  326. %v
  327. ==========`,
  328. want, string(got))
  329. }
  330. }
  331. // makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
  332. //
  333. // It does this by running protoc with the current binary as the protoc-gen-go
  334. // plugin. This "plugin" produces a single file, named 'request', which contains
  335. // the code generator request.
  336. func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
  337. workdir, err := ioutil.TempDir("", "test")
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. defer os.RemoveAll(workdir)
  342. cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
  343. cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
  344. cmd.Args = append(cmd.Args, args...)
  345. cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
  346. out, err := cmd.CombinedOutput()
  347. if len(out) > 0 || err != nil {
  348. t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
  349. }
  350. if len(out) > 0 {
  351. t.Log(string(out))
  352. }
  353. if err != nil {
  354. t.Fatalf("protoc: %v", err)
  355. }
  356. b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
  357. if err != nil {
  358. t.Fatal(err)
  359. }
  360. req := &pluginpb.CodeGeneratorRequest{}
  361. if err := proto.UnmarshalText(string(b), req); err != nil {
  362. t.Fatal(err)
  363. }
  364. return req
  365. }
  366. func init() {
  367. if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
  368. Run(nil, func(p *Plugin) error {
  369. g := p.NewGeneratedFile("request", "")
  370. return proto.MarshalText(g, p.Request)
  371. })
  372. os.Exit(0)
  373. }
  374. }