aboutsummaryrefslogtreecommitdiff
path: root/internal/filedesc/desc_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/filedesc/desc_test.go')
-rw-r--r--internal/filedesc/desc_test.go850
1 files changed, 850 insertions, 0 deletions
diff --git a/internal/filedesc/desc_test.go b/internal/filedesc/desc_test.go
new file mode 100644
index 00000000..4f204f88
--- /dev/null
+++ b/internal/filedesc/desc_test.go
@@ -0,0 +1,850 @@
+// 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"
+
+ detrand "google.golang.org/protobuf/internal/detrand"
+ "google.golang.org/protobuf/internal/filedesc"
+ "google.golang.org/protobuf/proto"
+ pdesc "google.golang.org/protobuf/reflect/protodesc"
+ pref "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(pref.Optional).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Optional).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Optional).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Repeated).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
+ TypeName: proto.String(".test.B.FieldFourEntry"),
+ }, {
+ Name: proto.String("field_five"),
+ Number: proto.Int32(5),
+ Label: descriptorpb.FieldDescriptorProto_Label(pref.Repeated).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.Int32Kind).Enum(),
+ Options: &descriptorpb.FieldOptions{Packed: proto.Bool(true)},
+ }, {
+ Name: proto.String("field_six"),
+ Number: proto.Int32(6),
+ Label: descriptorpb.FieldDescriptorProto_Label(pref.Required).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Optional).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
+ }, {
+ Name: proto.String("value"),
+ Number: proto.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_Label(pref.Optional).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Required).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Repeated).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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(pref.Repeated).Enum(),
+ Type: descriptorpb.FieldDescriptorProto_Type(pref.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 := pdesc.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 pref.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 pref.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]interface{}
+ want := M{
+ "Parent": nil,
+ "Index": 0,
+ "Syntax": pref.Proto2,
+ "Name": pref.Name("test"),
+ "FullName": pref.FullName("test"),
+ "Path": "path/to/file.proto",
+ "Package": pref.FullName("test"),
+ "IsPlaceholder": false,
+ "Options": &descriptorpb.FileOptions{Deprecated: proto.Bool(true)},
+ "Messages": M{
+ "Len": 3,
+ "Get:0": M{
+ "Parent": M{"FullName": pref.FullName("test")},
+ "Index": 0,
+ "Syntax": pref.Proto2,
+ "Name": pref.Name("A"),
+ "FullName": pref.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": pref.Name("B"),
+ "Index": 1,
+ "Fields": M{
+ "Len": 6,
+ "ByJSONName:field_one": nil,
+ "ByJSONName:fieldOne": M{
+ "Name": pref.Name("field_one"),
+ "Index": 0,
+ "JSONName": "fieldOne",
+ "Default": "hello, \"world!\"\n",
+ "ContainingOneof": M{"Name": pref.Name("O1"), "IsPlaceholder": false},
+ "ContainingMessage": M{"FullName": pref.FullName("test.B")},
+ },
+ "ByJSONName:fieldTwo": nil,
+ "ByJSONName:Field2": M{
+ "Name": pref.Name("field_two"),
+ "Index": 1,
+ "HasJSONName": true,
+ "JSONName": "Field2",
+ "Default": pref.EnumNumber(1),
+ "ContainingOneof": M{"Name": pref.Name("O2"), "IsPlaceholder": false},
+ },
+ "ByName:fieldThree": nil,
+ "ByName:field_three": M{
+ "IsExtension": false,
+ "IsMap": false,
+ "MapKey": nil,
+ "MapValue": nil,
+ "Message": M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false},
+ "ContainingOneof": M{"Name": pref.Name("O2"), "IsPlaceholder": false},
+ "ContainingMessage": M{"FullName": pref.FullName("test.B")},
+ },
+ "ByNumber:12": nil,
+ "ByNumber:4": M{
+ "Cardinality": pref.Repeated,
+ "IsExtension": false,
+ "IsList": false,
+ "IsMap": true,
+ "MapKey": M{"Kind": pref.StringKind},
+ "MapValue": M{"Kind": pref.MessageKind, "Message": M{"FullName": pref.FullName("test.B")}},
+ "Default": nil,
+ "Message": M{"FullName": pref.FullName("test.B.FieldFourEntry"), "IsPlaceholder": false},
+ },
+ "ByNumber:5": M{
+ "Cardinality": pref.Repeated,
+ "Kind": pref.Int32Kind,
+ "IsPacked": true,
+ "IsList": true,
+ "IsMap": false,
+ "Default": nil,
+ },
+ "ByNumber:6": M{
+ "Cardinality": pref.Required,
+ "Default": []byte(nil),
+ "ContainingOneof": nil,
+ },
+ },
+ "Oneofs": M{
+ "Len": 2,
+ "ByName:O0": nil,
+ "ByName:O1": M{
+ "FullName": pref.FullName("test.B.O1"),
+ "Index": 0,
+ "Options": &descriptorpb.OneofOptions{
+ UninterpretedOption: []*descriptorpb.UninterpretedOption{
+ {StringValue: []byte("option")},
+ },
+ },
+ "Fields": M{
+ "Len": 1,
+ "Get:0": M{"FullName": pref.FullName("test.B.field_one")},
+ },
+ },
+ "Get:1": M{
+ "FullName": pref.FullName("test.B.O2"),
+ "Index": 1,
+ "Fields": M{
+ "Len": 2,
+ "ByName:field_two": M{"Name": pref.Name("field_two")},
+ "Get:1": M{"Name": pref.Name("field_three")},
+ },
+ },
+ },
+ "ReservedNames": M{
+ "Len": 2,
+ "Get:0": pref.Name("fizz"),
+ "Has:buzz": true,
+ "Has:noexist": false,
+ },
+ "ReservedRanges": M{
+ "Len": 2,
+ "Get:0": [2]pref.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": pref.FieldNumber(6),
+ "Has:1": false,
+ "Has:6": true,
+ },
+ "ExtensionRanges": M{
+ "Len": 2,
+ "Get:0": [2]pref.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": pref.FullName("test.B.FieldFourEntry")},
+ "Index": 0,
+ "Name": pref.Name("key"),
+ "FullName": pref.FullName("test.B.FieldFourEntry.key"),
+ "Number": pref.FieldNumber(1),
+ "Cardinality": pref.Optional,
+ "Kind": pref.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": pref.FullName("test.B.FieldFourEntry")},
+ "Message": nil,
+ "Enum": nil,
+ },
+ "ByNumber:2": M{
+ "Parent": M{"FullName": pref.FullName("test.B.FieldFourEntry")},
+ "Index": 1,
+ "Name": pref.Name("value"),
+ "FullName": pref.FullName("test.B.FieldFourEntry.value"),
+ "Number": pref.FieldNumber(2),
+ "Cardinality": pref.Optional,
+ "Kind": pref.MessageKind,
+ "JSONName": "value",
+ "IsPacked": false,
+ "IsList": false,
+ "IsMap": false,
+ "IsExtension": false,
+ "IsWeak": false,
+ "Default": nil,
+ "ContainingOneof": nil,
+ "ContainingMessage": M{"FullName": pref.FullName("test.B.FieldFourEntry")},
+ "Message": M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
+ "Enum": nil,
+ },
+ "ByNumber:3": nil,
+ },
+ },
+ },
+ },
+ "Get:2": M{
+ "Name": pref.Name("C"),
+ "Index": 2,
+ "Messages": M{
+ "Len": 1,
+ "Get:0": M{"FullName": pref.FullName("test.C.A")},
+ },
+ "Enums": M{
+ "Len": 1,
+ "Get:0": M{"FullName": pref.FullName("test.C.E1")},
+ },
+ "Extensions": M{
+ "Len": 1,
+ "Get:0": M{"FullName": pref.FullName("test.C.X")},
+ },
+ },
+ },
+ "Enums": M{
+ "Len": 1,
+ "Get:0": M{
+ "Name": pref.Name("E1"),
+ "Options": &descriptorpb.EnumOptions{Deprecated: proto.Bool(true)},
+ "Values": M{
+ "Len": 2,
+ "ByName:Foo": nil,
+ "ByName:FOO": M{
+ "FullName": pref.FullName("test.FOO"),
+ "Options": &descriptorpb.EnumValueOptions{Deprecated: proto.Bool(true)},
+ },
+ "ByNumber:2": nil,
+ "ByNumber:1": M{"FullName": pref.FullName("test.BAR")},
+ },
+ "ReservedNames": M{
+ "Len": 2,
+ "Get:0": pref.Name("FIZZ"),
+ "Has:BUZZ": true,
+ "Has:NOEXIST": false,
+ },
+ "ReservedRanges": M{
+ "Len": 2,
+ "Get:0": [2]pref.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": pref.Name("X"),
+ "Number": pref.FieldNumber(1000),
+ "Cardinality": pref.Repeated,
+ "Kind": pref.EnumKind,
+ "IsExtension": true,
+ "IsPacked": true,
+ "IsList": true,
+ "IsMap": false,
+ "MapKey": nil,
+ "MapValue": nil,
+ "ContainingOneof": nil,
+ "ContainingMessage": M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
+ "Enum": M{"FullName": pref.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": pref.FullName("test")},
+ "Name": pref.Name("S"),
+ "FullName": pref.FullName("test.S"),
+ "Options": &descriptorpb.ServiceOptions{Deprecated: proto.Bool(true)},
+ "Methods": M{
+ "Len": 1,
+ "Get:0": M{
+ "Parent": M{"FullName": pref.FullName("test.S")},
+ "Name": pref.Name("M"),
+ "FullName": pref.FullName("test.S.M"),
+ "Input": M{"FullName": pref.FullName("test.A"), "IsPlaceholder": false},
+ "Output": M{"FullName": pref.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]interface{}) {
+ 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]interface{}); ok {
+ checkAccessors(t, p, rets[0], want)
+ continue
+ }
+
+ got := rets[0].Interface()
+ if pv, ok := got.(pref.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 pref.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}
+ ]
+ }]
+ 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]
+ }]
+ 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]
+}}`
+
+ 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 interface{}
+ }{
+ {"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)
+}