|  | // Copyright 2018 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package filedesc_test | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "reflect" | 
|  | "regexp" | 
|  | "strconv" | 
|  | "strings" | 
|  | "testing" | 
|  |  | 
|  | "github.com/google/go-cmp/cmp" | 
|  |  | 
|  | "google.golang.org/protobuf/internal/detrand" | 
|  | "google.golang.org/protobuf/internal/filedesc" | 
|  | "google.golang.org/protobuf/proto" | 
|  | "google.golang.org/protobuf/reflect/protodesc" | 
|  | "google.golang.org/protobuf/reflect/protoreflect" | 
|  | "google.golang.org/protobuf/types/descriptorpb" | 
|  | ) | 
|  |  | 
|  | func init() { | 
|  | // Disable detrand to enable direct comparisons on outputs. | 
|  | detrand.Disable() | 
|  | } | 
|  |  | 
|  | // TODO: Test protodesc.NewFile with imported files. | 
|  |  | 
|  | func TestFile(t *testing.T) { | 
|  | f1 := &descriptorpb.FileDescriptorProto{ | 
|  | Syntax:  proto.String("proto2"), | 
|  | Name:    proto.String("path/to/file.proto"), | 
|  | Package: proto.String("test"), | 
|  | Options: &descriptorpb.FileOptions{Deprecated: proto.Bool(true)}, | 
|  | MessageType: []*descriptorpb.DescriptorProto{{ | 
|  | Name: proto.String("A"), | 
|  | Options: &descriptorpb.MessageOptions{ | 
|  | Deprecated: proto.Bool(true), | 
|  | }, | 
|  | }, { | 
|  | Name: proto.String("B"), | 
|  | Field: []*descriptorpb.FieldDescriptorProto{{ | 
|  | Name:         proto.String("field_one"), | 
|  | Number:       proto.Int32(1), | 
|  | Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(), | 
|  | Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.StringKind).Enum(), | 
|  | DefaultValue: proto.String("hello, \"world!\"\n"), | 
|  | OneofIndex:   proto.Int32(0), | 
|  | }, { | 
|  | Name:         proto.String("field_two"), | 
|  | JsonName:     proto.String("Field2"), | 
|  | Number:       proto.Int32(2), | 
|  | Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(), | 
|  | Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.EnumKind).Enum(), | 
|  | DefaultValue: proto.String("BAR"), | 
|  | TypeName:     proto.String(".test.E1"), | 
|  | OneofIndex:   proto.Int32(1), | 
|  | }, { | 
|  | Name:       proto.String("field_three"), | 
|  | Number:     proto.Int32(3), | 
|  | Label:      descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(), | 
|  | Type:       descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(), | 
|  | TypeName:   proto.String(".test.C"), | 
|  | OneofIndex: proto.Int32(1), | 
|  | }, { | 
|  | Name:     proto.String("field_four"), | 
|  | JsonName: proto.String("Field4"), | 
|  | Number:   proto.Int32(4), | 
|  | Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(), | 
|  | TypeName: proto.String(".test.B.FieldFourEntry"), | 
|  | }, { | 
|  | Name:    proto.String("field_five"), | 
|  | Number:  proto.Int32(5), | 
|  | Label:   descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(), | 
|  | Type:    descriptorpb.FieldDescriptorProto_Type(protoreflect.Int32Kind).Enum(), | 
|  | Options: &descriptorpb.FieldOptions{Packed: proto.Bool(true)}, | 
|  | }, { | 
|  | Name:   proto.String("field_six"), | 
|  | Number: proto.Int32(6), | 
|  | Label:  descriptorpb.FieldDescriptorProto_Label(protoreflect.Required).Enum(), | 
|  | Type:   descriptorpb.FieldDescriptorProto_Type(protoreflect.BytesKind).Enum(), | 
|  | }}, | 
|  | OneofDecl: []*descriptorpb.OneofDescriptorProto{ | 
|  | { | 
|  | Name: proto.String("O1"), | 
|  | Options: &descriptorpb.OneofOptions{ | 
|  | UninterpretedOption: []*descriptorpb.UninterpretedOption{ | 
|  | {StringValue: []byte("option")}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | {Name: proto.String("O2")}, | 
|  | }, | 
|  | ReservedName: []string{"fizz", "buzz"}, | 
|  | ReservedRange: []*descriptorpb.DescriptorProto_ReservedRange{ | 
|  | {Start: proto.Int32(100), End: proto.Int32(200)}, | 
|  | {Start: proto.Int32(300), End: proto.Int32(301)}, | 
|  | }, | 
|  | ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{ | 
|  | {Start: proto.Int32(1000), End: proto.Int32(2000)}, | 
|  | {Start: proto.Int32(3000), End: proto.Int32(3001), Options: new(descriptorpb.ExtensionRangeOptions)}, | 
|  | }, | 
|  | NestedType: []*descriptorpb.DescriptorProto{{ | 
|  | Name: proto.String("FieldFourEntry"), | 
|  | Field: []*descriptorpb.FieldDescriptorProto{{ | 
|  | Name:   proto.String("key"), | 
|  | Number: proto.Int32(1), | 
|  | Label:  descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(), | 
|  | Type:   descriptorpb.FieldDescriptorProto_Type(protoreflect.StringKind).Enum(), | 
|  | }, { | 
|  | Name:     proto.String("value"), | 
|  | Number:   proto.Int32(2), | 
|  | Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Optional).Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(), | 
|  | TypeName: proto.String(".test.B"), | 
|  | }}, | 
|  | Options: &descriptorpb.MessageOptions{ | 
|  | MapEntry: proto.Bool(true), | 
|  | }, | 
|  | }}, | 
|  | }, { | 
|  | Name: proto.String("C"), | 
|  | NestedType: []*descriptorpb.DescriptorProto{{ | 
|  | Name: proto.String("A"), | 
|  | Field: []*descriptorpb.FieldDescriptorProto{{ | 
|  | Name:         proto.String("F"), | 
|  | Number:       proto.Int32(1), | 
|  | Label:        descriptorpb.FieldDescriptorProto_Label(protoreflect.Required).Enum(), | 
|  | Type:         descriptorpb.FieldDescriptorProto_Type(protoreflect.BytesKind).Enum(), | 
|  | DefaultValue: proto.String(`dead\276\357`), | 
|  | }}, | 
|  | }}, | 
|  | EnumType: []*descriptorpb.EnumDescriptorProto{{ | 
|  | Name: proto.String("E1"), | 
|  | Value: []*descriptorpb.EnumValueDescriptorProto{ | 
|  | {Name: proto.String("FOO"), Number: proto.Int32(0)}, | 
|  | {Name: proto.String("BAR"), Number: proto.Int32(1)}, | 
|  | }, | 
|  | }}, | 
|  | Extension: []*descriptorpb.FieldDescriptorProto{{ | 
|  | Name:     proto.String("X"), | 
|  | Number:   proto.Int32(1000), | 
|  | Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.MessageKind).Enum(), | 
|  | TypeName: proto.String(".test.C"), | 
|  | Extendee: proto.String(".test.B"), | 
|  | }}, | 
|  | }}, | 
|  | EnumType: []*descriptorpb.EnumDescriptorProto{{ | 
|  | Name:    proto.String("E1"), | 
|  | Options: &descriptorpb.EnumOptions{Deprecated: proto.Bool(true)}, | 
|  | Value: []*descriptorpb.EnumValueDescriptorProto{ | 
|  | { | 
|  | Name:    proto.String("FOO"), | 
|  | Number:  proto.Int32(0), | 
|  | Options: &descriptorpb.EnumValueOptions{Deprecated: proto.Bool(true)}, | 
|  | }, | 
|  | {Name: proto.String("BAR"), Number: proto.Int32(1)}, | 
|  | }, | 
|  | ReservedName: []string{"FIZZ", "BUZZ"}, | 
|  | ReservedRange: []*descriptorpb.EnumDescriptorProto_EnumReservedRange{ | 
|  | {Start: proto.Int32(10), End: proto.Int32(19)}, | 
|  | {Start: proto.Int32(30), End: proto.Int32(30)}, | 
|  | }, | 
|  | }}, | 
|  | Extension: []*descriptorpb.FieldDescriptorProto{{ | 
|  | Name:     proto.String("X"), | 
|  | Number:   proto.Int32(1000), | 
|  | Label:    descriptorpb.FieldDescriptorProto_Label(protoreflect.Repeated).Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_Type(protoreflect.EnumKind).Enum(), | 
|  | Options:  &descriptorpb.FieldOptions{Packed: proto.Bool(true)}, | 
|  | TypeName: proto.String(".test.E1"), | 
|  | Extendee: proto.String(".test.B"), | 
|  | }}, | 
|  | Service: []*descriptorpb.ServiceDescriptorProto{{ | 
|  | Name:    proto.String("S"), | 
|  | Options: &descriptorpb.ServiceOptions{Deprecated: proto.Bool(true)}, | 
|  | Method: []*descriptorpb.MethodDescriptorProto{{ | 
|  | Name:            proto.String("M"), | 
|  | InputType:       proto.String(".test.A"), | 
|  | OutputType:      proto.String(".test.C.A"), | 
|  | ClientStreaming: proto.Bool(true), | 
|  | ServerStreaming: proto.Bool(true), | 
|  | Options:         &descriptorpb.MethodOptions{Deprecated: proto.Bool(true)}, | 
|  | }}, | 
|  | }}, | 
|  | } | 
|  | fd1, err := protodesc.NewFile(f1, nil) | 
|  | if err != nil { | 
|  | t.Fatalf("protodesc.NewFile() error: %v", err) | 
|  | } | 
|  |  | 
|  | b, err := proto.Marshal(f1) | 
|  | if err != nil { | 
|  | t.Fatalf("proto.Marshal() error: %v", err) | 
|  | } | 
|  | fd2 := filedesc.Builder{RawDescriptor: b}.Build().File | 
|  |  | 
|  | tests := []struct { | 
|  | name string | 
|  | desc protoreflect.FileDescriptor | 
|  | }{ | 
|  | {"protodesc.NewFile", fd1}, | 
|  | {"filedesc.Builder.Build", fd2}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | tt := tt | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | // Run sub-tests in parallel to induce potential races. | 
|  | for i := 0; i < 2; i++ { | 
|  | t.Run("Accessors", func(t *testing.T) { t.Parallel(); testFileAccessors(t, tt.desc) }) | 
|  | t.Run("Format", func(t *testing.T) { t.Parallel(); testFileFormat(t, tt.desc) }) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func testFileAccessors(t *testing.T, fd protoreflect.FileDescriptor) { | 
|  | // Represent the descriptor as a map where each key is an accessor method | 
|  | // and the value is either the wanted tail value or another accessor map. | 
|  | type M = map[string]any | 
|  | want := M{ | 
|  | "Parent":        nil, | 
|  | "Index":         0, | 
|  | "Syntax":        protoreflect.Proto2, | 
|  | "Name":          protoreflect.Name("test"), | 
|  | "FullName":      protoreflect.FullName("test"), | 
|  | "Path":          "path/to/file.proto", | 
|  | "Package":       protoreflect.FullName("test"), | 
|  | "IsPlaceholder": false, | 
|  | "Options":       &descriptorpb.FileOptions{Deprecated: proto.Bool(true)}, | 
|  | "Messages": M{ | 
|  | "Len": 3, | 
|  | "Get:0": M{ | 
|  | "Parent":        M{"FullName": protoreflect.FullName("test")}, | 
|  | "Index":         0, | 
|  | "Syntax":        protoreflect.Proto2, | 
|  | "Name":          protoreflect.Name("A"), | 
|  | "FullName":      protoreflect.FullName("test.A"), | 
|  | "IsPlaceholder": false, | 
|  | "IsMapEntry":    false, | 
|  | "Options": &descriptorpb.MessageOptions{ | 
|  | Deprecated: proto.Bool(true), | 
|  | }, | 
|  | "Oneofs":          M{"Len": 0}, | 
|  | "RequiredNumbers": M{"Len": 0}, | 
|  | "ExtensionRanges": M{"Len": 0}, | 
|  | "Messages":        M{"Len": 0}, | 
|  | "Enums":           M{"Len": 0}, | 
|  | "Extensions":      M{"Len": 0}, | 
|  | }, | 
|  | "ByName:B": M{ | 
|  | "Name":  protoreflect.Name("B"), | 
|  | "Index": 1, | 
|  | "Fields": M{ | 
|  | "Len":                  6, | 
|  | "ByJSONName:field_one": nil, | 
|  | "ByJSONName:fieldOne": M{ | 
|  | "Name":              protoreflect.Name("field_one"), | 
|  | "Index":             0, | 
|  | "JSONName":          "fieldOne", | 
|  | "Default":           "hello, \"world!\"\n", | 
|  | "ContainingOneof":   M{"Name": protoreflect.Name("O1"), "IsPlaceholder": false}, | 
|  | "ContainingMessage": M{"FullName": protoreflect.FullName("test.B")}, | 
|  | }, | 
|  | "ByJSONName:fieldTwo": nil, | 
|  | "ByJSONName:Field2": M{ | 
|  | "Name":            protoreflect.Name("field_two"), | 
|  | "Index":           1, | 
|  | "HasJSONName":     true, | 
|  | "JSONName":        "Field2", | 
|  | "Default":         protoreflect.EnumNumber(1), | 
|  | "ContainingOneof": M{"Name": protoreflect.Name("O2"), "IsPlaceholder": false}, | 
|  | }, | 
|  | "ByName:fieldThree": nil, | 
|  | "ByName:field_three": M{ | 
|  | "IsExtension":       false, | 
|  | "IsMap":             false, | 
|  | "MapKey":            nil, | 
|  | "MapValue":          nil, | 
|  | "Message":           M{"FullName": protoreflect.FullName("test.C"), "IsPlaceholder": false}, | 
|  | "ContainingOneof":   M{"Name": protoreflect.Name("O2"), "IsPlaceholder": false}, | 
|  | "ContainingMessage": M{"FullName": protoreflect.FullName("test.B")}, | 
|  | }, | 
|  | "ByNumber:12": nil, | 
|  | "ByNumber:4": M{ | 
|  | "Cardinality": protoreflect.Repeated, | 
|  | "IsExtension": false, | 
|  | "IsList":      false, | 
|  | "IsMap":       true, | 
|  | "MapKey":      M{"Kind": protoreflect.StringKind}, | 
|  | "MapValue":    M{"Kind": protoreflect.MessageKind, "Message": M{"FullName": protoreflect.FullName("test.B")}}, | 
|  | "Default":     nil, | 
|  | "Message":     M{"FullName": protoreflect.FullName("test.B.FieldFourEntry"), "IsPlaceholder": false}, | 
|  | }, | 
|  | "ByNumber:5": M{ | 
|  | "Cardinality": protoreflect.Repeated, | 
|  | "Kind":        protoreflect.Int32Kind, | 
|  | "IsPacked":    true, | 
|  | "IsList":      true, | 
|  | "IsMap":       false, | 
|  | "Default":     nil, | 
|  | }, | 
|  | "ByNumber:6": M{ | 
|  | "Cardinality":     protoreflect.Required, | 
|  | "Default":         []byte(nil), | 
|  | "ContainingOneof": nil, | 
|  | }, | 
|  | }, | 
|  | "Oneofs": M{ | 
|  | "Len":       2, | 
|  | "ByName:O0": nil, | 
|  | "ByName:O1": M{ | 
|  | "FullName": protoreflect.FullName("test.B.O1"), | 
|  | "Index":    0, | 
|  | "Options": &descriptorpb.OneofOptions{ | 
|  | UninterpretedOption: []*descriptorpb.UninterpretedOption{ | 
|  | {StringValue: []byte("option")}, | 
|  | }, | 
|  | }, | 
|  | "Fields": M{ | 
|  | "Len":   1, | 
|  | "Get:0": M{"FullName": protoreflect.FullName("test.B.field_one")}, | 
|  | }, | 
|  | }, | 
|  | "Get:1": M{ | 
|  | "FullName": protoreflect.FullName("test.B.O2"), | 
|  | "Index":    1, | 
|  | "Fields": M{ | 
|  | "Len":              2, | 
|  | "ByName:field_two": M{"Name": protoreflect.Name("field_two")}, | 
|  | "Get:1":            M{"Name": protoreflect.Name("field_three")}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | "ReservedNames": M{ | 
|  | "Len":         2, | 
|  | "Get:0":       protoreflect.Name("fizz"), | 
|  | "Has:buzz":    true, | 
|  | "Has:noexist": false, | 
|  | }, | 
|  | "ReservedRanges": M{ | 
|  | "Len":     2, | 
|  | "Get:0":   [2]protoreflect.FieldNumber{100, 200}, | 
|  | "Has:99":  false, | 
|  | "Has:100": true, | 
|  | "Has:150": true, | 
|  | "Has:199": true, | 
|  | "Has:200": false, | 
|  | "Has:300": true, | 
|  | "Has:301": false, | 
|  | }, | 
|  | "RequiredNumbers": M{ | 
|  | "Len":   1, | 
|  | "Get:0": protoreflect.FieldNumber(6), | 
|  | "Has:1": false, | 
|  | "Has:6": true, | 
|  | }, | 
|  | "ExtensionRanges": M{ | 
|  | "Len":      2, | 
|  | "Get:0":    [2]protoreflect.FieldNumber{1000, 2000}, | 
|  | "Has:999":  false, | 
|  | "Has:1000": true, | 
|  | "Has:1500": true, | 
|  | "Has:1999": true, | 
|  | "Has:2000": false, | 
|  | "Has:3000": true, | 
|  | "Has:3001": false, | 
|  | }, | 
|  | "ExtensionRangeOptions:0": (*descriptorpb.ExtensionRangeOptions)(nil), | 
|  | "ExtensionRangeOptions:1": new(descriptorpb.ExtensionRangeOptions), | 
|  | "Messages": M{ | 
|  | "Get:0": M{ | 
|  | "Fields": M{ | 
|  | "Len": 2, | 
|  | "ByNumber:1": M{ | 
|  | "Parent":            M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")}, | 
|  | "Index":             0, | 
|  | "Name":              protoreflect.Name("key"), | 
|  | "FullName":          protoreflect.FullName("test.B.FieldFourEntry.key"), | 
|  | "Number":            protoreflect.FieldNumber(1), | 
|  | "Cardinality":       protoreflect.Optional, | 
|  | "Kind":              protoreflect.StringKind, | 
|  | "Options":           (*descriptorpb.FieldOptions)(nil), | 
|  | "HasJSONName":       false, | 
|  | "JSONName":          "key", | 
|  | "IsPacked":          false, | 
|  | "IsList":            false, | 
|  | "IsMap":             false, | 
|  | "IsExtension":       false, | 
|  | "IsWeak":            false, | 
|  | "Default":           "", | 
|  | "ContainingOneof":   nil, | 
|  | "ContainingMessage": M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")}, | 
|  | "Message":           nil, | 
|  | "Enum":              nil, | 
|  | }, | 
|  | "ByNumber:2": M{ | 
|  | "Parent":            M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")}, | 
|  | "Index":             1, | 
|  | "Name":              protoreflect.Name("value"), | 
|  | "FullName":          protoreflect.FullName("test.B.FieldFourEntry.value"), | 
|  | "Number":            protoreflect.FieldNumber(2), | 
|  | "Cardinality":       protoreflect.Optional, | 
|  | "Kind":              protoreflect.MessageKind, | 
|  | "JSONName":          "value", | 
|  | "IsPacked":          false, | 
|  | "IsList":            false, | 
|  | "IsMap":             false, | 
|  | "IsExtension":       false, | 
|  | "IsWeak":            false, | 
|  | "Default":           nil, | 
|  | "ContainingOneof":   nil, | 
|  | "ContainingMessage": M{"FullName": protoreflect.FullName("test.B.FieldFourEntry")}, | 
|  | "Message":           M{"FullName": protoreflect.FullName("test.B"), "IsPlaceholder": false}, | 
|  | "Enum":              nil, | 
|  | }, | 
|  | "ByNumber:3": nil, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | "Get:2": M{ | 
|  | "Name":  protoreflect.Name("C"), | 
|  | "Index": 2, | 
|  | "Messages": M{ | 
|  | "Len":   1, | 
|  | "Get:0": M{"FullName": protoreflect.FullName("test.C.A")}, | 
|  | }, | 
|  | "Enums": M{ | 
|  | "Len":   1, | 
|  | "Get:0": M{"FullName": protoreflect.FullName("test.C.E1")}, | 
|  | }, | 
|  | "Extensions": M{ | 
|  | "Len":   1, | 
|  | "Get:0": M{"FullName": protoreflect.FullName("test.C.X")}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | "Enums": M{ | 
|  | "Len": 1, | 
|  | "Get:0": M{ | 
|  | "Name":    protoreflect.Name("E1"), | 
|  | "Options": &descriptorpb.EnumOptions{Deprecated: proto.Bool(true)}, | 
|  | "Values": M{ | 
|  | "Len":        2, | 
|  | "ByName:Foo": nil, | 
|  | "ByName:FOO": M{ | 
|  | "FullName": protoreflect.FullName("test.FOO"), | 
|  | "Options":  &descriptorpb.EnumValueOptions{Deprecated: proto.Bool(true)}, | 
|  | }, | 
|  | "ByNumber:2": nil, | 
|  | "ByNumber:1": M{"FullName": protoreflect.FullName("test.BAR")}, | 
|  | }, | 
|  | "ReservedNames": M{ | 
|  | "Len":         2, | 
|  | "Get:0":       protoreflect.Name("FIZZ"), | 
|  | "Has:BUZZ":    true, | 
|  | "Has:NOEXIST": false, | 
|  | }, | 
|  | "ReservedRanges": M{ | 
|  | "Len":    2, | 
|  | "Get:0":  [2]protoreflect.EnumNumber{10, 19}, | 
|  | "Has:9":  false, | 
|  | "Has:10": true, | 
|  | "Has:15": true, | 
|  | "Has:19": true, | 
|  | "Has:20": false, | 
|  | "Has:30": true, | 
|  | "Has:31": false, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | "Extensions": M{ | 
|  | "Len": 1, | 
|  | "ByName:X": M{ | 
|  | "Name":              protoreflect.Name("X"), | 
|  | "Number":            protoreflect.FieldNumber(1000), | 
|  | "Cardinality":       protoreflect.Repeated, | 
|  | "Kind":              protoreflect.EnumKind, | 
|  | "IsExtension":       true, | 
|  | "IsPacked":          true, | 
|  | "IsList":            true, | 
|  | "IsMap":             false, | 
|  | "MapKey":            nil, | 
|  | "MapValue":          nil, | 
|  | "ContainingOneof":   nil, | 
|  | "ContainingMessage": M{"FullName": protoreflect.FullName("test.B"), "IsPlaceholder": false}, | 
|  | "Enum":              M{"FullName": protoreflect.FullName("test.E1"), "IsPlaceholder": false}, | 
|  | "Options":           &descriptorpb.FieldOptions{Packed: proto.Bool(true)}, | 
|  | }, | 
|  | }, | 
|  | "Services": M{ | 
|  | "Len":      1, | 
|  | "ByName:s": nil, | 
|  | "ByName:S": M{ | 
|  | "Parent":   M{"FullName": protoreflect.FullName("test")}, | 
|  | "Name":     protoreflect.Name("S"), | 
|  | "FullName": protoreflect.FullName("test.S"), | 
|  | "Options":  &descriptorpb.ServiceOptions{Deprecated: proto.Bool(true)}, | 
|  | "Methods": M{ | 
|  | "Len": 1, | 
|  | "Get:0": M{ | 
|  | "Parent":            M{"FullName": protoreflect.FullName("test.S")}, | 
|  | "Name":              protoreflect.Name("M"), | 
|  | "FullName":          protoreflect.FullName("test.S.M"), | 
|  | "Input":             M{"FullName": protoreflect.FullName("test.A"), "IsPlaceholder": false}, | 
|  | "Output":            M{"FullName": protoreflect.FullName("test.C.A"), "IsPlaceholder": false}, | 
|  | "IsStreamingClient": true, | 
|  | "IsStreamingServer": true, | 
|  | "Options":           &descriptorpb.MethodOptions{Deprecated: proto.Bool(true)}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | } | 
|  | checkAccessors(t, "", reflect.ValueOf(fd), want) | 
|  | } | 
|  | func checkAccessors(t *testing.T, p string, rv reflect.Value, want map[string]any) { | 
|  | p0 := p | 
|  | defer func() { | 
|  | if ex := recover(); ex != nil { | 
|  | t.Errorf("panic at %v: %v", p, ex) | 
|  | } | 
|  | }() | 
|  |  | 
|  | if rv.Interface() == nil { | 
|  | t.Errorf("%v is nil, want non-nil", p) | 
|  | return | 
|  | } | 
|  | for s, v := range want { | 
|  | // Call the accessor method. | 
|  | p = p0 + "." + s | 
|  | var rets []reflect.Value | 
|  | if i := strings.IndexByte(s, ':'); i >= 0 { | 
|  | // Accessor method takes in a single argument, which is encoded | 
|  | // after the accessor name, separated by a ':' delimiter. | 
|  | fnc := rv.MethodByName(s[:i]) | 
|  | arg := reflect.New(fnc.Type().In(0)).Elem() | 
|  | s = s[i+len(":"):] | 
|  | switch arg.Kind() { | 
|  | case reflect.String: | 
|  | arg.SetString(s) | 
|  | case reflect.Int32, reflect.Int: | 
|  | n, _ := strconv.ParseInt(s, 0, 64) | 
|  | arg.SetInt(n) | 
|  | } | 
|  | rets = fnc.Call([]reflect.Value{arg}) | 
|  | } else { | 
|  | rets = rv.MethodByName(s).Call(nil) | 
|  | } | 
|  |  | 
|  | // Check that (val, ok) pattern is internally consistent. | 
|  | if len(rets) == 2 { | 
|  | if rets[0].IsNil() && rets[1].Bool() { | 
|  | t.Errorf("%v = (nil, true), want (nil, false)", p) | 
|  | } | 
|  | if !rets[0].IsNil() && !rets[1].Bool() { | 
|  | t.Errorf("%v = (non-nil, false), want (non-nil, true)", p) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that the accessor output matches. | 
|  | if want, ok := v.(map[string]any); ok { | 
|  | checkAccessors(t, p, rets[0], want) | 
|  | continue | 
|  | } | 
|  |  | 
|  | got := rets[0].Interface() | 
|  | if pv, ok := got.(protoreflect.Value); ok { | 
|  | got = pv.Interface() | 
|  | } | 
|  |  | 
|  | // Compare with proto.Equal if possible. | 
|  | gotMsg, gotMsgOK := got.(proto.Message) | 
|  | wantMsg, wantMsgOK := v.(proto.Message) | 
|  | if gotMsgOK && wantMsgOK { | 
|  | gotNil := reflect.ValueOf(gotMsg).IsNil() | 
|  | wantNil := reflect.ValueOf(wantMsg).IsNil() | 
|  | switch { | 
|  | case !gotNil && wantNil: | 
|  | t.Errorf("%v = non-nil, want nil", p) | 
|  | case gotNil && !wantNil: | 
|  | t.Errorf("%v = nil, want non-nil", p) | 
|  | case !proto.Equal(gotMsg, wantMsg): | 
|  | t.Errorf("%v = %v, want %v", p, gotMsg, wantMsg) | 
|  | } | 
|  | continue | 
|  | } | 
|  |  | 
|  | if want := v; !reflect.DeepEqual(got, want) { | 
|  | t.Errorf("%v = %T(%v), want %T(%v)", p, got, got, want, want) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func testFileFormat(t *testing.T, fd protoreflect.FileDescriptor) { | 
|  | const wantFileDescriptor = `FileDescriptor{ | 
|  | Syntax:  proto2 | 
|  | Path:    "path/to/file.proto" | 
|  | Package: test | 
|  | Messages: [{ | 
|  | Name: A | 
|  | }, { | 
|  | Name: B | 
|  | Fields: [{ | 
|  | Name:        field_one | 
|  | Number:      1 | 
|  | Cardinality: optional | 
|  | Kind:        string | 
|  | JSONName:    "fieldOne" | 
|  | HasPresence: true | 
|  | HasDefault:  true | 
|  | Default:     "hello, \"world!\"\n" | 
|  | Oneof:       O1 | 
|  | }, { | 
|  | Name:        field_two | 
|  | Number:      2 | 
|  | Cardinality: optional | 
|  | Kind:        enum | 
|  | HasJSONName: true | 
|  | JSONName:    "Field2" | 
|  | HasPresence: true | 
|  | HasDefault:  true | 
|  | Default:     1 | 
|  | Oneof:       O2 | 
|  | Enum:        test.E1 | 
|  | }, { | 
|  | Name:        field_three | 
|  | Number:      3 | 
|  | Cardinality: optional | 
|  | Kind:        message | 
|  | JSONName:    "fieldThree" | 
|  | HasPresence: true | 
|  | Oneof:       O2 | 
|  | Message:     test.C | 
|  | }, { | 
|  | Name:        field_four | 
|  | Number:      4 | 
|  | Cardinality: repeated | 
|  | Kind:        message | 
|  | HasJSONName: true | 
|  | JSONName:    "Field4" | 
|  | IsMap:       true | 
|  | MapKey:      string | 
|  | MapValue:    test.B | 
|  | }, { | 
|  | Name:        field_five | 
|  | Number:      5 | 
|  | Cardinality: repeated | 
|  | Kind:        int32 | 
|  | JSONName:    "fieldFive" | 
|  | IsPacked:    true | 
|  | IsList:      true | 
|  | }, { | 
|  | Name:        field_six | 
|  | Number:      6 | 
|  | Cardinality: required | 
|  | Kind:        bytes | 
|  | JSONName:    "fieldSix" | 
|  | HasPresence: true | 
|  | }] | 
|  | Oneofs: [{ | 
|  | Name:   O1 | 
|  | Fields: [field_one] | 
|  | }, { | 
|  | Name:   O2 | 
|  | Fields: [field_two, field_three] | 
|  | }] | 
|  | ReservedNames:   [fizz, buzz] | 
|  | ReservedRanges:  [100:200, 300] | 
|  | RequiredNumbers: [6] | 
|  | ExtensionRanges: [1000:2000, 3000] | 
|  | Messages: [{ | 
|  | Name:       FieldFourEntry | 
|  | IsMapEntry: true | 
|  | Fields: [{ | 
|  | Name:        key | 
|  | Number:      1 | 
|  | Cardinality: optional | 
|  | Kind:        string | 
|  | JSONName:    "key" | 
|  | HasPresence: true | 
|  | }, { | 
|  | Name:        value | 
|  | Number:      2 | 
|  | Cardinality: optional | 
|  | Kind:        message | 
|  | JSONName:    "value" | 
|  | HasPresence: true | 
|  | Message:     test.B | 
|  | }] | 
|  | }] | 
|  | }, { | 
|  | Name: C | 
|  | Messages: [{ | 
|  | Name: A | 
|  | Fields: [{ | 
|  | Name:        F | 
|  | Number:      1 | 
|  | Cardinality: required | 
|  | Kind:        bytes | 
|  | JSONName:    "F" | 
|  | HasPresence: true | 
|  | HasDefault:  true | 
|  | Default:     "dead\xbe\xef" | 
|  | }] | 
|  | RequiredNumbers: [1] | 
|  | }] | 
|  | Enums: [{ | 
|  | Name: E1 | 
|  | Values: [ | 
|  | {Name: FOO} | 
|  | {Name: BAR, Number: 1} | 
|  | ] | 
|  | IsClosed: true | 
|  | }] | 
|  | Extensions: [{ | 
|  | Name:        X | 
|  | Number:      1000 | 
|  | Cardinality: repeated | 
|  | Kind:        message | 
|  | JSONName:    "[test.C.X]" | 
|  | IsExtension: true | 
|  | IsList:      true | 
|  | Extendee:    test.B | 
|  | Message:     test.C | 
|  | }] | 
|  | }] | 
|  | Enums: [{ | 
|  | Name: E1 | 
|  | Values: [ | 
|  | {Name: FOO} | 
|  | {Name: BAR, Number: 1} | 
|  | ] | 
|  | ReservedNames:  [FIZZ, BUZZ] | 
|  | ReservedRanges: [10:20, 30] | 
|  | IsClosed:       true | 
|  | }] | 
|  | Extensions: [{ | 
|  | Name:        X | 
|  | Number:      1000 | 
|  | Cardinality: repeated | 
|  | Kind:        enum | 
|  | JSONName:    "[test.X]" | 
|  | IsExtension: true | 
|  | IsPacked:    true | 
|  | IsList:      true | 
|  | Extendee:    test.B | 
|  | Enum:        test.E1 | 
|  | }] | 
|  | Services: [{ | 
|  | Name: S | 
|  | Methods: [{ | 
|  | Name:              M | 
|  | Input:             test.A | 
|  | Output:            test.C.A | 
|  | IsStreamingClient: true | 
|  | IsStreamingServer: true | 
|  | }] | 
|  | }] | 
|  | }` | 
|  |  | 
|  | const wantEnums = `Enums{{ | 
|  | Name: E1 | 
|  | Values: [ | 
|  | {Name: FOO} | 
|  | {Name: BAR, Number: 1} | 
|  | ] | 
|  | ReservedNames:  [FIZZ, BUZZ] | 
|  | ReservedRanges: [10:20, 30] | 
|  | IsClosed:       true | 
|  | }}` | 
|  |  | 
|  | const wantExtensions = `Extensions{{ | 
|  | Name:        X | 
|  | Number:      1000 | 
|  | Cardinality: repeated | 
|  | Kind:        enum | 
|  | JSONName:    "[test.X]" | 
|  | IsExtension: true | 
|  | IsPacked:    true | 
|  | IsList:      true | 
|  | Extendee:    test.B | 
|  | Enum:        test.E1 | 
|  | }}` | 
|  |  | 
|  | const wantImports = `FileImports{}` | 
|  |  | 
|  | const wantReservedNames = "Names{fizz, buzz}" | 
|  |  | 
|  | const wantReservedRanges = "FieldRanges{100:200, 300}" | 
|  |  | 
|  | const wantServices = `Services{{ | 
|  | Name: S | 
|  | Methods: [{ | 
|  | Name:              M | 
|  | Input:             test.A | 
|  | Output:            test.C.A | 
|  | IsStreamingClient: true | 
|  | IsStreamingServer: true | 
|  | }] | 
|  | }}` | 
|  |  | 
|  | tests := []struct { | 
|  | path string | 
|  | fmt  string | 
|  | want string | 
|  | val  any | 
|  | }{ | 
|  | {"fd", "%v", compactMultiFormat(wantFileDescriptor), fd}, | 
|  | {"fd", "%+v", wantFileDescriptor, fd}, | 
|  | {"fd.Enums()", "%v", compactMultiFormat(wantEnums), fd.Enums()}, | 
|  | {"fd.Enums()", "%+v", wantEnums, fd.Enums()}, | 
|  | {"fd.Extensions()", "%v", compactMultiFormat(wantExtensions), fd.Extensions()}, | 
|  | {"fd.Extensions()", "%+v", wantExtensions, fd.Extensions()}, | 
|  | {"fd.Imports()", "%v", compactMultiFormat(wantImports), fd.Imports()}, | 
|  | {"fd.Imports()", "%+v", wantImports, fd.Imports()}, | 
|  | {"fd.Messages(B).ReservedNames()", "%v", compactMultiFormat(wantReservedNames), fd.Messages().ByName("B").ReservedNames()}, | 
|  | {"fd.Messages(B).ReservedNames()", "%+v", wantReservedNames, fd.Messages().ByName("B").ReservedNames()}, | 
|  | {"fd.Messages(B).ReservedRanges()", "%v", compactMultiFormat(wantReservedRanges), fd.Messages().ByName("B").ReservedRanges()}, | 
|  | {"fd.Messages(B).ReservedRanges()", "%+v", wantReservedRanges, fd.Messages().ByName("B").ReservedRanges()}, | 
|  | {"fd.Services()", "%v", compactMultiFormat(wantServices), fd.Services()}, | 
|  | {"fd.Services()", "%+v", wantServices, fd.Services()}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | got := fmt.Sprintf(tt.fmt, tt.val) | 
|  | if diff := cmp.Diff(got, tt.want); diff != "" { | 
|  | t.Errorf("fmt.Sprintf(%q, %s) mismatch (-got +want):\n%s", tt.fmt, tt.path, diff) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // compactMultiFormat returns the single line form of a multi line output. | 
|  | func compactMultiFormat(s string) string { | 
|  | var b []byte | 
|  | for _, s := range strings.Split(s, "\n") { | 
|  | s = strings.TrimSpace(s) | 
|  | s = regexp.MustCompile(": +").ReplaceAllString(s, ": ") | 
|  | prevWord := len(b) > 0 && b[len(b)-1] != '[' && b[len(b)-1] != '{' | 
|  | nextWord := len(s) > 0 && s[0] != ']' && s[0] != '}' | 
|  | if prevWord && nextWord { | 
|  | b = append(b, ", "...) | 
|  | } | 
|  | b = append(b, s...) | 
|  | } | 
|  | return string(b) | 
|  | } | 
|  |  | 
|  | func TestMapsAreNotDelimited(t *testing.T) { | 
|  | fileDescriptor := &descriptorpb.FileDescriptorProto{ | 
|  | Name:    proto.String("test.proto"), | 
|  | Syntax:  proto.String("editions"), | 
|  | Edition: descriptorpb.Edition_EDITION_2023.Enum(), | 
|  | Options: &descriptorpb.FileOptions{ | 
|  | Features: &descriptorpb.FeatureSet{ | 
|  | MessageEncoding: descriptorpb.FeatureSet_DELIMITED.Enum(), | 
|  | }, | 
|  | }, | 
|  | MessageType: []*descriptorpb.DescriptorProto{ | 
|  | { | 
|  | Name: proto.String("MessageWithMaps"), | 
|  | Field: []*descriptorpb.FieldDescriptorProto{ | 
|  | { | 
|  | Name:     proto.String("map_with_message"), | 
|  | Number:   proto.Int32(1), | 
|  | Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), | 
|  | TypeName: proto.String(".MessageWithMaps.MapWithMessageEntry"), | 
|  | JsonName: proto.String("mapWithMessage"), | 
|  | }, | 
|  | { | 
|  | Name:     proto.String("message"), | 
|  | Number:   proto.Int32(2), | 
|  | Label:    descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), | 
|  | TypeName: proto.String(".MessageWithMaps"), | 
|  | JsonName: proto.String("message"), | 
|  | }, | 
|  | }, | 
|  | NestedType: []*descriptorpb.DescriptorProto{ | 
|  | { | 
|  | Name:    proto.String("MapWithMessageEntry"), | 
|  | Options: &descriptorpb.MessageOptions{MapEntry: proto.Bool(true)}, | 
|  | Field: []*descriptorpb.FieldDescriptorProto{ | 
|  | { | 
|  | Name:   proto.String("key"), | 
|  | Number: proto.Int32(1), | 
|  | Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), | 
|  | Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), | 
|  | }, | 
|  | { | 
|  | Name:     proto.String("value"), | 
|  | Number:   proto.Int32(2), | 
|  | Label:    descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), | 
|  | Type:     descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), | 
|  | TypeName: proto.String(".MessageWithMaps"), | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | } | 
|  | fd1, err := protodesc.NewFile(fileDescriptor, nil) | 
|  | if err != nil { | 
|  | t.Fatalf("protodesc.NewFile() error: %v", err) | 
|  | } | 
|  |  | 
|  | b, err := proto.Marshal(fileDescriptor) | 
|  | if err != nil { | 
|  | t.Fatalf("proto.Marshal() error: %v", err) | 
|  | } | 
|  | fd2 := filedesc.Builder{RawDescriptor: b}.Build().File | 
|  |  | 
|  | tests := []struct { | 
|  | name string | 
|  | desc protoreflect.FileDescriptor | 
|  | }{ | 
|  | {"protodesc.NewFile", fd1}, | 
|  | {"filedesc.Builder.Build", fd2}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | tt := tt | 
|  | t.Run(tt.name, func(t *testing.T) { | 
|  | mapField := tt.desc.Messages().Get(0).Fields().Get(0) | 
|  | if !mapField.IsMap() { | 
|  | t.Fatalf("field should be a map") | 
|  | } | 
|  | nonMapField := tt.desc.Messages().Get(0).Fields().Get(1) | 
|  | if nonMapField.IsMap() { | 
|  | t.Fatalf("field should not be a map") | 
|  | } | 
|  | // sanity check that delimited default has taken effect | 
|  | if nonMapField.Kind() != protoreflect.GroupKind { | 
|  | t.Fatalf("non-map field should have group kind, instead got %v", nonMapField.Kind()) | 
|  | } | 
|  | // now we can confirm that the map fields are NOT groups | 
|  | if mapField.Kind() != protoreflect.MessageKind { | 
|  | t.Fatalf("map field should have message kind, instead got %v", mapField.Kind()) | 
|  | } | 
|  | mapValField := mapField.Message().Fields().ByNumber(2) | 
|  | if mapValField.Kind() != protoreflect.MessageKind { | 
|  | t.Fatalf("map value field should have message kind, instead got %v", mapValField.Kind()) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } |