xref: /aosp_15_r20/external/golang-protobuf/internal/filedesc/desc_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 filedesc_test
6
7import (
8	"fmt"
9	"reflect"
10	"regexp"
11	"strconv"
12	"strings"
13	"testing"
14
15	"github.com/google/go-cmp/cmp"
16
17	"google.golang.org/protobuf/internal/detrand"
18	"google.golang.org/protobuf/internal/filedesc"
19	"google.golang.org/protobuf/proto"
20	"google.golang.org/protobuf/reflect/protodesc"
21	"google.golang.org/protobuf/reflect/protoreflect"
22
23	"google.golang.org/protobuf/types/descriptorpb"
24)
25
26func init() {
27	// Disable detrand to enable direct comparisons on outputs.
28	detrand.Disable()
29}
30
31// TODO: Test protodesc.NewFile with imported files.
32
33func TestFile(t *testing.T) {
34	f1 := &descriptorpb.FileDescriptorProto{
35		Syntax:  proto.String("proto2"),
36		Name:    proto.String("path/to/file.proto"),
37		Package: proto.String("test"),
38		Options: &descriptorpb.FileOptions{Deprecated: proto.Bool(true)},
39		MessageType: []*descriptorpb.DescriptorProto{{
40			Name: proto.String("A"),
41			Options: &descriptorpb.MessageOptions{
42				Deprecated: proto.Bool(true),
43			},
44		}, {
45			Name: proto.String("B"),
46			Field: []*descriptorpb.FieldDescriptorProto{{
47				Name:         proto.String("field_one"),
48				Number:       proto.Int32(1),
49				Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(),
50				Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.StringKind).Enum(),
51				DefaultValue: proto.String("hello, \"world!\"\n"),
52				OneofIndex:   proto.Int32(0),
53			}, {
54				Name:         proto.String("field_two"),
55				JsonName:     proto.String("Field2"),
56				Number:       proto.Int32(2),
57				Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(),
58				Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.EnumKind).Enum(),
59				DefaultValue: proto.String("BAR"),
60				TypeName:     proto.String(".test.E1"),
61				OneofIndex:   proto.Int32(1),
62			}, {
63				Name:       proto.String("field_three"),
64				Number:     proto.Int32(3),
65				Label:      descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(),
66				Type:       descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(),
67				TypeName:   proto.String(".test.C"),
68				OneofIndex: proto.Int32(1),
69			}, {
70				Name:     proto.String("field_four"),
71				JsonName: proto.String("Field4"),
72				Number:   proto.Int32(4),
73				Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(),
74				Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(),
75				TypeName: proto.String(".test.B.FieldFourEntry"),
76			}, {
77				Name:    proto.String("field_five"),
78				Number:  proto.Int32(5),
79				Label:   descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(),
80				Type:    descriptorpb.FieldDescriptorProto_Type(protoreflect.Int32Kind).Enum(),
81				Options: &descriptorpb.FieldOptions{Packed: proto.Bool(true)},
82			}, {
83				Name:   proto.String("field_six"),
84				Number: proto.Int32(6),
85				Label:  descriptorpb.FieldDescriptorProto_Label(protoreflect.Required).Enum(),
86				Type:   descriptorpb.FieldDescriptorProto_Type(protoreflect.BytesKind).Enum(),
87			}},
88			OneofDecl: []*descriptorpb.OneofDescriptorProto{
89				{
90					Name: proto.String("O1"),
91					Options: &descriptorpb.OneofOptions{
92						UninterpretedOption: []*descriptorpb.UninterpretedOption{
93							{StringValue: []byte("option")},
94						},
95					},
96				},
97				{Name: proto.String("O2")},
98			},
99			ReservedName: []string{"fizz", "buzz"},
100			ReservedRange: []*descriptorpb.DescriptorProto_ReservedRange{
101				{Start: proto.Int32(100), End: proto.Int32(200)},
102				{Start: proto.Int32(300), End: proto.Int32(301)},
103			},
104			ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{
105				{Start: proto.Int32(1000), End: proto.Int32(2000)},
106				{Start: proto.Int32(3000), End: proto.Int32(3001), Options: new(descriptorpb.ExtensionRangeOptions)},
107			},
108			NestedType: []*descriptorpb.DescriptorProto{{
109				Name: proto.String("FieldFourEntry"),
110				Field: []*descriptorpb.FieldDescriptorProto{{
111					Name:   proto.String("key"),
112					Number: proto.Int32(1),
113					Label:  descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(),
114					Type:   descriptorpb.FieldDescriptorProto_Type(protoreflect.StringKind).Enum(),
115				}, {
116					Name:     proto.String("value"),
117					Number:   proto.Int32(2),
118					Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(),
119					Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(),
120					TypeName: proto.String(".test.B"),
121				}},
122				Options: &descriptorpb.MessageOptions{
123					MapEntry: proto.Bool(true),
124				},
125			}},
126		}, {
127			Name: proto.String("C"),
128			NestedType: []*descriptorpb.DescriptorProto{{
129				Name: proto.String("A"),
130				Field: []*descriptorpb.FieldDescriptorProto{{
131					Name:         proto.String("F"),
132					Number:       proto.Int32(1),
133					Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Required).Enum(),
134					Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.BytesKind).Enum(),
135					DefaultValue: proto.String(`dead\276\357`),
136				}},
137			}},
138			EnumType: []*descriptorpb.EnumDescriptorProto{{
139				Name: proto.String("E1"),
140				Value: []*descriptorpb.EnumValueDescriptorProto{
141					{Name: proto.String("FOO"), Number: proto.Int32(0)},
142					{Name: proto.String("BAR"), Number: proto.Int32(1)},
143				},
144			}},
145			Extension: []*descriptorpb.FieldDescriptorProto{{
146				Name:     proto.String("X"),
147				Number:   proto.Int32(1000),
148				Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(),
149				Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(),
150				TypeName: proto.String(".test.C"),
151				Extendee: proto.String(".test.B"),
152			}},
153		}},
154		EnumType: []*descriptorpb.EnumDescriptorProto{{
155			Name:    proto.String("E1"),
156			Options: &descriptorpb.EnumOptions{Deprecated: proto.Bool(true)},
157			Value: []*descriptorpb.EnumValueDescriptorProto{
158				{
159					Name:    proto.String("FOO"),
160					Number:  proto.Int32(0),
161					Options: &descriptorpb.EnumValueOptions{Deprecated: proto.Bool(true)},
162				},
163				{Name: proto.String("BAR"), Number: proto.Int32(1)},
164			},
165			ReservedName: []string{"FIZZ", "BUZZ"},
166			ReservedRange: []*descriptorpb.EnumDescriptorProto_EnumReservedRange{
167				{Start: proto.Int32(10), End: proto.Int32(19)},
168				{Start: proto.Int32(30), End: proto.Int32(30)},
169			},
170		}},
171		Extension: []*descriptorpb.FieldDescriptorProto{{
172			Name:     proto.String("X"),
173			Number:   proto.Int32(1000),
174			Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(),
175			Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.EnumKind).Enum(),
176			Options:  &descriptorpb.FieldOptions{Packed: proto.Bool(true)},
177			TypeName: proto.String(".test.E1"),
178			Extendee: proto.String(".test.B"),
179		}},
180		Service: []*descriptorpb.ServiceDescriptorProto{{
181			Name:    proto.String("S"),
182			Options: &descriptorpb.ServiceOptions{Deprecated: proto.Bool(true)},
183			Method: []*descriptorpb.MethodDescriptorProto{{
184				Name:            proto.String("M"),
185				InputType:       proto.String(".test.A"),
186				OutputType:      proto.String(".test.C.A"),
187				ClientStreaming: proto.Bool(true),
188				ServerStreaming: proto.Bool(true),
189				Options:         &descriptorpb.MethodOptions{Deprecated: proto.Bool(true)},
190			}},
191		}},
192	}
193	fd1, err := protodesc.NewFile(f1, nil)
194	if err != nil {
195		t.Fatalf("protodesc.NewFile() error: %v", err)
196	}
197
198	b, err := proto.Marshal(f1)
199	if err != nil {
200		t.Fatalf("proto.Marshal() error: %v", err)
201	}
202	fd2 := filedesc.Builder{RawDescriptor: b}.Build().File
203
204	tests := []struct {
205		name string
206		desc protoreflect.FileDescriptor
207	}{
208		{"protodesc.NewFile", fd1},
209		{"filedesc.Builder.Build", fd2},
210	}
211	for _, tt := range tests {
212		tt := tt
213		t.Run(tt.name, func(t *testing.T) {
214			// Run sub-tests in parallel to induce potential races.
215			for i := 0; i < 2; i++ {
216				t.Run("Accessors", func(t *testing.T) { t.Parallel(); testFileAccessors(t, tt.desc) })
217				t.Run("Format", func(t *testing.T) { t.Parallel(); testFileFormat(t, tt.desc) })
218			}
219		})
220	}
221}
222
223func testFileAccessors(t *testing.T, fd protoreflect.FileDescriptor) {
224	// Represent the descriptor as a map where each key is an accessor method
225	// and the value is either the wanted tail value or another accessor map.
226	type M = map[string]interface{}
227	want := M{
228		"Parent":        nil,
229		"Index":         0,
230		"Syntax":        protoreflect.Proto2,
231		"Name":          protoreflect.Name("test"),
232		"FullName":      protoreflect.FullName("test"),
233		"Path":          "path/to/file.proto",
234		"Package":       protoreflect.FullName("test"),
235		"IsPlaceholder": false,
236		"Options":       &descriptorpb.FileOptions{Deprecated: proto.Bool(true)},
237		"Messages": M{
238			"Len": 3,
239			"Get:0": M{
240				"Parent":        M{"FullName": protoreflect.FullName("test")},
241				"Index":         0,
242				"Syntax":        protoreflect.Proto2,
243				"Name":          protoreflect.Name("A"),
244				"FullName":      protoreflect.FullName("test.A"),
245				"IsPlaceholder": false,
246				"IsMapEntry":    false,
247				"Options": &descriptorpb.MessageOptions{
248					Deprecated: proto.Bool(true),
249				},
250				"Oneofs":          M{"Len": 0},
251				"RequiredNumbers": M{"Len": 0},
252				"ExtensionRanges": M{"Len": 0},
253				"Messages":        M{"Len": 0},
254				"Enums":           M{"Len": 0},
255				"Extensions":      M{"Len": 0},
256			},
257			"ByName:B": M{
258				"Name":  protoreflect.Name("B"),
259				"Index": 1,
260				"Fields": M{
261					"Len":                  6,
262					"ByJSONName:field_one": nil,
263					"ByJSONName:fieldOne": M{
264						"Name":              protoreflect.Name("field_one"),
265						"Index":             0,
266						"JSONName":          "fieldOne",
267						"Default":           "hello, \"world!\"\n",
268						"ContainingOneof":   M{"Name": protoreflect.Name("O1"), "IsPlaceholder": false},
269						"ContainingMessage": M{"FullName": protoreflect.FullName("test.B")},
270					},
271					"ByJSONName:fieldTwo": nil,
272					"ByJSONName:Field2": M{
273						"Name":            protoreflect.Name("field_two"),
274						"Index":           1,
275						"HasJSONName":     true,
276						"JSONName":        "Field2",
277						"Default":         protoreflect.EnumNumber(1),
278						"ContainingOneof": M{"Name": protoreflect.Name("O2"), "IsPlaceholder": false},
279					},
280					"ByName:fieldThree": nil,
281					"ByName:field_three": M{
282						"IsExtension":       false,
283						"IsMap":             false,
284						"MapKey":            nil,
285						"MapValue":          nil,
286						"Message":           M{"FullName": protoreflect.FullName("test.C"), "IsPlaceholder": false},
287						"ContainingOneof":   M{"Name": protoreflect.Name("O2"), "IsPlaceholder": false},
288						"ContainingMessage": M{"FullName": protoreflect.FullName("test.B")},
289					},
290					"ByNumber:12": nil,
291					"ByNumber:4": M{
292						"Cardinality": protoreflect.Repeated,
293						"IsExtension": false,
294						"IsList":      false,
295						"IsMap":       true,
296						"MapKey":      M{"Kind": protoreflect.StringKind},
297						"MapValue":    M{"Kind": protoreflect.MessageKind, "Message": M{"FullName": protoreflect.FullName("test.B")}},
298						"Default":     nil,
299						"Message":     M{"FullName": protoreflect.FullName("test.B.FieldFourEntry"), "IsPlaceholder": false},
300					},
301					"ByNumber:5": M{
302						"Cardinality": protoreflect.Repeated,
303						"Kind":        protoreflect.Int32Kind,
304						"IsPacked":    true,
305						"IsList":      true,
306						"IsMap":       false,
307						"Default":     nil,
308					},
309					"ByNumber:6": M{
310						"Cardinality":     protoreflect.Required,
311						"Default":         []byte(nil),
312						"ContainingOneof": nil,
313					},
314				},
315				"Oneofs": M{
316					"Len":       2,
317					"ByName:O0": nil,
318					"ByName:O1": M{
319						"FullName": protoreflect.FullName("test.B.O1"),
320						"Index":    0,
321						"Options": &descriptorpb.OneofOptions{
322							UninterpretedOption: []*descriptorpb.UninterpretedOption{
323								{StringValue: []byte("option")},
324							},
325						},
326						"Fields": M{
327							"Len":   1,
328							"Get:0": M{"FullName": protoreflect.FullName("test.B.field_one")},
329						},
330					},
331					"Get:1": M{
332						"FullName": protoreflect.FullName("test.B.O2"),
333						"Index":    1,
334						"Fields": M{
335							"Len":              2,
336							"ByName:field_two": M{"Name": protoreflect.Name("field_two")},
337							"Get:1":            M{"Name": protoreflect.Name("field_three")},
338						},
339					},
340				},
341				"ReservedNames": M{
342					"Len":         2,
343					"Get:0":       protoreflect.Name("fizz"),
344					"Has:buzz":    true,
345					"Has:noexist": false,
346				},
347				"ReservedRanges": M{
348					"Len":     2,
349					"Get:0":   [2]protoreflect.FieldNumber{100, 200},
350					"Has:99":  false,
351					"Has:100": true,
352					"Has:150": true,
353					"Has:199": true,
354					"Has:200": false,
355					"Has:300": true,
356					"Has:301": false,
357				},
358				"RequiredNumbers": M{
359					"Len":   1,
360					"Get:0": protoreflect.FieldNumber(6),
361					"Has:1": false,
362					"Has:6": true,
363				},
364				"ExtensionRanges": M{
365					"Len":      2,
366					"Get:0":    [2]protoreflect.FieldNumber{1000, 2000},
367					"Has:999":  false,
368					"Has:1000": true,
369					"Has:1500": true,
370					"Has:1999": true,
371					"Has:2000": false,
372					"Has:3000": true,
373					"Has:3001": false,
374				},
375				"ExtensionRangeOptions:0": (*descriptorpb.ExtensionRangeOptions)(nil),
376				"ExtensionRangeOptions:1": new(descriptorpb.ExtensionRangeOptions),
377				"Messages": M{
378					"Get:0": M{
379						"Fields": M{
380							"Len": 2,
381							"ByNumber:1": M{
382								"Parent":            M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")},
383								"Index":             0,
384								"Name":              protoreflect.Name("key"),
385								"FullName":          protoreflect.FullName("test.B.FieldFourEntry.key"),
386								"Number":            protoreflect.FieldNumber(1),
387								"Cardinality":       protoreflect.Optional,
388								"Kind":              protoreflect.StringKind,
389								"Options":           (*descriptorpb.FieldOptions)(nil),
390								"HasJSONName":       false,
391								"JSONName":          "key",
392								"IsPacked":          false,
393								"IsList":            false,
394								"IsMap":             false,
395								"IsExtension":       false,
396								"IsWeak":            false,
397								"Default":           "",
398								"ContainingOneof":   nil,
399								"ContainingMessage": M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")},
400								"Message":           nil,
401								"Enum":              nil,
402							},
403							"ByNumber:2": M{
404								"Parent":            M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")},
405								"Index":             1,
406								"Name":              protoreflect.Name("value"),
407								"FullName":          protoreflect.FullName("test.B.FieldFourEntry.value"),
408								"Number":            protoreflect.FieldNumber(2),
409								"Cardinality":       protoreflect.Optional,
410								"Kind":              protoreflect.MessageKind,
411								"JSONName":          "value",
412								"IsPacked":          false,
413								"IsList":            false,
414								"IsMap":             false,
415								"IsExtension":       false,
416								"IsWeak":            false,
417								"Default":           nil,
418								"ContainingOneof":   nil,
419								"ContainingMessage": M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")},
420								"Message":           M{"FullName": protoreflect.FullName("test.B"), "IsPlaceholder": false},
421								"Enum":              nil,
422							},
423							"ByNumber:3": nil,
424						},
425					},
426				},
427			},
428			"Get:2": M{
429				"Name":  protoreflect.Name("C"),
430				"Index": 2,
431				"Messages": M{
432					"Len":   1,
433					"Get:0": M{"FullName": protoreflect.FullName("test.C.A")},
434				},
435				"Enums": M{
436					"Len":   1,
437					"Get:0": M{"FullName": protoreflect.FullName("test.C.E1")},
438				},
439				"Extensions": M{
440					"Len":   1,
441					"Get:0": M{"FullName": protoreflect.FullName("test.C.X")},
442				},
443			},
444		},
445		"Enums": M{
446			"Len": 1,
447			"Get:0": M{
448				"Name":    protoreflect.Name("E1"),
449				"Options": &descriptorpb.EnumOptions{Deprecated: proto.Bool(true)},
450				"Values": M{
451					"Len":        2,
452					"ByName:Foo": nil,
453					"ByName:FOO": M{
454						"FullName": protoreflect.FullName("test.FOO"),
455						"Options":  &descriptorpb.EnumValueOptions{Deprecated: proto.Bool(true)},
456					},
457					"ByNumber:2": nil,
458					"ByNumber:1": M{"FullName": protoreflect.FullName("test.BAR")},
459				},
460				"ReservedNames": M{
461					"Len":         2,
462					"Get:0":       protoreflect.Name("FIZZ"),
463					"Has:BUZZ":    true,
464					"Has:NOEXIST": false,
465				},
466				"ReservedRanges": M{
467					"Len":    2,
468					"Get:0":  [2]protoreflect.EnumNumber{10, 19},
469					"Has:9":  false,
470					"Has:10": true,
471					"Has:15": true,
472					"Has:19": true,
473					"Has:20": false,
474					"Has:30": true,
475					"Has:31": false,
476				},
477			},
478		},
479		"Extensions": M{
480			"Len": 1,
481			"ByName:X": M{
482				"Name":              protoreflect.Name("X"),
483				"Number":            protoreflect.FieldNumber(1000),
484				"Cardinality":       protoreflect.Repeated,
485				"Kind":              protoreflect.EnumKind,
486				"IsExtension":       true,
487				"IsPacked":          true,
488				"IsList":            true,
489				"IsMap":             false,
490				"MapKey":            nil,
491				"MapValue":          nil,
492				"ContainingOneof":   nil,
493				"ContainingMessage": M{"FullName": protoreflect.FullName("test.B"), "IsPlaceholder": false},
494				"Enum":              M{"FullName": protoreflect.FullName("test.E1"), "IsPlaceholder": false},
495				"Options":           &descriptorpb.FieldOptions{Packed: proto.Bool(true)},
496			},
497		},
498		"Services": M{
499			"Len":      1,
500			"ByName:s": nil,
501			"ByName:S": M{
502				"Parent":   M{"FullName": protoreflect.FullName("test")},
503				"Name":     protoreflect.Name("S"),
504				"FullName": protoreflect.FullName("test.S"),
505				"Options":  &descriptorpb.ServiceOptions{Deprecated: proto.Bool(true)},
506				"Methods": M{
507					"Len": 1,
508					"Get:0": M{
509						"Parent":            M{"FullName": protoreflect.FullName("test.S")},
510						"Name":              protoreflect.Name("M"),
511						"FullName":          protoreflect.FullName("test.S.M"),
512						"Input":             M{"FullName": protoreflect.FullName("test.A"), "IsPlaceholder": false},
513						"Output":            M{"FullName": protoreflect.FullName("test.C.A"), "IsPlaceholder": false},
514						"IsStreamingClient": true,
515						"IsStreamingServer": true,
516						"Options":           &descriptorpb.MethodOptions{Deprecated: proto.Bool(true)},
517					},
518				},
519			},
520		},
521	}
522	checkAccessors(t, "", reflect.ValueOf(fd), want)
523}
524func checkAccessors(t *testing.T, p string, rv reflect.Value, want map[string]interface{}) {
525	p0 := p
526	defer func() {
527		if ex := recover(); ex != nil {
528			t.Errorf("panic at %v: %v", p, ex)
529		}
530	}()
531
532	if rv.Interface() == nil {
533		t.Errorf("%v is nil, want non-nil", p)
534		return
535	}
536	for s, v := range want {
537		// Call the accessor method.
538		p = p0 + "." + s
539		var rets []reflect.Value
540		if i := strings.IndexByte(s, ':'); i >= 0 {
541			// Accessor method takes in a single argument, which is encoded
542			// after the accessor name, separated by a ':' delimiter.
543			fnc := rv.MethodByName(s[:i])
544			arg := reflect.New(fnc.Type().In(0)).Elem()
545			s = s[i+len(":"):]
546			switch arg.Kind() {
547			case reflect.String:
548				arg.SetString(s)
549			case reflect.Int32, reflect.Int:
550				n, _ := strconv.ParseInt(s, 0, 64)
551				arg.SetInt(n)
552			}
553			rets = fnc.Call([]reflect.Value{arg})
554		} else {
555			rets = rv.MethodByName(s).Call(nil)
556		}
557
558		// Check that (val, ok) pattern is internally consistent.
559		if len(rets) == 2 {
560			if rets[0].IsNil() && rets[1].Bool() {
561				t.Errorf("%v = (nil, true), want (nil, false)", p)
562			}
563			if !rets[0].IsNil() && !rets[1].Bool() {
564				t.Errorf("%v = (non-nil, false), want (non-nil, true)", p)
565			}
566		}
567
568		// Check that the accessor output matches.
569		if want, ok := v.(map[string]interface{}); ok {
570			checkAccessors(t, p, rets[0], want)
571			continue
572		}
573
574		got := rets[0].Interface()
575		if pv, ok := got.(protoreflect.Value); ok {
576			got = pv.Interface()
577		}
578
579		// Compare with proto.Equal if possible.
580		gotMsg, gotMsgOK := got.(proto.Message)
581		wantMsg, wantMsgOK := v.(proto.Message)
582		if gotMsgOK && wantMsgOK {
583			gotNil := reflect.ValueOf(gotMsg).IsNil()
584			wantNil := reflect.ValueOf(wantMsg).IsNil()
585			switch {
586			case !gotNil && wantNil:
587				t.Errorf("%v = non-nil, want nil", p)
588			case gotNil && !wantNil:
589				t.Errorf("%v = nil, want non-nil", p)
590			case !proto.Equal(gotMsg, wantMsg):
591				t.Errorf("%v = %v, want %v", p, gotMsg, wantMsg)
592			}
593			continue
594		}
595
596		if want := v; !reflect.DeepEqual(got, want) {
597			t.Errorf("%v = %T(%v), want %T(%v)", p, got, got, want, want)
598		}
599	}
600}
601
602func testFileFormat(t *testing.T, fd protoreflect.FileDescriptor) {
603	const wantFileDescriptor = `FileDescriptor{
604	Syntax:  proto2
605	Path:    "path/to/file.proto"
606	Package: test
607	Messages: [{
608		Name: A
609	}, {
610		Name: B
611		Fields: [{
612			Name:        field_one
613			Number:      1
614			Cardinality: optional
615			Kind:        string
616			JSONName:    "fieldOne"
617			HasPresence: true
618			HasDefault:  true
619			Default:     "hello, \"world!\"\n"
620			Oneof:       O1
621		}, {
622			Name:        field_two
623			Number:      2
624			Cardinality: optional
625			Kind:        enum
626			HasJSONName: true
627			JSONName:    "Field2"
628			HasPresence: true
629			HasDefault:  true
630			Default:     1
631			Oneof:       O2
632			Enum:        test.E1
633		}, {
634			Name:        field_three
635			Number:      3
636			Cardinality: optional
637			Kind:        message
638			JSONName:    "fieldThree"
639			HasPresence: true
640			Oneof:       O2
641			Message:     test.C
642		}, {
643			Name:        field_four
644			Number:      4
645			Cardinality: repeated
646			Kind:        message
647			HasJSONName: true
648			JSONName:    "Field4"
649			IsMap:       true
650			MapKey:      string
651			MapValue:    test.B
652		}, {
653			Name:        field_five
654			Number:      5
655			Cardinality: repeated
656			Kind:        int32
657			JSONName:    "fieldFive"
658			IsPacked:    true
659			IsList:      true
660		}, {
661			Name:        field_six
662			Number:      6
663			Cardinality: required
664			Kind:        bytes
665			JSONName:    "fieldSix"
666			HasPresence: true
667		}]
668		Oneofs: [{
669			Name:   O1
670			Fields: [field_one]
671		}, {
672			Name:   O2
673			Fields: [field_two, field_three]
674		}]
675		ReservedNames:   [fizz, buzz]
676		ReservedRanges:  [100:200, 300]
677		RequiredNumbers: [6]
678		ExtensionRanges: [1000:2000, 3000]
679		Messages: [{
680			Name:       FieldFourEntry
681			IsMapEntry: true
682			Fields: [{
683				Name:        key
684				Number:      1
685				Cardinality: optional
686				Kind:        string
687				JSONName:    "key"
688				HasPresence: true
689			}, {
690				Name:        value
691				Number:      2
692				Cardinality: optional
693				Kind:        message
694				JSONName:    "value"
695				HasPresence: true
696				Message:     test.B
697			}]
698		}]
699	}, {
700		Name: C
701		Messages: [{
702			Name: A
703			Fields: [{
704				Name:        F
705				Number:      1
706				Cardinality: required
707				Kind:        bytes
708				JSONName:    "F"
709				HasPresence: true
710				HasDefault:  true
711				Default:     "dead\xbe\xef"
712			}]
713			RequiredNumbers: [1]
714		}]
715		Enums: [{
716			Name: E1
717			Values: [
718				{Name: FOO}
719				{Name: BAR, Number: 1}
720			]
721		}]
722		Extensions: [{
723			Name:        X
724			Number:      1000
725			Cardinality: repeated
726			Kind:        message
727			JSONName:    "[test.C.X]"
728			IsExtension: true
729			IsList:      true
730			Extendee:    test.B
731			Message:     test.C
732		}]
733	}]
734	Enums: [{
735		Name: E1
736		Values: [
737			{Name: FOO}
738			{Name: BAR, Number: 1}
739		]
740		ReservedNames:  [FIZZ, BUZZ]
741		ReservedRanges: [10:20, 30]
742	}]
743	Extensions: [{
744		Name:        X
745		Number:      1000
746		Cardinality: repeated
747		Kind:        enum
748		JSONName:    "[test.X]"
749		IsExtension: true
750		IsPacked:    true
751		IsList:      true
752		Extendee:    test.B
753		Enum:        test.E1
754	}]
755	Services: [{
756		Name: S
757		Methods: [{
758			Name:              M
759			Input:             test.A
760			Output:            test.C.A
761			IsStreamingClient: true
762			IsStreamingServer: true
763		}]
764	}]
765}`
766
767	const wantEnums = `Enums{{
768	Name: E1
769	Values: [
770		{Name: FOO}
771		{Name: BAR, Number: 1}
772	]
773	ReservedNames:  [FIZZ, BUZZ]
774	ReservedRanges: [10:20, 30]
775}}`
776
777	const wantExtensions = `Extensions{{
778	Name:        X
779	Number:      1000
780	Cardinality: repeated
781	Kind:        enum
782	JSONName:    "[test.X]"
783	IsExtension: true
784	IsPacked:    true
785	IsList:      true
786	Extendee:    test.B
787	Enum:        test.E1
788}}`
789
790	const wantImports = `FileImports{}`
791
792	const wantReservedNames = "Names{fizz, buzz}"
793
794	const wantReservedRanges = "FieldRanges{100:200, 300}"
795
796	const wantServices = `Services{{
797	Name: S
798	Methods: [{
799		Name:              M
800		Input:             test.A
801		Output:            test.C.A
802		IsStreamingClient: true
803		IsStreamingServer: true
804	}]
805}}`
806
807	tests := []struct {
808		path string
809		fmt  string
810		want string
811		val  interface{}
812	}{
813		{"fd", "%v", compactMultiFormat(wantFileDescriptor), fd},
814		{"fd", "%+v", wantFileDescriptor, fd},
815		{"fd.Enums()", "%v", compactMultiFormat(wantEnums), fd.Enums()},
816		{"fd.Enums()", "%+v", wantEnums, fd.Enums()},
817		{"fd.Extensions()", "%v", compactMultiFormat(wantExtensions), fd.Extensions()},
818		{"fd.Extensions()", "%+v", wantExtensions, fd.Extensions()},
819		{"fd.Imports()", "%v", compactMultiFormat(wantImports), fd.Imports()},
820		{"fd.Imports()", "%+v", wantImports, fd.Imports()},
821		{"fd.Messages(B).ReservedNames()", "%v", compactMultiFormat(wantReservedNames), fd.Messages().ByName("B").ReservedNames()},
822		{"fd.Messages(B).ReservedNames()", "%+v", wantReservedNames, fd.Messages().ByName("B").ReservedNames()},
823		{"fd.Messages(B).ReservedRanges()", "%v", compactMultiFormat(wantReservedRanges), fd.Messages().ByName("B").ReservedRanges()},
824		{"fd.Messages(B).ReservedRanges()", "%+v", wantReservedRanges, fd.Messages().ByName("B").ReservedRanges()},
825		{"fd.Services()", "%v", compactMultiFormat(wantServices), fd.Services()},
826		{"fd.Services()", "%+v", wantServices, fd.Services()},
827	}
828	for _, tt := range tests {
829		got := fmt.Sprintf(tt.fmt, tt.val)
830		if diff := cmp.Diff(got, tt.want); diff != "" {
831			t.Errorf("fmt.Sprintf(%q, %s) mismatch (-got +want):\n%s", tt.fmt, tt.path, diff)
832		}
833	}
834}
835
836// compactMultiFormat returns the single line form of a multi line output.
837func compactMultiFormat(s string) string {
838	var b []byte
839	for _, s := range strings.Split(s, "\n") {
840		s = strings.TrimSpace(s)
841		s = regexp.MustCompile(": +").ReplaceAllString(s, ": ")
842		prevWord := len(b) > 0 && b[len(b)-1] != '[' && b[len(b)-1] != '{'
843		nextWord := len(s) > 0 && s[0] != ']' && s[0] != '}'
844		if prevWord && nextWord {
845			b = append(b, ", "...)
846		}
847		b = append(b, s...)
848	}
849	return string(b)
850}
851