...

Source file src/google.golang.org/protobuf/internal/filedesc/desc_test.go

Documentation: google.golang.org/protobuf/internal/filedesc

     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  
     5  package filedesc_test
     6  
     7  import (
     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  
    26  func init() {
    27  	// Disable detrand to enable direct comparisons on outputs.
    28  	detrand.Disable()
    29  }
    30  
    31  // TODO: Test protodesc.NewFile with imported files.
    32  
    33  func 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  
   223  func 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  }
   524  func 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  
   602  func 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.
   837  func 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  

View as plain text