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