mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-02-07 03:40:29 +00:00
24ceb2b095
In CL/152020, we checked in pre-generated versions of descriptor and plugin. This CL makes it such that they are generated by protoc-gen-go. We modify protoc-gen-go to avoid reflection support by default since the binary size increase is still an issue to investigate. Reflection support is temporarily enabled by setting a special PROTOC_GEN_GO_ENABLE_REFLECT environment variable. Reflection support is always enabled for descriptor and plugin. Furthermore, we change descriptor to depend on the protoapi package instead of the proto package. The reason we do not switch to protoapi for all generated protos is because we still depend on v1 proto for the table-driven InternalMessageInfo type. Dropping it from descriptor is semantically correct, but does incur slight performance cost. It does not seem appropriate to drop it for all generated messages. We could move InternalMessageInfo to protoapi, but the logic behind that is significant. Change-Id: I5c3fff7f6eab1a5a2399049d42fa6bf42d4c93f9 Reviewed-on: https://go-review.googlesource.com/c/152547 Reviewed-by: Damien Neil <dneil@google.com>
401 lines
10 KiB
Go
401 lines
10 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.
|
|
|
|
// +build golden
|
|
|
|
package protogen
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/golang/protobuf/v2/internal/scalar"
|
|
|
|
descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
|
|
pluginpb "github.com/golang/protobuf/v2/types/plugin"
|
|
)
|
|
|
|
func TestPluginParameters(t *testing.T) {
|
|
var flags flag.FlagSet
|
|
value := flags.Int("integer", 0, "")
|
|
opts := &Options{
|
|
ParamFunc: flags.Set,
|
|
}
|
|
const params = "integer=2"
|
|
_, err := New(&pluginpb.CodeGeneratorRequest{
|
|
Parameter: scalar.String(params),
|
|
}, opts)
|
|
if err != nil {
|
|
t.Errorf("New(generator parameters %q): %v", params, err)
|
|
}
|
|
if *value != 2 {
|
|
t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
|
|
}
|
|
}
|
|
|
|
func TestPluginParameterErrors(t *testing.T) {
|
|
for _, parameter := range []string{
|
|
"unknown=1",
|
|
"boolean=error",
|
|
} {
|
|
var flags flag.FlagSet
|
|
flags.Bool("boolean", false, "")
|
|
opts := &Options{
|
|
ParamFunc: flags.Set,
|
|
}
|
|
_, err := New(&pluginpb.CodeGeneratorRequest{
|
|
Parameter: scalar.String(parameter),
|
|
}, opts)
|
|
if err == nil {
|
|
t.Errorf("New(generator parameters %q): want error, got nil", parameter)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFiles(t *testing.T) {
|
|
gen, err := New(makeRequest(t, "testdata/go_package/no_go_package_import.proto"), nil)
|
|
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: scalar.String(test.parameter),
|
|
ProtoFile: []*descriptorpb.FileDescriptorProto{
|
|
{
|
|
Name: scalar.String(filename),
|
|
Package: scalar.String(protoPackageName),
|
|
Options: &descriptorpb.FileOptions{
|
|
GoPackage: scalar.String(test.goPackageOption),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if test.generate {
|
|
req.FileToGenerate = []string{filename}
|
|
}
|
|
gen, err := New(req, nil)
|
|
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: []*descriptorpb.FileDescriptorProto{
|
|
{
|
|
Name: scalar.String("dir/file1.proto"),
|
|
Package: scalar.String("proto.package"),
|
|
},
|
|
{
|
|
Name: scalar.String("dir/file2.proto"),
|
|
Package: scalar.String("proto.package"),
|
|
Options: &descriptorpb.FileOptions{
|
|
GoPackage: scalar.String("foo"),
|
|
},
|
|
},
|
|
},
|
|
FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
|
|
}, nil)
|
|
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: []*descriptorpb.FileDescriptorProto{
|
|
{
|
|
Name: scalar.String("dir/file1.proto"),
|
|
Package: scalar.String("proto.package"),
|
|
Options: &descriptorpb.FileOptions{
|
|
GoPackage: scalar.String("golang.org/x/foo"),
|
|
},
|
|
},
|
|
{
|
|
Name: scalar.String("dir/file2.proto"),
|
|
Package: scalar.String("proto.package"),
|
|
Options: &descriptorpb.FileOptions{
|
|
GoPackage: scalar.String("golang.org/x/foo;bar"),
|
|
},
|
|
},
|
|
},
|
|
FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
|
|
}, nil)
|
|
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{}, nil)
|
|
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",
|
|
// Reference to a package conflicting with a predeclared identifier.
|
|
"golang.org/z/string",
|
|
} {
|
|
g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
|
|
}
|
|
want := `package foo
|
|
|
|
import (
|
|
bar "golang.org/x/bar"
|
|
baz "golang.org/x/baz"
|
|
bar1 "golang.org/y/bar"
|
|
string1 "golang.org/z/string"
|
|
)
|
|
|
|
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"
|
|
var _ = string1.X // "golang.org/z/string"
|
|
`
|
|
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))
|
|
}
|
|
}
|
|
|
|
func TestImportRewrites(t *testing.T) {
|
|
gen, err := New(&pluginpb.CodeGeneratorRequest{}, &Options{
|
|
ImportRewriteFunc: func(i GoImportPath) GoImportPath {
|
|
return "prefix/" + i
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
|
|
g.P("package foo")
|
|
g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
|
|
want := `package foo
|
|
|
|
import bar "prefix/golang.org/x/bar"
|
|
|
|
var _ = bar.X
|
|
`
|
|
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(nil, func(p *Plugin) error {
|
|
g := p.NewGeneratedFile("request", "")
|
|
return proto.MarshalText(g, p.Request)
|
|
})
|
|
os.Exit(0)
|
|
}
|
|
}
|