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