github.com/jhump/protoreflect@v1.16.0/desc/builder/builder_test.go (about) 1 package builder 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "sort" 8 "strings" 9 "testing" 10 11 "github.com/golang/protobuf/proto" 12 "github.com/jhump/protoreflect/desc/protoparse" 13 "google.golang.org/protobuf/types/descriptorpb" 14 "google.golang.org/protobuf/types/known/anypb" 15 "google.golang.org/protobuf/types/known/emptypb" 16 "google.golang.org/protobuf/types/known/timestamppb" 17 18 "github.com/jhump/protoreflect/desc" 19 "github.com/jhump/protoreflect/dynamic" 20 _ "github.com/jhump/protoreflect/internal/testprotos" 21 "github.com/jhump/protoreflect/internal/testutil" 22 ) 23 24 func TestSimpleDescriptorsFromScratch(t *testing.T) { 25 md, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil)) 26 testutil.Ok(t, err) 27 28 file := NewFile("foo/bar.proto").SetPackageName("foo.bar") 29 en := NewEnum("Options"). 30 AddValue(NewEnumValue("OPTION_1")). 31 AddValue(NewEnumValue("OPTION_2")). 32 AddValue(NewEnumValue("OPTION_3")) 33 file.AddEnum(en) 34 35 msg := NewMessage("FooRequest"). 36 AddField(NewField("id", FieldTypeInt64())). 37 AddField(NewField("name", FieldTypeString())). 38 AddField(NewField("options", FieldTypeEnum(en)). 39 SetRepeated()) 40 file.AddMessage(msg) 41 42 sb := NewService("FooService"). 43 AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false))). 44 AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true))) 45 file.AddService(sb) 46 47 fd, err := file.Build() 48 testutil.Ok(t, err) 49 50 testutil.Eq(t, []*desc.FileDescriptor{md.GetFile()}, fd.GetDependencies()) 51 testutil.Require(t, fd.FindEnum("foo.bar.Options") != nil) 52 testutil.Eq(t, 3, len(fd.FindEnum("foo.bar.Options").GetValues())) 53 testutil.Require(t, fd.FindMessage("foo.bar.FooRequest") != nil) 54 testutil.Eq(t, 3, len(fd.FindMessage("foo.bar.FooRequest").GetFields())) 55 testutil.Require(t, fd.FindService("foo.bar.FooService") != nil) 56 testutil.Eq(t, 2, len(fd.FindService("foo.bar.FooService").GetMethods())) 57 58 // building the others produces same results 59 ed, err := en.Build() 60 testutil.Ok(t, err) 61 testutil.Require(t, proto.Equal(ed.AsProto(), fd.FindEnum("foo.bar.Options").AsProto())) 62 63 md, err = msg.Build() 64 testutil.Ok(t, err) 65 testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("foo.bar.FooRequest").AsProto())) 66 67 sd, err := sb.Build() 68 testutil.Ok(t, err) 69 testutil.Require(t, proto.Equal(sd.AsProto(), fd.FindService("foo.bar.FooService").AsProto())) 70 } 71 72 func TestSimpleDescriptorsFromScratch_SyntheticFiles(t *testing.T) { 73 md, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil)) 74 testutil.Ok(t, err) 75 76 en := NewEnum("Options") 77 en.AddValue(NewEnumValue("OPTION_1")) 78 en.AddValue(NewEnumValue("OPTION_2")) 79 en.AddValue(NewEnumValue("OPTION_3")) 80 81 msg := NewMessage("FooRequest") 82 msg.AddField(NewField("id", FieldTypeInt64())) 83 msg.AddField(NewField("name", FieldTypeString())) 84 msg.AddField(NewField("options", FieldTypeEnum(en)). 85 SetRepeated()) 86 87 sb := NewService("FooService") 88 sb.AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false))) 89 sb.AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true))) 90 91 sd, err := sb.Build() 92 testutil.Ok(t, err) 93 testutil.Eq(t, "FooService", sd.GetFullyQualifiedName()) 94 testutil.Eq(t, 2, len(sd.GetMethods())) 95 96 // it imports google/protobuf/empty.proto and a synthetic file that has message 97 testutil.Eq(t, 2, len(sd.GetFile().GetDependencies())) 98 fd := sd.GetFile().GetDependencies()[0] 99 testutil.Eq(t, "google/protobuf/empty.proto", fd.GetName()) 100 testutil.Eq(t, md.GetFile(), fd) 101 fd = sd.GetFile().GetDependencies()[1] 102 testutil.Require(t, strings.Contains(fd.GetName(), "generated")) 103 testutil.Require(t, fd.FindMessage("FooRequest") != nil) 104 testutil.Eq(t, 3, len(fd.FindMessage("FooRequest").GetFields())) 105 106 // this one imports only a synthetic file that has enum 107 testutil.Eq(t, 1, len(fd.GetDependencies())) 108 fd2 := fd.GetDependencies()[0] 109 testutil.Require(t, fd2.FindEnum("Options") != nil) 110 testutil.Eq(t, 3, len(fd2.FindEnum("Options").GetValues())) 111 112 // building the others produces same results 113 ed, err := en.Build() 114 testutil.Ok(t, err) 115 testutil.Require(t, proto.Equal(ed.AsProto(), fd2.FindEnum("Options").AsProto())) 116 117 md, err = msg.Build() 118 testutil.Ok(t, err) 119 testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("FooRequest").AsProto())) 120 } 121 122 func TestComplexDescriptorsFromScratch(t *testing.T) { 123 mdEmpty, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil)) 124 testutil.Ok(t, err) 125 mdAny, err := desc.LoadMessageDescriptorForMessage((*anypb.Any)(nil)) 126 testutil.Ok(t, err) 127 mdTimestamp, err := desc.LoadMessageDescriptorForMessage((*timestamppb.Timestamp)(nil)) 128 testutil.Ok(t, err) 129 mbAny, err := FromMessage(mdAny) 130 testutil.Ok(t, err) 131 132 msgA := NewMessage("FooA"). 133 AddField(NewField("id", FieldTypeUInt64())). 134 AddField(NewField("when", FieldTypeImportedMessage(mdTimestamp))). 135 AddField(NewField("extras", FieldTypeImportedMessage(mdAny)). 136 SetRepeated()). 137 AddField(NewField("builder", FieldTypeMessage(mbAny))). 138 SetExtensionRanges([]*descriptorpb.DescriptorProto_ExtensionRange{{Start: proto.Int32(100), End: proto.Int32(201)}}) 139 msgA2 := NewMessage("Nnn"). 140 AddField(NewField("uid1", FieldTypeFixed64())). 141 AddField(NewField("uid2", FieldTypeFixed64())) 142 NewFile(""). 143 SetPackageName("foo.bar"). 144 AddMessage(msgA). 145 AddMessage(msgA2) 146 147 msgB := NewMessage("FooB"). 148 AddField(NewField("foo_a", FieldTypeMessage(msgA)). 149 SetRepeated()). 150 AddField(NewField("name", FieldTypeString())) 151 NewFile(""). 152 SetPackageName("foo.bar"). 153 AddMessage(msgB) 154 155 enC := NewEnum("Vals"). 156 AddValue(NewEnumValue("DEFAULT")). 157 AddValue(NewEnumValue("VALUE_A")). 158 AddValue(NewEnumValue("VALUE_B")). 159 AddValue(NewEnumValue("VALUE_C")) 160 msgC := NewMessage("BarBaz"). 161 AddOneOf(NewOneOf("bbb"). 162 AddChoice(NewField("b1", FieldTypeMessage(msgA))). 163 AddChoice(NewField("b2", FieldTypeMessage(msgB)))). 164 AddField(NewField("v", FieldTypeEnum(enC))) 165 NewFile("some/path/file.proto"). 166 SetPackageName("foo.baz"). 167 AddEnum(enC). 168 AddMessage(msgC) 169 170 enD := NewEnum("Ppp"). 171 AddValue(NewEnumValue("P0")). 172 AddValue(NewEnumValue("P1")). 173 AddValue(NewEnumValue("P2")). 174 AddValue(NewEnumValue("P3")) 175 exD := NewExtension("ppp", 123, FieldTypeEnum(enD), msgA) 176 NewFile("some/other/path/file.proto"). 177 SetPackageName("foo.biz"). 178 AddEnum(enD). 179 AddExtension(exD) 180 181 msgE := NewMessage("Ppp"). 182 AddField(NewField("p", FieldTypeEnum(enD))). 183 AddField(NewField("n", FieldTypeMessage(msgA2))) 184 fd, err := NewFile(""). 185 SetPackageName("foo.bar"). 186 AddMessage(msgE). 187 AddService(NewService("PppSvc"). 188 AddMethod(NewMethod("Method1", RpcTypeMessage(msgE, false), RpcTypeImportedMessage(mdEmpty, false))). 189 AddMethod(NewMethod("Method2", RpcTypeMessage(msgB, false), RpcTypeMessage(msgC, true)))). 190 Build() 191 192 testutil.Ok(t, err) 193 194 testutil.Eq(t, 5, len(fd.GetDependencies())) 195 // dependencies sorted; those with generated names come last 196 depEmpty := fd.GetDependencies()[0] 197 testutil.Eq(t, "google/protobuf/empty.proto", depEmpty.GetName()) 198 testutil.Eq(t, mdEmpty.GetFile(), depEmpty) 199 depD := fd.GetDependencies()[1] 200 testutil.Eq(t, "some/other/path/file.proto", depD.GetName()) 201 depC := fd.GetDependencies()[2] 202 testutil.Eq(t, "some/path/file.proto", depC.GetName()) 203 depA := fd.GetDependencies()[3] 204 testutil.Require(t, strings.Contains(depA.GetName(), "generated")) 205 depB := fd.GetDependencies()[4] 206 testutil.Require(t, strings.Contains(depB.GetName(), "generated")) 207 208 // check contents of files 209 testutil.Require(t, depA.FindMessage("foo.bar.FooA") != nil) 210 testutil.Eq(t, 4, len(depA.FindMessage("foo.bar.FooA").GetFields())) 211 testutil.Require(t, depA.FindMessage("foo.bar.Nnn") != nil) 212 testutil.Eq(t, 2, len(depA.FindMessage("foo.bar.Nnn").GetFields())) 213 testutil.Eq(t, 2, len(depA.GetDependencies())) 214 215 testutil.Require(t, depB.FindMessage("foo.bar.FooB") != nil) 216 testutil.Eq(t, 2, len(depB.FindMessage("foo.bar.FooB").GetFields())) 217 testutil.Eq(t, 1, len(depB.GetDependencies())) 218 219 testutil.Require(t, depC.FindMessage("foo.baz.BarBaz") != nil) 220 testutil.Eq(t, 3, len(depC.FindMessage("foo.baz.BarBaz").GetFields())) 221 testutil.Require(t, depC.FindEnum("foo.baz.Vals") != nil) 222 testutil.Eq(t, 4, len(depC.FindEnum("foo.baz.Vals").GetValues())) 223 testutil.Eq(t, 2, len(depC.GetDependencies())) 224 225 testutil.Require(t, depD.FindEnum("foo.biz.Ppp") != nil) 226 testutil.Eq(t, 4, len(depD.FindEnum("foo.biz.Ppp").GetValues())) 227 testutil.Require(t, depD.FindExtensionByName("foo.biz.ppp") != nil) 228 testutil.Eq(t, 1, len(depD.GetDependencies())) 229 230 testutil.Require(t, fd.FindMessage("foo.bar.Ppp") != nil) 231 testutil.Eq(t, 2, len(fd.FindMessage("foo.bar.Ppp").GetFields())) 232 testutil.Require(t, fd.FindService("foo.bar.PppSvc") != nil) 233 testutil.Eq(t, 2, len(fd.FindService("foo.bar.PppSvc").GetMethods())) 234 } 235 236 func TestCreatingGroupField(t *testing.T) { 237 grpMb := NewMessage("GroupA"). 238 AddField(NewField("name", FieldTypeString())). 239 AddField(NewField("id", FieldTypeInt64())) 240 grpFlb := NewGroupField(grpMb) 241 242 mb := NewMessage("TestMessage"). 243 AddField(NewField("foo", FieldTypeBool())). 244 AddField(grpFlb) 245 md, err := mb.Build() 246 testutil.Ok(t, err) 247 248 testutil.Require(t, md.FindFieldByName("groupa") != nil) 249 testutil.Eq(t, descriptorpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("groupa").GetType()) 250 nmd := md.GetNestedMessageTypes()[0] 251 testutil.Eq(t, "GroupA", nmd.GetName()) 252 testutil.Eq(t, nmd, md.FindFieldByName("groupa").GetMessageType()) 253 254 // try a rename that will fail 255 err = grpMb.TrySetName("fooBarBaz") 256 testutil.Require(t, err != nil) 257 testutil.Eq(t, "group name fooBarBaz must start with capital letter", err.Error()) 258 // failed rename should not have modified any state 259 md2, err := mb.Build() 260 testutil.Ok(t, err) 261 testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto())) 262 // another attempt that will fail 263 err = grpFlb.TrySetName("foobarbaz") 264 testutil.Require(t, err != nil) 265 testutil.Eq(t, "cannot change name of group field TestMessage.groupa; change name of group instead", err.Error()) 266 // again, no state should have been modified 267 md2, err = mb.Build() 268 testutil.Ok(t, err) 269 testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto())) 270 271 // and a rename that succeeds 272 err = grpMb.TrySetName("FooBarBaz") 273 testutil.Ok(t, err) 274 md, err = mb.Build() 275 testutil.Ok(t, err) 276 277 // field also renamed 278 testutil.Require(t, md.FindFieldByName("foobarbaz") != nil) 279 testutil.Eq(t, descriptorpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("foobarbaz").GetType()) 280 nmd = md.GetNestedMessageTypes()[0] 281 testutil.Eq(t, "FooBarBaz", nmd.GetName()) 282 testutil.Eq(t, nmd, md.FindFieldByName("foobarbaz").GetMessageType()) 283 } 284 285 func TestCreatingMapField(t *testing.T) { 286 mapFlb := NewMapField("countsByName", FieldTypeString(), FieldTypeUInt64()) 287 testutil.Require(t, mapFlb.IsMap()) 288 289 mb := NewMessage("TestMessage"). 290 AddField(NewField("foo", FieldTypeBool())). 291 AddField(mapFlb) 292 md, err := mb.Build() 293 testutil.Ok(t, err) 294 295 testutil.Require(t, md.FindFieldByName("countsByName") != nil) 296 testutil.Require(t, md.FindFieldByName("countsByName").IsMap()) 297 nmd := md.GetNestedMessageTypes()[0] 298 testutil.Eq(t, "CountsByNameEntry", nmd.GetName()) 299 testutil.Eq(t, nmd, md.FindFieldByName("countsByName").GetMessageType()) 300 301 // try a rename that will fail 302 err = mapFlb.GetType().localMsgType.TrySetName("fooBarBaz") 303 testutil.Require(t, err != nil) 304 testutil.Eq(t, "cannot change name of map entry TestMessage.CountsByNameEntry; change name of field instead", err.Error()) 305 // failed rename should not have modified any state 306 md2, err := mb.Build() 307 testutil.Ok(t, err) 308 testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto())) 309 310 // and a rename that succeeds 311 err = mapFlb.TrySetName("fooBarBaz") 312 testutil.Ok(t, err) 313 md, err = mb.Build() 314 testutil.Ok(t, err) 315 316 // map entry also renamed 317 testutil.Require(t, md.FindFieldByName("fooBarBaz") != nil) 318 testutil.Require(t, md.FindFieldByName("fooBarBaz").IsMap()) 319 nmd = md.GetNestedMessageTypes()[0] 320 testutil.Eq(t, "FooBarBazEntry", nmd.GetName()) 321 testutil.Eq(t, nmd, md.FindFieldByName("fooBarBaz").GetMessageType()) 322 } 323 324 func TestProto3Optional(t *testing.T) { 325 mb := NewMessage("Foo") 326 flb := NewField("bar", FieldTypeBool()).SetProto3Optional(true) 327 mb.AddField(flb) 328 329 _, err := flb.Build() 330 testutil.Nok(t, err) // file does not have proto3 syntax 331 332 fb := NewFile("foo.proto").SetProto3(true) 333 fb.AddMessage(mb) 334 335 fld, err := flb.Build() 336 testutil.Ok(t, err) 337 338 testutil.Require(t, fld.IsProto3Optional()) 339 testutil.Require(t, fld.GetOneOf() != nil) 340 testutil.Require(t, fld.GetOneOf().IsSynthetic()) 341 testutil.Eq(t, "_bar", fld.GetOneOf().GetName()) 342 } 343 344 func TestBuildersFromDescriptors(t *testing.T) { 345 for _, s := range []string{ 346 "desc_test1.proto", "desc_test2.proto", 347 "desc_test_defaults.proto", "desc_test_editions.proto", "desc_test_options.proto", 348 "desc_test_proto3.proto", "desc_test_wellknowntypes.proto", 349 "nopkg/desc_test_nopkg.proto", "nopkg/desc_test_nopkg_new.proto", "pkg/desc_test_pkg.proto", 350 } { 351 t.Run(s, func(t *testing.T) { 352 fd, err := desc.LoadFileDescriptor(s) 353 testutil.Ok(t, err) 354 roundTripFile(t, fd) 355 }) 356 } 357 } 358 359 func TestBuildersFromDescriptors_PreserveComments(t *testing.T) { 360 fd, err := loadProtoset("../../internal/testprotos/desc_test1.protoset") 361 testutil.Ok(t, err) 362 363 fb, err := FromFile(fd) 364 testutil.Ok(t, err) 365 366 count := 0 367 var checkBuilderComments func(b Builder) 368 checkBuilderComments = func(b Builder) { 369 hasComment := true 370 switch b := b.(type) { 371 case *FileBuilder: 372 hasComment = false 373 case *FieldBuilder: 374 // comments for groups are on the message, not the field 375 hasComment = b.GetType().GetType() != descriptorpb.FieldDescriptorProto_TYPE_GROUP 376 case *MessageBuilder: 377 // comments for maps are on the field, not the entry message 378 if b.Options.GetMapEntry() { 379 // we just return to also skip checking child elements 380 // (map entry child elements are synthetic and have no comments) 381 return 382 } 383 } 384 385 if hasComment { 386 count++ 387 testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", b.GetName()), b.GetComments().LeadingComment, 388 "wrong comment for builder %s", GetFullyQualifiedName(b)) 389 } 390 for _, ch := range b.GetChildren() { 391 checkBuilderComments(ch) 392 } 393 } 394 395 checkBuilderComments(fb) 396 // sanity check that we didn't accidentally short-circuit above and fail to check comments 397 testutil.Require(t, count > 30, "too few elements checked") 398 399 // now check that they also come out in the resulting descriptor 400 fd, err = fb.Build() 401 testutil.Ok(t, err) 402 403 descCount := 0 404 var checkDescriptorComments func(d desc.Descriptor) 405 checkDescriptorComments = func(d desc.Descriptor) { 406 switch d := d.(type) { 407 case *desc.FileDescriptor: 408 for _, ch := range d.GetMessageTypes() { 409 checkDescriptorComments(ch) 410 } 411 for _, ch := range d.GetEnumTypes() { 412 checkDescriptorComments(ch) 413 } 414 for _, ch := range d.GetExtensions() { 415 checkDescriptorComments(ch) 416 } 417 for _, ch := range d.GetServices() { 418 checkDescriptorComments(ch) 419 } 420 // files don't have comments, so bail out before check below 421 return 422 case *desc.MessageDescriptor: 423 if d.IsMapEntry() { 424 // map entry messages have no comments (and neither do their child fields) 425 return 426 } 427 for _, ch := range d.GetFields() { 428 checkDescriptorComments(ch) 429 } 430 for _, ch := range d.GetNestedMessageTypes() { 431 checkDescriptorComments(ch) 432 } 433 for _, ch := range d.GetNestedEnumTypes() { 434 checkDescriptorComments(ch) 435 } 436 for _, ch := range d.GetNestedExtensions() { 437 checkDescriptorComments(ch) 438 } 439 for _, ch := range d.GetOneOfs() { 440 checkDescriptorComments(ch) 441 } 442 case *desc.FieldDescriptor: 443 if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP { 444 // groups comments are on the message, not hte field; so bail out before check below 445 return 446 } 447 case *desc.EnumDescriptor: 448 for _, ch := range d.GetValues() { 449 checkDescriptorComments(ch) 450 } 451 case *desc.ServiceDescriptor: 452 for _, ch := range d.GetMethods() { 453 checkDescriptorComments(ch) 454 } 455 } 456 457 descCount++ 458 testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(), 459 "wrong comment for descriptor %s", d.GetFullyQualifiedName()) 460 } 461 462 checkDescriptorComments(fd) 463 testutil.Eq(t, count, descCount) 464 } 465 466 func TestBuilder_PreserveAllCommentsAfterBuild(t *testing.T) { 467 files := map[string]string{"test.proto": ` 468 syntax = "proto3"; 469 470 // Leading detached comment for SimpleEnum 471 472 // Leading comment for SimpleEnum 473 enum SimpleEnum { 474 // Trailing comment for SimpleEnum 475 476 // Leading detached comment for VALUE0 477 478 // Leading comment for VALUE0 479 VALUE0 = 0; // Trailing comment for VALUE0 480 } 481 482 // Leading detached comment for SimpleMessage 483 484 // Leading comment for SimpleMessage 485 message SimpleMessage { 486 // Trailing comment for SimpleMessage 487 488 // Leading detached comment for field1 489 490 // Leading comment for field1 491 optional SimpleEnum field1 = 1; // Trailing comment for field1 492 } 493 `} 494 495 pa := &protoparse.Parser{ 496 Accessor: protoparse.FileContentsFromMap(files), 497 IncludeSourceCodeInfo: true, 498 } 499 fds, err := pa.ParseFiles("test.proto") 500 testutil.Ok(t, err) 501 502 fb, err := FromFile(fds[0]) 503 testutil.Ok(t, err) 504 505 fd, err := fb.Build() 506 testutil.Ok(t, err) 507 508 var checkDescriptorComments func(d desc.Descriptor) 509 checkDescriptorComments = func(d desc.Descriptor) { 510 // fmt.Println(d.GetFullyQualifiedName(), d.GetSourceInfo().GetLeadingDetachedComments(), d.GetSourceInfo().GetLeadingComments(), d.GetSourceInfo().GetTrailingComments()) 511 switch d := d.(type) { 512 case *desc.FileDescriptor: 513 for _, ch := range d.GetMessageTypes() { 514 checkDescriptorComments(ch) 515 } 516 for _, ch := range d.GetEnumTypes() { 517 checkDescriptorComments(ch) 518 } 519 // files don't have comments, so bail out before check below 520 return 521 case *desc.MessageDescriptor: 522 if d.IsMapEntry() { 523 // map entry messages have no comments (and neither do their child fields) 524 return 525 } 526 for _, ch := range d.GetFields() { 527 checkDescriptorComments(ch) 528 } 529 case *desc.FieldDescriptor: 530 if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP { 531 // groups comments are on the message, not hte field; so bail out before check below 532 return 533 } 534 case *desc.EnumDescriptor: 535 for _, ch := range d.GetValues() { 536 checkDescriptorComments(ch) 537 } 538 } 539 testutil.Eq(t, 1, len(d.GetSourceInfo().GetLeadingDetachedComments()), 540 "wrong number of leading detached comments for %s", d.GetFullyQualifiedName()) 541 testutil.Eq(t, fmt.Sprintf(" Leading detached comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingDetachedComments()[0], 542 "wrong leading detached comment for descriptor %s", d.GetFullyQualifiedName()) 543 testutil.Eq(t, fmt.Sprintf(" Leading comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(), 544 "wrong leading comment for descriptor %s", d.GetFullyQualifiedName()) 545 testutil.Eq(t, fmt.Sprintf(" Trailing comment for %s\n", d.GetName()), d.GetSourceInfo().GetTrailingComments(), 546 "wrong trailing comment for descriptor %s", d.GetFullyQualifiedName()) 547 } 548 549 checkDescriptorComments(fd) 550 } 551 552 func loadProtoset(path string) (*desc.FileDescriptor, error) { 553 var fds descriptorpb.FileDescriptorSet 554 f, err := os.Open(path) 555 if err != nil { 556 return nil, err 557 } 558 defer f.Close() 559 bb, err := io.ReadAll(f) 560 if err != nil { 561 return nil, err 562 } 563 if err = proto.Unmarshal(bb, &fds); err != nil { 564 return nil, err 565 } 566 return desc.CreateFileDescriptorFromSet(&fds) 567 } 568 569 func roundTripFile(t *testing.T, fd *desc.FileDescriptor) { 570 // First, recursively verify that every child element can be converted to a 571 // Builder and back without loss of fidelity. 572 for _, md := range fd.GetMessageTypes() { 573 roundTripMessage(t, md) 574 } 575 for _, ed := range fd.GetEnumTypes() { 576 roundTripEnum(t, ed) 577 } 578 for _, exd := range fd.GetExtensions() { 579 roundTripField(t, exd) 580 } 581 for _, sd := range fd.GetServices() { 582 roundTripService(t, sd) 583 } 584 585 // Finally, we check the whole file itself. 586 fb, err := FromFile(fd) 587 testutil.Ok(t, err) 588 589 roundTripped, err := fb.Build() 590 testutil.Ok(t, err) 591 592 // Round tripping from a file descriptor to a builder and back will 593 // experience some minor changes (that do not impact the semantics of 594 // any of the file's contents): 595 // 1. The builder sorts dependencies. However the original file 596 // descriptor has dependencies in the order they appear in import 597 // statements in the source file. 598 // 2. The builder imports the actual source of all elements and never 599 // uses public imports. The original file, on the other hand, could 600 // use public imports and "indirectly" import other files that way. 601 // 3. The builder never emits weak imports. 602 // 4. The builder behaves like protoc in that it emits nil as the file 603 // package if none is set. However the new protobuf runtime, when 604 // reconstructing the proto from a protoreflect.FileDescriptor, will 605 // instead emit a pointer to empty string :( 606 // 5. The builder tries to preserve SourceCodeInfo, but will not preserve 607 // position information. So that info does not survive round-tripping 608 // (though comments do: there is a separate test for that). Also, the 609 // round-tripped version will have source code info (even though it 610 // may have no comments and zero position info), even if the original 611 // descriptor had none. 612 // So we're going to modify the original descriptor in the same ways. 613 // That way, a simple proto.Equal() check will suffice to confirm that 614 // the file descriptor survived the round trip. 615 616 // The files we are testing have one occurrence of a public import. The 617 // file nopkg/desc_test_nopkg.proto declares nothing and public imports 618 // nopkg/desc_test_nopkg_new.proto. So any file that depends on the 619 // former will be updated to instead depend on the latter (since it is 620 // the actual file that declares used elements). 621 fdp := fd.AsFileDescriptorProto() 622 needsNopkgNew := false 623 hasNoPkgNew := false 624 for _, dep := range fdp.Dependency { 625 if dep == "nopkg/desc_test_nopkg.proto" { 626 needsNopkgNew = true 627 } 628 if dep == "nopkg/desc_test_nopkg_new.proto" { 629 hasNoPkgNew = false 630 } 631 } 632 if needsNopkgNew && !hasNoPkgNew { 633 fdp.Dependency = append(fdp.Dependency, "nopkg/desc_test_nopkg_new.proto") 634 } 635 636 // Strip any public and weak imports. (The step above should have "fixed" 637 // files to handle any actual public import encountered.) 638 fdp.PublicDependency = nil 639 fdp.WeakDependency = nil 640 641 // Fix the one we loaded so it uses nil as the package instead of an 642 // empty string, since that is what builders produce. 643 if fdp.GetPackage() == "" { 644 fdp.Package = nil 645 } 646 647 // Remove source code info: what the builder generates is not expected to 648 // match the original source. 649 fdp.SourceCodeInfo = nil 650 roundTripped.AsFileDescriptorProto().SourceCodeInfo = nil 651 652 // Finally, sort the imports. That way they match the built result (which 653 // is always sorted). 654 sort.Strings(fdp.Dependency) 655 656 // Now (after tweaking) the original should match the round-tripped descriptor: 657 testutil.Require(t, proto.Equal(fdp, roundTripped.AsProto()), "File %q failed round trip.\nExpecting: %s\nGot: %s\n", 658 fd.GetName(), proto.MarshalTextString(fdp), proto.MarshalTextString(roundTripped.AsProto())) 659 } 660 661 func roundTripMessage(t *testing.T, md *desc.MessageDescriptor) { 662 // first recursively validate all nested elements 663 for _, fld := range md.GetFields() { 664 roundTripField(t, fld) 665 } 666 for _, ood := range md.GetOneOfs() { 667 oob, err := FromOneOf(ood) 668 testutil.Ok(t, err) 669 roundTripped, err := oob.Build() 670 testutil.Ok(t, err) 671 checkDescriptors(t, ood, roundTripped) 672 } 673 for _, nmd := range md.GetNestedMessageTypes() { 674 roundTripMessage(t, nmd) 675 } 676 for _, ed := range md.GetNestedEnumTypes() { 677 roundTripEnum(t, ed) 678 } 679 for _, exd := range md.GetNestedExtensions() { 680 roundTripField(t, exd) 681 } 682 683 mb, err := FromMessage(md) 684 testutil.Ok(t, err) 685 roundTripped, err := mb.Build() 686 testutil.Ok(t, err) 687 checkDescriptors(t, md, roundTripped) 688 } 689 690 func roundTripEnum(t *testing.T, ed *desc.EnumDescriptor) { 691 // first recursively validate all nested elements 692 for _, evd := range ed.GetValues() { 693 evb, err := FromEnumValue(evd) 694 testutil.Ok(t, err) 695 roundTripped, err := evb.Build() 696 testutil.Ok(t, err) 697 checkDescriptors(t, evd, roundTripped) 698 } 699 700 eb, err := FromEnum(ed) 701 testutil.Ok(t, err) 702 roundTripped, err := eb.Build() 703 testutil.Ok(t, err) 704 checkDescriptors(t, ed, roundTripped) 705 } 706 707 func roundTripField(t *testing.T, fld *desc.FieldDescriptor) { 708 flb, err := FromField(fld) 709 testutil.Ok(t, err) 710 roundTripped, err := flb.Build() 711 testutil.Ok(t, err) 712 checkDescriptors(t, fld, roundTripped) 713 } 714 715 func roundTripService(t *testing.T, sd *desc.ServiceDescriptor) { 716 // first recursively validate all nested elements 717 for _, mtd := range sd.GetMethods() { 718 mtb, err := FromMethod(mtd) 719 testutil.Ok(t, err) 720 roundTripped, err := mtb.Build() 721 testutil.Ok(t, err) 722 checkDescriptors(t, mtd, roundTripped) 723 } 724 725 sb, err := FromService(sd) 726 testutil.Ok(t, err) 727 roundTripped, err := sb.Build() 728 testutil.Ok(t, err) 729 checkDescriptors(t, sd, roundTripped) 730 } 731 732 func checkDescriptors(t *testing.T, d1, d2 desc.Descriptor) { 733 testutil.Eq(t, d1.GetFullyQualifiedName(), d2.GetFullyQualifiedName()) 734 testutil.Require(t, proto.Equal(d1.AsProto(), d2.AsProto()), "%s failed round trip.\nExpecting: %s\nGot: %s\n", 735 d1.GetFullyQualifiedName(), proto.MarshalTextString(d1.AsProto()), proto.MarshalTextString(d2.AsProto())) 736 } 737 738 func TestAddRemoveMoveBuilders(t *testing.T) { 739 // add field to one-of 740 fld1 := NewField("foo", FieldTypeInt32()) 741 oo1 := NewOneOf("oofoo") 742 oo1.AddChoice(fld1) 743 checkChildren(t, oo1, fld1) 744 testutil.Eq(t, oo1.GetChoice("foo"), fld1) 745 746 // add one-of w/ field to a message 747 msg1 := NewMessage("foo") 748 msg1.AddOneOf(oo1) 749 checkChildren(t, msg1, oo1) 750 testutil.Eq(t, msg1.GetOneOf("oofoo"), oo1) 751 // field remains unchanged 752 testutil.Eq(t, fld1.GetParent(), oo1) 753 testutil.Eq(t, oo1.GetChoice("foo"), fld1) 754 // field also now registered with msg1 755 testutil.Eq(t, msg1.GetField("foo"), fld1) 756 757 // add empty one-of to message 758 oo2 := NewOneOf("oobar") 759 msg1.AddOneOf(oo2) 760 checkChildren(t, msg1, oo1, oo2) 761 testutil.Eq(t, msg1.GetOneOf("oobar"), oo2) 762 // now add field to that one-of 763 fld2 := NewField("bar", FieldTypeInt32()) 764 oo2.AddChoice(fld2) 765 checkChildren(t, oo2, fld2) 766 testutil.Eq(t, oo2.GetChoice("bar"), fld2) 767 // field also now registered with msg1 768 testutil.Eq(t, msg1.GetField("bar"), fld2) 769 770 // add fails due to name collisions 771 fld1dup := NewField("foo", FieldTypeInt32()) 772 err := oo1.TryAddChoice(fld1dup) 773 checkFailedAdd(t, err, oo1, fld1dup, "already contains field") 774 fld2 = NewField("bar", FieldTypeInt32()) 775 err = msg1.TryAddField(fld2) 776 checkFailedAdd(t, err, msg1, fld2, "already contains element") 777 msg2 := NewMessage("oofoo") 778 // name collision can be different type 779 // (here, nested message conflicts with a one-of) 780 err = msg1.TryAddNestedMessage(msg2) 781 checkFailedAdd(t, err, msg1, msg2, "already contains element") 782 783 msg2 = NewMessage("baz") 784 msg1.AddNestedMessage(msg2) 785 checkChildren(t, msg1, oo1, oo2, msg2) 786 testutil.Eq(t, msg1.GetNestedMessage("baz"), msg2) 787 788 // can't add extension or map fields to one-of 789 ext1 := NewExtension("abc", 123, FieldTypeInt32(), msg1) 790 err = oo1.TryAddChoice(ext1) 791 checkFailedAdd(t, err, oo1, ext1, "is an extension, not a regular field") 792 err = msg1.TryAddField(ext1) 793 checkFailedAdd(t, err, msg1, ext1, "is an extension, not a regular field") 794 mapField := NewMapField("abc", FieldTypeInt32(), FieldTypeString()) 795 err = oo1.TryAddChoice(mapField) 796 checkFailedAdd(t, err, oo1, mapField, "cannot add a map field") 797 // can add group field though 798 groupMsg := NewMessage("Group") 799 groupField := NewGroupField(groupMsg) 800 oo1.AddChoice(groupField) 801 checkChildren(t, oo1, fld1, groupField) 802 // adding map and group to msg succeeds 803 msg1.AddField(groupField) 804 msg1.AddField(mapField) 805 checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField) 806 // messages associated with map and group fields are not children of the 807 // message, but are in its scope and accessible via GetNestedMessage 808 testutil.Eq(t, msg1.GetNestedMessage("Group"), groupMsg) 809 testutil.Eq(t, msg1.GetNestedMessage("AbcEntry"), mapField.GetType().localMsgType) 810 811 // adding extension to message 812 ext2 := NewExtension("xyz", 234, FieldTypeInt32(), msg1) 813 msg1.AddNestedExtension(ext2) 814 checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField, ext2) 815 err = msg1.TryAddNestedExtension(ext1) // name collision 816 checkFailedAdd(t, err, msg1, ext1, "already contains element") 817 fld3 := NewField("ijk", FieldTypeString()) 818 err = msg1.TryAddNestedExtension(fld3) 819 checkFailedAdd(t, err, msg1, fld3, "is not an extension") 820 821 // add enum values to enum 822 enumVal1 := NewEnumValue("A") 823 enum1 := NewEnum("bazel") 824 enum1.AddValue(enumVal1) 825 checkChildren(t, enum1, enumVal1) 826 testutil.Eq(t, enum1.GetValue("A"), enumVal1) 827 enumVal2 := NewEnumValue("B") 828 enum1.AddValue(enumVal2) 829 checkChildren(t, enum1, enumVal1, enumVal2) 830 testutil.Eq(t, enum1.GetValue("B"), enumVal2) 831 // fail w/ name collision 832 enumVal3 := NewEnumValue("B") 833 err = enum1.TryAddValue(enumVal3) 834 checkFailedAdd(t, err, enum1, enumVal3, "already contains value") 835 836 msg2.AddNestedEnum(enum1) 837 checkChildren(t, msg2, enum1) 838 testutil.Eq(t, msg2.GetNestedEnum("bazel"), enum1) 839 ext3 := NewExtension("bazel", 987, FieldTypeString(), msg2) 840 err = msg2.TryAddNestedExtension(ext3) 841 checkFailedAdd(t, err, msg2, ext3, "already contains element") 842 843 // services and methods 844 mtd1 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false)) 845 svc1 := NewService("FooService") 846 svc1.AddMethod(mtd1) 847 checkChildren(t, svc1, mtd1) 848 testutil.Eq(t, svc1.GetMethod("foo"), mtd1) 849 mtd2 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false)) 850 err = svc1.TryAddMethod(mtd2) 851 checkFailedAdd(t, err, svc1, mtd2, "already contains method") 852 853 // finally, test adding things to a file 854 fb := NewFile("") 855 fb.AddMessage(msg1) 856 checkChildren(t, fb, msg1) 857 testutil.Eq(t, fb.GetMessage("foo"), msg1) 858 fb.AddService(svc1) 859 checkChildren(t, fb, msg1, svc1) 860 testutil.Eq(t, fb.GetService("FooService"), svc1) 861 enum2 := NewEnum("fizzle") 862 fb.AddEnum(enum2) 863 checkChildren(t, fb, msg1, svc1, enum2) 864 testutil.Eq(t, fb.GetEnum("fizzle"), enum2) 865 ext3 = NewExtension("foosball", 123, FieldTypeInt32(), msg1) 866 fb.AddExtension(ext3) 867 checkChildren(t, fb, msg1, svc1, enum2, ext3) 868 testutil.Eq(t, fb.GetExtension("foosball"), ext3) 869 870 // errors and name collisions 871 err = fb.TryAddExtension(fld3) 872 checkFailedAdd(t, err, fb, fld3, "is not an extension") 873 msg3 := NewMessage("fizzle") 874 err = fb.TryAddMessage(msg3) 875 checkFailedAdd(t, err, fb, msg3, "already contains element") 876 enum3 := NewEnum("foosball") 877 err = fb.TryAddEnum(enum3) 878 checkFailedAdd(t, err, fb, enum3, "already contains element") 879 880 // TODO: test moving and removing, too 881 } 882 883 func checkChildren(t *testing.T, parent Builder, children ...Builder) { 884 testutil.Eq(t, len(children), len(parent.GetChildren()), "Wrong number of children for %s (%T)", GetFullyQualifiedName(parent), parent) 885 ch := map[Builder]struct{}{} 886 for _, child := range children { 887 testutil.Eq(t, child.GetParent(), parent, "Child %s (%T) does not report %s (%T) as its parent", child.GetName(), child, GetFullyQualifiedName(parent), parent) 888 ch[child] = struct{}{} 889 } 890 for _, child := range parent.GetChildren() { 891 _, ok := ch[child] 892 testutil.Require(t, ok, "Child %s (%T) does appear in list of children for %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent) 893 } 894 } 895 896 func checkFailedAdd(t *testing.T, err error, parent Builder, child Builder, errorMsg string) { 897 testutil.Require(t, err != nil, "Expecting error assigning %s (%T) to %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent) 898 testutil.Require(t, strings.Contains(err.Error(), errorMsg), "Expecting error assigning %s (%T) to %s (%T) to contain text %q: %q", child.GetName(), child, GetFullyQualifiedName(parent), parent, errorMsg, err.Error()) 899 testutil.Eq(t, nil, child.GetParent(), "Child %s (%T) should not have a parent after failed add", child.GetName(), child) 900 for _, ch := range parent.GetChildren() { 901 testutil.Require(t, ch != child, "Child %s (%T) should not appear in list of children for %s (%T) but does", child.GetName(), child, GetFullyQualifiedName(parent), parent) 902 } 903 } 904 905 func TestRenamingBuilders(t *testing.T) { 906 // TODO 907 } 908 909 func TestRenumberingFields(t *testing.T) { 910 // TODO 911 } 912 913 var ( 914 fileOptionsDesc, msgOptionsDesc, fieldOptionsDesc, oneofOptionsDesc, extRangeOptionsDesc, 915 enumOptionsDesc, enumValOptionsDesc, svcOptionsDesc, mtdOptionsDesc *desc.MessageDescriptor 916 ) 917 918 func init() { 919 var err error 920 fileOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.FileOptions)(nil)) 921 if err != nil { 922 panic(err) 923 } 924 msgOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.MessageOptions)(nil)) 925 if err != nil { 926 panic(err) 927 } 928 fieldOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.FieldOptions)(nil)) 929 if err != nil { 930 panic(err) 931 } 932 oneofOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.OneofOptions)(nil)) 933 if err != nil { 934 panic(err) 935 } 936 extRangeOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.ExtensionRangeOptions)(nil)) 937 if err != nil { 938 panic(err) 939 } 940 enumOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.EnumOptions)(nil)) 941 if err != nil { 942 panic(err) 943 } 944 enumValOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.EnumValueOptions)(nil)) 945 if err != nil { 946 panic(err) 947 } 948 svcOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.ServiceOptions)(nil)) 949 if err != nil { 950 panic(err) 951 } 952 mtdOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.MethodOptions)(nil)) 953 if err != nil { 954 panic(err) 955 } 956 } 957 958 func TestCustomOptionsDiscoveredInSameFile(t *testing.T) { 959 // Add option for every type to file 960 file := NewFile("foo.proto") 961 962 fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc) 963 file.AddExtension(fileOpt) 964 965 msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc) 966 file.AddExtension(msgOpt) 967 968 fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc) 969 file.AddExtension(fieldOpt) 970 971 oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc) 972 file.AddExtension(oneofOpt) 973 974 extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc) 975 file.AddExtension(extRangeOpt) 976 977 enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc) 978 file.AddExtension(enumOpt) 979 980 enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc) 981 file.AddExtension(enumValOpt) 982 983 svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc) 984 file.AddExtension(svcOpt) 985 986 mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc) 987 file.AddExtension(mtdOpt) 988 989 // Now we can test referring to these and making sure they show up correctly 990 // in built descriptors 991 992 t.Run("file options", func(t *testing.T) { 993 fb := clone(t, file) 994 fb.Options = &descriptorpb.FileOptions{} 995 ext, err := fileOpt.Build() 996 testutil.Ok(t, err) 997 err = dynamic.SetExtension(fb.Options, ext, "fubar") 998 testutil.Ok(t, err) 999 checkBuildWithLocalExtensions(t, fb) 1000 }) 1001 1002 t.Run("message options", func(t *testing.T) { 1003 mb := NewMessage("Foo") 1004 mb.Options = &descriptorpb.MessageOptions{} 1005 ext, err := msgOpt.Build() 1006 testutil.Ok(t, err) 1007 err = dynamic.SetExtension(mb.Options, ext, "fubar") 1008 testutil.Ok(t, err) 1009 1010 fb := clone(t, file) 1011 fb.AddMessage(mb) 1012 checkBuildWithLocalExtensions(t, mb) 1013 }) 1014 1015 t.Run("field options", func(t *testing.T) { 1016 flb := NewField("foo", FieldTypeString()) 1017 flb.Options = &descriptorpb.FieldOptions{} 1018 // fields must be connected to a message 1019 mb := NewMessage("Foo").AddField(flb) 1020 ext, err := fieldOpt.Build() 1021 testutil.Ok(t, err) 1022 err = dynamic.SetExtension(flb.Options, ext, "fubar") 1023 testutil.Ok(t, err) 1024 1025 fb := clone(t, file) 1026 fb.AddMessage(mb) 1027 checkBuildWithLocalExtensions(t, flb) 1028 }) 1029 1030 t.Run("oneof options", func(t *testing.T) { 1031 oob := NewOneOf("oo") 1032 oob.AddChoice(NewField("foo", FieldTypeString())) 1033 oob.Options = &descriptorpb.OneofOptions{} 1034 // oneofs must be connected to a message 1035 mb := NewMessage("Foo").AddOneOf(oob) 1036 ext, err := oneofOpt.Build() 1037 testutil.Ok(t, err) 1038 err = dynamic.SetExtension(oob.Options, ext, "fubar") 1039 testutil.Ok(t, err) 1040 1041 fb := clone(t, file) 1042 fb.AddMessage(mb) 1043 checkBuildWithLocalExtensions(t, oob) 1044 }) 1045 1046 t.Run("extension range options", func(t *testing.T) { 1047 var erOpts descriptorpb.ExtensionRangeOptions 1048 ext, err := extRangeOpt.Build() 1049 testutil.Ok(t, err) 1050 err = dynamic.SetExtension(&erOpts, ext, "fubar") 1051 testutil.Ok(t, err) 1052 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 1053 1054 fb := clone(t, file) 1055 fb.AddMessage(mb) 1056 checkBuildWithLocalExtensions(t, mb) 1057 }) 1058 1059 t.Run("enum options", func(t *testing.T) { 1060 eb := NewEnum("Foo") 1061 eb.AddValue(NewEnumValue("FOO")) 1062 eb.Options = &descriptorpb.EnumOptions{} 1063 ext, err := enumOpt.Build() 1064 testutil.Ok(t, err) 1065 err = dynamic.SetExtension(eb.Options, ext, "fubar") 1066 testutil.Ok(t, err) 1067 1068 fb := clone(t, file) 1069 fb.AddEnum(eb) 1070 checkBuildWithLocalExtensions(t, eb) 1071 }) 1072 1073 t.Run("enum val options", func(t *testing.T) { 1074 evb := NewEnumValue("FOO") 1075 // enum values must be connected to an enum 1076 eb := NewEnum("Foo").AddValue(evb) 1077 evb.Options = &descriptorpb.EnumValueOptions{} 1078 ext, err := enumValOpt.Build() 1079 testutil.Ok(t, err) 1080 err = dynamic.SetExtension(evb.Options, ext, "fubar") 1081 testutil.Ok(t, err) 1082 1083 fb := clone(t, file) 1084 fb.AddEnum(eb) 1085 checkBuildWithLocalExtensions(t, evb) 1086 }) 1087 1088 t.Run("service options", func(t *testing.T) { 1089 sb := NewService("Foo") 1090 sb.Options = &descriptorpb.ServiceOptions{} 1091 ext, err := svcOpt.Build() 1092 testutil.Ok(t, err) 1093 err = dynamic.SetExtension(sb.Options, ext, "fubar") 1094 testutil.Ok(t, err) 1095 1096 fb := clone(t, file) 1097 fb.AddService(sb) 1098 checkBuildWithLocalExtensions(t, sb) 1099 }) 1100 1101 t.Run("method options", func(t *testing.T) { 1102 req := NewMessage("Request") 1103 resp := NewMessage("Response") 1104 mtb := NewMethod("Foo", 1105 RpcTypeMessage(req, false), 1106 RpcTypeMessage(resp, false)) 1107 // methods must be connected to a service 1108 sb := NewService("Bar").AddMethod(mtb) 1109 mtb.Options = &descriptorpb.MethodOptions{} 1110 ext, err := mtdOpt.Build() 1111 testutil.Ok(t, err) 1112 err = dynamic.SetExtension(mtb.Options, ext, "fubar") 1113 testutil.Ok(t, err) 1114 1115 fb := clone(t, file) 1116 fb.AddService(sb).AddMessage(req).AddMessage(resp) 1117 checkBuildWithLocalExtensions(t, mtb) 1118 }) 1119 } 1120 1121 func checkBuildWithLocalExtensions(t *testing.T, builder Builder) { 1122 // requiring options and succeeding (since they are defined locally) 1123 var opts BuilderOptions 1124 opts.RequireInterpretedOptions = true 1125 d, err := opts.Build(builder) 1126 testutil.Ok(t, err) 1127 // since they are defined locally, no extra imports 1128 testutil.Eq(t, []string{"google/protobuf/descriptor.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency()) 1129 } 1130 1131 func TestCustomOptionsDiscoveredInDependencies(t *testing.T) { 1132 // Add option for every type to file 1133 file := NewFile("options.proto") 1134 1135 fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc) 1136 file.AddExtension(fileOpt) 1137 1138 msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc) 1139 file.AddExtension(msgOpt) 1140 1141 fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc) 1142 file.AddExtension(fieldOpt) 1143 1144 oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc) 1145 file.AddExtension(oneofOpt) 1146 1147 extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc) 1148 file.AddExtension(extRangeOpt) 1149 1150 enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc) 1151 file.AddExtension(enumOpt) 1152 1153 enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc) 1154 file.AddExtension(enumValOpt) 1155 1156 svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc) 1157 file.AddExtension(svcOpt) 1158 1159 mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc) 1160 file.AddExtension(mtdOpt) 1161 1162 fileDesc, err := file.Build() 1163 testutil.Ok(t, err) 1164 1165 // Another file that imports the options. Since it's not a public import, presence 1166 // of this file should not prevent builder from correctly adding options.proto dependency 1167 // in the "auto" case below. 1168 otherFileDesc, err := NewFile("other.proto").AddImportedDependency(fileDesc).Build() 1169 testutil.Ok(t, err) 1170 1171 regWithOpts := &dynamic.ExtensionRegistry{} 1172 regWithOpts.AddExtensionsFromFileRecursively(fileDesc) 1173 1174 // Now we can test referring to these and making sure they show up correctly 1175 // in built descriptors 1176 for name, useBuilder := range map[string]*bool{"descriptor": proto.Bool(false), "builder": proto.Bool(true), "auto": nil} { 1177 newFile := func() *FileBuilder { 1178 fb := NewFile("foo.proto").AddImportedDependency(otherFileDesc) 1179 if useBuilder != nil { 1180 if *useBuilder { 1181 fb.AddDependency(file) 1182 } else { 1183 fb.AddImportedDependency(fileDesc) 1184 } 1185 } 1186 return fb 1187 } 1188 var extReg *dynamic.ExtensionRegistry 1189 if useBuilder == nil { 1190 // if providing neither builder nor descriptor, we need to provide 1191 // a registry for resolving custom options 1192 extReg = regWithOpts 1193 } 1194 t.Run(name, func(t *testing.T) { 1195 t.Run("file options", func(t *testing.T) { 1196 fb := newFile() 1197 fb.Options = &descriptorpb.FileOptions{} 1198 ext, err := fileOpt.Build() 1199 testutil.Ok(t, err) 1200 err = dynamic.SetExtension(fb.Options, ext, "fubar") 1201 testutil.Ok(t, err) 1202 checkBuildWithImportedExtensions(t, fb, extReg) 1203 }) 1204 1205 t.Run("message options", func(t *testing.T) { 1206 mb := NewMessage("Foo") 1207 mb.Options = &descriptorpb.MessageOptions{} 1208 ext, err := msgOpt.Build() 1209 testutil.Ok(t, err) 1210 err = dynamic.SetExtension(mb.Options, ext, "fubar") 1211 testutil.Ok(t, err) 1212 1213 fb := newFile() 1214 fb.AddMessage(mb) 1215 checkBuildWithImportedExtensions(t, mb, extReg) 1216 }) 1217 1218 t.Run("field options", func(t *testing.T) { 1219 flb := NewField("foo", FieldTypeString()) 1220 flb.Options = &descriptorpb.FieldOptions{} 1221 // fields must be connected to a message 1222 mb := NewMessage("Foo").AddField(flb) 1223 ext, err := fieldOpt.Build() 1224 testutil.Ok(t, err) 1225 err = dynamic.SetExtension(flb.Options, ext, "fubar") 1226 testutil.Ok(t, err) 1227 1228 fb := newFile() 1229 fb.AddMessage(mb) 1230 checkBuildWithImportedExtensions(t, flb, extReg) 1231 }) 1232 1233 t.Run("oneof options", func(t *testing.T) { 1234 oob := NewOneOf("oo") 1235 oob.AddChoice(NewField("foo", FieldTypeString())) 1236 oob.Options = &descriptorpb.OneofOptions{} 1237 // oneofs must be connected to a message 1238 mb := NewMessage("Foo").AddOneOf(oob) 1239 ext, err := oneofOpt.Build() 1240 testutil.Ok(t, err) 1241 err = dynamic.SetExtension(oob.Options, ext, "fubar") 1242 testutil.Ok(t, err) 1243 1244 fb := newFile() 1245 fb.AddMessage(mb) 1246 checkBuildWithImportedExtensions(t, oob, extReg) 1247 }) 1248 1249 t.Run("extension range options", func(t *testing.T) { 1250 var erOpts descriptorpb.ExtensionRangeOptions 1251 ext, err := extRangeOpt.Build() 1252 testutil.Ok(t, err) 1253 err = dynamic.SetExtension(&erOpts, ext, "fubar") 1254 testutil.Ok(t, err) 1255 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 1256 1257 fb := newFile() 1258 fb.AddMessage(mb) 1259 checkBuildWithImportedExtensions(t, mb, extReg) 1260 }) 1261 1262 t.Run("enum options", func(t *testing.T) { 1263 eb := NewEnum("Foo") 1264 eb.AddValue(NewEnumValue("FOO")) 1265 eb.Options = &descriptorpb.EnumOptions{} 1266 ext, err := enumOpt.Build() 1267 testutil.Ok(t, err) 1268 err = dynamic.SetExtension(eb.Options, ext, "fubar") 1269 testutil.Ok(t, err) 1270 1271 fb := newFile() 1272 fb.AddEnum(eb) 1273 checkBuildWithImportedExtensions(t, eb, extReg) 1274 }) 1275 1276 t.Run("enum val options", func(t *testing.T) { 1277 evb := NewEnumValue("FOO") 1278 // enum values must be connected to an enum 1279 eb := NewEnum("Foo").AddValue(evb) 1280 evb.Options = &descriptorpb.EnumValueOptions{} 1281 ext, err := enumValOpt.Build() 1282 testutil.Ok(t, err) 1283 err = dynamic.SetExtension(evb.Options, ext, "fubar") 1284 testutil.Ok(t, err) 1285 1286 fb := newFile() 1287 fb.AddEnum(eb) 1288 checkBuildWithImportedExtensions(t, evb, extReg) 1289 }) 1290 1291 t.Run("service options", func(t *testing.T) { 1292 sb := NewService("Foo") 1293 sb.Options = &descriptorpb.ServiceOptions{} 1294 ext, err := svcOpt.Build() 1295 testutil.Ok(t, err) 1296 err = dynamic.SetExtension(sb.Options, ext, "fubar") 1297 testutil.Ok(t, err) 1298 1299 fb := newFile() 1300 fb.AddService(sb) 1301 checkBuildWithImportedExtensions(t, sb, extReg) 1302 }) 1303 1304 t.Run("method options", func(t *testing.T) { 1305 req := NewMessage("Request") 1306 resp := NewMessage("Response") 1307 mtb := NewMethod("Foo", 1308 RpcTypeMessage(req, false), 1309 RpcTypeMessage(resp, false)) 1310 // methods must be connected to a service 1311 sb := NewService("Bar").AddMethod(mtb) 1312 mtb.Options = &descriptorpb.MethodOptions{} 1313 ext, err := mtdOpt.Build() 1314 testutil.Ok(t, err) 1315 err = dynamic.SetExtension(mtb.Options, ext, "fubar") 1316 testutil.Ok(t, err) 1317 1318 fb := newFile() 1319 fb.AddService(sb).AddMessage(req).AddMessage(resp) 1320 checkBuildWithImportedExtensions(t, mtb, extReg) 1321 }) 1322 }) 1323 } 1324 } 1325 1326 func checkBuildWithImportedExtensions(t *testing.T, builder Builder, extReg *dynamic.ExtensionRegistry) { 1327 // requiring options and succeeding (since they are defined in explicit import) 1328 opts := BuilderOptions{ 1329 RequireInterpretedOptions: true, 1330 Extensions: extReg, 1331 } 1332 d, err := opts.Build(builder) 1333 testutil.Ok(t, err) 1334 // the only import is the explicitly added one and one added for the custom options 1335 testutil.Eq(t, []string{"options.proto", "other.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency()) 1336 } 1337 1338 func TestUseOfExtensionRegistry(t *testing.T) { 1339 // Add option for every type to extension registry 1340 var exts dynamic.ExtensionRegistry 1341 1342 fileOpt, err := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc).Build() 1343 testutil.Ok(t, err) 1344 err = exts.AddExtension(fileOpt) 1345 testutil.Ok(t, err) 1346 1347 msgOpt, err := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc).Build() 1348 testutil.Ok(t, err) 1349 err = exts.AddExtension(msgOpt) 1350 testutil.Ok(t, err) 1351 1352 fieldOpt, err := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc).Build() 1353 testutil.Ok(t, err) 1354 err = exts.AddExtension(fieldOpt) 1355 testutil.Ok(t, err) 1356 1357 oneofOpt, err := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc).Build() 1358 testutil.Ok(t, err) 1359 err = exts.AddExtension(oneofOpt) 1360 testutil.Ok(t, err) 1361 1362 extRangeOpt, err := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc).Build() 1363 testutil.Ok(t, err) 1364 err = exts.AddExtension(extRangeOpt) 1365 testutil.Ok(t, err) 1366 1367 enumOpt, err := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc).Build() 1368 testutil.Ok(t, err) 1369 err = exts.AddExtension(enumOpt) 1370 testutil.Ok(t, err) 1371 1372 enumValOpt, err := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc).Build() 1373 testutil.Ok(t, err) 1374 err = exts.AddExtension(enumValOpt) 1375 testutil.Ok(t, err) 1376 1377 svcOpt, err := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc).Build() 1378 testutil.Ok(t, err) 1379 err = exts.AddExtension(svcOpt) 1380 testutil.Ok(t, err) 1381 1382 mtdOpt, err := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc).Build() 1383 testutil.Ok(t, err) 1384 err = exts.AddExtension(mtdOpt) 1385 testutil.Ok(t, err) 1386 1387 // Now we can test referring to these and making sure they show up correctly 1388 // in built descriptors 1389 1390 t.Run("file options", func(t *testing.T) { 1391 fb := NewFile("foo.proto") 1392 fb.Options = &descriptorpb.FileOptions{} 1393 err = dynamic.SetExtension(fb.Options, fileOpt, "fubar") 1394 testutil.Ok(t, err) 1395 checkBuildWithExtensions(t, &exts, fileOpt.GetFile(), fb) 1396 }) 1397 1398 t.Run("message options", func(t *testing.T) { 1399 mb := NewMessage("Foo") 1400 mb.Options = &descriptorpb.MessageOptions{} 1401 err = dynamic.SetExtension(mb.Options, msgOpt, "fubar") 1402 testutil.Ok(t, err) 1403 checkBuildWithExtensions(t, &exts, msgOpt.GetFile(), mb) 1404 }) 1405 1406 t.Run("field options", func(t *testing.T) { 1407 flb := NewField("foo", FieldTypeString()) 1408 flb.Options = &descriptorpb.FieldOptions{} 1409 // fields must be connected to a message 1410 NewMessage("Foo").AddField(flb) 1411 err = dynamic.SetExtension(flb.Options, fieldOpt, "fubar") 1412 testutil.Ok(t, err) 1413 checkBuildWithExtensions(t, &exts, fieldOpt.GetFile(), flb) 1414 }) 1415 1416 t.Run("oneof options", func(t *testing.T) { 1417 oob := NewOneOf("oo") 1418 oob.AddChoice(NewField("foo", FieldTypeString())) 1419 oob.Options = &descriptorpb.OneofOptions{} 1420 // oneofs must be connected to a message 1421 NewMessage("Foo").AddOneOf(oob) 1422 err = dynamic.SetExtension(oob.Options, oneofOpt, "fubar") 1423 testutil.Ok(t, err) 1424 checkBuildWithExtensions(t, &exts, oneofOpt.GetFile(), oob) 1425 }) 1426 1427 t.Run("extension range options", func(t *testing.T) { 1428 var erOpts descriptorpb.ExtensionRangeOptions 1429 err = dynamic.SetExtension(&erOpts, extRangeOpt, "fubar") 1430 testutil.Ok(t, err) 1431 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 1432 checkBuildWithExtensions(t, &exts, extRangeOpt.GetFile(), mb) 1433 }) 1434 1435 t.Run("enum options", func(t *testing.T) { 1436 eb := NewEnum("Foo") 1437 eb.AddValue(NewEnumValue("FOO")) 1438 eb.Options = &descriptorpb.EnumOptions{} 1439 err = dynamic.SetExtension(eb.Options, enumOpt, "fubar") 1440 testutil.Ok(t, err) 1441 checkBuildWithExtensions(t, &exts, enumOpt.GetFile(), eb) 1442 }) 1443 1444 t.Run("enum val options", func(t *testing.T) { 1445 evb := NewEnumValue("FOO") 1446 // enum values must be connected to an enum 1447 NewEnum("Foo").AddValue(evb) 1448 evb.Options = &descriptorpb.EnumValueOptions{} 1449 err = dynamic.SetExtension(evb.Options, enumValOpt, "fubar") 1450 testutil.Ok(t, err) 1451 checkBuildWithExtensions(t, &exts, enumValOpt.GetFile(), evb) 1452 }) 1453 1454 t.Run("service options", func(t *testing.T) { 1455 sb := NewService("Foo") 1456 sb.Options = &descriptorpb.ServiceOptions{} 1457 err = dynamic.SetExtension(sb.Options, svcOpt, "fubar") 1458 testutil.Ok(t, err) 1459 checkBuildWithExtensions(t, &exts, svcOpt.GetFile(), sb) 1460 }) 1461 1462 t.Run("method options", func(t *testing.T) { 1463 mtb := NewMethod("Foo", 1464 RpcTypeMessage(NewMessage("Request"), false), 1465 RpcTypeMessage(NewMessage("Response"), false)) 1466 // methods must be connected to a service 1467 NewService("Bar").AddMethod(mtb) 1468 mtb.Options = &descriptorpb.MethodOptions{} 1469 err = dynamic.SetExtension(mtb.Options, mtdOpt, "fubar") 1470 testutil.Ok(t, err) 1471 checkBuildWithExtensions(t, &exts, mtdOpt.GetFile(), mtb) 1472 }) 1473 } 1474 1475 func checkBuildWithExtensions(t *testing.T, exts *dynamic.ExtensionRegistry, expected *desc.FileDescriptor, builder Builder) { 1476 // without interpreting custom option 1477 d, err := builder.BuildDescriptor() 1478 testutil.Ok(t, err) 1479 for _, dep := range d.GetFile().GetDependencies() { 1480 testutil.Neq(t, expected, dep) 1481 } 1482 numDeps := len(d.GetFile().GetDependencies()) 1483 1484 // requiring options (and failing) 1485 var opts BuilderOptions 1486 opts.RequireInterpretedOptions = true 1487 _, err = opts.Build(builder) 1488 testutil.Require(t, err != nil) 1489 1490 // able to interpret options via extension registry 1491 opts.Extensions = exts 1492 d, err = opts.Build(builder) 1493 testutil.Ok(t, err) 1494 testutil.Eq(t, numDeps+1, len(d.GetFile().GetDependencies())) 1495 found := false 1496 for _, dep := range d.GetFile().GetDependencies() { 1497 if expected == dep { 1498 found = true 1499 break 1500 } 1501 } 1502 testutil.Require(t, found) 1503 } 1504 1505 func TestRemoveField(t *testing.T) { 1506 msg := NewMessage("FancyMessage"). 1507 AddField(NewField("one", FieldTypeInt64())). 1508 AddField(NewField("two", FieldTypeString())). 1509 AddField(NewField("three", FieldTypeString())) 1510 1511 ok := msg.TryRemoveField("two") 1512 children := msg.GetChildren() 1513 1514 testutil.Require(t, ok) 1515 testutil.Eq(t, 2, len(children)) 1516 testutil.Eq(t, "one", children[0].GetName()) 1517 testutil.Eq(t, "three", children[1].GetName()) 1518 } 1519 1520 func TestInterleavedFieldNumbers(t *testing.T) { 1521 msg := NewMessage("MessageWithInterleavedFieldNumbers"). 1522 AddField(NewField("one", FieldTypeInt64()).SetNumber(1)). 1523 AddField(NewField("two", FieldTypeInt64())). 1524 AddField(NewField("three", FieldTypeString()).SetNumber(3)). 1525 AddField(NewField("four", FieldTypeInt64())). 1526 AddField(NewField("five", FieldTypeString()).SetNumber(5)) 1527 1528 md, err := msg.Build() 1529 testutil.Ok(t, err) 1530 1531 testutil.Require(t, md.FindFieldByName("one") != nil) 1532 testutil.Eq(t, int32(1), md.FindFieldByName("one").GetNumber()) 1533 1534 testutil.Require(t, md.FindFieldByName("two") != nil) 1535 testutil.Eq(t, int32(2), md.FindFieldByName("two").GetNumber()) 1536 1537 testutil.Require(t, md.FindFieldByName("three") != nil) 1538 testutil.Eq(t, int32(3), md.FindFieldByName("three").GetNumber()) 1539 1540 testutil.Require(t, md.FindFieldByName("four") != nil) 1541 testutil.Eq(t, int32(4), md.FindFieldByName("four").GetNumber()) 1542 1543 testutil.Require(t, md.FindFieldByName("five") != nil) 1544 testutil.Eq(t, int32(5), md.FindFieldByName("five").GetNumber()) 1545 } 1546 1547 func clone(t *testing.T, fb *FileBuilder) *FileBuilder { 1548 fd, err := fb.Build() 1549 testutil.Ok(t, err) 1550 fb, err = FromFile(fd) 1551 testutil.Ok(t, err) 1552 return fb 1553 } 1554 1555 func TestPruneDependencies(t *testing.T) { 1556 msgOpts := &descriptorpb.MessageOptions{} 1557 msgOptsDesc, err := desc.LoadMessageDescriptorForMessage(msgOpts) 1558 testutil.Ok(t, err) 1559 extDesc, err := NewExtensionImported("foo", 20001, FieldTypeString(), msgOptsDesc).Build() 1560 testutil.Ok(t, err) 1561 1562 dm := dynamic.NewMessage(msgOptsDesc) 1563 dm.SetField(extDesc, "bar") 1564 err = dm.ConvertTo(msgOpts) 1565 testutil.Ok(t, err) 1566 1567 emptyDesc, err := desc.LoadMessageDescriptorForMessage(&emptypb.Empty{}) 1568 testutil.Ok(t, err) 1569 1570 // we have to explicitly import the file for the custom option 1571 fileB := NewFile("").AddImportedDependency(extDesc.GetFile()) 1572 msgB := NewMessage("Foo"). 1573 AddField(NewField("a", FieldTypeImportedMessage(emptyDesc))). 1574 SetOptions(msgOpts) 1575 fileDesc, err := fileB.AddMessage(msgB).Build() 1576 testutil.Ok(t, err) 1577 1578 // The file for msgDesc should have two imports: one for the custom option and 1579 // one for empty.proto. 1580 testutil.Eq(t, 2, len(fileDesc.GetDependencies())) 1581 testutil.Eq(t, "google/protobuf/empty.proto", fileDesc.GetDependencies()[0].GetName()) 1582 testutil.Eq(t, extDesc.GetFile().GetName(), fileDesc.GetDependencies()[1].GetName()) 1583 1584 // If we now remove the message's field, both imports are still there even 1585 // though the import for empty.proto is now unused. 1586 fileB, err = FromFile(fileDesc) 1587 testutil.Ok(t, err) 1588 fileB.GetMessage("Foo").RemoveField("a") 1589 newFileDesc, err := fileB.Build() 1590 testutil.Ok(t, err) 1591 testutil.Eq(t, 2, len(newFileDesc.GetDependencies())) 1592 testutil.Eq(t, "google/protobuf/empty.proto", newFileDesc.GetDependencies()[0].GetName()) 1593 testutil.Eq(t, extDesc.GetFile().GetName(), newFileDesc.GetDependencies()[1].GetName()) 1594 1595 // But if we prune unused dependencies, we'll see the import for empty.proto 1596 // gone. The other import for the custom option should be preserved. 1597 fileB, err = FromFile(fileDesc) 1598 testutil.Ok(t, err) 1599 fileB.GetMessage("Foo").RemoveField("a") 1600 newFileDesc, err = fileB.PruneUnusedDependencies().Build() 1601 testutil.Ok(t, err) 1602 testutil.Eq(t, 1, len(newFileDesc.GetDependencies())) 1603 testutil.Eq(t, extDesc.GetFile().GetName(), newFileDesc.GetDependencies()[0].GetName()) 1604 } 1605 1606 func TestInterleavedEnumNumbers(t *testing.T) { 1607 en := NewEnum("Options"). 1608 AddValue(NewEnumValue("OPTION_1").SetNumber(-1)). 1609 AddValue(NewEnumValue("OPTION_2")). 1610 AddValue(NewEnumValue("OPTION_3").SetNumber(2)). 1611 AddValue(NewEnumValue("OPTION_4").SetNumber(1)). 1612 AddValue(NewEnumValue("OPTION_5")). 1613 AddValue(NewEnumValue("OPTION_6").SetNumber(100)) 1614 1615 ed, err := en.Build() 1616 testutil.Ok(t, err) 1617 1618 testutil.Require(t, ed.FindValueByName("OPTION_1") != nil) 1619 testutil.Eq(t, int32(-1), ed.FindValueByName("OPTION_1").GetNumber()) 1620 1621 testutil.Require(t, ed.FindValueByName("OPTION_2") != nil) 1622 testutil.Eq(t, int32(0), ed.FindValueByName("OPTION_2").GetNumber()) 1623 1624 testutil.Require(t, ed.FindValueByName("OPTION_3") != nil) 1625 testutil.Eq(t, int32(2), ed.FindValueByName("OPTION_3").GetNumber()) 1626 1627 testutil.Require(t, ed.FindValueByName("OPTION_4") != nil) 1628 testutil.Eq(t, int32(1), ed.FindValueByName("OPTION_4").GetNumber()) 1629 1630 testutil.Require(t, ed.FindValueByName("OPTION_5") != nil) 1631 testutil.Eq(t, int32(3), ed.FindValueByName("OPTION_5").GetNumber()) 1632 1633 testutil.Require(t, ed.FindValueByName("OPTION_6") != nil) 1634 testutil.Eq(t, int32(100), ed.FindValueByName("OPTION_6").GetNumber()) 1635 } 1636 1637 func TestInvalid(t *testing.T) { 1638 testCases := []struct { 1639 name string 1640 builder func() Builder 1641 expectedError string 1642 }{ 1643 { 1644 name: "required in proto3", 1645 builder: func() Builder { 1646 return NewFile("foo.proto").SetProto3(true). 1647 AddMessage( 1648 NewMessage("Foo").AddField(NewField("foo", FieldTypeBool()).SetRequired()), 1649 ) 1650 }, 1651 expectedError: "only proto2 allows required fields", 1652 }, 1653 { 1654 name: "extension range in proto3", 1655 builder: func() Builder { 1656 return NewFile("foo.proto").SetProto3(true). 1657 AddMessage( 1658 NewMessage("Foo").AddExtensionRange(100, 1000), 1659 ) 1660 }, 1661 expectedError: "proto3 semantics cannot have extension ranges", 1662 }, 1663 { 1664 name: "group in proto3", 1665 builder: func() Builder { 1666 return NewFile("foo.proto").SetProto3(true). 1667 AddMessage( 1668 NewMessage("Foo").AddField(NewGroupField(NewMessage("Bar"))), 1669 ) 1670 }, 1671 expectedError: "invalid group: invalid under proto3 semantics", 1672 }, 1673 { 1674 name: "default value in proto3", 1675 builder: func() Builder { 1676 return NewFile("foo.proto").SetProto3(true). 1677 AddMessage( 1678 NewMessage("Foo").AddField(NewField("foo", FieldTypeString()).SetDefaultValue("abc")), 1679 ) 1680 }, 1681 expectedError: "invalid default: cannot be specified with implicit field presence", 1682 }, 1683 { 1684 name: "extension tag outside range", 1685 builder: func() Builder { 1686 msg := NewMessage("Foo").AddExtensionRange(100, 1000) 1687 return NewFile("foo.proto"). 1688 AddMessage(msg). 1689 AddExtension(NewExtension("foo", 1, FieldTypeString(), msg)) 1690 }, 1691 expectedError: "non-extension field number: 1", 1692 }, 1693 { 1694 name: "non-extension tag in extension range", 1695 builder: func() Builder { 1696 return NewFile("foo.proto"). 1697 AddMessage(NewMessage("Foo"). 1698 AddField(NewField("foo", FieldTypeBool()).SetNumber(100)). 1699 AddExtensionRange(100, 1000)) 1700 }, 1701 expectedError: "number 100 in extension range", 1702 }, 1703 { 1704 name: "tag in reserved range", 1705 builder: func() Builder { 1706 return NewFile("foo.proto"). 1707 AddMessage(NewMessage("Foo"). 1708 AddField(NewField("foo", FieldTypeBool()).SetNumber(100)). 1709 AddReservedRange(100, 1000)) 1710 }, 1711 expectedError: "must not use reserved number 100", 1712 }, 1713 { 1714 name: "field has reserved name", 1715 builder: func() Builder { 1716 return NewFile("foo.proto"). 1717 AddMessage(NewMessage("Foo"). 1718 AddField(NewField("foo", FieldTypeBool())). 1719 AddReservedName("foo")) 1720 }, 1721 expectedError: "must not use reserved name", 1722 }, 1723 { 1724 name: "ranges overlap", 1725 builder: func() Builder { 1726 return NewFile("foo.proto"). 1727 AddMessage(NewMessage("Foo"). 1728 AddReservedRange(100, 1000). 1729 AddExtensionRange(200, 300)) 1730 }, 1731 expectedError: "reserved and extension ranges has overlapping ranges", 1732 }, 1733 } 1734 for _, testCase := range testCases { 1735 t.Run(testCase.name, func(t *testing.T) { 1736 _, err := testCase.builder().BuildDescriptor() 1737 testutil.Nok(t, err) 1738 testutil.Require(t, strings.Contains(err.Error(), testCase.expectedError), "unexpected error: want %q, got %q", testCase.expectedError, err.Error()) 1739 }) 1740 } 1741 }