protogen_test.go 10 KB

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