123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- package main
- import (
- "bytes"
- "flag"
- "go/build"
- "go/parser"
- "go/token"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "testing"
- )
- // Set --regenerate to regenerate the golden files.
- var regenerate = flag.Bool("regenerate", false, "regenerate golden files")
- // When the environment variable RUN_AS_PROTOC_GEN_GO is set, we skip running
- // tests and instead act as protoc-gen-go. This allows the test binary to
- // pass itself to protoc.
- func init() {
- if os.Getenv("RUN_AS_PROTOC_GEN_GO") != "" {
- main()
- os.Exit(0)
- }
- }
- func TestGolden(t *testing.T) {
- workdir, err := ioutil.TempDir("", "proto-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(workdir)
- // Find all the proto files we need to compile. We assume that each directory
- // contains the files for a single package.
- supportTypeAliases := hasReleaseTag("go1.9")
- packages := map[string][]string{}
- err = filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
- if filepath.Base(path) == "import_public" && !supportTypeAliases {
- // Public imports require type alias support.
- return filepath.SkipDir
- }
- if !strings.HasSuffix(path, ".proto") {
- return nil
- }
- dir := filepath.Dir(path)
- packages[dir] = append(packages[dir], path)
- return nil
- })
- if err != nil {
- t.Fatal(err)
- }
- // Compile each package, using this binary as protoc-gen-go.
- for _, sources := range packages {
- args := []string{"-Itestdata", "--go_out=plugins=grpc,paths=source_relative:" + workdir}
- args = append(args, sources...)
- protoc(t, args)
- }
- // Compare each generated file to the golden version.
- filepath.Walk(workdir, func(genPath string, info os.FileInfo, _ error) error {
- if info.IsDir() {
- return nil
- }
- // For each generated file, figure out the path to the corresponding
- // golden file in the testdata directory.
- relPath, err := filepath.Rel(workdir, genPath)
- if err != nil {
- t.Errorf("filepath.Rel(%q, %q): %v", workdir, genPath, err)
- return nil
- }
- if filepath.SplitList(relPath)[0] == ".." {
- t.Errorf("generated file %q is not relative to %q", genPath, workdir)
- }
- goldenPath := filepath.Join("testdata", relPath)
- got, err := ioutil.ReadFile(genPath)
- if err != nil {
- t.Error(err)
- return nil
- }
- if *regenerate {
- // If --regenerate set, just rewrite the golden files.
- err := ioutil.WriteFile(goldenPath, got, 0666)
- if err != nil {
- t.Error(err)
- }
- return nil
- }
- want, err := ioutil.ReadFile(goldenPath)
- if err != nil {
- t.Error(err)
- return nil
- }
- want = fdescRE.ReplaceAll(want, nil)
- got = fdescRE.ReplaceAll(got, nil)
- if bytes.Equal(got, want) {
- return nil
- }
- cmd := exec.Command("diff", "-u", goldenPath, genPath)
- out, _ := cmd.CombinedOutput()
- t.Errorf("golden file differs: %v\n%v", relPath, string(out))
- return nil
- })
- }
- var fdescRE = regexp.MustCompile(`(?ms)^var fileDescriptor.*}`)
- // Source files used by TestParameters.
- const (
- aProto = `
- syntax = "proto3";
- package test.alpha;
- option go_package = "package/alpha";
- import "beta/b.proto";
- message M { test.beta.M field = 1; }`
- bProto = `
- syntax = "proto3";
- package test.beta;
- // no go_package option
- message M {}`
- )
- func TestParameters(t *testing.T) {
- for _, test := range []struct {
- parameters string
- wantFiles map[string]bool
- wantImportsA map[string]bool
- wantPackageA string
- wantPackageB string
- }{{
- parameters: "",
- wantFiles: map[string]bool{
- "package/alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- wantImportsA: map[string]bool{
- "github.com/golang/protobuf/proto": true,
- "beta": true,
- },
- }, {
- parameters: "import_prefix=prefix",
- wantFiles: map[string]bool{
- "package/alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- wantImportsA: map[string]bool{
- // This really doesn't seem like useful behavior.
- "prefixgithub.com/golang/protobuf/proto": true,
- "prefixbeta": true,
- },
- }, {
- // import_path only affects the 'package' line.
- parameters: "import_path=import/path/of/pkg",
- wantPackageA: "alpha",
- wantPackageB: "pkg",
- wantFiles: map[string]bool{
- "package/alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- }, {
- parameters: "Mbeta/b.proto=package/gamma",
- wantFiles: map[string]bool{
- "package/alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- wantImportsA: map[string]bool{
- "github.com/golang/protobuf/proto": true,
- // Rewritten by the M parameter.
- "package/gamma": true,
- },
- }, {
- parameters: "import_prefix=prefix,Mbeta/b.proto=package/gamma",
- wantFiles: map[string]bool{
- "package/alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- wantImportsA: map[string]bool{
- // import_prefix applies after M.
- "prefixpackage/gamma": true,
- },
- }, {
- parameters: "paths=source_relative",
- wantFiles: map[string]bool{
- "alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- }, {
- parameters: "paths=source_relative,import_prefix=prefix",
- wantFiles: map[string]bool{
- // import_prefix doesn't affect filenames.
- "alpha/a.pb.go": true,
- "beta/b.pb.go": true,
- },
- wantPackageA: "alpha",
- wantPackageB: "test_beta",
- }} {
- name := test.parameters
- if name == "" {
- name = "defaults"
- }
- // TODO: Switch to t.Run when we no longer support Go 1.6.
- t.Logf("TEST: %v", name)
- workdir, err := ioutil.TempDir("", "proto-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(workdir)
- for _, dir := range []string{"alpha", "beta", "out"} {
- if err := os.MkdirAll(filepath.Join(workdir, dir), 0777); err != nil {
- t.Fatal(err)
- }
- }
- if err := ioutil.WriteFile(filepath.Join(workdir, "alpha", "a.proto"), []byte(aProto), 0666); err != nil {
- t.Fatal(err)
- }
- if err := ioutil.WriteFile(filepath.Join(workdir, "beta", "b.proto"), []byte(bProto), 0666); err != nil {
- t.Fatal(err)
- }
- protoc(t, []string{
- "-I" + workdir,
- "--go_out=" + test.parameters + ":" + filepath.Join(workdir, "out"),
- filepath.Join(workdir, "alpha", "a.proto"),
- })
- protoc(t, []string{
- "-I" + workdir,
- "--go_out=" + test.parameters + ":" + filepath.Join(workdir, "out"),
- filepath.Join(workdir, "beta", "b.proto"),
- })
- contents := make(map[string]string)
- gotFiles := make(map[string]bool)
- outdir := filepath.Join(workdir, "out")
- filepath.Walk(outdir, func(p string, info os.FileInfo, _ error) error {
- if info.IsDir() {
- return nil
- }
- base := filepath.Base(p)
- if base == "a.pb.go" || base == "b.pb.go" {
- b, err := ioutil.ReadFile(p)
- if err != nil {
- t.Fatal(err)
- }
- contents[base] = string(b)
- }
- relPath, _ := filepath.Rel(outdir, p)
- gotFiles[relPath] = true
- return nil
- })
- for got := range gotFiles {
- if runtime.GOOS == "windows" {
- got = filepath.ToSlash(got)
- }
- if !test.wantFiles[got] {
- t.Errorf("unexpected output file: %v", got)
- }
- }
- for want := range test.wantFiles {
- if runtime.GOOS == "windows" {
- want = filepath.FromSlash(want)
- }
- if !gotFiles[want] {
- t.Errorf("missing output file: %v", want)
- }
- }
- gotPackageA, gotImports, err := parseFile(contents["a.pb.go"])
- if err != nil {
- t.Fatal(err)
- }
- gotPackageB, _, err := parseFile(contents["b.pb.go"])
- if err != nil {
- t.Fatal(err)
- }
- if got, want := gotPackageA, test.wantPackageA; want != got {
- t.Errorf("output file a.pb.go is package %q, want %q", got, want)
- }
- if got, want := gotPackageB, test.wantPackageB; want != got {
- t.Errorf("output file b.pb.go is package %q, want %q", got, want)
- }
- missingImport := false
- WantImport:
- for want := range test.wantImportsA {
- for _, imp := range gotImports {
- if `"`+want+`"` == imp {
- continue WantImport
- }
- }
- t.Errorf("output file a.pb.go does not contain expected import %q", want)
- missingImport = true
- }
- if missingImport {
- t.Error("got imports:")
- for _, imp := range gotImports {
- t.Errorf(" %v", imp)
- }
- }
- }
- }
- // parseFile returns a file's package name and a list of all packages it imports.
- func parseFile(source string) (packageName string, imports []string, err error) {
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, "<source>", source, parser.ImportsOnly)
- if err != nil {
- return "", nil, err
- }
- for _, imp := range f.Imports {
- imports = append(imports, imp.Path.Value)
- }
- return f.Name.Name, imports, nil
- }
- func protoc(t *testing.T, args []string) {
- cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
- cmd.Args = append(cmd.Args, args...)
- // We set the RUN_AS_PROTOC_GEN_GO environment variable to indicate that
- // the subprocess should act as a proto compiler rather than a test.
- cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_GEN_GO=1")
- out, err := cmd.CombinedOutput()
- if len(out) > 0 || err != nil {
- t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
- }
- if len(out) > 0 {
- t.Log(string(out))
- }
- if err != nil {
- t.Fatalf("protoc: %v", err)
- }
- }
- func hasReleaseTag(want string) bool {
- for _, tag := range build.Default.ReleaseTags {
- if tag == want {
- return true
- }
- }
- return false
- }
|