github.com/jhump/protoreflect@v1.16.0/desc/protoparse/linker_test.go (about) 1 package protoparse 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "reflect" 9 "strings" 10 "testing" 11 12 protov1 "github.com/golang/protobuf/proto" 13 "google.golang.org/protobuf/encoding/protojson" 14 "google.golang.org/protobuf/proto" 15 "google.golang.org/protobuf/types/descriptorpb" 16 17 "github.com/jhump/protoreflect/desc" 18 _ "github.com/jhump/protoreflect/internal/testprotos" 19 "github.com/jhump/protoreflect/internal/testutil" 20 ) 21 22 func TestSimpleLink(t *testing.T) { 23 fds, err := Parser{ImportPaths: []string{"../../internal/testprotos"}}.ParseFiles("desc_test_complex.proto") 24 testutil.Ok(t, err) 25 26 b, err := os.ReadFile("../../internal/testprotos/desc_test_complex.protoset") 27 testutil.Ok(t, err) 28 29 var fdSet descriptorpb.FileDescriptorSet 30 err = proto.Unmarshal(b, &fdSet) 31 testutil.Ok(t, err) 32 33 testutil.Require(t, proto.Equal(fdSet.File[0], protov1.MessageV2(fds[0].AsProto())), "linked descriptor did not match output from protoc:\nwanted: %s\ngot: %s", toString(fdSet.File[0]), toString(protov1.MessageV2(fds[0].AsProto()))) 34 } 35 36 func TestMultiFileLink(t *testing.T) { 37 for _, name := range []string{"desc_test2.proto", "desc_test_defaults.proto", "desc_test_field_types.proto", "desc_test_options.proto", "desc_test_proto3.proto", "desc_test_wellknowntypes.proto"} { 38 fds, err := Parser{ImportPaths: []string{"../../internal/testprotos"}}.ParseFiles(name) 39 testutil.Ok(t, err) 40 41 exp, err := desc.LoadFileDescriptor(name) 42 testutil.Ok(t, err) 43 44 checkFiles(t, fds[0], exp, map[string]struct{}{}) 45 } 46 } 47 48 func TestProto3Optional(t *testing.T) { 49 data, err := os.ReadFile("../../internal/testprotos/proto3_optional/desc_test_proto3_optional.protoset") 50 testutil.Ok(t, err) 51 var fdset descriptorpb.FileDescriptorSet 52 err = proto.Unmarshal(data, &fdset) 53 testutil.Ok(t, err) 54 55 var descriptorProto *descriptorpb.FileDescriptorProto 56 for _, fd := range fdset.File { 57 // not comparing source code info 58 fd.SourceCodeInfo = nil 59 60 // we want to use the same descriptor.proto as in protoset, so we don't have to 61 // worry about this test breaking when updating to newer versions of the Go 62 // descriptor package (which may have a different version of descriptor.proto 63 // compiled in). 64 if fd.GetName() == "google/protobuf/descriptor.proto" { 65 descriptorProto = fd 66 } 67 } 68 testutil.Require(t, descriptorProto != nil, "failed to find google/protobuf/descriptor.proto in protoset") 69 70 exp, err := desc.CreateFileDescriptorFromSet(&fdset) 71 testutil.Ok(t, err) 72 73 fds, err := Parser{ 74 ImportPaths: []string{"../../internal/testprotos"}, 75 LookupImportProto: func(name string) (*descriptorpb.FileDescriptorProto, error) { 76 if name == "google/protobuf/descriptor.proto" { 77 return descriptorProto, nil 78 } 79 return nil, errors.New("not found") 80 }, 81 }.ParseFiles("proto3_optional/desc_test_proto3_optional.proto") 82 testutil.Ok(t, err) 83 84 checkFiles(t, fds[0], exp, map[string]struct{}{}) 85 } 86 87 func checkFiles(t *testing.T, act, exp *desc.FileDescriptor, checked map[string]struct{}) { 88 if _, ok := checked[act.GetName()]; ok { 89 // already checked 90 return 91 } 92 checked[act.GetName()] = struct{}{} 93 94 // remove any source code info from expected value, since actual won't have any 95 exp.AsFileDescriptorProto().SourceCodeInfo = nil 96 97 testutil.Require(t, proto.Equal(exp.AsFileDescriptorProto(), protov1.MessageV2(act.AsProto())), "linked descriptor did not match output from protoc:\nwanted: %s\ngot: %s", toString(protov1.MessageV2(exp.AsProto())), toString(protov1.MessageV2(act.AsProto()))) 98 99 for i, dep := range act.GetDependencies() { 100 checkFiles(t, dep, exp.GetDependencies()[i], checked) 101 } 102 } 103 104 func toString(m proto.Message) string { 105 msh := protojson.MarshalOptions{Indent: " "} 106 data, err := msh.Marshal(m) 107 if err != nil { 108 panic(err) 109 } 110 return string(data) 111 } 112 113 func TestLinkerValidation(t *testing.T) { 114 testCases := []struct { 115 input map[string]string 116 errMsg string 117 }{ 118 { 119 map[string]string{ 120 "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; }`, 121 "foo2.proto": `syntax = "proto3"; package namespace.b; message Bar{}`, 122 "foo3.proto": `syntax = "proto3"; package namespace.b; message Baz{}`, 123 "foo4.proto": `syntax = "proto3"; package namespace.b; message Buzz{}`, 124 }, 125 "", // should succeed 126 }, 127 { 128 map[string]string{ 129 "foo.proto": "import \"foo2.proto\"; message fubar{}", 130 }, 131 `foo.proto:1:8: file not found: foo2.proto`, 132 }, 133 { 134 map[string]string{ 135 "foo.proto": "import \"foo2.proto\"; message fubar{}", 136 "foo2.proto": "import \"foo.proto\"; message baz{}", 137 }, 138 `foo.proto:1:8: cycle found in imports: "foo.proto" -> "foo2.proto" -> "foo.proto" 139 || foo2.proto:1:8: cycle found in imports: "foo2.proto" -> "foo.proto" -> "foo2.proto"`, 140 }, 141 { 142 map[string]string{ 143 "foo.proto": "enum foo { bar = 1; baz = 2; } enum fu { bar = 1; baz = 2; }", 144 }, 145 `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`, 146 }, 147 { 148 map[string]string{ 149 "foo.proto": "message foo {} enum foo { V = 0; }", 150 }, 151 `foo.proto:1:21: symbol "foo" already defined at foo.proto:1:9`, 152 }, 153 { 154 map[string]string{ 155 "foo.proto": "message foo { optional string a = 1; optional string a = 2; }", 156 }, 157 `foo.proto:1:54: symbol "foo.a" already defined at foo.proto:1:31`, 158 }, 159 { 160 map[string]string{ 161 "foo.proto": "message foo {}", 162 "foo2.proto": "enum foo { V = 0; }", 163 }, 164 `foo.proto:1:9: symbol "foo" already defined at foo2.proto:1:6 165 || foo2.proto:1:6: symbol "foo" already defined at foo.proto:1:9`, 166 }, 167 { 168 map[string]string{ 169 "foo.proto": "message foo { optional blah a = 1; }", 170 }, 171 "foo.proto:1:24: field foo.a: unknown type blah", 172 }, 173 { 174 map[string]string{ 175 "foo.proto": "message foo { optional bar.baz a = 1; } service bar { rpc baz (foo) returns (foo); }", 176 }, 177 "foo.proto:1:24: field foo.a: invalid type: bar.baz is a method, not a message or enum", 178 }, 179 { 180 map[string]string{ 181 "foo.proto": "message foo { extensions 1 to 2; } extend foo { optional string a = 1; } extend foo { optional int32 b = 1; }", 182 }, 183 "foo.proto:1:106: extension with tag 1 for message foo already defined at foo.proto:1:69", 184 }, 185 { 186 map[string]string{ 187 "foo.proto": ` 188 syntax = "proto3"; 189 import "google/protobuf/descriptor.proto"; 190 package google.protobuf; 191 message DescriptorProto { } 192 `, 193 }, 194 `foo.proto:5:49: symbol "google.protobuf.DescriptorProto" already defined at google/protobuf/descriptor.proto`, 195 }, 196 { 197 map[string]string{ 198 "foo.proto": "package fu.baz; extend foobar { optional string a = 1; }", 199 }, 200 "foo.proto:1:24: unknown extendee type foobar", 201 }, 202 { 203 map[string]string{ 204 "foo.proto": "package fu.baz; service foobar{} extend foobar { optional string a = 1; }", 205 }, 206 "foo.proto:1:41: extendee is invalid: fu.baz.foobar is a service, not a message", 207 }, 208 { 209 map[string]string{ 210 "foo.proto": "message foo{} message bar{} service foobar{ rpc foo(foo) returns (bar); }", 211 }, 212 "foo.proto:1:53: method foobar.foo: invalid request type: foobar.foo is a method, not a message", 213 }, 214 { 215 map[string]string{ 216 "foo.proto": "message foo{} message bar{} service foobar{ rpc foo(bar) returns (foo); }", 217 }, 218 "foo.proto:1:67: method foobar.foo: invalid response type: foobar.foo is a method, not a message", 219 }, 220 { 221 map[string]string{ 222 "foo.proto": "package fu.baz; message foobar{ extensions 1; } extend foobar { optional string a = 2; }", 223 }, 224 "foo.proto:1:85: extension fu.baz.a: tag 2 is not in valid range for extended type fu.baz.foobar", 225 }, 226 { 227 map[string]string{ 228 "foo.proto": "package fu.baz; import public \"foo2.proto\"; message foobar{ optional baz a = 1; }", 229 "foo2.proto": "package fu.baz; import \"foo3.proto\"; message fizzle{ }", 230 "foo3.proto": "package fu.baz; message baz{ }", 231 }, 232 "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", 233 }, 234 { 235 map[string]string{ 236 "foo.proto": ` 237 syntax = "proto2"; 238 package foo; 239 import "google/protobuf/descriptor.proto"; 240 extend google.protobuf.FileOptions { optional string fil_foo = 12000; } 241 extend google.protobuf.MessageOptions { optional string msg_foo = 12000; } 242 extend google.protobuf.FieldOptions { optional string fld_foo = 12000 [(fld_foo) = "extension"]; } 243 extend google.protobuf.OneofOptions { optional string oof_foo = 12000; } 244 extend google.protobuf.EnumOptions { optional string enm_foo = 12000; } 245 extend google.protobuf.EnumValueOptions { optional string env_foo = 12000; } 246 extend google.protobuf.ExtensionRangeOptions { optional string ext_foo = 12000; } 247 extend google.protobuf.ServiceOptions { optional string svc_foo = 12000; } 248 extend google.protobuf.MethodOptions { optional string mtd_foo = 12000; } 249 option (fil_foo) = "file"; 250 message Foo { 251 option (msg_foo) = "message"; 252 oneof foo { 253 option (oof_foo) = "oneof"; 254 string bar = 1 [(fld_foo) = "field"]; 255 } 256 extensions 100 to 200 [(ext_foo) = "extensionrange"]; 257 } 258 enum Baz { 259 option (enm_foo) = "enum"; 260 ZERO = 0 [(env_foo) = "enumvalue"]; 261 } 262 service FooService { 263 option (svc_foo) = "service"; 264 rpc Bar(Foo) returns (Foo) { 265 option (mtd_foo) = "method"; 266 } 267 } 268 `, 269 }, 270 "", // should success 271 }, 272 { 273 map[string]string{ 274 "foo.proto": "package fu.baz; message foobar{ repeated string a = 1 [default = \"abc\"]; }", 275 }, 276 "foo.proto:1:56: field fu.baz.foobar.a: default value cannot be set because field is repeated", 277 }, 278 { 279 map[string]string{ 280 "foo.proto": "package fu.baz; message foobar{ optional foobar a = 1 [default = { a: {} }]; }", 281 }, 282 "foo.proto:1:56: field fu.baz.foobar.a: default value cannot be set because field is a message", 283 }, 284 { 285 map[string]string{ 286 "foo.proto": "package fu.baz; message foobar{ optional string a = 1 [default = { a: \"abc\" }]; }", 287 }, 288 "foo.proto:1:66: field fu.baz.foobar.a: option default: default value cannot be a message", 289 }, 290 { 291 map[string]string{ 292 "foo.proto": "package fu.baz; message foobar{ optional string a = 1 [default = 1.234]; }", 293 }, 294 "foo.proto:1:66: field fu.baz.foobar.a: option default: expecting string, got double", 295 }, 296 { 297 map[string]string{ 298 "foo.proto": "package fu.baz; enum abc { OK=0; NOK=1; } message foobar{ optional abc a = 1 [default = NACK]; }", 299 }, 300 "foo.proto:1:89: field fu.baz.foobar.a: option default: enum fu.baz.abc has no value named NACK", 301 }, 302 { 303 map[string]string{ 304 "foo.proto": "option b = 123;", 305 }, 306 "foo.proto:1:8: option b: field b of google.protobuf.FileOptions does not exist", 307 }, 308 { 309 map[string]string{ 310 "foo.proto": "option (foo.bar) = 123;", 311 }, 312 "foo.proto:1:8: unknown extension foo.bar", 313 }, 314 { 315 map[string]string{ 316 "foo.proto": "option uninterpreted_option = { };", 317 }, 318 "foo.proto:1:8: invalid option 'uninterpreted_option'", 319 }, 320 { 321 map[string]string{ 322 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 323 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 324 "extend foo { optional int32 b = 10; }\n" + 325 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 326 "option (f).b = 123;", 327 }, 328 "foo.proto:5:12: option (f).b: field b of foo does not exist", 329 }, 330 { 331 map[string]string{ 332 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 333 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 334 "extend foo { optional int32 b = 10; }\n" + 335 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 336 "option (f).a = 123;", 337 }, 338 "foo.proto:5:16: option (f).a: expecting string, got integer", 339 }, 340 { 341 map[string]string{ 342 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 343 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 344 "extend foo { optional int32 b = 10; }\n" + 345 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 346 "option (b) = 123;", 347 }, 348 "foo.proto:5:8: option (b): extension b should extend google.protobuf.FileOptions but instead extends foo", 349 }, 350 { 351 map[string]string{ 352 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 353 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 354 "extend foo { optional int32 b = 10; }\n" + 355 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 356 "option (foo) = 123;", 357 }, 358 "foo.proto:5:8: invalid extension: foo is a message, not an extension", 359 }, 360 { 361 map[string]string{ 362 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 363 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 364 "extend foo { optional int32 b = 10; }\n" + 365 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 366 "option (foo.a) = 123;", 367 }, 368 "foo.proto:5:8: invalid extension: foo.a is a field but not an extension", 369 }, 370 { 371 map[string]string{ 372 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 373 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 374 "extend foo { optional int32 b = 10; }\n" + 375 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 376 "option (f) = { a: [ 123 ] };", 377 }, 378 "foo.proto:5:19: option (f): value is an array but field is not repeated", 379 }, 380 { 381 map[string]string{ 382 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 383 "message foo { repeated string a = 1; extensions 10 to 20; }\n" + 384 "extend foo { optional int32 b = 10; }\n" + 385 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 386 "option (f) = { a: [ \"a\", \"b\", 123 ] };", 387 }, 388 "foo.proto:5:31: option (f): expecting string, got integer", 389 }, 390 { 391 map[string]string{ 392 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 393 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 394 "extend foo { optional int32 b = 10; }\n" + 395 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 396 "option (f) = { a: \"a\" };\n" + 397 "option (f) = { a: \"b\" };", 398 }, 399 "foo.proto:6:8: option (f): non-repeated option field (f) already set", 400 }, 401 { 402 map[string]string{ 403 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 404 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 405 "extend foo { optional int32 b = 10; }\n" + 406 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 407 "option (f) = { a: \"a\" };\n" + 408 "option (f).a = \"b\";", 409 }, 410 "foo.proto:6:12: option (f).a: non-repeated option field a already set", 411 }, 412 { 413 map[string]string{ 414 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 415 "message foo { optional string a = 1; extensions 10 to 20; }\n" + 416 "extend foo { optional int32 b = 10; }\n" + 417 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 418 "option (f) = { a: \"a\" };\n" + 419 "option (f).(b) = \"b\";", 420 }, 421 "foo.proto:6:18: option (f).(b): expecting int32, got string", 422 }, 423 { 424 map[string]string{ 425 "foo.proto": "import \"google/protobuf/descriptor.proto\";\n" + 426 "message foo { required string a = 1; required string b = 2; }\n" + 427 "extend google.protobuf.FileOptions { optional foo f = 20000; }\n" + 428 "option (f) = { a: \"a\" };\n", 429 }, 430 "foo.proto:1:1: error in file options: some required fields missing: (f).b", 431 }, 432 { 433 map[string]string{ 434 "foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional int32 bar = 1; }", 435 }, 436 "foo.proto:1:99: messages with message-set wire format cannot contain scalar extensions, only messages", 437 }, 438 { 439 map[string]string{ 440 "foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { optional Foo bar = 1; }", 441 }, 442 "", // should succeed 443 }, 444 { 445 map[string]string{ 446 "foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to 100; } extend Foo { repeated Foo bar = 1; }", 447 }, 448 "foo.proto:1:90: messages with message-set wire format cannot contain repeated extensions, only optional", 449 }, 450 { 451 map[string]string{ 452 "foo.proto": "message Foo { extensions 1 to max; } extend Foo { optional int32 bar = 536870912; }", 453 }, 454 "foo.proto:1:72: extension bar: tag 536870912 is not in valid range for extended type Foo", 455 }, 456 { 457 map[string]string{ 458 "foo.proto": "message Foo { option message_set_wire_format = true; extensions 1 to max; } extend Foo { optional Foo bar = 536870912; }", 459 }, 460 "", // should succeed 461 }, 462 { 463 map[string]string{ 464 "foo.proto": `syntax = "proto3"; package com.google; import "google/protobuf/wrappers.proto"; message Foo { google.protobuf.StringValue str = 1; }`, 465 }, 466 "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", 467 }, 468 { 469 map[string]string{ 470 "foo.proto": "syntax = \"proto2\";\n" + 471 "import \"google/protobuf/descriptor.proto\";\n" + 472 "message Foo {\n" + 473 " optional group Bar = 1 { optional string name = 1; }\n" + 474 "}\n" + 475 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 476 "message Baz { option (foo).bar.name = \"abc\"; }\n", 477 }, 478 "", // should succeed 479 }, 480 { 481 map[string]string{ 482 "foo.proto": "syntax = \"proto2\";\n" + 483 "import \"google/protobuf/descriptor.proto\";\n" + 484 "message Foo {\n" + 485 " optional group Bar = 1 { optional string name = 1; }\n" + 486 "}\n" + 487 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 488 "message Baz { option (foo).Bar.name = \"abc\"; }\n", 489 }, 490 "foo.proto:7:28: message Baz: option (foo).Bar.name: field Bar of Foo does not exist", 491 }, 492 { 493 map[string]string{ 494 "foo.proto": "syntax = \"proto2\";\n" + 495 "import \"google/protobuf/descriptor.proto\";\n" + 496 "extend google.protobuf.MessageOptions {\n" + 497 " optional group Foo = 10001 { optional string name = 1; }\n" + 498 "}\n" + 499 "message Bar { option (foo).name = \"abc\"; }\n", 500 }, 501 "", // should succeed 502 }, 503 { 504 map[string]string{ 505 "foo.proto": "syntax = \"proto2\";\n" + 506 "import \"google/protobuf/descriptor.proto\";\n" + 507 "extend google.protobuf.MessageOptions {\n" + 508 " optional group Foo = 10001 { optional string name = 1; }\n" + 509 "}\n" + 510 "message Bar { option (Foo).name = \"abc\"; }\n", 511 }, 512 "foo.proto:6:22: message Bar: invalid extension: Foo is a message, not an extension", 513 }, 514 { 515 map[string]string{ 516 "foo.proto": "syntax = \"proto2\";\n" + 517 "import \"google/protobuf/descriptor.proto\";\n" + 518 "message Foo {\n" + 519 " optional group Bar = 1 { optional string name = 1; }\n" + 520 "}\n" + 521 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 522 "message Baz { option (foo) = { Bar< name: \"abc\" > }; }\n", 523 }, 524 "", // should succeed 525 }, 526 { 527 map[string]string{ 528 "foo.proto": "syntax = \"proto2\";\n" + 529 "import \"google/protobuf/descriptor.proto\";\n" + 530 "message Foo {\n" + 531 " optional group Bar = 1 { optional string name = 1; }\n" + 532 "}\n" + 533 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 534 "message Baz { option (foo) = { bar< name: \"abc\" > }; }\n", 535 }, 536 "foo.proto:7:32: message Baz: option (foo): field bar not found (did you mean the group named Bar?)", 537 }, 538 { 539 map[string]string{ 540 "foo.proto": "syntax = \"proto2\";\n" + 541 "import \"google/protobuf/descriptor.proto\";\n" + 542 "message Foo { extensions 1 to 10; }\n" + 543 "extend Foo { optional group Bar = 10 { optional string name = 1; } }\n" + 544 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 545 "message Baz { option (foo) = { [bar]< name: \"abc\" > }; }\n", 546 }, 547 "", // should succeed 548 }, 549 { 550 map[string]string{ 551 "foo.proto": "syntax = \"proto2\";\n" + 552 "import \"google/protobuf/descriptor.proto\";\n" + 553 "message Foo { extensions 1 to 10; }\n" + 554 "extend Foo { optional group Bar = 10 { optional string name = 1; } }\n" + 555 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 556 "message Baz { option (foo) = { [Bar]< name: \"abc\" > }; }\n", 557 }, 558 "foo.proto:6:33: message Baz: option (foo): invalid extension: Bar is a message, not an extension", 559 }, 560 { 561 map[string]string{ 562 "foo.proto": "syntax = \"proto3\";\n" + 563 "import \"google/protobuf/descriptor.proto\";\n" + 564 "message Foo { oneof bar { string baz = 1; string buzz = 2; } }\n" + 565 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 566 "message Baz { option (foo) = { baz: \"abc\" buzz: \"xyz\" }; }\n", 567 }, 568 `foo.proto:5:43: message Baz: option (foo): oneof "bar" already has field "baz" set`, 569 }, 570 { 571 map[string]string{ 572 "foo.proto": "syntax = \"proto3\";\n" + 573 "import \"google/protobuf/descriptor.proto\";\n" + 574 "message Foo { oneof bar { string baz = 1; string buzz = 2; } }\n" + 575 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 576 "message Baz {\n" + 577 " option (foo).baz = \"abc\";\n" + 578 " option (foo).buzz = \"xyz\";\n" + 579 "}", 580 }, 581 `foo.proto:7:16: message Baz: option (foo).buzz: oneof "bar" already has field "baz" set`, 582 }, 583 { 584 map[string]string{ 585 "a.proto": "syntax = \"proto3\";\n" + 586 "message m{\n" + 587 " oneof z{\n" + 588 " int64 z=1;\n" + 589 " }\n" + 590 "}", 591 }, 592 `a.proto:4:11: symbol "m.z" already defined at a.proto:3:9`, 593 }, 594 { 595 map[string]string{ 596 "a.proto": "syntax=\"proto3\";\n" + 597 "message m{\n" + 598 " string z = 1;\n" + 599 " oneof z{int64 b=2;}\n" + 600 "}", 601 }, 602 `a.proto:4:9: symbol "m.z" already defined at a.proto:3:10`, 603 }, 604 { 605 map[string]string{ 606 "test.proto": "syntax=\"proto2\";\n" + 607 "package foo.bar;\n" + 608 "import \"google/protobuf/descriptor.proto\";\n" + 609 "message a { extensions 1 to 100; }\n" + 610 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 611 "message b {\n" + 612 " message c {\n" + 613 " extend a { repeated int32 i = 1; repeated float f = 2; }\n" + 614 " }\n" + 615 " option (msga) = {\n" + 616 " [foo.bar.b.c.i]: 123\n" + 617 " [bar.b.c.i]: 234\n" + 618 " [b.c.i]: 345\n" + 619 " };\n" + 620 " option (msga).(foo.bar.b.c.f) = 1.23;\n" + 621 " option (msga).(bar.b.c.f) = 2.34;\n" + 622 " option (msga).(b.c.f) = 3.45;\n" + 623 "}", 624 }, 625 "", // should succeed 626 }, 627 { 628 map[string]string{ 629 "test.proto": "syntax=\"proto2\";\n" + 630 "package foo.bar;\n" + 631 "import \"google/protobuf/descriptor.proto\";\n" + 632 "message a { extensions 1 to 100; }\n" + 633 "message b { extensions 1 to 100; }\n" + 634 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 635 "message c {\n" + 636 " extend a { optional b b = 1; }\n" + 637 " extend foo.bar.b { repeated int32 i = 1; repeated float f = 2; }\n" + 638 " option (msga) = {\n" + 639 " [foo.bar.c.b] {\n" + 640 " [foo.bar.c.i]: 123\n" + 641 " [bar.c.i]: 234\n" + 642 " [c.i]: 345\n" + 643 " }\n" + 644 " };\n" + 645 " option (msga).(foo.bar.c.b).(foo.bar.c.f) = 1.23;\n" + 646 " option (msga).(foo.bar.c.b).(bar.c.f) = 2.34;\n" + 647 " option (msga).(foo.bar.c.b).(c.f) = 3.45;\n" + 648 "}", 649 }, 650 "", // should succeed 651 }, 652 { 653 map[string]string{ 654 "test.proto": "syntax=\"proto2\";\n" + 655 "package foo.bar;\n" + 656 "import \"google/protobuf/descriptor.proto\";\n" + 657 "message a { extensions 1 to 100; }\n" + 658 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 659 "message b {\n" + 660 " message c {\n" + 661 " extend a { repeated int32 i = 1; repeated float f = 2; }\n" + 662 " }\n" + 663 " option (msga) = {\n" + 664 " [c.i]: 456\n" + 665 " };\n" + 666 "}", 667 }, 668 "test.proto:11:6: message foo.bar.b: option (foo.bar.msga): unknown extension c.i", 669 }, 670 { 671 map[string]string{ 672 "test.proto": "syntax=\"proto2\";\n" + 673 "package foo.bar;\n" + 674 "import \"google/protobuf/descriptor.proto\";\n" + 675 "message a { extensions 1 to 100; }\n" + 676 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 677 "message b {\n" + 678 " message c {\n" + 679 " extend a { repeated int32 i = 1; repeated float f = 2; }\n" + 680 " }\n" + 681 " option (msga) = {\n" + 682 " [i]: 567\n" + 683 " };\n" + 684 "}", 685 }, 686 "test.proto:11:6: message foo.bar.b: option (foo.bar.msga): unknown extension i", 687 }, 688 { 689 map[string]string{ 690 "test.proto": "syntax=\"proto2\";\n" + 691 "package foo.bar;\n" + 692 "import \"google/protobuf/descriptor.proto\";\n" + 693 "message a { extensions 1 to 100; }\n" + 694 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 695 "message b {\n" + 696 " message c {\n" + 697 " extend a { repeated int32 i = 1; repeated float f = 2; }\n" + 698 " }\n" + 699 " option (msga).(c.f) = 4.56;\n" + 700 "}", 701 }, 702 "test.proto:10:17: message foo.bar.b: unknown extension c.f", 703 }, 704 { 705 map[string]string{ 706 "test.proto": "syntax=\"proto2\";\n" + 707 "package foo.bar;\n" + 708 "import \"google/protobuf/descriptor.proto\";\n" + 709 "message a { extensions 1 to 100; }\n" + 710 "extend google.protobuf.MessageOptions { optional a msga = 10000; }\n" + 711 "message b {\n" + 712 " message c {\n" + 713 " extend a { repeated int32 i = 1; repeated float f = 2; }\n" + 714 " }\n" + 715 " option (msga).(f) = 5.67;\n" + 716 "}", 717 }, 718 "test.proto:10:17: message foo.bar.b: unknown extension f", 719 }, 720 { 721 map[string]string{ 722 "a.proto": "syntax=\"proto3\";\nmessage m{\n" + 723 " oneof z{int64 a=1;}\n" + 724 " oneof z{int64 b=2;}\n" + 725 "}", 726 }, 727 `a.proto:4:9: symbol "m.z" already defined at a.proto:3:9`, 728 }, 729 { 730 map[string]string{ 731 "foo.proto": "syntax = \"proto3\";\n" + 732 "import \"google/protobuf/descriptor.proto\";\n" + 733 "message Foo { oneof bar { google.protobuf.DescriptorProto baz = 1; google.protobuf.DescriptorProto buzz = 2; } }\n" + 734 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 735 "message Baz {\n" + 736 " option (foo).baz.name = \"abc\";\n" + 737 " option (foo).buzz.name = \"xyz\";\n" + 738 "}", 739 }, 740 `foo.proto:7:16: message Baz: option (foo).buzz.name: oneof "bar" already has field "baz" set`, 741 }, 742 { 743 map[string]string{ 744 "foo.proto": "syntax = \"proto3\";\n" + 745 "import \"google/protobuf/descriptor.proto\";\n" + 746 "message Foo { oneof bar { google.protobuf.DescriptorProto baz = 1; google.protobuf.DescriptorProto buzz = 2; } }\n" + 747 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 748 "message Baz {\n" + 749 " option (foo).baz.options.(foo).baz.name = \"abc\";\n" + 750 " option (foo).baz.options.(foo).buzz.name = \"xyz\";\n" + 751 "}", 752 }, 753 `foo.proto:7:34: message Baz: option (foo).baz.options.(foo).buzz.name: oneof "bar" already has field "baz" set`, 754 }, 755 { 756 map[string]string{ 757 "foo.proto": "syntax = \"proto3\";\n" + 758 "import \"google/protobuf/descriptor.proto\";\n" + 759 "enum Foo { option allow_alias = true; true = 0; false = 1; True = 0; False = 1; t = 2; f = 3; inf = 4; nan = 5; }\n" + 760 "extend google.protobuf.MessageOptions { repeated Foo foo = 10001; }\n" + 761 "message Baz {\n" + 762 " option (foo) = true; option (foo) = false;\n" + 763 " option (foo) = t; option (foo) = f;\n" + 764 " option (foo) = True; option (foo) = False;\n" + 765 " option (foo) = inf; option (foo) = nan;\n" + 766 "}\n", 767 }, 768 "", // should succeed 769 }, 770 { 771 map[string]string{ 772 "foo.proto": "syntax = \"proto3\";\n" + 773 "import \"google/protobuf/descriptor.proto\";\n" + 774 "extend google.protobuf.MessageOptions { repeated bool foo = 10001; }\n" + 775 "message Baz {\n" + 776 " option (foo) = true; option (foo) = false;\n" + 777 " option (foo) = t; option (foo) = f;\n" + 778 " option (foo) = True; option (foo) = False;\n" + 779 "}\n", 780 }, 781 "foo.proto:6:18: message Baz: option (foo): expecting bool, got identifier", 782 }, 783 { 784 map[string]string{ 785 "foo.proto": "syntax = \"proto3\";\n" + 786 "import \"google/protobuf/descriptor.proto\";\n" + 787 "message Foo { repeated bool b = 1; }\n" + 788 "extend google.protobuf.MessageOptions { Foo foo = 10001; }\n" + 789 "message Baz {\n" + 790 " option (foo) = {\n" + 791 " b: t b: f\n" + 792 " b: true b: false\n" + 793 " b: True b: False\n" + 794 " };\n" + 795 "}\n", 796 }, 797 "", // should succeed 798 }, 799 { 800 map[string]string{ 801 "foo.proto": "syntax = \"proto2\";\n" + 802 "import \"google/protobuf/descriptor.proto\";\n" + 803 "message Foo { extensions 1 to 10; }\n" + 804 "extend Foo { optional bool b = 10; }\n" + 805 "extend google.protobuf.MessageOptions { optional Foo foo = 10001; }\n" + 806 "message Baz {\n" + 807 " option (foo) = {\n" + 808 " [.b]: true\n" + 809 " };\n" + 810 "}\n", 811 }, 812 "foo.proto:8:6: syntax error: unexpected '.'", 813 }, 814 { 815 map[string]string{ 816 "foo.proto": "syntax = \"proto3\";\n" + 817 "package foo.bar;\n" + 818 "import \"google/protobuf/any.proto\";\n" + 819 "import \"google/protobuf/descriptor.proto\";\n" + 820 "message Foo { string a = 1; int32 b = 2; }\n" + 821 "extend google.protobuf.MessageOptions { optional google.protobuf.Any any = 10001; }\n" + 822 "message Baz {\n" + 823 " option (any) = {\n" + 824 " [type.googleapis.com/foo.bar.Foo] <\n" + 825 " a: \"abc\"\n" + 826 " b: 123\n" + 827 " >\n" + 828 " };\n" + 829 "}\n", 830 }, 831 "", // should succeed 832 }, 833 { 834 map[string]string{ 835 "foo.proto": "syntax = \"proto3\";\n" + 836 "package foo.bar;\n" + 837 "import \"google/protobuf/descriptor.proto\";\n" + 838 "message Foo { string a = 1; int32 b = 2; }\n" + 839 "extend google.protobuf.MessageOptions { optional Foo f = 10001; }\n" + 840 "message Baz {\n" + 841 " option (f) = {\n" + 842 " [type.googleapis.com/foo.bar.Foo] <\n" + 843 " a: \"abc\"\n" + 844 " b: 123\n" + 845 " >\n" + 846 " };\n" + 847 "}\n", 848 }, 849 "foo.proto:8:6: message foo.bar.Baz: option (foo.bar.f): type references are only allowed for google.protobuf.Any, but this type is foo.bar.Foo", 850 }, 851 { 852 map[string]string{ 853 "foo.proto": "syntax = \"proto3\";\n" + 854 "package foo.bar;\n" + 855 "import \"google/protobuf/any.proto\";\n" + 856 "import \"google/protobuf/descriptor.proto\";\n" + 857 "message Foo { string a = 1; int32 b = 2; }\n" + 858 "extend google.protobuf.MessageOptions { optional google.protobuf.Any any = 10001; }\n" + 859 "message Baz {\n" + 860 " option (any) = {\n" + 861 " [types.custom.io/foo.bar.Foo] <\n" + 862 " a: \"abc\"\n" + 863 " b: 123\n" + 864 " >\n" + 865 " };\n" + 866 "}\n", 867 }, 868 "foo.proto:9:6: message foo.bar.Baz: option (foo.bar.any): could not resolve type reference types.custom.io/foo.bar.Foo", 869 }, 870 { 871 map[string]string{ 872 "foo.proto": "syntax = \"proto3\";\n" + 873 "package foo.bar;\n" + 874 "import \"google/protobuf/any.proto\";\n" + 875 "import \"google/protobuf/descriptor.proto\";\n" + 876 "message Foo { string a = 1; int32 b = 2; }\n" + 877 "extend google.protobuf.MessageOptions { optional google.protobuf.Any any = 10001; }\n" + 878 "message Baz {\n" + 879 " option (any) = {\n" + 880 " [type.googleapis.com/foo.bar.Foo]: 123\n" + 881 " };\n" + 882 "}\n", 883 }, 884 "foo.proto:9:40: message foo.bar.Baz: option (foo.bar.any): type references for google.protobuf.Any must have message literal value", 885 }, 886 { 887 map[string]string{ 888 "foo.proto": "syntax = \"proto3\";\n" + 889 "package foo.bar;\n" + 890 "import \"google/protobuf/any.proto\";\n" + 891 "import \"google/protobuf/descriptor.proto\";\n" + 892 "message Foo { string a = 1; int32 b = 2; }\n" + 893 "extend google.protobuf.MessageOptions { optional google.protobuf.Any any = 10001; }\n" + 894 "message Baz {\n" + 895 " option (any) = {\n" + 896 " [type.googleapis.com/Foo] <\n" + 897 " a: \"abc\"\n" + 898 " b: 123\n" + 899 " >\n" + 900 " };\n" + 901 "}\n", 902 }, 903 "foo.proto:9:6: message foo.bar.Baz: option (foo.bar.any): could not resolve type reference type.googleapis.com/Foo", 904 }, 905 { 906 map[string]string{ 907 "foo.proto": "syntax = \"proto3\";\n" + 908 "import \"google/protobuf/descriptor.proto\";\n" + 909 "extend google.protobuf.MessageOptions {\n" + 910 " string foobar = 10001 [json_name=\"FooBar\"];\n" + 911 "}\n", 912 }, 913 "foo.proto:4:26: field foobar: option json_name is not allowed on extensions", 914 }, 915 { 916 map[string]string{ 917 "foo.proto": "syntax = \"proto3\";\n" + 918 "package foo.foo;\n" + 919 "import \"other.proto\";\n" + 920 "service Foo { rpc Bar (Baz) returns (Baz); }\n" + 921 "message Baz {\n" + 922 " foo.Foo.Bar f = 1;\n" + 923 "}\n", 924 "other.proto": "syntax = \"proto3\";\n" + 925 "package foo;\n" + 926 "message Foo {\n" + 927 " enum Bar { ZED = 0; }\n" + 928 "}\n", 929 }, 930 "foo.proto:6:3: field foo.foo.Baz.f: invalid type: foo.foo.Foo.Bar is a method, not a message or enum", 931 }, 932 { 933 map[string]string{ 934 "foo.proto": "syntax = \"proto3\";\n" + 935 "import \"google/protobuf/descriptor.proto\";\n" + 936 "message Foo {\n" + 937 " enum Bar { ZED = 0; }\n" + 938 " message Foo {\n" + 939 " extend google.protobuf.MessageOptions {\n" + 940 " string Bar = 30000;\n" + 941 " }\n" + 942 " Foo.Bar f = 1;\n" + 943 " }\n" + 944 "}\n", 945 }, 946 "foo.proto:9:5: field Foo.Foo.f: invalid type: Foo.Foo.Bar is an extension, not a message or enum", 947 }, 948 { 949 map[string]string{ 950 "foo.proto": "syntax = \"proto3\";\n" + 951 "import \"google/protobuf/descriptor.proto\";\n" + 952 "extend google.protobuf.ServiceOptions {\n" + 953 " string Bar = 30000;\n" + 954 "}\n" + 955 "message Empty {}\n" + 956 "service Foo {\n" + 957 " option (Bar) = \"blah\";\n" + 958 " rpc Bar (Empty) returns (Empty);\n" + 959 "}\n", 960 }, 961 "", // should succeed 962 }, 963 { 964 map[string]string{ 965 "foo.proto": "syntax = \"proto3\";\n" + 966 "import \"google/protobuf/descriptor.proto\";\n" + 967 "extend google.protobuf.MethodOptions {\n" + 968 " string Bar = 30000;\n" + 969 "}\n" + 970 "message Empty {}\n" + 971 "service Foo {\n" + 972 " rpc Bar (Empty) returns (Empty) { option (Bar) = \"blah\"; }\n" + 973 "}\n", 974 }, 975 "foo.proto:8:44: method Foo.Bar: invalid extension: Bar is a method, not an extension", 976 }, 977 { 978 map[string]string{ 979 "foo.proto": "syntax = \"proto3\";\n" + 980 "import \"google/protobuf/descriptor.proto\";\n" + 981 "enum Bar { ZED = 0; }\n" + 982 "message Foo {\n" + 983 " extend google.protobuf.MessageOptions {\n" + 984 " string Bar = 30000;\n" + 985 " }\n" + 986 " message Foo {\n" + 987 " Bar f = 1;\n" + 988 " }\n" + 989 "}\n", 990 }, 991 "", // should succeed 992 }, 993 { 994 map[string]string{ 995 "foo.proto": "syntax = \"proto3\";\n" + 996 "message Foo {\n" + 997 " map<string,string> bar = 1;\n" + 998 "}\n" + 999 "message Baz {\n" + 1000 " Foo.BarEntry e = 1;\n" + 1001 "}\n", 1002 }, 1003 "foo.proto:6:3: field Baz.e: Foo.BarEntry is a synthetic map entry and may not be referenced explicitly", 1004 }, 1005 { 1006 map[string]string{ 1007 "foo.proto": "syntax = \"proto3\";\n" + 1008 "import \"google/protobuf/struct.proto\";\n" + 1009 "message Foo {\n" + 1010 " google.protobuf.Struct.FieldsEntry e = 1;\n" + 1011 "}\n", 1012 }, 1013 "foo.proto:4:3: field Foo.e: google.protobuf.Struct.FieldsEntry is a synthetic map entry and may not be referenced explicitly", 1014 }, 1015 { 1016 map[string]string{ 1017 "foo.proto": "syntax = \"proto3\";\n" + 1018 "message Foo {\n" + 1019 " string foo = 1;\n" + 1020 " string bar = 2 [json_name=\"foo\"];\n" + 1021 "}\n", 1022 }, 1023 "foo.proto:4:3: field Foo.bar: custom JSON name \"foo\" conflicts with default JSON name of field foo, defined at foo.proto:3:3", 1024 }, 1025 { 1026 map[string]string{ 1027 "foo.proto": "syntax = \"proto3\";\n" + 1028 "message Blah {\n" + 1029 " message Foo {\n" + 1030 " string foo = 1;\n" + 1031 " string bar = 2 [json_name=\"foo\"];\n" + 1032 " }\n" + 1033 "}\n", 1034 }, 1035 "foo.proto:5:5: field Foo.bar: custom JSON name \"foo\" conflicts with default JSON name of field foo, defined at foo.proto:4:5", 1036 }, { 1037 map[string]string{ 1038 "foo.proto": "syntax = \"proto3\";\n" + 1039 "message Foo {\n" + 1040 " string foo = 1 [json_name=\"foo_bar\"];\n" + 1041 " string bar = 2 [json_name=\"Foo_Bar\"];\n" + 1042 "}\n", 1043 }, 1044 "", // should succeed 1045 }, 1046 { 1047 map[string]string{ 1048 "foo.proto": "syntax = \"proto3\";\n" + 1049 "message Foo {\n" + 1050 " string fooBar = 1;\n" + 1051 " string foo_bar = 2;\n" + 1052 "}\n", 1053 }, 1054 "foo.proto:4:3: field Foo.foo_bar: default JSON name \"fooBar\" conflicts with default JSON name of field fooBar, defined at foo.proto:3:3", 1055 }, 1056 { 1057 map[string]string{ 1058 "foo.proto": "syntax = \"proto3\";\n" + 1059 "message Foo {\n" + 1060 " string fooBar = 1;\n" + 1061 " string foo_bar = 2 [json_name=\"fuber\"];\n" + 1062 "}\n", 1063 }, 1064 "foo.proto:4:3: field Foo.foo_bar: default JSON name \"fooBar\" conflicts with default JSON name of field fooBar, defined at foo.proto:3:3", 1065 }, { 1066 map[string]string{ 1067 "foo.proto": "syntax = \"proto3\";\n" + 1068 "message Foo {\n" + 1069 " string fooBar = 1;\n" + 1070 " string FOO_BAR = 2;\n" + 1071 "}\n", 1072 }, 1073 "", // should succeed 1074 }, 1075 { 1076 map[string]string{ 1077 "foo.proto": "syntax = \"proto3\";\n" + 1078 "message Foo {\n" + 1079 " string fooBar = 1;\n" + 1080 " string __foo_bar = 2;\n" + 1081 "}\n", 1082 }, 1083 "", // should succeed 1084 }, 1085 { 1086 map[string]string{ 1087 "foo.proto": "syntax = \"proto2\";\n" + 1088 "message Foo {\n" + 1089 " optional string foo = 1 [json_name=\"foo_bar\"];\n" + 1090 " optional string bar = 2 [json_name=\"Foo_Bar\"];\n" + 1091 "}\n", 1092 }, 1093 "", // should succeed 1094 }, 1095 { 1096 map[string]string{ 1097 "foo.proto": "syntax = \"proto2\";\n" + 1098 "message Blah {\n" + 1099 " message Foo {\n" + 1100 " optional string foo = 1 [json_name=\"foo_bar\"];\n" + 1101 " optional string bar = 2 [json_name=\"Foo_Bar\"];\n" + 1102 " }\n" + 1103 "}\n", 1104 }, 1105 "", // should succeed 1106 }, 1107 { 1108 map[string]string{ 1109 "foo.proto": "syntax = \"proto2\";\n" + 1110 "message Foo {\n" + 1111 " optional string fooBar = 1;\n" + 1112 " optional string foo_bar = 2;\n" + 1113 "}\n", 1114 }, 1115 "", // should succeed: only check default JSON names in proto3 1116 }, 1117 { 1118 map[string]string{ 1119 "foo.proto": "syntax = \"proto2\";\n" + 1120 "message Foo {\n" + 1121 " optional string fooBar = 1 [json_name=\"fooBar\"];\n" + 1122 " optional string foo_bar = 2 [json_name=\"fooBar\"];\n" + 1123 "}\n", 1124 }, 1125 "foo.proto:4:3: field Foo.foo_bar: custom JSON name \"fooBar\" conflicts with custom JSON name of field fooBar, defined at foo.proto:3:3", 1126 }, 1127 { 1128 map[string]string{ 1129 "foo.proto": "syntax = \"proto2\";\n" + 1130 "message Foo {\n" + 1131 " optional string fooBar = 1;\n" + 1132 " optional string FOO_BAR = 2;\n" + 1133 "}\n", 1134 }, 1135 "", // should succeed: only check default JSON names in proto3 1136 }, 1137 { 1138 map[string]string{ 1139 "foo.proto": "syntax = \"proto2\";\n" + 1140 "message Foo {\n" + 1141 " optional string fooBar = 1;\n" + 1142 " optional string __foo_bar = 2;\n" + 1143 "}\n", 1144 }, 1145 "", // should succeed: only check default JSON names in proto3 1146 }, 1147 { 1148 map[string]string{ 1149 "foo.proto": "syntax = \"proto3\";\n" + 1150 "enum Foo {\n" + 1151 " true = 0;\n" + 1152 " TRUE = 1;\n" + 1153 "}\n", 1154 }, 1155 "foo.proto:4:3: enum value Foo.TRUE: camel-case name (with optional enum name prefix removed) \"True\" conflicts with camel-case name of enum value true, defined at foo.proto:3:3", 1156 }, 1157 { 1158 map[string]string{ 1159 "foo.proto": "syntax = \"proto3\";\n" + 1160 "message Blah {\n" + 1161 " enum Foo {\n" + 1162 " true = 0;\n" + 1163 " TRUE = 1;\n" + 1164 " }\n" + 1165 "}\n", 1166 }, 1167 "foo.proto:5:5: enum value Foo.TRUE: camel-case name (with optional enum name prefix removed) \"True\" conflicts with camel-case name of enum value true, defined at foo.proto:4:5", 1168 }, { 1169 map[string]string{ 1170 "foo.proto": "syntax = \"proto3\";\n" + 1171 "enum Foo {\n" + 1172 " BAR_BAZ = 0;\n" + 1173 " Foo_Bar_Baz = 1;\n" + 1174 "}\n", 1175 }, 1176 "foo.proto:4:3: enum value Foo.Foo_Bar_Baz: camel-case name (with optional enum name prefix removed) \"BarBaz\" conflicts with camel-case name of enum value BAR_BAZ, defined at foo.proto:3:3", 1177 }, 1178 { 1179 map[string]string{ 1180 "foo.proto": "syntax = \"proto3\";\n" + 1181 "enum Foo {\n" + 1182 " option allow_alias = true;\n" + 1183 " BAR_BAZ = 0;\n" + 1184 " FooBarBaz = 0;\n" + 1185 "}\n", 1186 }, 1187 "", // should succeed: not a conflict if both values have same number 1188 }, 1189 } 1190 for i, tc := range testCases { 1191 acc := func(filename string) (io.ReadCloser, error) { 1192 f, ok := tc.input[filename] 1193 if !ok { 1194 return nil, fmt.Errorf("file not found: %s", filename) 1195 } 1196 return io.NopCloser(strings.NewReader(f)), nil 1197 } 1198 names := make([]string, 0, len(tc.input)) 1199 for k := range tc.input { 1200 names = append(names, k) 1201 } 1202 _, err := Parser{Accessor: acc}.ParseFiles(names...) 1203 if tc.errMsg == "" { 1204 if err != nil { 1205 t.Errorf("case %d: expecting no error; instead got error %q", i, err) 1206 } 1207 } else if err == nil { 1208 t.Errorf("case %d: expecting validation error %q; instead got no error", i, tc.errMsg) 1209 } else { 1210 options := strings.Split(tc.errMsg, "||") 1211 matched := false 1212 for i := range options { 1213 options[i] = strings.TrimSpace(options[i]) 1214 if err.Error() == options[i] { 1215 matched = true 1216 break 1217 } 1218 } 1219 if !matched { 1220 t.Errorf("case %d: expecting validation error %q; instead got: %q", i, strings.Join(options, " || "), err) 1221 } 1222 } 1223 } 1224 } 1225 1226 func TestProto3Enums(t *testing.T) { 1227 file1 := `syntax = "<SYNTAX>"; enum bar { A = 0; B = 1; }` 1228 file2 := `syntax = "<SYNTAX>"; import "f1.proto"; message foo { <LABEL> bar bar = 1; }` 1229 getFileContents := func(file, syntax string) string { 1230 contents := strings.Replace(file, "<SYNTAX>", syntax, 1) 1231 label := "" 1232 if syntax == "proto2" { 1233 label = "optional" 1234 } 1235 return strings.Replace(contents, "<LABEL>", label, 1) 1236 } 1237 1238 syntaxOptions := []string{"proto2", "proto3"} 1239 for _, o1 := range syntaxOptions { 1240 fc1 := getFileContents(file1, o1) 1241 1242 for _, o2 := range syntaxOptions { 1243 fc2 := getFileContents(file2, o2) 1244 1245 // now parse the protos 1246 acc := func(filename string) (io.ReadCloser, error) { 1247 var data string 1248 switch filename { 1249 case "f1.proto": 1250 data = fc1 1251 case "f2.proto": 1252 data = fc2 1253 default: 1254 return nil, fmt.Errorf("file not found: %s", filename) 1255 } 1256 return io.NopCloser(strings.NewReader(data)), nil 1257 } 1258 _, err := Parser{Accessor: acc}.ParseFiles("f1.proto", "f2.proto") 1259 1260 if o1 != o2 && o2 == "proto3" { 1261 expected := "f2.proto:1:54: cannot use closed enum bar in a field with implicit presence" 1262 if err == nil { 1263 t.Errorf("expecting validation error; instead got no error") 1264 } else if err.Error() != expected { 1265 t.Errorf("expecting validation error %q; instead got: %q", expected, err) 1266 } 1267 } else { 1268 // other cases succeed (okay to for proto2 to use enum from proto3 file and 1269 // obviously okay for proto2 importing proto2 and proto3 importing proto3) 1270 testutil.Ok(t, err) 1271 } 1272 } 1273 } 1274 } 1275 1276 func TestCustomErrorReporterWithLinker(t *testing.T) { 1277 input := map[string]string{ 1278 "a/b/b.proto": `package a.b; 1279 1280 import "google/protobuf/descriptor.proto"; 1281 1282 extend google.protobuf.FieldOptions { 1283 optional Foo foo = 50001; 1284 } 1285 1286 message Foo { 1287 optional string bar = 1; 1288 }`, 1289 "a/c/c.proto": `import "a/b/b.proto"; 1290 1291 message ReferencesFooOption { 1292 optional string baz = 1 [(a.b.foo).bat = "hello"]; 1293 }`, 1294 } 1295 errMsg := "a/c/c.proto:4:38: field ReferencesFooOption.baz: option (a.b.foo).bat: field bat of a.b.Foo does not exist" 1296 1297 acc := func(filename string) (io.ReadCloser, error) { 1298 f, ok := input[filename] 1299 if !ok { 1300 return nil, fmt.Errorf("file not found: %s", filename) 1301 } 1302 return io.NopCloser(strings.NewReader(f)), nil 1303 } 1304 names := make([]string, 0, len(input)) 1305 for k := range input { 1306 names = append(names, k) 1307 } 1308 var errs []error 1309 _, err := Parser{ 1310 Accessor: acc, 1311 ErrorReporter: func(errorWithPos ErrorWithPos) error { 1312 errs = append(errs, errorWithPos) 1313 // need to return nil to make sure this test case works 1314 // this will result in us only getting an error from errorHandler.getError() 1315 // we need to make sure this is called correctly in the linker so that all 1316 // errors are properly propagated from the return value of linkFiles(), and 1317 // therefor Parse returns ErrInvalidSource 1318 return nil 1319 }, 1320 }.ParseFiles(names...) 1321 if err != ErrInvalidSource { 1322 t.Errorf("expecting validation error %v; instead got: %v", ErrInvalidSource, err) 1323 } else if len(errs) != 1 || errs[0].Error() != errMsg { 1324 t.Errorf("expecting validation error %q; instead got: %q", errs[0].Error(), errMsg) 1325 } 1326 } 1327 1328 func TestSyntheticOneOfCollisions(t *testing.T) { 1329 input := map[string]string{ 1330 "foo1.proto": "syntax = \"proto3\";\n" + 1331 "message Foo {\n" + 1332 " optional string bar = 1;\n" + 1333 "}\n", 1334 "foo2.proto": "syntax = \"proto3\";\n" + 1335 "message Foo {\n" + 1336 " optional string bar = 1;\n" + 1337 "}\n", 1338 } 1339 acc := func(filename string) (io.ReadCloser, error) { 1340 f, ok := input[filename] 1341 if !ok { 1342 return nil, fmt.Errorf("file not found: %s", filename) 1343 } 1344 return io.NopCloser(strings.NewReader(f)), nil 1345 } 1346 1347 var errs []error 1348 errReporter := func(errorWithPos ErrorWithPos) error { 1349 errs = append(errs, errorWithPos) 1350 // need to return nil to accumulate all errors so we can report synthetic 1351 // oneof collision; otherwise, the link will fail after the first collision 1352 // and we'll never test the synthetic oneofs 1353 return nil 1354 } 1355 1356 _, err := Parser{ 1357 Accessor: acc, 1358 ErrorReporter: errReporter, 1359 }.ParseFiles("foo1.proto", "foo2.proto") 1360 1361 testutil.Eq(t, ErrInvalidSource, err) 1362 expectedOption1 := []string{ 1363 `foo1.proto:2:9: symbol "Foo" already defined at foo2.proto:2:9`, 1364 `foo1.proto:3:19: symbol "Foo.bar" already defined at foo2.proto:3:19`, 1365 `foo1.proto:3:19: symbol "Foo._bar" already defined at foo2.proto:3:19`, 1366 } 1367 expectedOption2 := []string{ 1368 `foo2.proto:2:9: symbol "Foo" already defined at foo1.proto:2:9`, 1369 `foo2.proto:3:19: symbol "Foo.bar" already defined at foo1.proto:3:19`, 1370 `foo2.proto:3:19: symbol "Foo._bar" already defined at foo1.proto:3:19`, 1371 } 1372 1373 var actual []string 1374 for _, err := range errs { 1375 actual = append(actual, err.Error()) 1376 } 1377 // Errors expected depend on which file is compiled first. This is mostly deterministic 1378 // with parallelism of 1, but some things (like enabling -race in tests) can change the 1379 // expected order. 1380 if !reflect.DeepEqual(expectedOption1, actual) && !reflect.DeepEqual(expectedOption2, actual) { 1381 t.Errorf("got errors:\n:%v\nbut wanted EITHER:\n%v\n OR:\n%v", actual, expectedOption1, expectedOption2) 1382 } 1383 } 1384 1385 func TestCustomJSONNameWarnings(t *testing.T) { 1386 testCases := []struct { 1387 source string 1388 warning string 1389 }{ 1390 { 1391 source: "syntax = \"proto2\";\n" + 1392 "message Foo {\n" + 1393 " optional string foo_bar = 1;\n" + 1394 " optional string fooBar = 2;\n" + 1395 "}\n", 1396 warning: "test.proto:4:3: field Foo.fooBar: default JSON name \"fooBar\" conflicts with default JSON name of field foo_bar, defined at test.proto:3:3", 1397 }, 1398 { 1399 source: "syntax = \"proto2\";\n" + 1400 "message Foo {\n" + 1401 " optional string foo_bar = 1;\n" + 1402 " optional string fooBar = 2;\n" + 1403 "}\n", 1404 warning: "test.proto:4:3: field Foo.fooBar: default JSON name \"fooBar\" conflicts with default JSON name of field foo_bar, defined at test.proto:3:3", 1405 }, 1406 // in nested message 1407 { 1408 source: "syntax = \"proto2\";\n" + 1409 "message Blah { message Foo {\n" + 1410 " optional string foo_bar = 1;\n" + 1411 " optional string fooBar = 2;\n" + 1412 "} }\n", 1413 warning: "test.proto:4:3: field Foo.fooBar: default JSON name \"fooBar\" conflicts with default JSON name of field foo_bar, defined at test.proto:3:3", 1414 }, 1415 { 1416 source: "syntax = \"proto2\";\n" + 1417 "message Blah { message Foo {\n" + 1418 " optional string foo_bar = 1;\n" + 1419 " optional string fooBar = 2;\n" + 1420 "} }\n", 1421 warning: "test.proto:4:3: field Foo.fooBar: default JSON name \"fooBar\" conflicts with default JSON name of field foo_bar, defined at test.proto:3:3", 1422 }, 1423 // enum values 1424 { 1425 source: "syntax = \"proto2\";\n" + 1426 "enum Foo {\n" + 1427 " true = 0;\n" + 1428 " TRUE = 1;\n" + 1429 "}\n", 1430 warning: "test.proto:4:3: enum value Foo.TRUE: camel-case name (with optional enum name prefix removed) \"True\" conflicts with camel-case name of enum value true, defined at test.proto:3:3", 1431 }, 1432 { 1433 source: "syntax = \"proto2\";\n" + 1434 "enum Foo {\n" + 1435 " fooBar_Baz = 0;\n" + 1436 " _FOO__BAR_BAZ = 1;\n" + 1437 "}\n", 1438 warning: "test.proto:4:3: enum value Foo._FOO__BAR_BAZ: camel-case name (with optional enum name prefix removed) \"BarBaz\" conflicts with camel-case name of enum value fooBar_Baz, defined at test.proto:3:3", 1439 }, 1440 { 1441 source: "syntax = \"proto2\";\n" + 1442 "enum Foo {\n" + 1443 " fooBar_Baz = 0;\n" + 1444 " FOO__BAR__BAZ__ = 1;\n" + 1445 "}\n", 1446 warning: "test.proto:4:3: enum value Foo.FOO__BAR__BAZ__: camel-case name (with optional enum name prefix removed) \"BarBaz\" conflicts with camel-case name of enum value fooBar_Baz, defined at test.proto:3:3", 1447 }, 1448 { 1449 source: "syntax = \"proto2\";\n" + 1450 "enum Foo {\n" + 1451 " fooBarBaz = 0;\n" + 1452 " _FOO__BAR_BAZ = 1;\n" + 1453 "}\n", 1454 warning: "", 1455 }, 1456 { 1457 source: "syntax = \"proto2\";\n" + 1458 "enum Foo {\n" + 1459 " option allow_alias = true;\n" + 1460 " Bar_Baz = 0;\n" + 1461 " _BAR_BAZ_ = 0;\n" + 1462 " FOO_BAR_BAZ = 0;\n" + 1463 " foobar_baz = 0;\n" + 1464 "}\n", 1465 warning: "", 1466 }, 1467 // in nested message 1468 { 1469 source: "syntax = \"proto2\";\n" + 1470 "message Blah { enum Foo {\n" + 1471 " true = 0;\n" + 1472 " TRUE = 1;\n" + 1473 "} }\n", 1474 warning: "test.proto:4:3: enum value Foo.TRUE: camel-case name (with optional enum name prefix removed) \"True\" conflicts with camel-case name of enum value true, defined at test.proto:3:3", 1475 }, 1476 { 1477 source: "syntax = \"proto2\";\n" + 1478 "message Blah { enum Foo {\n" + 1479 " fooBar_Baz = 0;\n" + 1480 " _FOO__BAR_BAZ = 1;\n" + 1481 "} }\n", 1482 warning: "test.proto:4:3: enum value Foo._FOO__BAR_BAZ: camel-case name (with optional enum name prefix removed) \"BarBaz\" conflicts with camel-case name of enum value fooBar_Baz, defined at test.proto:3:3", 1483 }, 1484 { 1485 source: "syntax = \"proto2\";\n" + 1486 "message Blah { enum Foo {\n" + 1487 " option allow_alias = true;\n" + 1488 " Bar_Baz = 0;\n" + 1489 " _BAR_BAZ_ = 0;\n" + 1490 " FOO_BAR_BAZ = 0;\n" + 1491 " foobar_baz = 0;\n" + 1492 "} }\n", 1493 warning: "", 1494 }, 1495 } 1496 for i, tc := range testCases { 1497 acc := func(filename string) (io.ReadCloser, error) { 1498 if filename == "test.proto" { 1499 return io.NopCloser(strings.NewReader(tc.source)), nil 1500 } 1501 return nil, fmt.Errorf("file not found: %s", filename) 1502 } 1503 var warnings []string 1504 warnFunc := func(err ErrorWithPos) { 1505 warnings = append(warnings, err.Error()) 1506 } 1507 _, err := Parser{Accessor: acc, WarningReporter: warnFunc}.ParseFiles("test.proto") 1508 if err != nil { 1509 t.Errorf("case %d: expecting no error; instead got error %q", i, err) 1510 } 1511 if tc.warning == "" && len(warnings) > 0 { 1512 t.Errorf("case %d: expecting no warnings; instead got: %v", i, warnings) 1513 } else if tc.warning != "" { 1514 found := false 1515 for _, w := range warnings { 1516 if w == tc.warning { 1517 found = true 1518 break 1519 } 1520 } 1521 if !found { 1522 t.Errorf("case %d: expecting warning %q; instead got: %v", i, tc.warning, warnings) 1523 } 1524 } 1525 } 1526 }