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