1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package protoregistry_test 6 7import ( 8 "fmt" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 15 "google.golang.org/protobuf/encoding/prototext" 16 pimpl "google.golang.org/protobuf/internal/impl" 17 pdesc "google.golang.org/protobuf/reflect/protodesc" 18 pref "google.golang.org/protobuf/reflect/protoreflect" 19 preg "google.golang.org/protobuf/reflect/protoregistry" 20 21 testpb "google.golang.org/protobuf/internal/testprotos/registry" 22 "google.golang.org/protobuf/types/descriptorpb" 23) 24 25func mustMakeFile(s string) pref.FileDescriptor { 26 pb := new(descriptorpb.FileDescriptorProto) 27 if err := prototext.Unmarshal([]byte(s), pb); err != nil { 28 panic(err) 29 } 30 fd, err := pdesc.NewFile(pb, nil) 31 if err != nil { 32 panic(err) 33 } 34 return fd 35} 36 37func TestFiles(t *testing.T) { 38 type ( 39 file struct { 40 Path string 41 Pkg pref.FullName 42 } 43 testFile struct { 44 inFile pref.FileDescriptor 45 wantErr string 46 } 47 testFindDesc struct { 48 inName pref.FullName 49 wantFound bool 50 } 51 testRangePkg struct { 52 inPkg pref.FullName 53 wantFiles []file 54 } 55 testFindPath struct { 56 inPath string 57 wantFiles []file 58 } 59 ) 60 61 tests := []struct { 62 files []testFile 63 findDescs []testFindDesc 64 rangePkgs []testRangePkg 65 findPaths []testFindPath 66 }{{ 67 // Test that overlapping packages and files are permitted. 68 files: []testFile{ 69 {inFile: mustMakeFile(`syntax:"proto2" name:"test1.proto" package:"foo.bar"`)}, 70 {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/test.proto" package:"my.test"`)}, 71 {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/test.proto" package:"foo.bar.baz"`), wantErr: "already registered"}, 72 {inFile: mustMakeFile(`syntax:"proto2" name:"test2.proto" package:"my.test.package"`)}, 73 {inFile: mustMakeFile(`syntax:"proto2" name:"weird" package:"foo.bar"`)}, 74 {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/baz/../test.proto" package:"my.test"`)}, 75 }, 76 77 rangePkgs: []testRangePkg{{ 78 inPkg: "nothing", 79 }, { 80 inPkg: "", 81 }, { 82 inPkg: ".", 83 }, { 84 inPkg: "foo", 85 }, { 86 inPkg: "foo.", 87 }, { 88 inPkg: "foo..", 89 }, { 90 inPkg: "foo.bar", 91 wantFiles: []file{ 92 {"test1.proto", "foo.bar"}, 93 {"weird", "foo.bar"}, 94 }, 95 }, { 96 inPkg: "my.test", 97 wantFiles: []file{ 98 {"foo/bar/baz/../test.proto", "my.test"}, 99 {"foo/bar/test.proto", "my.test"}, 100 }, 101 }, { 102 inPkg: "fo", 103 }}, 104 105 findPaths: []testFindPath{{ 106 inPath: "nothing", 107 }, { 108 inPath: "weird", 109 wantFiles: []file{ 110 {"weird", "foo.bar"}, 111 }, 112 }, { 113 inPath: "foo/bar/test.proto", 114 wantFiles: []file{ 115 {"foo/bar/test.proto", "my.test"}, 116 }, 117 }}, 118 }, { 119 // Test when new enum conflicts with existing package. 120 files: []testFile{{ 121 inFile: mustMakeFile(`syntax:"proto2" name:"test1a.proto" package:"foo.bar.baz"`), 122 }, { 123 inFile: mustMakeFile(`syntax:"proto2" name:"test1b.proto" enum_type:[{name:"foo" value:[{name:"VALUE" number:0}]}]`), 124 wantErr: `file "test1b.proto" has a name conflict over foo`, 125 }}, 126 }, { 127 // Test when new package conflicts with existing enum. 128 files: []testFile{{ 129 inFile: mustMakeFile(`syntax:"proto2" name:"test2a.proto" enum_type:[{name:"foo" value:[{name:"VALUE" number:0}]}]`), 130 }, { 131 inFile: mustMakeFile(`syntax:"proto2" name:"test2b.proto" package:"foo.bar.baz"`), 132 wantErr: `file "test2b.proto" has a package name conflict over foo`, 133 }}, 134 }, { 135 // Test when new enum conflicts with existing enum in same package. 136 files: []testFile{{ 137 inFile: mustMakeFile(`syntax:"proto2" name:"test3a.proto" package:"foo" enum_type:[{name:"BAR" value:[{name:"VALUE" number:0}]}]`), 138 }, { 139 inFile: mustMakeFile(`syntax:"proto2" name:"test3b.proto" package:"foo" enum_type:[{name:"BAR" value:[{name:"VALUE2" number:0}]}]`), 140 wantErr: `file "test3b.proto" has a name conflict over foo.BAR`, 141 }}, 142 }, { 143 files: []testFile{{ 144 inFile: mustMakeFile(` 145 syntax: "proto2" 146 name: "test1.proto" 147 package: "fizz.buzz" 148 message_type: [{ 149 name: "Message" 150 field: [ 151 {name:"Field" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0} 152 ] 153 oneof_decl: [{name:"Oneof"}] 154 extension_range: [{start:1000 end:2000}] 155 156 enum_type: [ 157 {name:"Enum" value:[{name:"EnumValue" number:0}]} 158 ] 159 nested_type: [ 160 {name:"Message" field:[{name:"Field" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]} 161 ] 162 extension: [ 163 {name:"Extension" number:1001 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".fizz.buzz.Message"} 164 ] 165 }] 166 enum_type: [{ 167 name: "Enum" 168 value: [{name:"EnumValue" number:0}] 169 }] 170 extension: [ 171 {name:"Extension" number:1000 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".fizz.buzz.Message"} 172 ] 173 service: [{ 174 name: "Service" 175 method: [{ 176 name: "Method" 177 input_type: ".fizz.buzz.Message" 178 output_type: ".fizz.buzz.Message" 179 client_streaming: true 180 server_streaming: true 181 }] 182 }] 183 `), 184 }, { 185 inFile: mustMakeFile(` 186 syntax: "proto2" 187 name: "test2.proto" 188 package: "fizz.buzz.gazz" 189 enum_type: [{ 190 name: "Enum" 191 value: [{name:"EnumValue" number:0}] 192 }] 193 `), 194 }, { 195 inFile: mustMakeFile(` 196 syntax: "proto2" 197 name: "test3.proto" 198 package: "fizz.buzz" 199 enum_type: [{ 200 name: "Enum1" 201 value: [{name:"EnumValue1" number:0}] 202 }, { 203 name: "Enum2" 204 value: [{name:"EnumValue2" number:0}] 205 }] 206 `), 207 }, { 208 // Make sure we can register without package name. 209 inFile: mustMakeFile(` 210 name: "weird" 211 syntax: "proto2" 212 message_type: [{ 213 name: "Message" 214 nested_type: [{ 215 name: "Message" 216 nested_type: [{ 217 name: "Message" 218 }] 219 }] 220 }] 221 `), 222 }}, 223 findDescs: []testFindDesc{ 224 {inName: "fizz.buzz.message", wantFound: false}, 225 {inName: "fizz.buzz.Message", wantFound: true}, 226 {inName: "fizz.buzz.Message.X", wantFound: false}, 227 {inName: "fizz.buzz.Field", wantFound: false}, 228 {inName: "fizz.buzz.Oneof", wantFound: false}, 229 {inName: "fizz.buzz.Message.Field", wantFound: true}, 230 {inName: "fizz.buzz.Message.Field.X", wantFound: false}, 231 {inName: "fizz.buzz.Message.Oneof", wantFound: true}, 232 {inName: "fizz.buzz.Message.Oneof.X", wantFound: false}, 233 {inName: "fizz.buzz.Message.Message", wantFound: true}, 234 {inName: "fizz.buzz.Message.Message.X", wantFound: false}, 235 {inName: "fizz.buzz.Message.Enum", wantFound: true}, 236 {inName: "fizz.buzz.Message.Enum.X", wantFound: false}, 237 {inName: "fizz.buzz.Message.EnumValue", wantFound: true}, 238 {inName: "fizz.buzz.Message.EnumValue.X", wantFound: false}, 239 {inName: "fizz.buzz.Message.Extension", wantFound: true}, 240 {inName: "fizz.buzz.Message.Extension.X", wantFound: false}, 241 {inName: "fizz.buzz.enum", wantFound: false}, 242 {inName: "fizz.buzz.Enum", wantFound: true}, 243 {inName: "fizz.buzz.Enum.X", wantFound: false}, 244 {inName: "fizz.buzz.EnumValue", wantFound: true}, 245 {inName: "fizz.buzz.EnumValue.X", wantFound: false}, 246 {inName: "fizz.buzz.Enum.EnumValue", wantFound: false}, 247 {inName: "fizz.buzz.Extension", wantFound: true}, 248 {inName: "fizz.buzz.Extension.X", wantFound: false}, 249 {inName: "fizz.buzz.service", wantFound: false}, 250 {inName: "fizz.buzz.Service", wantFound: true}, 251 {inName: "fizz.buzz.Service.X", wantFound: false}, 252 {inName: "fizz.buzz.Method", wantFound: false}, 253 {inName: "fizz.buzz.Service.Method", wantFound: true}, 254 {inName: "fizz.buzz.Service.Method.X", wantFound: false}, 255 256 {inName: "fizz.buzz.gazz", wantFound: false}, 257 {inName: "fizz.buzz.gazz.Enum", wantFound: true}, 258 {inName: "fizz.buzz.gazz.EnumValue", wantFound: true}, 259 {inName: "fizz.buzz.gazz.Enum.EnumValue", wantFound: false}, 260 261 {inName: "fizz.buzz", wantFound: false}, 262 {inName: "fizz.buzz.Enum1", wantFound: true}, 263 {inName: "fizz.buzz.EnumValue1", wantFound: true}, 264 {inName: "fizz.buzz.Enum1.EnumValue1", wantFound: false}, 265 {inName: "fizz.buzz.Enum2", wantFound: true}, 266 {inName: "fizz.buzz.EnumValue2", wantFound: true}, 267 {inName: "fizz.buzz.Enum2.EnumValue2", wantFound: false}, 268 {inName: "fizz.buzz.Enum3", wantFound: false}, 269 270 {inName: "", wantFound: false}, 271 {inName: "Message", wantFound: true}, 272 {inName: "Message.Message", wantFound: true}, 273 {inName: "Message.Message.Message", wantFound: true}, 274 {inName: "Message.Message.Message.Message", wantFound: false}, 275 }, 276 }} 277 278 sortFiles := cmpopts.SortSlices(func(x, y file) bool { 279 return x.Path < y.Path || (x.Path == y.Path && x.Pkg < y.Pkg) 280 }) 281 for _, tt := range tests { 282 t.Run("", func(t *testing.T) { 283 var files preg.Files 284 for i, tc := range tt.files { 285 gotErr := files.RegisterFile(tc.inFile) 286 if ((gotErr == nil) != (tc.wantErr == "")) || !strings.Contains(fmt.Sprint(gotErr), tc.wantErr) { 287 t.Errorf("file %d, Register() = %v, want %v", i, gotErr, tc.wantErr) 288 } 289 } 290 291 for _, tc := range tt.findDescs { 292 d, _ := files.FindDescriptorByName(tc.inName) 293 gotFound := d != nil 294 if gotFound != tc.wantFound { 295 t.Errorf("FindDescriptorByName(%v) find mismatch: got %v, want %v", tc.inName, gotFound, tc.wantFound) 296 } 297 } 298 299 for _, tc := range tt.rangePkgs { 300 var gotFiles []file 301 var gotCnt int 302 wantCnt := files.NumFilesByPackage(tc.inPkg) 303 files.RangeFilesByPackage(tc.inPkg, func(fd pref.FileDescriptor) bool { 304 gotFiles = append(gotFiles, file{fd.Path(), fd.Package()}) 305 gotCnt++ 306 return true 307 }) 308 if gotCnt != wantCnt { 309 t.Errorf("NumFilesByPackage(%v) = %v, want %v", tc.inPkg, gotCnt, wantCnt) 310 } 311 if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" { 312 t.Errorf("RangeFilesByPackage(%v) mismatch (-want +got):\n%v", tc.inPkg, diff) 313 } 314 } 315 316 for _, tc := range tt.findPaths { 317 var gotFiles []file 318 if fd, err := files.FindFileByPath(tc.inPath); err == nil { 319 gotFiles = append(gotFiles, file{fd.Path(), fd.Package()}) 320 } 321 if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" { 322 t.Errorf("FindFileByPath(%v) mismatch (-want +got):\n%v", tc.inPath, diff) 323 } 324 } 325 }) 326 } 327} 328 329func TestTypes(t *testing.T) { 330 mt1 := pimpl.Export{}.MessageTypeOf(&testpb.Message1{}) 331 et1 := pimpl.Export{}.EnumTypeOf(testpb.Enum1_ONE) 332 xt1 := testpb.E_StringField 333 xt2 := testpb.E_Message4_MessageField 334 registry := new(preg.Types) 335 if err := registry.RegisterMessage(mt1); err != nil { 336 t.Fatalf("registry.RegisterMessage(%v) returns unexpected error: %v", mt1.Descriptor().FullName(), err) 337 } 338 if err := registry.RegisterEnum(et1); err != nil { 339 t.Fatalf("registry.RegisterEnum(%v) returns unexpected error: %v", et1.Descriptor().FullName(), err) 340 } 341 if err := registry.RegisterExtension(xt1); err != nil { 342 t.Fatalf("registry.RegisterExtension(%v) returns unexpected error: %v", xt1.TypeDescriptor().FullName(), err) 343 } 344 if err := registry.RegisterExtension(xt2); err != nil { 345 t.Fatalf("registry.RegisterExtension(%v) returns unexpected error: %v", xt2.TypeDescriptor().FullName(), err) 346 } 347 348 t.Run("FindMessageByName", func(t *testing.T) { 349 tests := []struct { 350 name string 351 messageType pref.MessageType 352 wantErr bool 353 wantNotFound bool 354 }{{ 355 name: "testprotos.Message1", 356 messageType: mt1, 357 }, { 358 name: "testprotos.NoSuchMessage", 359 wantErr: true, 360 wantNotFound: true, 361 }, { 362 name: "testprotos.Enum1", 363 wantErr: true, 364 }, { 365 name: "testprotos.Enum2", 366 wantErr: true, 367 }, { 368 name: "testprotos.Enum3", 369 wantErr: true, 370 }} 371 for _, tc := range tests { 372 got, err := registry.FindMessageByName(pref.FullName(tc.name)) 373 gotErr := err != nil 374 if gotErr != tc.wantErr { 375 t.Errorf("FindMessageByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 376 continue 377 } 378 if tc.wantNotFound && err != preg.NotFound { 379 t.Errorf("FindMessageByName(%v) got error: %v, want NotFound error", tc.name, err) 380 continue 381 } 382 if got != tc.messageType { 383 t.Errorf("FindMessageByName(%v) got wrong value: %v", tc.name, got) 384 } 385 } 386 }) 387 388 t.Run("FindMessageByURL", func(t *testing.T) { 389 tests := []struct { 390 name string 391 messageType pref.MessageType 392 wantErr bool 393 wantNotFound bool 394 }{{ 395 name: "testprotos.Message1", 396 messageType: mt1, 397 }, { 398 name: "type.googleapis.com/testprotos.Nada", 399 wantErr: true, 400 wantNotFound: true, 401 }, { 402 name: "testprotos.Enum1", 403 wantErr: true, 404 }} 405 for _, tc := range tests { 406 got, err := registry.FindMessageByURL(tc.name) 407 gotErr := err != nil 408 if gotErr != tc.wantErr { 409 t.Errorf("FindMessageByURL(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 410 continue 411 } 412 if tc.wantNotFound && err != preg.NotFound { 413 t.Errorf("FindMessageByURL(%v) got error: %v, want NotFound error", tc.name, err) 414 continue 415 } 416 if got != tc.messageType { 417 t.Errorf("FindMessageByURL(%v) got wrong value: %v", tc.name, got) 418 } 419 } 420 }) 421 422 t.Run("FindEnumByName", func(t *testing.T) { 423 tests := []struct { 424 name string 425 enumType pref.EnumType 426 wantErr bool 427 wantNotFound bool 428 }{{ 429 name: "testprotos.Enum1", 430 enumType: et1, 431 }, { 432 name: "testprotos.None", 433 wantErr: true, 434 wantNotFound: true, 435 }, { 436 name: "testprotos.Message1", 437 wantErr: true, 438 }} 439 for _, tc := range tests { 440 got, err := registry.FindEnumByName(pref.FullName(tc.name)) 441 gotErr := err != nil 442 if gotErr != tc.wantErr { 443 t.Errorf("FindEnumByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 444 continue 445 } 446 if tc.wantNotFound && err != preg.NotFound { 447 t.Errorf("FindEnumByName(%v) got error: %v, want NotFound error", tc.name, err) 448 continue 449 } 450 if got != tc.enumType { 451 t.Errorf("FindEnumByName(%v) got wrong value: %v", tc.name, got) 452 } 453 } 454 }) 455 456 t.Run("FindExtensionByName", func(t *testing.T) { 457 tests := []struct { 458 name string 459 extensionType pref.ExtensionType 460 wantErr bool 461 wantNotFound bool 462 }{{ 463 name: "testprotos.string_field", 464 extensionType: xt1, 465 }, { 466 name: "testprotos.Message4.message_field", 467 extensionType: xt2, 468 }, { 469 name: "testprotos.None", 470 wantErr: true, 471 wantNotFound: true, 472 }, { 473 name: "testprotos.Message1", 474 wantErr: true, 475 }} 476 for _, tc := range tests { 477 got, err := registry.FindExtensionByName(pref.FullName(tc.name)) 478 gotErr := err != nil 479 if gotErr != tc.wantErr { 480 t.Errorf("FindExtensionByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 481 continue 482 } 483 if tc.wantNotFound && err != preg.NotFound { 484 t.Errorf("FindExtensionByName(%v) got error: %v, want NotFound error", tc.name, err) 485 continue 486 } 487 if got != tc.extensionType { 488 t.Errorf("FindExtensionByName(%v) got wrong value: %v", tc.name, got) 489 } 490 } 491 }) 492 493 t.Run("FindExtensionByNumber", func(t *testing.T) { 494 tests := []struct { 495 parent string 496 number int32 497 extensionType pref.ExtensionType 498 wantErr bool 499 wantNotFound bool 500 }{{ 501 parent: "testprotos.Message1", 502 number: 11, 503 extensionType: xt1, 504 }, { 505 parent: "testprotos.Message1", 506 number: 13, 507 wantErr: true, 508 wantNotFound: true, 509 }, { 510 parent: "testprotos.Message1", 511 number: 21, 512 extensionType: xt2, 513 }, { 514 parent: "testprotos.Message1", 515 number: 23, 516 wantErr: true, 517 wantNotFound: true, 518 }, { 519 parent: "testprotos.NoSuchMessage", 520 number: 11, 521 wantErr: true, 522 wantNotFound: true, 523 }, { 524 parent: "testprotos.Message1", 525 number: 30, 526 wantErr: true, 527 wantNotFound: true, 528 }, { 529 parent: "testprotos.Message1", 530 number: 99, 531 wantErr: true, 532 wantNotFound: true, 533 }} 534 for _, tc := range tests { 535 got, err := registry.FindExtensionByNumber(pref.FullName(tc.parent), pref.FieldNumber(tc.number)) 536 gotErr := err != nil 537 if gotErr != tc.wantErr { 538 t.Errorf("FindExtensionByNumber(%v, %d) = (_, %v), want error? %t", tc.parent, tc.number, err, tc.wantErr) 539 continue 540 } 541 if tc.wantNotFound && err != preg.NotFound { 542 t.Errorf("FindExtensionByNumber(%v, %d) got error %v, want NotFound error", tc.parent, tc.number, err) 543 continue 544 } 545 if got != tc.extensionType { 546 t.Errorf("FindExtensionByNumber(%v, %d) got wrong value: %v", tc.parent, tc.number, got) 547 } 548 } 549 }) 550 551 sortTypes := cmp.Options{ 552 cmpopts.SortSlices(func(x, y pref.EnumType) bool { 553 return x.Descriptor().FullName() < y.Descriptor().FullName() 554 }), 555 cmpopts.SortSlices(func(x, y pref.MessageType) bool { 556 return x.Descriptor().FullName() < y.Descriptor().FullName() 557 }), 558 cmpopts.SortSlices(func(x, y pref.ExtensionType) bool { 559 return x.TypeDescriptor().FullName() < y.TypeDescriptor().FullName() 560 }), 561 } 562 compare := cmp.Options{ 563 cmp.Comparer(func(x, y pref.EnumType) bool { 564 return x == y 565 }), 566 cmp.Comparer(func(x, y pref.ExtensionType) bool { 567 return x == y 568 }), 569 cmp.Comparer(func(x, y pref.MessageType) bool { 570 return x == y 571 }), 572 } 573 574 t.Run("RangeEnums", func(t *testing.T) { 575 want := []pref.EnumType{et1} 576 var got []pref.EnumType 577 var gotCnt int 578 wantCnt := registry.NumEnums() 579 registry.RangeEnums(func(et pref.EnumType) bool { 580 got = append(got, et) 581 gotCnt++ 582 return true 583 }) 584 585 if gotCnt != wantCnt { 586 t.Errorf("NumEnums() = %v, want %v", gotCnt, wantCnt) 587 } 588 if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 589 t.Errorf("RangeEnums() mismatch (-want +got):\n%v", diff) 590 } 591 }) 592 593 t.Run("RangeMessages", func(t *testing.T) { 594 want := []pref.MessageType{mt1} 595 var got []pref.MessageType 596 var gotCnt int 597 wantCnt := registry.NumMessages() 598 registry.RangeMessages(func(mt pref.MessageType) bool { 599 got = append(got, mt) 600 gotCnt++ 601 return true 602 }) 603 604 if gotCnt != wantCnt { 605 t.Errorf("NumMessages() = %v, want %v", gotCnt, wantCnt) 606 } 607 if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 608 t.Errorf("RangeMessages() mismatch (-want +got):\n%v", diff) 609 } 610 }) 611 612 t.Run("RangeExtensions", func(t *testing.T) { 613 want := []pref.ExtensionType{xt1, xt2} 614 var got []pref.ExtensionType 615 var gotCnt int 616 wantCnt := registry.NumExtensions() 617 registry.RangeExtensions(func(xt pref.ExtensionType) bool { 618 got = append(got, xt) 619 gotCnt++ 620 return true 621 }) 622 623 if gotCnt != wantCnt { 624 t.Errorf("NumExtensions() = %v, want %v", gotCnt, wantCnt) 625 } 626 if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 627 t.Errorf("RangeExtensions() mismatch (-want +got):\n%v", diff) 628 } 629 }) 630 631 t.Run("RangeExtensionsByMessage", func(t *testing.T) { 632 want := []pref.ExtensionType{xt1, xt2} 633 var got []pref.ExtensionType 634 var gotCnt int 635 wantCnt := registry.NumExtensionsByMessage("testprotos.Message1") 636 registry.RangeExtensionsByMessage("testprotos.Message1", func(xt pref.ExtensionType) bool { 637 got = append(got, xt) 638 gotCnt++ 639 return true 640 }) 641 642 if gotCnt != wantCnt { 643 t.Errorf("NumExtensionsByMessage() = %v, want %v", gotCnt, wantCnt) 644 } 645 if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 646 t.Errorf("RangeExtensionsByMessage() mismatch (-want +got):\n%v", diff) 647 } 648 }) 649} 650