protobuf-go/protogen/protogen_test.go
Damien Neil 082ce923d3 protogen: compute package names, import paths, generated filenames
Copy/duplicate the logic in github.com/golang/protobuf for computing
package names and import paths and the names of generated files.

This is all sufficiently complicated that the code is the best
documentation. In practice, users should always set a go_package option
containing an import path in every file and pass the
paths=source_relative generator flag to get reasonable behavior.

Change-Id: I34ae38fcc8db6909a4b25b16c73b982a7bad0463
Reviewed-on: https://go-review.googlesource.com/133876
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2018-09-07 17:58:10 +00:00

319 lines
8.5 KiB
Go

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protogen
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/golang/protobuf/proto"
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
)
func TestFiles(t *testing.T) {
gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"))
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
path string
wantGenerate bool
}{
{
path: "go_package/no_go_package_import.proto",
wantGenerate: true,
},
{
path: "go_package/no_go_package.proto",
wantGenerate: false,
},
} {
f, ok := gen.FileByName(test.path)
if !ok {
t.Errorf("%q: not found by gen.FileByName", test.path)
continue
}
if f.Generate != test.wantGenerate {
t.Errorf("%q: Generate=%v, want %v", test.path, f.Generate, test.wantGenerate)
}
}
}
func TestPackageNamesAndPaths(t *testing.T) {
const (
filename = "dir/filename.proto"
protoPackageName = "proto.package"
)
for _, test := range []struct {
desc string
parameter string
goPackageOption string
generate bool
wantPackageName GoPackageName
wantImportPath GoImportPath
wantFilenamePrefix string
}{
{
desc: "no parameters, no go_package option",
generate: true,
wantPackageName: "proto_package",
wantImportPath: "dir",
wantFilenamePrefix: "dir/filename",
},
{
desc: "go_package option sets import path",
goPackageOption: "golang.org/x/foo",
generate: true,
wantPackageName: "foo",
wantImportPath: "golang.org/x/foo",
wantFilenamePrefix: "golang.org/x/foo/filename",
},
{
desc: "go_package option sets import path and package",
goPackageOption: "golang.org/x/foo;bar",
generate: true,
wantPackageName: "bar",
wantImportPath: "golang.org/x/foo",
wantFilenamePrefix: "golang.org/x/foo/filename",
},
{
desc: "go_package option sets package",
goPackageOption: "foo",
generate: true,
wantPackageName: "foo",
wantImportPath: "dir",
wantFilenamePrefix: "dir/filename",
},
{
desc: "command line sets import path for a file",
parameter: "Mdir/filename.proto=golang.org/x/bar",
goPackageOption: "golang.org/x/foo",
generate: true,
wantPackageName: "foo",
wantImportPath: "golang.org/x/bar",
wantFilenamePrefix: "golang.org/x/foo/filename",
},
{
desc: "import_path parameter sets import path of generated files",
parameter: "import_path=golang.org/x/bar",
goPackageOption: "golang.org/x/foo",
generate: true,
wantPackageName: "foo",
wantImportPath: "golang.org/x/bar",
wantFilenamePrefix: "golang.org/x/foo/filename",
},
{
desc: "import_path parameter does not set import path of dependencies",
parameter: "import_path=golang.org/x/bar",
goPackageOption: "golang.org/x/foo",
generate: false,
wantPackageName: "foo",
wantImportPath: "golang.org/x/foo",
wantFilenamePrefix: "golang.org/x/foo/filename",
},
} {
context := fmt.Sprintf(`
TEST: %v
--go_out=%v:.
file %q: generate=%v
option go_package = %q;
`,
test.desc, test.parameter, filename, test.generate, test.goPackageOption)
req := &pluginpb.CodeGeneratorRequest{
Parameter: proto.String(test.parameter),
ProtoFile: []*descpb.FileDescriptorProto{
{
Name: proto.String(filename),
Package: proto.String(protoPackageName),
Options: &descpb.FileOptions{
GoPackage: proto.String(test.goPackageOption),
},
},
},
}
if test.generate {
req.FileToGenerate = []string{filename}
}
gen, err := New(req)
if err != nil {
t.Errorf("%vNew(req) = %v", context, err)
continue
}
gotFile, ok := gen.FileByName(filename)
if !ok {
t.Errorf("%v%v: missing file info", context, filename)
continue
}
if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
}
if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
}
if got, want := gotFile.GeneratedFilenamePrefix, test.wantFilenamePrefix; got != want {
t.Errorf("%vGeneratedFilenamePrefix=%v, want %v", context, got, want)
}
}
}
func TestPackageNameInference(t *testing.T) {
gen, err := New(&pluginpb.CodeGeneratorRequest{
ProtoFile: []*descpb.FileDescriptorProto{
{
Name: proto.String("dir/file1.proto"),
Package: proto.String("proto.package"),
},
{
Name: proto.String("dir/file2.proto"),
Package: proto.String("proto.package"),
Options: &descpb.FileOptions{
GoPackage: proto.String("foo"),
},
},
},
FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
})
if err != nil {
t.Fatalf("New(req) = %v", err)
}
if f1, ok := gen.FileByName("dir/file1.proto"); !ok {
t.Errorf("missing file info for dir/file1.proto")
} else if f1.GoPackageName != "foo" {
t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
}
}
func TestInconsistentPackageNames(t *testing.T) {
_, err := New(&pluginpb.CodeGeneratorRequest{
ProtoFile: []*descpb.FileDescriptorProto{
{
Name: proto.String("dir/file1.proto"),
Package: proto.String("proto.package"),
Options: &descpb.FileOptions{
GoPackage: proto.String("golang.org/x/foo"),
},
},
{
Name: proto.String("dir/file2.proto"),
Package: proto.String("proto.package"),
Options: &descpb.FileOptions{
GoPackage: proto.String("golang.org/x/foo;bar"),
},
},
},
FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
})
if err == nil {
t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
}
}
func TestImports(t *testing.T) {
gen, err := New(&pluginpb.CodeGeneratorRequest{})
if err != nil {
t.Fatal(err)
}
g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
g.P("package foo")
g.P()
for _, importPath := range []GoImportPath{
"golang.org/x/foo",
// Multiple references to the same package.
"golang.org/x/bar",
"golang.org/x/bar",
// Reference to a different package with the same basename.
"golang.org/y/bar",
"golang.org/x/baz",
} {
g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
}
want := `package foo
import (
bar "golang.org/x/bar"
bar1 "golang.org/y/bar"
baz "golang.org/x/baz"
)
var _ = X // "golang.org/x/foo"
var _ = bar.X // "golang.org/x/bar"
var _ = bar.X // "golang.org/x/bar"
var _ = bar1.X // "golang.org/y/bar"
var _ = baz.X // "golang.org/x/baz"
`
got, err := g.Content()
if err != nil {
t.Fatalf("g.Content() = %v", err)
}
if want != string(got) {
t.Fatalf(`want:
==========
%v
==========
got:
==========
%v
==========`,
want, string(got))
}
}
// makeRequest returns a CodeGeneratorRequest for the given protoc inputs.
//
// It does this by running protoc with the current binary as the protoc-gen-go
// plugin. This "plugin" produces a single file, named 'request', which contains
// the code generator request.
func makeRequest(t *testing.T, args ...string) *pluginpb.CodeGeneratorRequest {
workdir, err := ioutil.TempDir("", "test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(workdir)
cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
cmd.Args = append(cmd.Args, "--go_out="+workdir, "-Itestdata")
cmd.Args = append(cmd.Args, args...)
cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=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)
}
b, err := ioutil.ReadFile(filepath.Join(workdir, "request"))
if err != nil {
t.Fatal(err)
}
req := &pluginpb.CodeGeneratorRequest{}
if err := proto.UnmarshalText(string(b), req); err != nil {
t.Fatal(err)
}
return req
}
func init() {
if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
Run(func(p *Plugin) error {
g := p.NewGeneratedFile("request", "")
return proto.MarshalText(g, p.Request)
})
os.Exit(0)
}
}