github.com/syumai/protoreflect@v1.7.1-0.20200810020253-2ac7e3b3a321/desc/builder/builder_test.go (about) 1 package builder 2 3 import ( 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/syumai/protoreflect/desc" 18 "github.com/syumai/protoreflect/dynamic" 19 _ "github.com/syumai/protoreflect/internal/testprotos" 20 "github.com/syumai/protoreflect/internal/testutil" 21 ) 22 23 func 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 71 func 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 121 func 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 228 func 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 277 func 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 316 func TestProto3Optional(t *testing.T) { 317 mb := NewMessage("Foo") 318 flb := NewField("bar", FieldTypeBool()).SetProto3Optional(true) 319 mb.AddField(flb) 320 321 _, err := flb.Build() 322 testutil.Nok(t, err) // file does not have proto3 syntax 323 324 fb := NewFile("foo.proto").SetProto3(true) 325 fb.AddMessage(mb) 326 327 fld, err := flb.Build() 328 testutil.Ok(t, err) 329 330 testutil.Require(t, fld.IsProto3Optional()) 331 testutil.Require(t, fld.GetOneOf() != nil) 332 testutil.Require(t, fld.GetOneOf().IsSynthetic()) 333 testutil.Eq(t, "_bar", fld.GetOneOf().GetName()) 334 } 335 336 func TestBuildersFromDescriptors(t *testing.T) { 337 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"} { 338 fd, err := desc.LoadFileDescriptor(s) 339 testutil.Ok(t, err) 340 roundTripFile(t, fd) 341 } 342 } 343 344 func TestBuildersFromDescriptors_PreserveComments(t *testing.T) { 345 fd, err := loadProtoset("../../internal/testprotos/desc_test1.protoset") 346 testutil.Ok(t, err) 347 348 fb, err := FromFile(fd) 349 testutil.Ok(t, err) 350 351 count := 0 352 var checkBuilderComments func(b Builder) 353 checkBuilderComments = func(b Builder) { 354 hasComment := true 355 switch b := b.(type) { 356 case *FileBuilder: 357 hasComment = false 358 case *FieldBuilder: 359 // comments for groups are on the message, not the field 360 hasComment = b.GetType().GetType() != dpb.FieldDescriptorProto_TYPE_GROUP 361 case *MessageBuilder: 362 // comments for maps are on the field, not the entry message 363 if b.Options.GetMapEntry() { 364 // we just return to also skip checking child elements 365 // (map entry child elements are synthetic and have no comments) 366 return 367 } 368 } 369 370 if hasComment { 371 count++ 372 testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", b.GetName()), b.GetComments().LeadingComment, 373 "wrong comment for builder %s", GetFullyQualifiedName(b)) 374 } 375 for _, ch := range b.GetChildren() { 376 checkBuilderComments(ch) 377 } 378 } 379 380 checkBuilderComments(fb) 381 // sanity check that we didn't accidentally short-circuit above and fail to check comments 382 testutil.Require(t, count > 30, "too few elements checked") 383 384 // now check that they also come out in the resulting descriptor 385 fd, err = fb.Build() 386 testutil.Ok(t, err) 387 388 descCount := 0 389 var checkDescriptorComments func(d desc.Descriptor) 390 checkDescriptorComments = func(d desc.Descriptor) { 391 switch d := d.(type) { 392 case *desc.FileDescriptor: 393 for _, ch := range d.GetMessageTypes() { 394 checkDescriptorComments(ch) 395 } 396 for _, ch := range d.GetEnumTypes() { 397 checkDescriptorComments(ch) 398 } 399 for _, ch := range d.GetExtensions() { 400 checkDescriptorComments(ch) 401 } 402 for _, ch := range d.GetServices() { 403 checkDescriptorComments(ch) 404 } 405 // files don't have comments, so bail out before check below 406 return 407 case *desc.MessageDescriptor: 408 if d.IsMapEntry() { 409 // map entry messages have no comments (and neither do their child fields) 410 return 411 } 412 for _, ch := range d.GetFields() { 413 checkDescriptorComments(ch) 414 } 415 for _, ch := range d.GetNestedMessageTypes() { 416 checkDescriptorComments(ch) 417 } 418 for _, ch := range d.GetNestedEnumTypes() { 419 checkDescriptorComments(ch) 420 } 421 for _, ch := range d.GetNestedExtensions() { 422 checkDescriptorComments(ch) 423 } 424 for _, ch := range d.GetOneOfs() { 425 checkDescriptorComments(ch) 426 } 427 case *desc.FieldDescriptor: 428 if d.GetType() == dpb.FieldDescriptorProto_TYPE_GROUP { 429 // groups comments are on the message, not hte field; so bail out before check below 430 return 431 } 432 case *desc.EnumDescriptor: 433 for _, ch := range d.GetValues() { 434 checkDescriptorComments(ch) 435 } 436 case *desc.ServiceDescriptor: 437 for _, ch := range d.GetMethods() { 438 checkDescriptorComments(ch) 439 } 440 } 441 442 descCount++ 443 testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(), 444 "wrong comment for descriptor %s", d.GetFullyQualifiedName()) 445 } 446 447 checkDescriptorComments(fd) 448 testutil.Eq(t, count, descCount) 449 } 450 451 func loadProtoset(path string) (*desc.FileDescriptor, error) { 452 var fds dpb.FileDescriptorSet 453 f, err := os.Open(path) 454 if err != nil { 455 return nil, err 456 } 457 defer f.Close() 458 bb, err := ioutil.ReadAll(f) 459 if err != nil { 460 return nil, err 461 } 462 if err = proto.Unmarshal(bb, &fds); err != nil { 463 return nil, err 464 } 465 return desc.CreateFileDescriptorFromSet(&fds) 466 } 467 468 func roundTripFile(t *testing.T, fd *desc.FileDescriptor) { 469 // First, recursively verify that every child element can be converted to a 470 // Builder and back without loss of fidelity. 471 for _, md := range fd.GetMessageTypes() { 472 roundTripMessage(t, md) 473 } 474 for _, ed := range fd.GetEnumTypes() { 475 roundTripEnum(t, ed) 476 } 477 for _, exd := range fd.GetExtensions() { 478 roundTripField(t, exd) 479 } 480 for _, sd := range fd.GetServices() { 481 roundTripService(t, sd) 482 } 483 484 // Finally, we check the whole file itself. 485 fb, err := FromFile(fd) 486 testutil.Ok(t, err) 487 488 roundTripped, err := fb.Build() 489 testutil.Ok(t, err) 490 491 // Round tripping from a file descriptor to a builder and back will 492 // experience some minor changes (that do not impact the semantics of 493 // any of the file's contents): 494 // 1. The builder sorts dependencies. However the original file 495 // descriptor has dependencies in the order they appear in import 496 // statements in the source file. 497 // 2. The builder imports the actual source of all elements and never 498 // uses public imports. The original file, on the other hand, could 499 // used public imports and "indirectly" import other files that way. 500 // 3. The builder never emits weak imports. 501 // 4. The builder tries to preserve SourceCodeInfo, but will not preserve 502 // position information. So that info does not survive round-tripping 503 // (though comments do: there is a separate test for that). Also, the 504 // round-tripped version will have source code info (even though it 505 // may have no comments and zero position info), even if the original 506 // descriptor had none. 507 // So we're going to modify the original descriptor in the same ways. 508 // That way, a simple proto.Equal() check will suffice to confirm that 509 // the file descriptor survived the round trip. 510 511 // The files we are testing have one occurrence of a public import. The 512 // file nopkg/desc_test_nopkg.proto declares nothing and public imports 513 // nopkg/desc_test_nopkg_new.proto. So any file that depends on the 514 // former will be updated to instead depend on the latter (since it is 515 // the actual file that declares used elements). 516 fdp := fd.AsFileDescriptorProto() 517 needsNopkgNew := false 518 hasNoPkgNew := false 519 for _, dep := range fdp.Dependency { 520 if dep == "nopkg/desc_test_nopkg.proto" { 521 needsNopkgNew = true 522 } 523 if dep == "nopkg/desc_test_nopkg_new.proto" { 524 hasNoPkgNew = false 525 } 526 } 527 if needsNopkgNew && !hasNoPkgNew { 528 fdp.Dependency = append(fdp.Dependency, "nopkg/desc_test_nopkg_new.proto") 529 } 530 531 // Strip any public and weak imports. (The step above should have "fixed" 532 // files to handle any actual public import encountered.) 533 fdp.PublicDependency = nil 534 fdp.WeakDependency = nil 535 536 // Remove source code info that the builder generated since the original 537 // has none. 538 roundTripped.AsFileDescriptorProto().SourceCodeInfo = nil 539 540 // Finally, sort the imports. That way they match the built result (which 541 // is always sorted). 542 sort.Strings(fdp.Dependency) 543 544 // Now (after tweaking) the original should match the round-tripped descriptor: 545 testutil.Require(t, proto.Equal(fdp, roundTripped.AsProto()), "File %q failed round trip.\nExpecting: %s\nGot: %s\n", 546 fd.GetName(), proto.MarshalTextString(fdp), proto.MarshalTextString(roundTripped.AsProto())) 547 } 548 549 func roundTripMessage(t *testing.T, md *desc.MessageDescriptor) { 550 // first recursively validate all nested elements 551 for _, fld := range md.GetFields() { 552 roundTripField(t, fld) 553 } 554 for _, ood := range md.GetOneOfs() { 555 oob, err := FromOneOf(ood) 556 testutil.Ok(t, err) 557 roundTripped, err := oob.Build() 558 testutil.Ok(t, err) 559 checkDescriptors(t, ood, roundTripped) 560 } 561 for _, nmd := range md.GetNestedMessageTypes() { 562 roundTripMessage(t, nmd) 563 } 564 for _, ed := range md.GetNestedEnumTypes() { 565 roundTripEnum(t, ed) 566 } 567 for _, exd := range md.GetNestedExtensions() { 568 roundTripField(t, exd) 569 } 570 571 mb, err := FromMessage(md) 572 testutil.Ok(t, err) 573 roundTripped, err := mb.Build() 574 testutil.Ok(t, err) 575 checkDescriptors(t, md, roundTripped) 576 } 577 578 func roundTripEnum(t *testing.T, ed *desc.EnumDescriptor) { 579 // first recursively validate all nested elements 580 for _, evd := range ed.GetValues() { 581 evb, err := FromEnumValue(evd) 582 testutil.Ok(t, err) 583 roundTripped, err := evb.Build() 584 testutil.Ok(t, err) 585 checkDescriptors(t, evd, roundTripped) 586 } 587 588 eb, err := FromEnum(ed) 589 testutil.Ok(t, err) 590 roundTripped, err := eb.Build() 591 testutil.Ok(t, err) 592 checkDescriptors(t, ed, roundTripped) 593 } 594 595 func roundTripField(t *testing.T, fld *desc.FieldDescriptor) { 596 flb, err := FromField(fld) 597 testutil.Ok(t, err) 598 roundTripped, err := flb.Build() 599 testutil.Ok(t, err) 600 checkDescriptors(t, fld, roundTripped) 601 } 602 603 func roundTripService(t *testing.T, sd *desc.ServiceDescriptor) { 604 // first recursively validate all nested elements 605 for _, mtd := range sd.GetMethods() { 606 mtb, err := FromMethod(mtd) 607 testutil.Ok(t, err) 608 roundTripped, err := mtb.Build() 609 testutil.Ok(t, err) 610 checkDescriptors(t, mtd, roundTripped) 611 } 612 613 sb, err := FromService(sd) 614 testutil.Ok(t, err) 615 roundTripped, err := sb.Build() 616 testutil.Ok(t, err) 617 checkDescriptors(t, sd, roundTripped) 618 } 619 620 func checkDescriptors(t *testing.T, d1, d2 desc.Descriptor) { 621 testutil.Eq(t, d1.GetFullyQualifiedName(), d2.GetFullyQualifiedName()) 622 testutil.Require(t, proto.Equal(d1.AsProto(), d2.AsProto()), "%s failed round trip.\nExpecting: %s\nGot: %s\n", 623 d1.GetFullyQualifiedName(), proto.MarshalTextString(d1.AsProto()), proto.MarshalTextString(d2.AsProto())) 624 } 625 626 func TestAddRemoveMoveBuilders(t *testing.T) { 627 // add field to one-of 628 fld1 := NewField("foo", FieldTypeInt32()) 629 oo1 := NewOneOf("oofoo") 630 oo1.AddChoice(fld1) 631 checkChildren(t, oo1, fld1) 632 testutil.Eq(t, oo1.GetChoice("foo"), fld1) 633 634 // add one-of w/ field to a message 635 msg1 := NewMessage("foo") 636 msg1.AddOneOf(oo1) 637 checkChildren(t, msg1, oo1) 638 testutil.Eq(t, msg1.GetOneOf("oofoo"), oo1) 639 // field remains unchanged 640 testutil.Eq(t, fld1.GetParent(), oo1) 641 testutil.Eq(t, oo1.GetChoice("foo"), fld1) 642 // field also now registered with msg1 643 testutil.Eq(t, msg1.GetField("foo"), fld1) 644 645 // add empty one-of to message 646 oo2 := NewOneOf("oobar") 647 msg1.AddOneOf(oo2) 648 checkChildren(t, msg1, oo1, oo2) 649 testutil.Eq(t, msg1.GetOneOf("oobar"), oo2) 650 // now add field to that one-of 651 fld2 := NewField("bar", FieldTypeInt32()) 652 oo2.AddChoice(fld2) 653 checkChildren(t, oo2, fld2) 654 testutil.Eq(t, oo2.GetChoice("bar"), fld2) 655 // field also now registered with msg1 656 testutil.Eq(t, msg1.GetField("bar"), fld2) 657 658 // add fails due to name collisions 659 fld1dup := NewField("foo", FieldTypeInt32()) 660 err := oo1.TryAddChoice(fld1dup) 661 checkFailedAdd(t, err, oo1, fld1dup, "already contains field") 662 fld2 = NewField("bar", FieldTypeInt32()) 663 err = msg1.TryAddField(fld2) 664 checkFailedAdd(t, err, msg1, fld2, "already contains element") 665 msg2 := NewMessage("oofoo") 666 // name collision can be different type 667 // (here, nested message conflicts with a one-of) 668 err = msg1.TryAddNestedMessage(msg2) 669 checkFailedAdd(t, err, msg1, msg2, "already contains element") 670 671 msg2 = NewMessage("baz") 672 msg1.AddNestedMessage(msg2) 673 checkChildren(t, msg1, oo1, oo2, msg2) 674 testutil.Eq(t, msg1.GetNestedMessage("baz"), msg2) 675 676 // can't add extension or map fields to one-of 677 ext1 := NewExtension("abc", 123, FieldTypeInt32(), msg1) 678 err = oo1.TryAddChoice(ext1) 679 checkFailedAdd(t, err, oo1, ext1, "is an extension, not a regular field") 680 err = msg1.TryAddField(ext1) 681 checkFailedAdd(t, err, msg1, ext1, "is an extension, not a regular field") 682 mapField := NewMapField("abc", FieldTypeInt32(), FieldTypeString()) 683 err = oo1.TryAddChoice(mapField) 684 checkFailedAdd(t, err, oo1, mapField, "cannot add a map field") 685 // can add group field though 686 groupMsg := NewMessage("Group") 687 groupField := NewGroupField(groupMsg) 688 oo1.AddChoice(groupField) 689 checkChildren(t, oo1, fld1, groupField) 690 // adding map and group to msg succeeds 691 msg1.AddField(groupField) 692 msg1.AddField(mapField) 693 checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField) 694 // messages associated with map and group fields are not children of the 695 // message, but are in its scope and accessible via GetNestedMessage 696 testutil.Eq(t, msg1.GetNestedMessage("Group"), groupMsg) 697 testutil.Eq(t, msg1.GetNestedMessage("AbcEntry"), mapField.GetType().localMsgType) 698 699 // adding extension to message 700 ext2 := NewExtension("xyz", 234, FieldTypeInt32(), msg1) 701 msg1.AddNestedExtension(ext2) 702 checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField, ext2) 703 err = msg1.TryAddNestedExtension(ext1) // name collision 704 checkFailedAdd(t, err, msg1, ext1, "already contains element") 705 fld3 := NewField("ijk", FieldTypeString()) 706 err = msg1.TryAddNestedExtension(fld3) 707 checkFailedAdd(t, err, msg1, fld3, "is not an extension") 708 709 // add enum values to enum 710 enumVal1 := NewEnumValue("A") 711 enum1 := NewEnum("bazel") 712 enum1.AddValue(enumVal1) 713 checkChildren(t, enum1, enumVal1) 714 testutil.Eq(t, enum1.GetValue("A"), enumVal1) 715 enumVal2 := NewEnumValue("B") 716 enum1.AddValue(enumVal2) 717 checkChildren(t, enum1, enumVal1, enumVal2) 718 testutil.Eq(t, enum1.GetValue("B"), enumVal2) 719 // fail w/ name collision 720 enumVal3 := NewEnumValue("B") 721 err = enum1.TryAddValue(enumVal3) 722 checkFailedAdd(t, err, enum1, enumVal3, "already contains value") 723 724 msg2.AddNestedEnum(enum1) 725 checkChildren(t, msg2, enum1) 726 testutil.Eq(t, msg2.GetNestedEnum("bazel"), enum1) 727 ext3 := NewExtension("bazel", 987, FieldTypeString(), msg2) 728 err = msg2.TryAddNestedExtension(ext3) 729 checkFailedAdd(t, err, msg2, ext3, "already contains element") 730 731 // services and methods 732 mtd1 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false)) 733 svc1 := NewService("FooService") 734 svc1.AddMethod(mtd1) 735 checkChildren(t, svc1, mtd1) 736 testutil.Eq(t, svc1.GetMethod("foo"), mtd1) 737 mtd2 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false)) 738 err = svc1.TryAddMethod(mtd2) 739 checkFailedAdd(t, err, svc1, mtd2, "already contains method") 740 741 // finally, test adding things to a file 742 fb := NewFile("") 743 fb.AddMessage(msg1) 744 checkChildren(t, fb, msg1) 745 testutil.Eq(t, fb.GetMessage("foo"), msg1) 746 fb.AddService(svc1) 747 checkChildren(t, fb, msg1, svc1) 748 testutil.Eq(t, fb.GetService("FooService"), svc1) 749 enum2 := NewEnum("fizzle") 750 fb.AddEnum(enum2) 751 checkChildren(t, fb, msg1, svc1, enum2) 752 testutil.Eq(t, fb.GetEnum("fizzle"), enum2) 753 ext3 = NewExtension("foosball", 123, FieldTypeInt32(), msg1) 754 fb.AddExtension(ext3) 755 checkChildren(t, fb, msg1, svc1, enum2, ext3) 756 testutil.Eq(t, fb.GetExtension("foosball"), ext3) 757 758 // errors and name collisions 759 err = fb.TryAddExtension(fld3) 760 checkFailedAdd(t, err, fb, fld3, "is not an extension") 761 msg3 := NewMessage("fizzle") 762 err = fb.TryAddMessage(msg3) 763 checkFailedAdd(t, err, fb, msg3, "already contains element") 764 enum3 := NewEnum("foosball") 765 err = fb.TryAddEnum(enum3) 766 checkFailedAdd(t, err, fb, enum3, "already contains element") 767 768 // TODO: test moving and removing, too 769 } 770 771 func checkChildren(t *testing.T, parent Builder, children ...Builder) { 772 testutil.Eq(t, len(children), len(parent.GetChildren()), "Wrong number of children for %s (%T)", GetFullyQualifiedName(parent), parent) 773 ch := map[Builder]struct{}{} 774 for _, child := range children { 775 testutil.Eq(t, child.GetParent(), parent, "Child %s (%T) does not report %s (%T) as its parent", child.GetName(), child, GetFullyQualifiedName(parent), parent) 776 ch[child] = struct{}{} 777 } 778 for _, child := range parent.GetChildren() { 779 _, ok := ch[child] 780 testutil.Require(t, ok, "Child %s (%T) does appear in list of children for %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent) 781 } 782 } 783 784 func checkFailedAdd(t *testing.T, err error, parent Builder, child Builder, errorMsg string) { 785 testutil.Require(t, err != nil, "Expecting error assigning %s (%T) to %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent) 786 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()) 787 testutil.Eq(t, nil, child.GetParent(), "Child %s (%T) should not have a parent after failed add", child.GetName(), child) 788 for _, ch := range parent.GetChildren() { 789 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) 790 } 791 } 792 793 func TestRenamingBuilders(t *testing.T) { 794 // TODO 795 } 796 797 func TestRenumberingFields(t *testing.T) { 798 // TODO 799 } 800 801 var ( 802 fileOptionsDesc, msgOptionsDesc, fieldOptionsDesc, oneofOptionsDesc, extRangeOptionsDesc, 803 enumOptionsDesc, enumValOptionsDesc, svcOptionsDesc, mtdOptionsDesc *desc.MessageDescriptor 804 ) 805 806 func init() { 807 var err error 808 fileOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.FileOptions)(nil)) 809 if err != nil { 810 panic(err) 811 } 812 msgOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.MessageOptions)(nil)) 813 if err != nil { 814 panic(err) 815 } 816 fieldOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.FieldOptions)(nil)) 817 if err != nil { 818 panic(err) 819 } 820 oneofOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.OneofOptions)(nil)) 821 if err != nil { 822 panic(err) 823 } 824 extRangeOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.ExtensionRangeOptions)(nil)) 825 if err != nil { 826 panic(err) 827 } 828 enumOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.EnumOptions)(nil)) 829 if err != nil { 830 panic(err) 831 } 832 enumValOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.EnumValueOptions)(nil)) 833 if err != nil { 834 panic(err) 835 } 836 svcOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.ServiceOptions)(nil)) 837 if err != nil { 838 panic(err) 839 } 840 mtdOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.MethodOptions)(nil)) 841 if err != nil { 842 panic(err) 843 } 844 } 845 846 func TestCustomOptionsDiscoveredInSameFile(t *testing.T) { 847 // Add option for every type to file 848 file := NewFile("foo.proto") 849 850 fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc) 851 file.AddExtension(fileOpt) 852 853 msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc) 854 file.AddExtension(msgOpt) 855 856 fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc) 857 file.AddExtension(fieldOpt) 858 859 oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc) 860 file.AddExtension(oneofOpt) 861 862 extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc) 863 file.AddExtension(extRangeOpt) 864 865 enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc) 866 file.AddExtension(enumOpt) 867 868 enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc) 869 file.AddExtension(enumValOpt) 870 871 svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc) 872 file.AddExtension(svcOpt) 873 874 mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc) 875 file.AddExtension(mtdOpt) 876 877 // Now we can test referring to these and making sure they show up correctly 878 // in built descriptors 879 880 t.Run("file options", func(t *testing.T) { 881 fb := clone(t, file) 882 fb.Options = &dpb.FileOptions{} 883 ext, err := fileOpt.Build() 884 testutil.Ok(t, err) 885 err = dynamic.SetExtension(fb.Options, ext, "fubar") 886 testutil.Ok(t, err) 887 checkBuildWithLocalExtensions(t, fb) 888 }) 889 890 t.Run("message options", func(t *testing.T) { 891 mb := NewMessage("Foo") 892 mb.Options = &dpb.MessageOptions{} 893 ext, err := msgOpt.Build() 894 testutil.Ok(t, err) 895 err = dynamic.SetExtension(mb.Options, ext, "fubar") 896 testutil.Ok(t, err) 897 898 fb := clone(t, file) 899 fb.AddMessage(mb) 900 checkBuildWithLocalExtensions(t, mb) 901 }) 902 903 t.Run("field options", func(t *testing.T) { 904 flb := NewField("foo", FieldTypeString()) 905 flb.Options = &dpb.FieldOptions{} 906 // fields must be connected to a message 907 mb := NewMessage("Foo").AddField(flb) 908 ext, err := fieldOpt.Build() 909 testutil.Ok(t, err) 910 err = dynamic.SetExtension(flb.Options, ext, "fubar") 911 testutil.Ok(t, err) 912 913 fb := clone(t, file) 914 fb.AddMessage(mb) 915 checkBuildWithLocalExtensions(t, flb) 916 }) 917 918 t.Run("oneof options", func(t *testing.T) { 919 oob := NewOneOf("oo") 920 oob.Options = &dpb.OneofOptions{} 921 // oneofs must be connected to a message 922 mb := NewMessage("Foo").AddOneOf(oob) 923 ext, err := oneofOpt.Build() 924 testutil.Ok(t, err) 925 err = dynamic.SetExtension(oob.Options, ext, "fubar") 926 testutil.Ok(t, err) 927 928 fb := clone(t, file) 929 fb.AddMessage(mb) 930 checkBuildWithLocalExtensions(t, oob) 931 }) 932 933 t.Run("extension range options", func(t *testing.T) { 934 var erOpts dpb.ExtensionRangeOptions 935 ext, err := extRangeOpt.Build() 936 testutil.Ok(t, err) 937 err = dynamic.SetExtension(&erOpts, ext, "fubar") 938 testutil.Ok(t, err) 939 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 940 941 fb := clone(t, file) 942 fb.AddMessage(mb) 943 checkBuildWithLocalExtensions(t, mb) 944 }) 945 946 t.Run("enum options", func(t *testing.T) { 947 eb := NewEnum("Foo") 948 eb.Options = &dpb.EnumOptions{} 949 ext, err := enumOpt.Build() 950 testutil.Ok(t, err) 951 err = dynamic.SetExtension(eb.Options, ext, "fubar") 952 testutil.Ok(t, err) 953 954 fb := clone(t, file) 955 fb.AddEnum(eb) 956 checkBuildWithLocalExtensions(t, eb) 957 }) 958 959 t.Run("enum val options", func(t *testing.T) { 960 evb := NewEnumValue("FOO") 961 // enum values must be connected to an enum 962 eb := NewEnum("Foo").AddValue(evb) 963 evb.Options = &dpb.EnumValueOptions{} 964 ext, err := enumValOpt.Build() 965 testutil.Ok(t, err) 966 err = dynamic.SetExtension(evb.Options, ext, "fubar") 967 testutil.Ok(t, err) 968 969 fb := clone(t, file) 970 fb.AddEnum(eb) 971 checkBuildWithLocalExtensions(t, evb) 972 }) 973 974 t.Run("service options", func(t *testing.T) { 975 sb := NewService("Foo") 976 sb.Options = &dpb.ServiceOptions{} 977 ext, err := svcOpt.Build() 978 testutil.Ok(t, err) 979 err = dynamic.SetExtension(sb.Options, ext, "fubar") 980 testutil.Ok(t, err) 981 982 fb := clone(t, file) 983 fb.AddService(sb) 984 checkBuildWithLocalExtensions(t, sb) 985 }) 986 987 t.Run("method options", func(t *testing.T) { 988 req := NewMessage("Request") 989 resp := NewMessage("Response") 990 mtb := NewMethod("Foo", 991 RpcTypeMessage(req, false), 992 RpcTypeMessage(resp, false)) 993 // methods must be connected to a service 994 sb := NewService("Bar").AddMethod(mtb) 995 mtb.Options = &dpb.MethodOptions{} 996 ext, err := mtdOpt.Build() 997 testutil.Ok(t, err) 998 err = dynamic.SetExtension(mtb.Options, ext, "fubar") 999 testutil.Ok(t, err) 1000 1001 fb := clone(t, file) 1002 fb.AddService(sb).AddMessage(req).AddMessage(resp) 1003 checkBuildWithLocalExtensions(t, mtb) 1004 }) 1005 } 1006 1007 func checkBuildWithLocalExtensions(t *testing.T, builder Builder) { 1008 // requiring options and succeeding (since they are defined locally) 1009 var opts BuilderOptions 1010 opts.RequireInterpretedOptions = true 1011 d, err := opts.Build(builder) 1012 testutil.Ok(t, err) 1013 // since they are defined locally, no extra imports 1014 testutil.Eq(t, []string{"google/protobuf/descriptor.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency()) 1015 } 1016 1017 func TestCustomOptionsDiscoveredInDependencies(t *testing.T) { 1018 // Add option for every type to file 1019 file := NewFile("options.proto") 1020 1021 fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc) 1022 file.AddExtension(fileOpt) 1023 1024 msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc) 1025 file.AddExtension(msgOpt) 1026 1027 fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc) 1028 file.AddExtension(fieldOpt) 1029 1030 oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc) 1031 file.AddExtension(oneofOpt) 1032 1033 extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc) 1034 file.AddExtension(extRangeOpt) 1035 1036 enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc) 1037 file.AddExtension(enumOpt) 1038 1039 enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc) 1040 file.AddExtension(enumValOpt) 1041 1042 svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc) 1043 file.AddExtension(svcOpt) 1044 1045 mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc) 1046 file.AddExtension(mtdOpt) 1047 1048 fileDesc, err := file.Build() 1049 testutil.Ok(t, err) 1050 1051 // Now we can test referring to these and making sure they show up correctly 1052 // in built descriptors 1053 for name, useBuilder := range map[string]bool{"descriptor": false, "builder": true} { 1054 newFile := func() *FileBuilder { 1055 fb := NewFile("foo.proto") 1056 if useBuilder { 1057 fb.AddDependency(file) 1058 } else { 1059 fb.AddImportedDependency(fileDesc) 1060 } 1061 return fb 1062 } 1063 t.Run(name, func(t *testing.T) { 1064 t.Run("file options", func(t *testing.T) { 1065 fb := newFile() 1066 fb.Options = &dpb.FileOptions{} 1067 ext, err := fileOpt.Build() 1068 testutil.Ok(t, err) 1069 err = dynamic.SetExtension(fb.Options, ext, "fubar") 1070 testutil.Ok(t, err) 1071 checkBuildWithImportedExtensions(t, fb) 1072 }) 1073 1074 t.Run("message options", func(t *testing.T) { 1075 mb := NewMessage("Foo") 1076 mb.Options = &dpb.MessageOptions{} 1077 ext, err := msgOpt.Build() 1078 testutil.Ok(t, err) 1079 err = dynamic.SetExtension(mb.Options, ext, "fubar") 1080 testutil.Ok(t, err) 1081 1082 fb := newFile() 1083 fb.AddMessage(mb) 1084 checkBuildWithImportedExtensions(t, mb) 1085 }) 1086 1087 t.Run("field options", func(t *testing.T) { 1088 flb := NewField("foo", FieldTypeString()) 1089 flb.Options = &dpb.FieldOptions{} 1090 // fields must be connected to a message 1091 mb := NewMessage("Foo").AddField(flb) 1092 ext, err := fieldOpt.Build() 1093 testutil.Ok(t, err) 1094 err = dynamic.SetExtension(flb.Options, ext, "fubar") 1095 testutil.Ok(t, err) 1096 1097 fb := newFile() 1098 fb.AddMessage(mb) 1099 checkBuildWithImportedExtensions(t, flb) 1100 }) 1101 1102 t.Run("oneof options", func(t *testing.T) { 1103 oob := NewOneOf("oo") 1104 oob.Options = &dpb.OneofOptions{} 1105 // oneofs must be connected to a message 1106 mb := NewMessage("Foo").AddOneOf(oob) 1107 ext, err := oneofOpt.Build() 1108 testutil.Ok(t, err) 1109 err = dynamic.SetExtension(oob.Options, ext, "fubar") 1110 testutil.Ok(t, err) 1111 1112 fb := newFile() 1113 fb.AddMessage(mb) 1114 checkBuildWithImportedExtensions(t, oob) 1115 }) 1116 1117 t.Run("extension range options", func(t *testing.T) { 1118 var erOpts dpb.ExtensionRangeOptions 1119 ext, err := extRangeOpt.Build() 1120 testutil.Ok(t, err) 1121 err = dynamic.SetExtension(&erOpts, ext, "fubar") 1122 testutil.Ok(t, err) 1123 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 1124 1125 fb := newFile() 1126 fb.AddMessage(mb) 1127 checkBuildWithImportedExtensions(t, mb) 1128 }) 1129 1130 t.Run("enum options", func(t *testing.T) { 1131 eb := NewEnum("Foo") 1132 eb.Options = &dpb.EnumOptions{} 1133 ext, err := enumOpt.Build() 1134 testutil.Ok(t, err) 1135 err = dynamic.SetExtension(eb.Options, ext, "fubar") 1136 testutil.Ok(t, err) 1137 1138 fb := newFile() 1139 fb.AddEnum(eb) 1140 checkBuildWithImportedExtensions(t, eb) 1141 }) 1142 1143 t.Run("enum val options", func(t *testing.T) { 1144 evb := NewEnumValue("FOO") 1145 // enum values must be connected to an enum 1146 eb := NewEnum("Foo").AddValue(evb) 1147 evb.Options = &dpb.EnumValueOptions{} 1148 ext, err := enumValOpt.Build() 1149 testutil.Ok(t, err) 1150 err = dynamic.SetExtension(evb.Options, ext, "fubar") 1151 testutil.Ok(t, err) 1152 1153 fb := newFile() 1154 fb.AddEnum(eb) 1155 checkBuildWithImportedExtensions(t, evb) 1156 }) 1157 1158 t.Run("service options", func(t *testing.T) { 1159 sb := NewService("Foo") 1160 sb.Options = &dpb.ServiceOptions{} 1161 ext, err := svcOpt.Build() 1162 testutil.Ok(t, err) 1163 err = dynamic.SetExtension(sb.Options, ext, "fubar") 1164 testutil.Ok(t, err) 1165 1166 fb := newFile() 1167 fb.AddService(sb) 1168 checkBuildWithImportedExtensions(t, sb) 1169 }) 1170 1171 t.Run("method options", func(t *testing.T) { 1172 req := NewMessage("Request") 1173 resp := NewMessage("Response") 1174 mtb := NewMethod("Foo", 1175 RpcTypeMessage(req, false), 1176 RpcTypeMessage(resp, false)) 1177 // methods must be connected to a service 1178 sb := NewService("Bar").AddMethod(mtb) 1179 mtb.Options = &dpb.MethodOptions{} 1180 ext, err := mtdOpt.Build() 1181 testutil.Ok(t, err) 1182 err = dynamic.SetExtension(mtb.Options, ext, "fubar") 1183 testutil.Ok(t, err) 1184 1185 fb := newFile() 1186 fb.AddService(sb).AddMessage(req).AddMessage(resp) 1187 checkBuildWithImportedExtensions(t, mtb) 1188 }) 1189 }) 1190 } 1191 } 1192 1193 func checkBuildWithImportedExtensions(t *testing.T, builder Builder) { 1194 // requiring options and succeeding (since they are defined in explicit import) 1195 var opts BuilderOptions 1196 opts.RequireInterpretedOptions = true 1197 d, err := opts.Build(builder) 1198 testutil.Ok(t, err) 1199 // the only import is for the custom options 1200 testutil.Eq(t, []string{"options.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency()) 1201 } 1202 1203 func TestUseOfExtensionRegistry(t *testing.T) { 1204 // Add option for every type to extension registry 1205 var exts dynamic.ExtensionRegistry 1206 1207 fileOpt, err := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc).Build() 1208 testutil.Ok(t, err) 1209 err = exts.AddExtension(fileOpt) 1210 testutil.Ok(t, err) 1211 1212 msgOpt, err := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc).Build() 1213 testutil.Ok(t, err) 1214 err = exts.AddExtension(msgOpt) 1215 testutil.Ok(t, err) 1216 1217 fieldOpt, err := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc).Build() 1218 testutil.Ok(t, err) 1219 err = exts.AddExtension(fieldOpt) 1220 testutil.Ok(t, err) 1221 1222 oneofOpt, err := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc).Build() 1223 testutil.Ok(t, err) 1224 err = exts.AddExtension(oneofOpt) 1225 testutil.Ok(t, err) 1226 1227 extRangeOpt, err := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc).Build() 1228 testutil.Ok(t, err) 1229 err = exts.AddExtension(extRangeOpt) 1230 testutil.Ok(t, err) 1231 1232 enumOpt, err := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc).Build() 1233 testutil.Ok(t, err) 1234 err = exts.AddExtension(enumOpt) 1235 testutil.Ok(t, err) 1236 1237 enumValOpt, err := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc).Build() 1238 testutil.Ok(t, err) 1239 err = exts.AddExtension(enumValOpt) 1240 testutil.Ok(t, err) 1241 1242 svcOpt, err := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc).Build() 1243 testutil.Ok(t, err) 1244 err = exts.AddExtension(svcOpt) 1245 testutil.Ok(t, err) 1246 1247 mtdOpt, err := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc).Build() 1248 testutil.Ok(t, err) 1249 err = exts.AddExtension(mtdOpt) 1250 testutil.Ok(t, err) 1251 1252 // Now we can test referring to these and making sure they show up correctly 1253 // in built descriptors 1254 1255 t.Run("file options", func(t *testing.T) { 1256 fb := NewFile("foo.proto") 1257 fb.Options = &dpb.FileOptions{} 1258 err = dynamic.SetExtension(fb.Options, fileOpt, "fubar") 1259 testutil.Ok(t, err) 1260 checkBuildWithExtensions(t, &exts, fileOpt.GetFile(), fb) 1261 }) 1262 1263 t.Run("message options", func(t *testing.T) { 1264 mb := NewMessage("Foo") 1265 mb.Options = &dpb.MessageOptions{} 1266 err = dynamic.SetExtension(mb.Options, msgOpt, "fubar") 1267 testutil.Ok(t, err) 1268 checkBuildWithExtensions(t, &exts, msgOpt.GetFile(), mb) 1269 }) 1270 1271 t.Run("field options", func(t *testing.T) { 1272 flb := NewField("foo", FieldTypeString()) 1273 flb.Options = &dpb.FieldOptions{} 1274 // fields must be connected to a message 1275 NewMessage("Foo").AddField(flb) 1276 err = dynamic.SetExtension(flb.Options, fieldOpt, "fubar") 1277 testutil.Ok(t, err) 1278 checkBuildWithExtensions(t, &exts, fieldOpt.GetFile(), flb) 1279 }) 1280 1281 t.Run("oneof options", func(t *testing.T) { 1282 oob := NewOneOf("oo") 1283 oob.Options = &dpb.OneofOptions{} 1284 // oneofs must be connected to a message 1285 NewMessage("Foo").AddOneOf(oob) 1286 err = dynamic.SetExtension(oob.Options, oneofOpt, "fubar") 1287 testutil.Ok(t, err) 1288 checkBuildWithExtensions(t, &exts, oneofOpt.GetFile(), oob) 1289 }) 1290 1291 t.Run("extension range options", func(t *testing.T) { 1292 var erOpts dpb.ExtensionRangeOptions 1293 err = dynamic.SetExtension(&erOpts, extRangeOpt, "fubar") 1294 testutil.Ok(t, err) 1295 mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts) 1296 checkBuildWithExtensions(t, &exts, extRangeOpt.GetFile(), mb) 1297 }) 1298 1299 t.Run("enum options", func(t *testing.T) { 1300 eb := NewEnum("Foo") 1301 eb.Options = &dpb.EnumOptions{} 1302 err = dynamic.SetExtension(eb.Options, enumOpt, "fubar") 1303 testutil.Ok(t, err) 1304 checkBuildWithExtensions(t, &exts, enumOpt.GetFile(), eb) 1305 }) 1306 1307 t.Run("enum val options", func(t *testing.T) { 1308 evb := NewEnumValue("FOO") 1309 // enum values must be connected to an enum 1310 NewEnum("Foo").AddValue(evb) 1311 evb.Options = &dpb.EnumValueOptions{} 1312 err = dynamic.SetExtension(evb.Options, enumValOpt, "fubar") 1313 testutil.Ok(t, err) 1314 checkBuildWithExtensions(t, &exts, enumValOpt.GetFile(), evb) 1315 }) 1316 1317 t.Run("service options", func(t *testing.T) { 1318 sb := NewService("Foo") 1319 sb.Options = &dpb.ServiceOptions{} 1320 err = dynamic.SetExtension(sb.Options, svcOpt, "fubar") 1321 testutil.Ok(t, err) 1322 checkBuildWithExtensions(t, &exts, svcOpt.GetFile(), sb) 1323 }) 1324 1325 t.Run("method options", func(t *testing.T) { 1326 mtb := NewMethod("Foo", 1327 RpcTypeMessage(NewMessage("Request"), false), 1328 RpcTypeMessage(NewMessage("Response"), false)) 1329 // methods must be connected to a service 1330 NewService("Bar").AddMethod(mtb) 1331 mtb.Options = &dpb.MethodOptions{} 1332 err = dynamic.SetExtension(mtb.Options, mtdOpt, "fubar") 1333 testutil.Ok(t, err) 1334 checkBuildWithExtensions(t, &exts, mtdOpt.GetFile(), mtb) 1335 }) 1336 } 1337 1338 func checkBuildWithExtensions(t *testing.T, exts *dynamic.ExtensionRegistry, expected *desc.FileDescriptor, builder Builder) { 1339 // without interpreting custom option 1340 d, err := builder.BuildDescriptor() 1341 testutil.Ok(t, err) 1342 for _, dep := range d.GetFile().GetDependencies() { 1343 testutil.Neq(t, expected, dep) 1344 } 1345 numDeps := len(d.GetFile().GetDependencies()) 1346 1347 // requiring options (and failing) 1348 var opts BuilderOptions 1349 opts.RequireInterpretedOptions = true 1350 _, err = opts.Build(builder) 1351 testutil.Require(t, err != nil) 1352 1353 // able to interpret options via extension registry 1354 opts.Extensions = exts 1355 d, err = opts.Build(builder) 1356 testutil.Ok(t, err) 1357 testutil.Eq(t, numDeps+1, len(d.GetFile().GetDependencies())) 1358 found := false 1359 for _, dep := range d.GetFile().GetDependencies() { 1360 if expected == dep { 1361 found = true 1362 break 1363 } 1364 } 1365 testutil.Require(t, found) 1366 } 1367 1368 func TestRemoveField(t *testing.T) { 1369 msg := NewMessage("FancyMessage"). 1370 AddField(NewField("one", FieldTypeInt64())). 1371 AddField(NewField("two", FieldTypeString())). 1372 AddField(NewField("three", FieldTypeString())) 1373 1374 ok := msg.TryRemoveField("two") 1375 children := msg.GetChildren() 1376 1377 testutil.Require(t, ok) 1378 testutil.Eq(t, 2, len(children)) 1379 testutil.Eq(t, "one", children[0].GetName()) 1380 testutil.Eq(t, "three", children[1].GetName()) 1381 } 1382 1383 func clone(t *testing.T, fb *FileBuilder) *FileBuilder { 1384 fd, err := fb.Build() 1385 testutil.Ok(t, err) 1386 fb, err = FromFile(fd) 1387 testutil.Ok(t, err) 1388 return fb 1389 }