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