github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/linker/linker_test.go (about)

     1  package linker_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"math"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"google.golang.org/protobuf/encoding/protojson"
    16  	"google.golang.org/protobuf/encoding/protowire"
    17  	"google.golang.org/protobuf/proto"
    18  	"google.golang.org/protobuf/reflect/protodesc"
    19  	"google.golang.org/protobuf/reflect/protoreflect"
    20  	"google.golang.org/protobuf/reflect/protoregistry"
    21  	"google.golang.org/protobuf/types/descriptorpb"
    22  
    23  	"github.com/jhump/protocompile"
    24  	_ "github.com/jhump/protocompile/internal/testprotos"
    25  	"github.com/jhump/protocompile/linker"
    26  )
    27  
    28  func TestSimpleLink(t *testing.T) {
    29  	compiler := protocompile.Compiler{
    30  		Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
    31  			ImportPaths: []string{"../internal/testprotos"},
    32  		}),
    33  	}
    34  	fds, err := compiler.Compile(context.Background(), "desc_test_complex.proto")
    35  	if !assert.Nil(t, err) {
    36  		return
    37  	}
    38  
    39  	res := fds[0].(linker.Result)
    40  	fdset := loadDescriptorSet(t, "../internal/testprotos/desc_test_complex.protoset", linker.ResolverFromFile(fds[0]))
    41  	checkFiles(t, res, (*fdsProtoSet)(fdset), map[string]struct{}{})
    42  }
    43  
    44  func loadDescriptorSet(t *testing.T, path string, res linker.Resolver) *descriptorpb.FileDescriptorSet {
    45  	data, err := ioutil.ReadFile(path)
    46  	if !assert.Nil(t, err) {
    47  		t.Fail()
    48  	}
    49  	var fdset descriptorpb.FileDescriptorSet
    50  	err = proto.UnmarshalOptions{Resolver: res}.Unmarshal(data, &fdset)
    51  	if !assert.Nil(t, err) {
    52  		t.Fail()
    53  	}
    54  	return &fdset
    55  }
    56  
    57  func TestMultiFileLink(t *testing.T) {
    58  	for _, name := range []string{"desc_test_defaults.proto", "desc_test_field_types.proto", "desc_test_options.proto", "desc_test_wellknowntypes.proto"} {
    59  		compiler := protocompile.Compiler{
    60  			Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
    61  				ImportPaths: []string{"../internal/testprotos"},
    62  			}),
    63  		}
    64  		fds, err := compiler.Compile(context.Background(), name)
    65  		if !assert.Nil(t, err) {
    66  			continue
    67  		}
    68  
    69  		res := fds[0].(linker.Result)
    70  		checkFiles(t, res, (*regProtoSet)(protoregistry.GlobalFiles), map[string]struct{}{})
    71  	}
    72  }
    73  
    74  func TestProto3Optional(t *testing.T) {
    75  	compiler := protocompile.Compiler{
    76  		Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
    77  			ImportPaths: []string{"../internal/testprotos"},
    78  		}),
    79  	}
    80  	fds, err := compiler.Compile(context.Background(), "desc_test_proto3_optional.proto")
    81  	if !assert.Nil(t, err) {
    82  		return
    83  	}
    84  
    85  	fdset := loadDescriptorSet(t, "../internal/testprotos/desc_test_proto3_optional.protoset", fds.AsResolver())
    86  
    87  	res := fds[0].(linker.Result)
    88  	checkFiles(t, res, (*fdsProtoSet)(fdset), map[string]struct{}{})
    89  }
    90  
    91  func checkFiles(t *testing.T, act protoreflect.FileDescriptor, expSet fileProtoSet, checked map[string]struct{}) {
    92  	if _, ok := checked[act.Path()]; ok {
    93  		// already checked
    94  		return
    95  	}
    96  	checked[act.Path()] = struct{}{}
    97  
    98  	expProto := expSet.findFile(act.Path())
    99  	actProto := toProto(act)
   100  	checkFileDescriptor(t, actProto, expProto)
   101  
   102  	for i := 0; i < act.Imports().Len(); i++ {
   103  		checkFiles(t, act.Imports().Get(i), expSet, checked)
   104  	}
   105  }
   106  
   107  func checkFileDescriptor(t *testing.T, act, exp *descriptorpb.FileDescriptorProto) {
   108  	compareFiles(t, fmt.Sprintf("%q", act.GetName()), exp, act)
   109  }
   110  
   111  func toProto(f protoreflect.FileDescriptor) *descriptorpb.FileDescriptorProto {
   112  	type canProto interface {
   113  		Proto() *descriptorpb.FileDescriptorProto
   114  	}
   115  	if can, ok := f.(canProto); ok {
   116  		return can.Proto()
   117  	}
   118  	return protodesc.ToFileDescriptorProto(f)
   119  }
   120  
   121  func toString(m proto.Message) string {
   122  	mo := protojson.MarshalOptions{Indent: " ", Multiline: true}
   123  	js, err := mo.Marshal(m)
   124  	if err != nil {
   125  		panic(err)
   126  	}
   127  	return string(js)
   128  }
   129  
   130  type fileProtoSet interface {
   131  	findFile(name string) *descriptorpb.FileDescriptorProto
   132  }
   133  
   134  type fdsProtoSet descriptorpb.FileDescriptorSet
   135  
   136  var _ fileProtoSet = &fdsProtoSet{}
   137  
   138  func (fps *fdsProtoSet) findFile(name string) *descriptorpb.FileDescriptorProto {
   139  	files := (*descriptorpb.FileDescriptorSet)(fps).File
   140  	for _, fd := range files {
   141  		if fd.GetName() == name {
   142  			return fd
   143  		}
   144  	}
   145  	return nil
   146  }
   147  
   148  type regProtoSet protoregistry.Files
   149  
   150  var _ fileProtoSet = &regProtoSet{}
   151  
   152  func (fps *regProtoSet) findFile(name string) *descriptorpb.FileDescriptorProto {
   153  	f, err := (*protoregistry.Files)(fps).FindFileByPath(name)
   154  	if err != nil {
   155  		return nil
   156  	}
   157  	return toProto(f)
   158  }
   159  
   160  func TestLinkerValidation(t *testing.T) {
   161  	testCases := []struct {
   162  		input  map[string]string
   163  		errMsg string
   164  	}{
   165  		{
   166  			map[string]string{
   167  				"foo.proto":  `syntax = "proto3"; package namespace.a; import "foo2.proto"; import "foo3.proto"; import "foo4.proto"; message Foo{ b.Bar a = 1; b.Baz b = 2; b.Buzz c = 3; }`,
   168  				"foo2.proto": `syntax = "proto3"; package namespace.b; message Bar{}`,
   169  				"foo3.proto": `syntax = "proto3"; package namespace.b; message Baz{}`,
   170  				"foo4.proto": `syntax = "proto3"; package namespace.b; message Buzz{}`,
   171  			},
   172  			"", // should succeed
   173  		},
   174  		{
   175  			map[string]string{
   176  				"foo.proto": "import \"foo2.proto\"; message fubar{}",
   177  			},
   178  			`foo.proto:1:8: file not found: foo2.proto`,
   179  		},
   180  		{
   181  			map[string]string{
   182  				"foo.proto":  "import \"foo2.proto\"; message fubar{}",
   183  				"foo2.proto": "import \"foo.proto\"; message baz{}",
   184  			},
   185  			// since files are compiled concurrently, there are two possible outcomes
   186  			`foo.proto:1:8: cycle found in imports: "foo.proto" -> "foo2.proto" -> "foo.proto"` +
   187  				` || foo2.proto:1:8: cycle found in imports: "foo2.proto" -> "foo.proto" -> "foo2.proto"`,
   188  		},
   189  		{
   190  			map[string]string{
   191  				"foo.proto": "enum foo { bar = 1; baz = 2; } enum fu { bar = 1; baz = 2; }",
   192  			},
   193  			`foo.proto:1:42: symbol "bar" already defined at foo.proto:1:12; protobuf uses C++ scoping rules for enum values, so they exist in the scope enclosing the enum`,
   194  		},
   195  		{
   196  			map[string]string{
   197  				"foo.proto": "message foo {} enum foo { V = 0; }",
   198  			},
   199  			`foo.proto:1:21: symbol "foo" already defined at foo.proto:1:9`,
   200  		},
   201  		{
   202  			map[string]string{
   203  				"foo.proto": "message foo { optional string a = 1; optional string a = 2; }",
   204  			},
   205  			`foo.proto:1:54: symbol "foo.a" already defined at foo.proto:1:31`,
   206  		},
   207  		{
   208  			map[string]string{
   209  				"foo.proto":  "message foo {}",
   210  				"foo2.proto": "enum foo { V = 0; }",
   211  			},
   212  			// since files are compiled concurrently, there are two possible outcomes
   213  			"foo.proto:1:9: symbol \"foo\" already defined at foo2.proto:1:6" +
   214  				" || foo2.proto:1:6: symbol \"foo\" already defined at foo.proto:1:9",
   215  		},
   216  		{
   217  			map[string]string{
   218  				"foo.proto": "message foo { optional blah a = 1; }",
   219  			},
   220  			"foo.proto:1:24: field foo.a: unknown type blah",
   221  		},
   222  		{
   223  			map[string]string{
   224  				"foo.proto": "message foo { optional bar.baz a = 1; } service bar { rpc baz (foo) returns (foo); }",
   225  			},
   226  			"foo.proto:1:24: field foo.a: invalid type: bar.baz is a method, not a message or enum",
   227  		},
   228  		{
   229  			map[string]string{
   230  				"foo.proto": "message foo { extensions 1 to 2; } extend foo { optional string a = 1; } extend foo { optional int32 b = 1; }",
   231  			},
   232  			"foo.proto:1:106: extension with tag 1 for message foo already defined at foo.proto:1:69",
   233  		},
   234  		{
   235  			map[string]string{
   236  				"foo.proto": "package fu.baz; extend foobar { optional string a = 1; }",
   237  			},
   238  			"foo.proto:1:24: unknown extendee type foobar",
   239  		},
   240  		{
   241  			map[string]string{
   242  				"foo.proto": "package fu.baz; service foobar{} extend foobar { optional string a = 1; }",
   243  			},
   244  			"foo.proto:1:41: extendee is invalid: fu.baz.foobar is a service, not a message",
   245  		},
   246  		{
   247  			map[string]string{
   248  				"foo.proto": "message foo{} message bar{} service foobar{ rpc foo(foo) returns (bar); }",
   249  			},
   250  			"foo.proto:1:53: method foobar.foo: invalid request type: foobar.foo is a method, not a message",
   251  		},
   252  		{
   253  			map[string]string{
   254  				"foo.proto": "message foo{} message bar{} service foobar{ rpc foo(bar) returns (foo); }",
   255  			},
   256  			"foo.proto:1:67: method foobar.foo: invalid response type: foobar.foo is a method, not a message",
   257  		},
   258  		{
   259  			map[string]string{
   260  				"foo.proto": "package fu.baz; message foobar{ extensions 1; } extend foobar { optional string a = 2; }",
   261  			},
   262  			"foo.proto:1:85: field fu.baz.a: tag 2 is not in valid range for extended type fu.baz.foobar",
   263  		},
   264  		{
   265  			map[string]string{
   266  				"foo.proto":  "package fu.baz; import public \"foo2.proto\"; message foobar{ optional baz a = 1; }",
   267  				"foo2.proto": "package fu.baz; import \"foo3.proto\"; message fizzle{ }",
   268  				"foo3.proto": "package fu.baz; message baz{ }",
   269  			},
   270  			"foo.proto:1:70: field fu.baz.foobar.a: unknown type baz; resolved to fu.baz which is not defined; consider using a leading dot",
   271  		},
   272  		{
   273  			map[string]string{
   274  				"foo.proto": `
   275  					syntax = "proto2";
   276  					package foo;
   277  					import "google/protobuf/descriptor.proto";
   278  					extend google.protobuf.FileOptions           { optional string fil_foo = 12000; }
   279  					extend google.protobuf.MessageOptions        { optional string msg_foo = 12000; }
   280  					extend google.protobuf.FieldOptions          { optional string fld_foo = 12000 [(fld_foo) = "extension"]; }
   281  					extend google.protobuf.OneofOptions          { optional string oof_foo = 12000; }
   282  					extend google.protobuf.EnumOptions           { optional string enm_foo = 12000; }
   283  					extend google.protobuf.EnumValueOptions      { optional string env_foo = 12000; }
   284  					extend google.protobuf.ExtensionRangeOptions { optional string ext_foo = 12000; }
   285  					extend google.protobuf.ServiceOptions        { optional string svc_foo = 12000; }
   286  					extend google.protobuf.MethodOptions         { optional string mtd_foo = 12000; }
   287  					option (fil_foo) = "file";
   288  					message Foo {
   289  						option (msg_foo) = "message";
   290  						oneof foo {
   291  							option (oof_foo) = "oneof";
   292  							string bar = 1 [(fld_foo) = "field"];
   293  						}
   294  						extensions 100 to 200 [(ext_foo) = "extensionrange"];
   295  					}
   296  					enum Baz {
   297  						option (enm_foo) = "enum";
   298  						ZERO = 0 [(env_foo) = "enumvalue"];
   299  					}
   300  					service FooService {
   301  						option (svc_foo) = "service";
   302  						rpc Bar(Foo) returns (Foo) {
   303  							option (mtd_foo) = "method";
   304  						}
   305  					}
   306  					`,
   307  			},
   308  			"", // should succeed
   309  		},
   310  		{
   311  			map[string]string{
   312  				"foo.proto": "package fu.baz; message foobar{ repeated string a = 1 [default = \"abc\"]; }",
   313  			},
   314  			"foo.proto:1:56: field fu.baz.foobar.a: default value cannot be set because field is repeated",
   315  		},
   316  		{
   317  			map[string]string{
   318  				"foo.proto": "package fu.baz; message foobar{ optional foobar a = 1 [default = { a: {} }]; }",
   319  			},
   320  			"foo.proto:1:56: field fu.baz.foobar.a: default value cannot be set because field is a message",
   321  		},
   322  		{
   323  			map[string]string{
   324  				"foo.proto": "package fu.baz; message foobar{ optional string a = 1 [default = { a: \"abc\" }]; }",
   325  			},
   326  			"foo.proto:1:66: field fu.baz.foobar.a: default value cannot be a message",
   327  		},
   328  		{
   329  			map[string]string{
   330  				"foo.proto": "package fu.baz; message foobar{ optional string a = 1 [default = 1.234]; }",
   331  			},
   332  			"foo.proto:1:66: field fu.baz.foobar.a: option default: expecting string, got double",
   333  		},
   334  		{
   335  			map[string]string{
   336  				"foo.proto": "package fu.baz; enum abc { OK=0; NOK=1; } message foobar{ optional abc a = 1 [default = NACK]; }",
   337  			},
   338  			"foo.proto:1:89: field fu.baz.foobar.a: option default: enum fu.baz.abc has no value named NACK",
   339  		},
   340  		{
   341  			map[string]string{
   342  				"foo.proto": "option b = 123;",
   343  			},
   344  			"foo.proto:1:8: option b: field b of google.protobuf.FileOptions does not exist",
   345  		},
   346  		{
   347  			map[string]string{
   348  				"foo.proto": "option (foo.bar) = 123;",
   349  			},
   350  			"foo.proto:1:8: unknown extension foo.bar",
   351  		},
   352  		{
   353  			map[string]string{
   354  				"foo.proto": "option uninterpreted_option = { };",
   355  			},
   356  			"foo.proto:1:8: invalid option 'uninterpreted_option'",
   357  		},
   358  		{
   359  			map[string]string{
   360  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   361  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   362  					"extend foo { optional int32 b = 10; }\n" +
   363  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   364  					"option (f).b = 123;",
   365  			},
   366  			"foo.proto:5:12: option (f).b: field b of foo does not exist",
   367  		},
   368  		{
   369  			map[string]string{
   370  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   371  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   372  					"extend foo { optional int32 b = 10; }\n" +
   373  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   374  					"option (f).a = 123;",
   375  			},
   376  			"foo.proto:5:16: option (f).a: expecting string, got integer",
   377  		},
   378  		{
   379  			map[string]string{
   380  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   381  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   382  					"extend foo { optional int32 b = 10; }\n" +
   383  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   384  					"option (b) = 123;",
   385  			},
   386  			"foo.proto:5:8: option (b): extension b should extend google.protobuf.FileOptions but instead extends foo",
   387  		},
   388  		{
   389  			map[string]string{
   390  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   391  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   392  					"extend foo { optional int32 b = 10; }\n" +
   393  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   394  					"option (foo) = 123;",
   395  			},
   396  			"foo.proto:5:8: invalid extension: foo is a message, not an extension",
   397  		},
   398  		{
   399  			map[string]string{
   400  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   401  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   402  					"extend foo { optional int32 b = 10; }\n" +
   403  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   404  					"option (foo.a) = 123;",
   405  			},
   406  			"foo.proto:5:8: invalid extension: foo.a is a field but not an extension",
   407  		},
   408  		{
   409  			map[string]string{
   410  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   411  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   412  					"extend foo { optional int32 b = 10; }\n" +
   413  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   414  					"option (f) = { a: [ 123 ] };",
   415  			},
   416  			"foo.proto:5:19: option (f): value is an array but field is not repeated",
   417  		},
   418  		{
   419  			map[string]string{
   420  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   421  					"message foo { repeated string a = 1; extensions 10 to 20; }\n" +
   422  					"extend foo { optional int32 b = 10; }\n" +
   423  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   424  					"option (f) = { a: [ \"a\", \"b\", 123 ] };",
   425  			},
   426  			"foo.proto:5:31: option (f): expecting string, got integer",
   427  		},
   428  		{
   429  			map[string]string{
   430  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   431  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   432  					"extend foo { optional int32 b = 10; }\n" +
   433  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   434  					"option (f) = { a: \"a\" };\n" +
   435  					"option (f) = { a: \"b\" };",
   436  			},
   437  			"foo.proto:6:8: option (f): non-repeated option field (f) already set",
   438  		},
   439  		{
   440  			map[string]string{
   441  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   442  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   443  					"extend foo { optional int32 b = 10; }\n" +
   444  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   445  					"option (f) = { a: \"a\" };\n" +
   446  					"option (f).a = \"b\";",
   447  			},
   448  			"foo.proto:6:12: option (f).a: non-repeated option field a already set",
   449  		},
   450  		{
   451  			map[string]string{
   452  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   453  					"message foo { optional string a = 1; extensions 10 to 20; }\n" +
   454  					"extend foo { optional int32 b = 10; }\n" +
   455  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   456  					"option (f) = { a: \"a\" };\n" +
   457  					"option (f).(b) = \"b\";",
   458  			},
   459  			"foo.proto:6:18: option (f).(b): expecting int32, got string",
   460  		},
   461  		{
   462  			map[string]string{
   463  				"foo.proto": "import \"google/protobuf/descriptor.proto\";\n" +
   464  					"message foo { required string a = 1; required string b = 2; }\n" +
   465  					"extend google.protobuf.FileOptions { optional foo f = 20000; }\n" +
   466  					"option (f) = { a: \"a\" };\n",
   467  			},
   468  			"foo.proto:1:1: error in file options: some required fields missing: (f).b",
   469  		},
   470  		{
   471  			map[string]string{
   472  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional int32 bar = 1; }",
   473  			},
   474  			"foo.proto:1:99: messages with message-set wire format cannot contain scalar extensions, only messages",
   475  		},
   476  		{
   477  			map[string]string{
   478  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional Foo bar = 1; }",
   479  			},
   480  			"", // should succeed
   481  		},
   482  		{
   483  			map[string]string{
   484  				"foo.proto": "message Foo { extensions 1 to max; } extend Foo { optional int32 bar = 536870912; }",
   485  			},
   486  			"foo.proto:1:72: field bar: tag 536870912 is not in valid range for extended type Foo",
   487  		},
   488  		{
   489  			map[string]string{
   490  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to max; } extend Foo { optional Foo bar = 536870912; }",
   491  			},
   492  			"", // should succeed
   493  		},
   494  		{
   495  			map[string]string{
   496  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional int32 bar = 1; }",
   497  			},
   498  			"foo.proto:1:99: messages with message-set wire format cannot contain scalar extensions, only messages",
   499  		},
   500  		{
   501  			map[string]string{
   502  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional Foo bar = 1; }",
   503  			},
   504  			"", // should succeed
   505  		},
   506  		{
   507  			map[string]string{
   508  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { repeated Foo bar = 1; }",
   509  			},
   510  			"foo.proto:1:90: messages with message-set wire format cannot contain repeated extensions, only optional",
   511  		},
   512  		{
   513  			map[string]string{
   514  				"foo.proto": "message Foo { extensions 1 to max; } extend Foo { optional int32 bar = 536870912; }",
   515  			},
   516  			"foo.proto:1:72: field bar: tag 536870912 is not in valid range for extended type Foo",
   517  		},
   518  		{
   519  			map[string]string{
   520  				"foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to max; } extend Foo { optional Foo bar = 536870912; }",
   521  			},
   522  			"", // should succeed
   523  		},
   524  		{
   525  			map[string]string{
   526  				"foo.proto": `syntax = "proto3"; package com.google; import "google/protobuf/wrappers.proto"; message Foo { google.protobuf.StringValue str = 1; }`,
   527  			},
   528  			"foo.proto:1:95: field com.google.Foo.str: unknown type google.protobuf.StringValue; resolved to com.google.protobuf.StringValue which is not defined; consider using a leading dot",
   529  		},
   530  		{
   531  			map[string]string{
   532  				"foo.proto": "syntax = \"proto2\";\n" +
   533  					"import \"google/protobuf/descriptor.proto\";\n" +
   534  					"message Foo {\n" +
   535  					"  optional group Bar = 1 { optional string name = 1; }\n" +
   536  					"}\n" +
   537  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   538  					"message Baz { option (foo).bar.name = \"abc\"; }\n",
   539  			},
   540  			"", // should succeed
   541  		},
   542  		{
   543  			map[string]string{
   544  				"foo.proto": "syntax = \"proto2\";\n" +
   545  					"import \"google/protobuf/descriptor.proto\";\n" +
   546  					"message Foo {\n" +
   547  					"  optional group Bar = 1 { optional string name = 1; }\n" +
   548  					"}\n" +
   549  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   550  					"message Baz { option (foo).Bar.name = \"abc\"; }\n",
   551  			},
   552  			"foo.proto:7:28: message Baz: option (foo).Bar.name: field Bar of Foo does not exist",
   553  		},
   554  		{
   555  			map[string]string{
   556  				"foo.proto": "syntax = \"proto2\";\n" +
   557  					"import \"google/protobuf/descriptor.proto\";\n" +
   558  					"extend google.protobuf.MessageOptions {\n" +
   559  					"  optional group Foo = 10001 { optional string name = 1; }\n" +
   560  					"}\n" +
   561  					"message Bar { option (foo).name = \"abc\"; }\n",
   562  			},
   563  			"", // should succeed
   564  		},
   565  		{
   566  			map[string]string{
   567  				"foo.proto": "syntax = \"proto2\";\n" +
   568  					"import \"google/protobuf/descriptor.proto\";\n" +
   569  					"extend google.protobuf.MessageOptions {\n" +
   570  					"  optional group Foo = 10001 { optional string name = 1; }\n" +
   571  					"}\n" +
   572  					"message Bar { option (Foo).name = \"abc\"; }\n",
   573  			},
   574  			"foo.proto:6:22: message Bar: invalid extension: Foo is a message, not an extension",
   575  		},
   576  		{
   577  			map[string]string{
   578  				"foo.proto": "syntax = \"proto2\";\n" +
   579  					"import \"google/protobuf/descriptor.proto\";\n" +
   580  					"message Foo {\n" +
   581  					"  optional group Bar = 1 { optional string name = 1; }\n" +
   582  					"}\n" +
   583  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   584  					"message Baz { option (foo) = { Bar< name: \"abc\" > }; }\n",
   585  			},
   586  			"", // should succeed
   587  		},
   588  		{
   589  			map[string]string{
   590  				"foo.proto": "syntax = \"proto2\";\n" +
   591  					"import \"google/protobuf/descriptor.proto\";\n" +
   592  					"message Foo {\n" +
   593  					"  optional group Bar = 1 { optional string name = 1; }\n" +
   594  					"}\n" +
   595  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   596  					"message Baz { option (foo) = { bar< name: \"abc\" > }; }\n",
   597  			},
   598  			"foo.proto:7:30: message Baz: option (foo): field bar not found (did you mean the group named Bar?)",
   599  		},
   600  		{
   601  			map[string]string{
   602  				"foo.proto": "syntax = \"proto2\";\n" +
   603  					"import \"google/protobuf/descriptor.proto\";\n" +
   604  					"message Foo { extensions 1 to 10; }\n" +
   605  					"extend Foo { optional group Bar = 10 { optional string name = 1; } }\n" +
   606  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   607  					"message Baz { option (foo) = { [bar]< name: \"abc\" > }; }\n",
   608  			},
   609  			"", // should succeed
   610  		},
   611  		{
   612  			map[string]string{
   613  				"foo.proto": "syntax = \"proto2\";\n" +
   614  					"import \"google/protobuf/descriptor.proto\";\n" +
   615  					"message Foo { extensions 1 to 10; }\n" +
   616  					"extend Foo { optional group Bar = 10 { optional string name = 1; } }\n" +
   617  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   618  					"message Baz { option (foo) = { [Bar]< name: \"abc\" > }; }\n",
   619  			},
   620  			"foo.proto:6:30: message Baz: option (foo): field Bar not found",
   621  		},
   622  		{
   623  			map[string]string{
   624  				"foo.proto": "syntax = \"proto3\";\n" +
   625  					"import \"google/protobuf/descriptor.proto\";\n" +
   626  					"message Foo { oneof bar { string baz = 1; string buzz = 2; } }\n" +
   627  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   628  					"message Baz { option (foo) = { baz: \"abc\" buzz: \"xyz\" }; }\n",
   629  			},
   630  			`foo.proto:5:43: message Baz: option (foo): oneof "bar" already has field "baz" set`,
   631  		},
   632  		{
   633  			map[string]string{
   634  				"foo.proto": "syntax = \"proto3\";\n" +
   635  					"import \"google/protobuf/descriptor.proto\";\n" +
   636  					"message Foo { oneof bar { string baz = 1; string buzz = 2; } }\n" +
   637  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   638  					"message Baz {\n" +
   639  					"  option (foo).baz = \"abc\";\n" +
   640  					"  option (foo).buzz = \"xyz\";\n" +
   641  					"}",
   642  			},
   643  			`foo.proto:7:16: message Baz: option (foo).buzz: oneof "bar" already has field "baz" set`,
   644  		},
   645  		{
   646  			map[string]string{
   647  				"foo.proto": "syntax = \"proto3\";\n" +
   648  					"import \"google/protobuf/descriptor.proto\";\n" +
   649  					"message Foo { oneof bar { google.protobuf.DescriptorProto baz = 1; google.protobuf.DescriptorProto buzz = 2; } }\n" +
   650  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   651  					"message Baz {\n" +
   652  					"  option (foo).baz.name = \"abc\";\n" +
   653  					"  option (foo).buzz.name = \"xyz\";\n" +
   654  					"}",
   655  			},
   656  			`foo.proto:7:16: message Baz: option (foo).buzz.name: oneof "bar" already has field "baz" set`,
   657  		},
   658  		{
   659  			map[string]string{
   660  				"foo.proto": "syntax = \"proto3\";\n" +
   661  					"import \"google/protobuf/descriptor.proto\";\n" +
   662  					"message Foo { oneof bar { google.protobuf.DescriptorProto baz = 1; google.protobuf.DescriptorProto buzz = 2; } }\n" +
   663  					"extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" +
   664  					"message Baz {\n" +
   665  					"  option (foo).baz.options.(foo).baz.name = \"abc\";\n" +
   666  					"  option (foo).baz.options.(foo).buzz.name = \"xyz\";\n" +
   667  					"}",
   668  			},
   669  			`foo.proto:7:34: message Baz: option (foo).baz.options.(foo).buzz.name: oneof "bar" already has field "baz" set`,
   670  		},
   671  	}
   672  
   673  	for i, tc := range testCases {
   674  		t.Log("test case", i+1)
   675  		acc := func(filename string) (io.ReadCloser, error) {
   676  			f, ok := tc.input[filename]
   677  			if !ok {
   678  				return nil, fmt.Errorf("file not found: %s", filename)
   679  			}
   680  			return ioutil.NopCloser(strings.NewReader(f)), nil
   681  		}
   682  		names := make([]string, 0, len(tc.input))
   683  		for k := range tc.input {
   684  			names = append(names, k)
   685  		}
   686  
   687  		compiler := protocompile.Compiler{
   688  			Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
   689  				Accessor: acc,
   690  			}),
   691  		}
   692  		_, err := compiler.Compile(context.Background(), names...)
   693  		if tc.errMsg == "" {
   694  			if err != nil {
   695  				t.Errorf("case %d: expecting no error; instead got error %q", i, err)
   696  			}
   697  		} else if err == nil {
   698  			t.Errorf("case %d: expecting validation error %q; instead got no error", i, tc.errMsg)
   699  		} else {
   700  			msgs := strings.Split(tc.errMsg, " || ")
   701  			found := false
   702  			for _, errMsg := range msgs {
   703  				if err.Error() == errMsg {
   704  					found = true
   705  					break
   706  				}
   707  			}
   708  			if !found {
   709  				t.Errorf("case %d: expecting validation error %q; instead got: %q", i, tc.errMsg, err)
   710  			}
   711  		}
   712  	}
   713  }
   714  
   715  func TestProto3Enums(t *testing.T) {
   716  	file1 := `syntax = "<SYNTAX>"; enum bar { A = 0; B = 1; }`
   717  	file2 := `syntax = "<SYNTAX>"; import "f1.proto"; message foo { <LABEL> bar bar = 1; }`
   718  	getFileContents := func(file, syntax string) string {
   719  		contents := strings.Replace(file, "<SYNTAX>", syntax, 1)
   720  		label := ""
   721  		if syntax == "proto2" {
   722  			label = "optional"
   723  		}
   724  		return strings.Replace(contents, "<LABEL>", label, 1)
   725  	}
   726  
   727  	syntaxOptions := []string{"proto2", "proto3"}
   728  	for _, o1 := range syntaxOptions {
   729  		fc1 := getFileContents(file1, o1)
   730  
   731  		for _, o2 := range syntaxOptions {
   732  			fc2 := getFileContents(file2, o2)
   733  
   734  			// now parse the protos
   735  			acc := func(filename string) (io.ReadCloser, error) {
   736  				var data string
   737  				switch filename {
   738  				case "f1.proto":
   739  					data = fc1
   740  				case "f2.proto":
   741  					data = fc2
   742  				default:
   743  					return nil, fmt.Errorf("file not found: %s", filename)
   744  				}
   745  				return ioutil.NopCloser(strings.NewReader(data)), nil
   746  			}
   747  			compiler := protocompile.Compiler{
   748  				Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
   749  					Accessor: acc,
   750  				}),
   751  			}
   752  			_, err := compiler.Compile(context.Background(), "f1.proto", "f2.proto")
   753  
   754  			if o1 != o2 && o2 == "proto3" {
   755  				expected := "f2.proto:1:54: field foo.bar: cannot use proto2 enum bar in a proto3 message"
   756  				if err == nil {
   757  					t.Errorf("expecting validation error; instead got no error")
   758  				} else if err.Error() != expected {
   759  					t.Errorf("expecting validation error %q; instead got: %q", expected, err)
   760  				}
   761  			} else {
   762  				// other cases succeed (okay to for proto2 to use enum from proto3 file and
   763  				// obviously okay for proto2 importing proto2 and proto3 importing proto3)
   764  				assert.Nil(t, err)
   765  			}
   766  		}
   767  	}
   768  }
   769  
   770  // adapted from implementation of proto.Equal, but records an error for each discrepancy
   771  // found (does NOT exit early when a discrepancy is found)
   772  func compareFiles(t *testing.T, path string, exp, act *descriptorpb.FileDescriptorProto) {
   773  	if (exp == nil) != (act == nil) {
   774  		if exp == nil {
   775  			t.Errorf("%s: expected is nil; actual is not", path)
   776  		} else {
   777  			t.Errorf("%s: expected is not nil, but actual is", path)
   778  		}
   779  		return
   780  	}
   781  	mexp := exp.ProtoReflect()
   782  	mact := act.ProtoReflect()
   783  	if mexp.IsValid() != mact.IsValid() {
   784  		if mexp.IsValid() {
   785  			t.Errorf("%s: expected is valid; actual is not", path)
   786  		} else {
   787  			t.Errorf("%s: expected is not valid, but actual is", path)
   788  		}
   789  		return
   790  	}
   791  	compareMessages(t, path, mexp, mact)
   792  }
   793  
   794  func compareMessages(t *testing.T, path string, exp, act protoreflect.Message) {
   795  	if exp.Descriptor() != act.Descriptor() {
   796  		t.Errorf("%s: descriptors do not match: exp %#v, actual %#v", path, exp.Descriptor(), act.Descriptor())
   797  		return
   798  	}
   799  	exp.Range(func(fd protoreflect.FieldDescriptor, expVal protoreflect.Value) bool {
   800  		name := fieldDisplayName(fd)
   801  		actVal := act.Get(fd)
   802  		if !act.Has(fd) {
   803  			t.Errorf("%s: expected has field %s but actual does not", path, name)
   804  		} else {
   805  			compareFields(t, path+"."+name, fd, expVal, actVal)
   806  		}
   807  		return true
   808  	})
   809  	act.Range(func(fd protoreflect.FieldDescriptor, actVal protoreflect.Value) bool {
   810  		name := fieldDisplayName(fd)
   811  		if !exp.Has(fd) {
   812  			t.Errorf("%s: actual has field %s but expected does not", path, name)
   813  		}
   814  		return true
   815  	})
   816  
   817  	compareUnknown(t, path, exp.GetUnknown(), act.GetUnknown())
   818  }
   819  
   820  func fieldDisplayName(fd protoreflect.FieldDescriptor) string {
   821  	if fd.IsExtension() {
   822  		return "(" + string(fd.FullName()) + ")"
   823  	}
   824  	return string(fd.Name())
   825  }
   826  
   827  func compareFields(t *testing.T, path string, fd protoreflect.FieldDescriptor, exp, act protoreflect.Value) {
   828  	switch {
   829  	case fd.IsList():
   830  		compareLists(t, path, fd, exp.List(), act.List())
   831  	case fd.IsMap():
   832  		compareMaps(t, path, fd, exp.Map(), act.Map())
   833  	default:
   834  		compareValues(t, path, fd, exp, act)
   835  	}
   836  }
   837  
   838  func compareMaps(t *testing.T, path string, fd protoreflect.FieldDescriptor, exp, act protoreflect.Map) {
   839  	exp.Range(func(k protoreflect.MapKey, expVal protoreflect.Value) bool {
   840  		actVal := act.Get(k)
   841  		if !act.Has(k) {
   842  			t.Errorf("%s: expected map has key %s but actual does not", path, k.String())
   843  		} else {
   844  			compareValues(t, path+"["+k.String()+"]", fd.MapValue(), expVal, actVal)
   845  		}
   846  		return true
   847  	})
   848  	act.Range(func(k protoreflect.MapKey, actVal protoreflect.Value) bool {
   849  		if !exp.Has(k) {
   850  			t.Errorf("%s: actual map has key %s but expected does not", path, k.String())
   851  		}
   852  		return true
   853  	})
   854  }
   855  
   856  func compareLists(t *testing.T, path string, fd protoreflect.FieldDescriptor, exp, act protoreflect.List) {
   857  	if exp.Len() != act.Len() {
   858  		t.Errorf("%s: expected is list with %d items but actual has %d", path, exp.Len(), act.Len())
   859  	}
   860  	lim := exp.Len()
   861  	if act.Len() < lim {
   862  		lim = act.Len()
   863  	}
   864  	for i := 0; i < lim; i++ {
   865  		compareValues(t, path+"["+strconv.Itoa(i)+"]", fd, exp.Get(i), act.Get(i))
   866  	}
   867  }
   868  
   869  func compareValues(t *testing.T, path string, fd protoreflect.FieldDescriptor, exp, act protoreflect.Value) {
   870  	if fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind {
   871  		compareMessages(t, path, exp.Message(), act.Message())
   872  		return
   873  	}
   874  
   875  	var eq bool
   876  	switch fd.Kind() {
   877  	case protoreflect.BoolKind:
   878  		eq = exp.Bool() == act.Bool()
   879  	case protoreflect.EnumKind:
   880  		eq = exp.Enum() == act.Enum()
   881  	case protoreflect.Int32Kind, protoreflect.Sint32Kind,
   882  		protoreflect.Int64Kind, protoreflect.Sint64Kind,
   883  		protoreflect.Sfixed32Kind, protoreflect.Sfixed64Kind:
   884  		eq = exp.Int() == act.Int()
   885  	case protoreflect.Uint32Kind, protoreflect.Uint64Kind,
   886  		protoreflect.Fixed32Kind, protoreflect.Fixed64Kind:
   887  		eq = exp.Uint() == act.Uint()
   888  	case protoreflect.FloatKind, protoreflect.DoubleKind:
   889  		fx := exp.Float()
   890  		fy := act.Float()
   891  		if math.IsNaN(fx) || math.IsNaN(fy) {
   892  			eq = math.IsNaN(fx) && math.IsNaN(fy)
   893  		} else {
   894  			eq = fx == fy
   895  		}
   896  	case protoreflect.StringKind:
   897  		eq = exp.String() == act.String()
   898  	case protoreflect.BytesKind:
   899  		eq = bytes.Equal(exp.Bytes(), act.Bytes())
   900  	default:
   901  		eq = exp.Interface() == act.Interface()
   902  	}
   903  	if !eq {
   904  		t.Errorf("%s: expected is %v but actual is %v", path, exp, act)
   905  	}
   906  }
   907  
   908  func compareUnknown(t *testing.T, path string, exp, act protoreflect.RawFields) {
   909  	if bytes.Equal(exp, act) {
   910  		return
   911  	}
   912  
   913  	mexp := make(map[protoreflect.FieldNumber]protoreflect.RawFields)
   914  	mact := make(map[protoreflect.FieldNumber]protoreflect.RawFields)
   915  	for len(exp) > 0 {
   916  		fnum, _, n := protowire.ConsumeField(exp)
   917  		mexp[fnum] = append(mexp[fnum], exp[:n]...)
   918  		exp = exp[n:]
   919  	}
   920  	for len(act) > 0 {
   921  		fnum, _, n := protowire.ConsumeField(act)
   922  		bact := act[:n]
   923  		mact[fnum] = append(mact[fnum], bact...)
   924  		if bexp, ok := mexp[fnum]; !ok {
   925  			t.Errorf("%s: expected has data for unknown field with tag %d but actual does not", path, fnum)
   926  		} else if !bytes.Equal(bexp, bact) {
   927  			t.Errorf("%s: expected has %v for unknown field with tag %d but actual has %v", path, bexp, fnum, bact)
   928  		}
   929  		act = act[n:]
   930  	}
   931  	for fnum := range mexp {
   932  		_, ok := mact[fnum]
   933  		if !ok {
   934  			t.Errorf("%s: actual has data for unknown field with tag %d but expected does not", path, fnum)
   935  		}
   936  	}
   937  }