internal/cmd/generate-protos: generate test for testdata
Running "go build ./..." does not descend into testdata directories.
However, the testdata in this repository is source code that is
intended to build properly. We could rename the directory, but that does
not test whether the generated packages can initialize properly.
Thus, we generate a trivial test that simply blank imports all packages.
Doing this reveals that some of the generated files have incorrect imports,
leading to registration conflicts.
To avoid introducing a dependency on gRPC from our go.mod file, we put
the testdata directories in their own module. Also, we avoid running
internal/testprotos through the grpc plugin because the servie and method
definitions in that directory are more for testing proto file initialization
rather than testing grpc generation.
Change-Id: Iaa6a06449787a085200e31bc7606e3ac904d3180
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/164917
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/cmd/generate-protos/main.go b/internal/cmd/generate-protos/main.go
index cf7c688..94f735e 100644
--- a/internal/cmd/generate-protos/main.go
+++ b/internal/cmd/generate-protos/main.go
@@ -9,12 +9,14 @@
import (
"flag"
"fmt"
+ "go/format"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
+ "sort"
"strings"
gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc"
@@ -27,13 +29,19 @@
// When the environment variable RUN_AS_PROTOC_PLUGIN is set,
// we skip running main and instead act as a protoc plugin.
// This allows the binary to pass itself to protoc.
- if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
+ if plugins := os.Getenv("RUN_AS_PROTOC_PLUGIN"); plugins != "" {
protogen.Run(nil, func(gen *protogen.Plugin) error {
- for _, file := range gen.Files {
- if file.Generate {
- gengo.GenerateFile(gen, file)
- gengogrpc.GenerateFile(gen, file)
- generateDescriptorFields(gen, file)
+ for _, plugin := range strings.Split(plugins, ",") {
+ for _, file := range gen.Files {
+ if file.Generate {
+ switch plugin {
+ case "go":
+ gengo.GenerateFile(gen, file)
+ generateDescriptorFields(gen, file)
+ case "gogrpc":
+ gengogrpc.GenerateFile(gen, file)
+ }
+ }
}
}
return nil
@@ -43,9 +51,10 @@
}
var (
- run bool
- protoRoot string
- repoRoot string
+ run bool
+ protoRoot string
+ repoRoot string
+ modulePath string
generatedPreamble = []string{
"// Copyright 2019 The Go Authors. All rights reserved.",
@@ -70,6 +79,13 @@
check(err)
repoRoot = strings.TrimSpace(string(out))
+ // Determine the module path.
+ cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
+ cmd.Dir = repoRoot
+ out, err = cmd.CombinedOutput()
+ check(err)
+ modulePath = strings.TrimSpace(string(out))
+
generateLocalProtos()
generateRemoteProtos()
}
@@ -82,16 +98,19 @@
// Generate all local proto files (except version-locked files).
dirs := []struct {
path string
+ grpcPlugin bool
annotateFor map[string]bool
}{
- {"cmd/protoc-gen-go/testdata", map[string]bool{"annotations/annotations.proto": true}},
- {"cmd/protoc-gen-go-grpc/testdata", nil},
- {"internal/testprotos", nil},
- {"encoding/testprotos", nil},
- {"reflect/protoregistry/testprotos", nil},
+ {path: "cmd/protoc-gen-go/testdata", annotateFor: map[string]bool{"annotations/annotations.proto": true}},
+ {path: "cmd/protoc-gen-go-grpc/testdata", grpcPlugin: true},
+ {path: "internal/testprotos"},
+ {path: "encoding/testprotos"},
+ {path: "reflect/protoregistry/testprotos"},
}
semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`)
for _, d := range dirs {
+ subDirs := map[string]bool{}
+
dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path))
check(os.MkdirAll(dstDir, 0775))
@@ -102,6 +121,7 @@
}
relPath, err := filepath.Rel(srcDir, srcPath)
check(err)
+ subDirs[filepath.Dir(relPath)] = true
// Emit a .meta file for certain files.
var opts string
@@ -109,9 +129,36 @@
opts = ",annotate_code"
}
- protoc("-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
+ // Determine which set of plugins to use.
+ plugins := "go"
+ if d.grpcPlugin {
+ plugins += ",gogrpc"
+ }
+
+ protoc(plugins, "-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
return nil
})
+
+ // For directories in testdata, generate a test that links in all
+ // generated packages to ensure that it builds and initializes properly.
+ // This is done because "go build ./..." does not build sub-packages
+ // under testdata.
+ if filepath.Base(d.path) == "testdata" {
+ var imports []string
+ for sd := range subDirs {
+ imports = append(imports, fmt.Sprintf("_ %q", path.Join(modulePath, d.path, filepath.ToSlash(sd))))
+ }
+ sort.Strings(imports)
+
+ s := strings.Join(append(generatedPreamble, []string{
+ "package main",
+ "",
+ "import (" + strings.Join(imports, "\n") + ")",
+ }...), "\n")
+ b, err := format.Source([]byte(s))
+ check(err)
+ check(ioutil.WriteFile(filepath.Join(tmpDir, filepath.FromSlash(d.path+"/gen_test.go")), b, 0664))
+ }
}
syncOutput(repoRoot, tmpDir)
@@ -139,23 +186,16 @@
{"src", "google/protobuf/compiler/plugin.proto"},
}
for _, f := range files {
- protoc("-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
+ protoc("go", "-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
}
- // Determine the module path.
- cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
- cmd.Dir = repoRoot
- out, err := cmd.CombinedOutput()
- check(err)
- modulePath := strings.TrimSpace(string(out))
-
syncOutput(repoRoot, filepath.Join(tmpDir, modulePath))
}
-func protoc(args ...string) {
+func protoc(plugins string, args ...string) {
cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
cmd.Args = append(cmd.Args, args...)
- cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
+ cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN="+plugins)
cmd.Env = append(cmd.Env, "PROTOC_GEN_GO_ENABLE_REFLECT=1")
out, err := cmd.CombinedOutput()
if err != nil {
@@ -170,8 +210,8 @@
return
}
- const importPath = "github.com/golang/protobuf/v2/internal/descfield"
- g := gen.NewGeneratedFile(importPath+"/field_gen.go", importPath)
+ importPath := modulePath + "/internal/descfield"
+ g := gen.NewGeneratedFile(importPath+"/field_gen.go", protogen.GoImportPath(importPath))
for _, s := range generatedPreamble {
g.P(s)
}
diff --git a/internal/testprotos/test/test_grpc.pb.go b/internal/testprotos/test/test_grpc.pb.go
deleted file mode 100644
index a2f94ff..0000000
--- a/internal/testprotos/test/test_grpc.pb.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
-
-package test
-
-import (
- context "context"
- grpc "google.golang.org/grpc"
-)
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ context.Context
-var _ grpc.ClientConn
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the grpc package it is being compiled against.
-const _ = grpc.SupportPackageIsVersion4
-
-// TestServiceClient is the client API for TestService service.
-//
-// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
-type TestServiceClient interface {
- Foo(ctx context.Context, in *FooRequest, opts ...grpc.CallOption) (*FooResponse, error)
- TestStream(ctx context.Context, opts ...grpc.CallOption) (TestService_TestStreamClient, error)
-}
-
-type testServiceClient struct {
- cc *grpc.ClientConn
-}
-
-func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient {
- return &testServiceClient{cc}
-}
-
-func (c *testServiceClient) Foo(ctx context.Context, in *FooRequest, opts ...grpc.CallOption) (*FooResponse, error) {
- out := new(FooResponse)
- err := c.cc.Invoke(ctx, "/goproto.proto.test.TestService/Foo", in, out, opts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *testServiceClient) TestStream(ctx context.Context, opts ...grpc.CallOption) (TestService_TestStreamClient, error) {
- stream, err := c.cc.NewStream(ctx, &_TestService_serviceDesc.Streams[0], "/goproto.proto.test.TestService/TestStream", opts...)
- if err != nil {
- return nil, err
- }
- x := &testServiceTestStreamClient{stream}
- return x, nil
-}
-
-type TestService_TestStreamClient interface {
- Send(*FooRequest) error
- Recv() (*FooResponse, error)
- grpc.ClientStream
-}
-
-type testServiceTestStreamClient struct {
- grpc.ClientStream
-}
-
-func (x *testServiceTestStreamClient) Send(m *FooRequest) error {
- return x.ClientStream.SendMsg(m)
-}
-
-func (x *testServiceTestStreamClient) Recv() (*FooResponse, error) {
- m := new(FooResponse)
- if err := x.ClientStream.RecvMsg(m); err != nil {
- return nil, err
- }
- return m, nil
-}
-
-// TestServiceServer is the server API for TestService service.
-type TestServiceServer interface {
- Foo(context.Context, *FooRequest) (*FooResponse, error)
- TestStream(TestService_TestStreamServer) error
-}
-
-func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) {
- s.RegisterService(&_TestService_serviceDesc, srv)
-}
-
-func _TestService_Foo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(FooRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(TestServiceServer).Foo(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: "/goproto.proto.test.TestService/Foo",
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(TestServiceServer).Foo(ctx, req.(*FooRequest))
- }
- return interceptor(ctx, in, info, handler)
-}
-
-func _TestService_TestStream_Handler(srv interface{}, stream grpc.ServerStream) error {
- return srv.(TestServiceServer).TestStream(&testServiceTestStreamServer{stream})
-}
-
-type TestService_TestStreamServer interface {
- Send(*FooResponse) error
- Recv() (*FooRequest, error)
- grpc.ServerStream
-}
-
-type testServiceTestStreamServer struct {
- grpc.ServerStream
-}
-
-func (x *testServiceTestStreamServer) Send(m *FooResponse) error {
- return x.ServerStream.SendMsg(m)
-}
-
-func (x *testServiceTestStreamServer) Recv() (*FooRequest, error) {
- m := new(FooRequest)
- if err := x.ServerStream.RecvMsg(m); err != nil {
- return nil, err
- }
- return m, nil
-}
-
-var _TestService_serviceDesc = grpc.ServiceDesc{
- ServiceName: "goproto.proto.test.TestService",
- HandlerType: (*TestServiceServer)(nil),
- Methods: []grpc.MethodDesc{
- {
- MethodName: "Foo",
- Handler: _TestService_Foo_Handler,
- },
- },
- Streams: []grpc.StreamDesc{
- {
- StreamName: "TestStream",
- Handler: _TestService_TestStream_Handler,
- ServerStreams: true,
- ClientStreams: true,
- },
- },
- Metadata: "test/test.proto",
-}
-
-// TestDeprecatedServiceClient is the client API for TestDeprecatedService service.
-//
-// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
-//
-// Deprecated: Do not use.
-type TestDeprecatedServiceClient interface {
- Deprecated(ctx context.Context, in *TestDeprecatedMessage, opts ...grpc.CallOption) (*TestDeprecatedMessage, error)
-}
-
-type testDeprecatedServiceClient struct {
- cc *grpc.ClientConn
-}
-
-// Deprecated: Do not use.
-func NewTestDeprecatedServiceClient(cc *grpc.ClientConn) TestDeprecatedServiceClient {
- return &testDeprecatedServiceClient{cc}
-}
-
-// Deprecated: Do not use.
-func (c *testDeprecatedServiceClient) Deprecated(ctx context.Context, in *TestDeprecatedMessage, opts ...grpc.CallOption) (*TestDeprecatedMessage, error) {
- out := new(TestDeprecatedMessage)
- err := c.cc.Invoke(ctx, "/goproto.proto.test.TestDeprecatedService/Deprecated", in, out, opts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-// TestDeprecatedServiceServer is the server API for TestDeprecatedService service.
-//
-// Deprecated: Do not use.
-type TestDeprecatedServiceServer interface {
- Deprecated(context.Context, *TestDeprecatedMessage) (*TestDeprecatedMessage, error)
-}
-
-// Deprecated: Do not use.
-func RegisterTestDeprecatedServiceServer(s *grpc.Server, srv TestDeprecatedServiceServer) {
- s.RegisterService(&_TestDeprecatedService_serviceDesc, srv)
-}
-
-func _TestDeprecatedService_Deprecated_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(TestDeprecatedMessage)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(TestDeprecatedServiceServer).Deprecated(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: "/goproto.proto.test.TestDeprecatedService/Deprecated",
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(TestDeprecatedServiceServer).Deprecated(ctx, req.(*TestDeprecatedMessage))
- }
- return interceptor(ctx, in, info, handler)
-}
-
-var _TestDeprecatedService_serviceDesc = grpc.ServiceDesc{
- ServiceName: "goproto.proto.test.TestDeprecatedService",
- HandlerType: (*TestDeprecatedServiceServer)(nil),
- Methods: []grpc.MethodDesc{
- {
- MethodName: "Deprecated",
- Handler: _TestDeprecatedService_Deprecated_Handler,
- },
- },
- Streams: []grpc.StreamDesc{},
- Metadata: "test/test.proto",
-}