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 impl_test
6
7import (
8	"fmt"
9	"reflect"
10	"sync"
11	"testing"
12
13	"github.com/google/go-cmp/cmp"
14	"github.com/google/go-cmp/cmp/cmpopts"
15
16	"google.golang.org/protobuf/encoding/prototext"
17	pimpl "google.golang.org/protobuf/internal/impl"
18	"google.golang.org/protobuf/internal/pragma"
19	"google.golang.org/protobuf/proto"
20	pdesc "google.golang.org/protobuf/reflect/protodesc"
21	pref "google.golang.org/protobuf/reflect/protoreflect"
22	preg "google.golang.org/protobuf/reflect/protoregistry"
23	piface "google.golang.org/protobuf/runtime/protoiface"
24
25	proto2_20180125 "google.golang.org/protobuf/internal/testprotos/legacy/proto2_20180125_92554152"
26	"google.golang.org/protobuf/types/descriptorpb"
27)
28
29type LegacyTestMessage struct {
30	XXX_unrecognized       []byte
31	XXX_InternalExtensions map[int32]pimpl.ExtensionField
32}
33
34func (*LegacyTestMessage) Reset()         {}
35func (*LegacyTestMessage) String() string { return "" }
36func (*LegacyTestMessage) ProtoMessage()  {}
37func (*LegacyTestMessage) ExtensionRangeArray() []piface.ExtensionRangeV1 {
38	return []piface.ExtensionRangeV1{{Start: 10, End: 20}, {Start: 40, End: 80}, {Start: 10000, End: 20000}}
39}
40func (*LegacyTestMessage) Descriptor() ([]byte, []int) { return legacyFD, []int{0} }
41
42var legacyFD = func() []byte {
43	b, _ := proto.Marshal(pdesc.ToFileDescriptorProto(mustMakeFileDesc(`
44		name:   "legacy.proto"
45		syntax: "proto2"
46		message_type: [{
47			name:            "LegacyTestMessage"
48			extension_range: [{start:10 end:20}, {start:40 end:80}, {start:10000 end:20000}]
49		}]
50	`, nil)))
51	return pimpl.Export{}.CompressGZIP(b)
52}()
53
54func init() {
55	mt := pimpl.Export{}.MessageTypeOf((*LegacyTestMessage)(nil))
56	preg.GlobalFiles.RegisterFile(mt.Descriptor().ParentFile())
57	preg.GlobalTypes.RegisterMessage(mt)
58}
59
60func mustMakeExtensionType(fileDesc, extDesc string, t reflect.Type, r pdesc.Resolver) pref.ExtensionType {
61	s := fmt.Sprintf(`name:"test.proto" syntax:"proto2" %s extension:[{%s}]`, fileDesc, extDesc)
62	xd := mustMakeFileDesc(s, r).Extensions().Get(0)
63	xi := &pimpl.ExtensionInfo{}
64	pimpl.InitExtensionInfo(xi, xd, t)
65	return xi
66}
67
68func mustMakeFileDesc(s string, r pdesc.Resolver) pref.FileDescriptor {
69	pb := new(descriptorpb.FileDescriptorProto)
70	if err := prototext.Unmarshal([]byte(s), pb); err != nil {
71		panic(err)
72	}
73	fd, err := pdesc.NewFile(pb, r)
74	if err != nil {
75		panic(err)
76	}
77	return fd
78}
79
80var (
81	testParentDesc    = pimpl.Export{}.MessageDescriptorOf((*LegacyTestMessage)(nil))
82	testEnumV1Desc    = pimpl.Export{}.EnumDescriptorOf(proto2_20180125.Message_ChildEnum(0))
83	testMessageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
84	testMessageV2Desc = enumMessagesType.Desc
85
86	depReg = newFileRegistry(
87		testParentDesc.ParentFile(),
88		testEnumV1Desc.ParentFile(),
89		testMessageV1Desc.ParentFile(),
90		enumProto2Desc.ParentFile(),
91		testMessageV2Desc.ParentFile(),
92	)
93	extensionTypes = []pref.ExtensionType{
94		mustMakeExtensionType(
95			`package:"fizz.buzz" dependency:"legacy.proto"`,
96			`name:"optional_bool" number:10000 label:LABEL_OPTIONAL type:TYPE_BOOL default_value:"true" extendee:".LegacyTestMessage"`,
97			reflect.TypeOf(false), depReg,
98		),
99		mustMakeExtensionType(
100			`package:"fizz.buzz" dependency:"legacy.proto"`,
101			`name:"optional_int32" number:10001 label:LABEL_OPTIONAL type:TYPE_INT32 default_value:"-12345" extendee:".LegacyTestMessage"`,
102			reflect.TypeOf(int32(0)), depReg,
103		),
104		mustMakeExtensionType(
105			`package:"fizz.buzz" dependency:"legacy.proto"`,
106			`name:"optional_uint32" number:10002 label:LABEL_OPTIONAL type:TYPE_UINT32 default_value:"3200" extendee:".LegacyTestMessage"`,
107			reflect.TypeOf(uint32(0)), depReg,
108		),
109		mustMakeExtensionType(
110			`package:"fizz.buzz" dependency:"legacy.proto"`,
111			`name:"optional_float" number:10003 label:LABEL_OPTIONAL type:TYPE_FLOAT default_value:"3.14159" extendee:".LegacyTestMessage"`,
112			reflect.TypeOf(float32(0)), depReg,
113		),
114		mustMakeExtensionType(
115			`package:"fizz.buzz" dependency:"legacy.proto"`,
116			`name:"optional_string" number:10004 label:LABEL_OPTIONAL type:TYPE_STRING default_value:"hello, \"world!\"\n" extendee:".LegacyTestMessage"`,
117			reflect.TypeOf(""), depReg,
118		),
119		mustMakeExtensionType(
120			`package:"fizz.buzz" dependency:"legacy.proto"`,
121			`name:"optional_bytes" number:10005 label:LABEL_OPTIONAL type:TYPE_BYTES default_value:"dead\\336\\255\\276\\357beef" extendee:".LegacyTestMessage"`,
122			reflect.TypeOf(([]byte)(nil)), depReg,
123		),
124		mustMakeExtensionType(
125			`package:"fizz.buzz" dependency:["legacy.proto", "proto2_20180125_92554152/test.proto"]`,
126			`name:"optional_enum_v1" number:10006 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".google.golang.org.proto2_20180125.Message.ChildEnum" default_value:"ALPHA" extendee:".LegacyTestMessage"`,
127			reflect.TypeOf(proto2_20180125.Message_ChildEnum(0)), depReg,
128		),
129		mustMakeExtensionType(
130			`package:"fizz.buzz" dependency:["legacy.proto", "proto2_20180125_92554152/test.proto"]`,
131			`name:"optional_message_v1" number:10007 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".google.golang.org.proto2_20180125.Message.ChildMessage" extendee:".LegacyTestMessage"`,
132			reflect.TypeOf((*proto2_20180125.Message_ChildMessage)(nil)), depReg,
133		),
134		mustMakeExtensionType(
135			`package:"fizz.buzz" dependency:["legacy.proto", "enum2.proto"]`,
136			`name:"optional_enum_v2" number:10008 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".EnumProto2" default_value:"DEAD" extendee:".LegacyTestMessage"`,
137			reflect.TypeOf(EnumProto2(0)), depReg,
138		),
139		mustMakeExtensionType(
140			`package:"fizz.buzz" dependency:["legacy.proto", "enum-messages.proto"]`,
141			`name:"optional_message_v2" number:10009 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".EnumMessages" extendee:".LegacyTestMessage"`,
142			reflect.TypeOf((*EnumMessages)(nil)), depReg,
143		),
144		mustMakeExtensionType(
145			`package:"fizz.buzz" dependency:"legacy.proto"`,
146			`name:"repeated_bool" number:10010 label:LABEL_REPEATED type:TYPE_BOOL extendee:".LegacyTestMessage"`,
147			reflect.TypeOf([]bool(nil)), depReg,
148		),
149		mustMakeExtensionType(
150			`package:"fizz.buzz" dependency:"legacy.proto"`,
151			`name:"repeated_int32" number:10011 label:LABEL_REPEATED type:TYPE_INT32 extendee:".LegacyTestMessage"`,
152			reflect.TypeOf([]int32(nil)), depReg,
153		),
154		mustMakeExtensionType(
155			`package:"fizz.buzz" dependency:"legacy.proto"`,
156			`name:"repeated_uint32" number:10012 label:LABEL_REPEATED type:TYPE_UINT32 extendee:".LegacyTestMessage"`,
157			reflect.TypeOf([]uint32(nil)), depReg,
158		),
159		mustMakeExtensionType(
160			`package:"fizz.buzz" dependency:"legacy.proto"`,
161			`name:"repeated_float" number:10013 label:LABEL_REPEATED type:TYPE_FLOAT extendee:".LegacyTestMessage"`,
162			reflect.TypeOf([]float32(nil)), depReg,
163		),
164		mustMakeExtensionType(
165			`package:"fizz.buzz" dependency:"legacy.proto"`,
166			`name:"repeated_string" number:10014 label:LABEL_REPEATED type:TYPE_STRING extendee:".LegacyTestMessage"`,
167			reflect.TypeOf([]string(nil)), depReg,
168		),
169		mustMakeExtensionType(
170			`package:"fizz.buzz" dependency:"legacy.proto"`,
171			`name:"repeated_bytes" number:10015 label:LABEL_REPEATED type:TYPE_BYTES extendee:".LegacyTestMessage"`,
172			reflect.TypeOf([][]byte(nil)), depReg,
173		),
174		mustMakeExtensionType(
175			`package:"fizz.buzz" dependency:["legacy.proto", "proto2_20180125_92554152/test.proto"]`,
176			`name:"repeated_enum_v1" number:10016 label:LABEL_REPEATED type:TYPE_ENUM type_name:".google.golang.org.proto2_20180125.Message.ChildEnum" extendee:".LegacyTestMessage"`,
177			reflect.TypeOf([]proto2_20180125.Message_ChildEnum(nil)), depReg,
178		),
179		mustMakeExtensionType(
180			`package:"fizz.buzz" dependency:["legacy.proto", "proto2_20180125_92554152/test.proto"]`,
181			`name:"repeated_message_v1" number:10017 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.proto2_20180125.Message.ChildMessage" extendee:".LegacyTestMessage"`,
182			reflect.TypeOf([]*proto2_20180125.Message_ChildMessage(nil)), depReg,
183		),
184		mustMakeExtensionType(
185			`package:"fizz.buzz" dependency:["legacy.proto", "enum2.proto"]`,
186			`name:"repeated_enum_v2" number:10018 label:LABEL_REPEATED type:TYPE_ENUM type_name:".EnumProto2" extendee:".LegacyTestMessage"`,
187			reflect.TypeOf([]EnumProto2(nil)), depReg,
188		),
189		mustMakeExtensionType(
190			`package:"fizz.buzz" dependency:["legacy.proto", "enum-messages.proto"]`,
191			`name:"repeated_message_v2" number:10019 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".EnumMessages" extendee:".LegacyTestMessage"`,
192			reflect.TypeOf([]*EnumMessages(nil)), depReg,
193		),
194	}
195
196	extensionDescs = []*pimpl.ExtensionInfo{{
197		ExtendedType:  (*LegacyTestMessage)(nil),
198		ExtensionType: (*bool)(nil),
199		Field:         10000,
200		Name:          "fizz.buzz.optional_bool",
201		Tag:           "varint,10000,opt,name=optional_bool,def=1",
202		Filename:      "test.proto",
203	}, {
204		ExtendedType:  (*LegacyTestMessage)(nil),
205		ExtensionType: (*int32)(nil),
206		Field:         10001,
207		Name:          "fizz.buzz.optional_int32",
208		Tag:           "varint,10001,opt,name=optional_int32,def=-12345",
209		Filename:      "test.proto",
210	}, {
211		ExtendedType:  (*LegacyTestMessage)(nil),
212		ExtensionType: (*uint32)(nil),
213		Field:         10002,
214		Name:          "fizz.buzz.optional_uint32",
215		Tag:           "varint,10002,opt,name=optional_uint32,def=3200",
216		Filename:      "test.proto",
217	}, {
218		ExtendedType:  (*LegacyTestMessage)(nil),
219		ExtensionType: (*float32)(nil),
220		Field:         10003,
221		Name:          "fizz.buzz.optional_float",
222		Tag:           "fixed32,10003,opt,name=optional_float,def=3.14159",
223		Filename:      "test.proto",
224	}, {
225		ExtendedType:  (*LegacyTestMessage)(nil),
226		ExtensionType: (*string)(nil),
227		Field:         10004,
228		Name:          "fizz.buzz.optional_string",
229		Tag:           "bytes,10004,opt,name=optional_string,def=hello, \"world!\"\n",
230		Filename:      "test.proto",
231	}, {
232		ExtendedType:  (*LegacyTestMessage)(nil),
233		ExtensionType: ([]byte)(nil),
234		Field:         10005,
235		Name:          "fizz.buzz.optional_bytes",
236		Tag:           "bytes,10005,opt,name=optional_bytes,def=dead\\336\\255\\276\\357beef",
237		Filename:      "test.proto",
238	}, {
239		ExtendedType:  (*LegacyTestMessage)(nil),
240		ExtensionType: (*proto2_20180125.Message_ChildEnum)(nil),
241		Field:         10006,
242		Name:          "fizz.buzz.optional_enum_v1",
243		Tag:           "varint,10006,opt,name=optional_enum_v1,enum=google.golang.org.proto2_20180125.Message_ChildEnum,def=0",
244		Filename:      "test.proto",
245	}, {
246		ExtendedType:  (*LegacyTestMessage)(nil),
247		ExtensionType: (*proto2_20180125.Message_ChildMessage)(nil),
248		Field:         10007,
249		Name:          "fizz.buzz.optional_message_v1",
250		Tag:           "bytes,10007,opt,name=optional_message_v1",
251		Filename:      "test.proto",
252	}, {
253		ExtendedType:  (*LegacyTestMessage)(nil),
254		ExtensionType: (*EnumProto2)(nil),
255		Field:         10008,
256		Name:          "fizz.buzz.optional_enum_v2",
257		Tag:           "varint,10008,opt,name=optional_enum_v2,enum=EnumProto2,def=57005",
258		Filename:      "test.proto",
259	}, {
260		ExtendedType:  (*LegacyTestMessage)(nil),
261		ExtensionType: (*EnumMessages)(nil),
262		Field:         10009,
263		Name:          "fizz.buzz.optional_message_v2",
264		Tag:           "bytes,10009,opt,name=optional_message_v2",
265		Filename:      "test.proto",
266	}, {
267		ExtendedType:  (*LegacyTestMessage)(nil),
268		ExtensionType: ([]bool)(nil),
269		Field:         10010,
270		Name:          "fizz.buzz.repeated_bool",
271		Tag:           "varint,10010,rep,name=repeated_bool",
272		Filename:      "test.proto",
273	}, {
274		ExtendedType:  (*LegacyTestMessage)(nil),
275		ExtensionType: ([]int32)(nil),
276		Field:         10011,
277		Name:          "fizz.buzz.repeated_int32",
278		Tag:           "varint,10011,rep,name=repeated_int32",
279		Filename:      "test.proto",
280	}, {
281		ExtendedType:  (*LegacyTestMessage)(nil),
282		ExtensionType: ([]uint32)(nil),
283		Field:         10012,
284		Name:          "fizz.buzz.repeated_uint32",
285		Tag:           "varint,10012,rep,name=repeated_uint32",
286		Filename:      "test.proto",
287	}, {
288		ExtendedType:  (*LegacyTestMessage)(nil),
289		ExtensionType: ([]float32)(nil),
290		Field:         10013,
291		Name:          "fizz.buzz.repeated_float",
292		Tag:           "fixed32,10013,rep,name=repeated_float",
293		Filename:      "test.proto",
294	}, {
295		ExtendedType:  (*LegacyTestMessage)(nil),
296		ExtensionType: ([]string)(nil),
297		Field:         10014,
298		Name:          "fizz.buzz.repeated_string",
299		Tag:           "bytes,10014,rep,name=repeated_string",
300		Filename:      "test.proto",
301	}, {
302		ExtendedType:  (*LegacyTestMessage)(nil),
303		ExtensionType: ([][]byte)(nil),
304		Field:         10015,
305		Name:          "fizz.buzz.repeated_bytes",
306		Tag:           "bytes,10015,rep,name=repeated_bytes",
307		Filename:      "test.proto",
308	}, {
309		ExtendedType:  (*LegacyTestMessage)(nil),
310		ExtensionType: ([]proto2_20180125.Message_ChildEnum)(nil),
311		Field:         10016,
312		Name:          "fizz.buzz.repeated_enum_v1",
313		Tag:           "varint,10016,rep,name=repeated_enum_v1,enum=google.golang.org.proto2_20180125.Message_ChildEnum",
314		Filename:      "test.proto",
315	}, {
316		ExtendedType:  (*LegacyTestMessage)(nil),
317		ExtensionType: ([]*proto2_20180125.Message_ChildMessage)(nil),
318		Field:         10017,
319		Name:          "fizz.buzz.repeated_message_v1",
320		Tag:           "bytes,10017,rep,name=repeated_message_v1",
321		Filename:      "test.proto",
322	}, {
323		ExtendedType:  (*LegacyTestMessage)(nil),
324		ExtensionType: ([]EnumProto2)(nil),
325		Field:         10018,
326		Name:          "fizz.buzz.repeated_enum_v2",
327		Tag:           "varint,10018,rep,name=repeated_enum_v2,enum=EnumProto2",
328		Filename:      "test.proto",
329	}, {
330		ExtendedType:  (*LegacyTestMessage)(nil),
331		ExtensionType: ([]*EnumMessages)(nil),
332		Field:         10019,
333		Name:          "fizz.buzz.repeated_message_v2",
334		Tag:           "bytes,10019,rep,name=repeated_message_v2",
335		Filename:      "test.proto",
336	}}
337)
338
339func TestLegacyExtensions(t *testing.T) {
340	opts := cmp.Options{cmp.Comparer(func(x, y *proto2_20180125.Message_ChildMessage) bool {
341		return x == y // pointer compare messages for object identity
342	})}
343
344	m := pimpl.Export{}.MessageOf(new(LegacyTestMessage))
345
346	// Check that getting the zero value returns the default value for scalars,
347	// nil for singular messages, and an empty list for repeated fields.
348	defaultValues := map[int]interface{}{
349		0: bool(true),
350		1: int32(-12345),
351		2: uint32(3200),
352		3: float32(3.14159),
353		4: string("hello, \"world!\"\n"),
354		5: []byte("dead\xde\xad\xbe\xefbeef"),
355		6: proto2_20180125.Message_ALPHA,
356		7: nil,
357		8: EnumProto2(0xdead),
358		9: nil,
359	}
360	for i, xt := range extensionTypes {
361		var got interface{}
362		xd := xt.TypeDescriptor()
363		if !(xd.IsList() || xd.IsMap() || xd.Message() != nil) {
364			got = xt.InterfaceOf(m.Get(xd))
365		}
366		want := defaultValues[i]
367		if diff := cmp.Diff(want, got, opts); diff != "" {
368			t.Errorf("Message.Get(%d) mismatch (-want +got):\n%v", xd.Number(), diff)
369		}
370	}
371
372	// All fields should be unpopulated.
373	for _, xt := range extensionTypes {
374		xd := xt.TypeDescriptor()
375		if m.Has(xd) {
376			t.Errorf("Message.Has(%d) = true, want false", xd.Number())
377		}
378	}
379
380	// Set some values and append to values to the lists.
381	m1a := &proto2_20180125.Message_ChildMessage{F1: proto.String("m1a")}
382	m1b := &proto2_20180125.Message_ChildMessage{F1: proto.String("m2b")}
383	m2a := &EnumMessages{EnumP2: EnumProto2(0x1b).Enum()}
384	m2b := &EnumMessages{EnumP2: EnumProto2(0x2b).Enum()}
385	setValues := map[int]interface{}{
386		0:  bool(false),
387		1:  int32(-54321),
388		2:  uint32(6400),
389		3:  float32(2.71828),
390		4:  string("goodbye, \"world!\"\n"),
391		5:  []byte("live\xde\xad\xbe\xefchicken"),
392		6:  proto2_20180125.Message_CHARLIE,
393		7:  m1a,
394		8:  EnumProto2(0xbeef),
395		9:  m2a,
396		10: []bool{true},
397		11: []int32{-1000},
398		12: []uint32{1280},
399		13: []float32{1.6180},
400		14: []string{"zero"},
401		15: [][]byte{[]byte("zero")},
402		16: []proto2_20180125.Message_ChildEnum{proto2_20180125.Message_BRAVO},
403		17: []*proto2_20180125.Message_ChildMessage{m1b},
404		18: []EnumProto2{0xdead},
405		19: []*EnumMessages{m2b},
406	}
407	for i, xt := range extensionTypes {
408		m.Set(xt.TypeDescriptor(), xt.ValueOf(setValues[i]))
409	}
410	for i, xt := range extensionTypes[len(extensionTypes)/2:] {
411		v := extensionTypes[i].ValueOf(setValues[i])
412		m.Get(xt.TypeDescriptor()).List().Append(v)
413	}
414
415	// Get the values and check for equality.
416	getValues := map[int]interface{}{
417		0:  bool(false),
418		1:  int32(-54321),
419		2:  uint32(6400),
420		3:  float32(2.71828),
421		4:  string("goodbye, \"world!\"\n"),
422		5:  []byte("live\xde\xad\xbe\xefchicken"),
423		6:  proto2_20180125.Message_ChildEnum(proto2_20180125.Message_CHARLIE),
424		7:  m1a,
425		8:  EnumProto2(0xbeef),
426		9:  m2a,
427		10: []bool{true, false},
428		11: []int32{-1000, -54321},
429		12: []uint32{1280, 6400},
430		13: []float32{1.6180, 2.71828},
431		14: []string{"zero", "goodbye, \"world!\"\n"},
432		15: [][]byte{[]byte("zero"), []byte("live\xde\xad\xbe\xefchicken")},
433		16: []proto2_20180125.Message_ChildEnum{proto2_20180125.Message_BRAVO, proto2_20180125.Message_CHARLIE},
434		17: []*proto2_20180125.Message_ChildMessage{m1b, m1a},
435		18: []EnumProto2{0xdead, 0xbeef},
436		19: []*EnumMessages{m2b, m2a},
437	}
438	for i, xt := range extensionTypes {
439		xd := xt.TypeDescriptor()
440		got := xt.InterfaceOf(m.Get(xd))
441		want := getValues[i]
442		if diff := cmp.Diff(want, got, opts); diff != "" {
443			t.Errorf("Message.Get(%d) mismatch (-want +got):\n%v", xd.Number(), diff)
444		}
445	}
446
447	// Clear all singular fields and truncate all repeated fields.
448	for _, xt := range extensionTypes[:len(extensionTypes)/2] {
449		m.Clear(xt.TypeDescriptor())
450	}
451	for _, xt := range extensionTypes[len(extensionTypes)/2:] {
452		m.Get(xt.TypeDescriptor()).List().Truncate(0)
453	}
454
455	// Clear all repeated fields.
456	for _, xt := range extensionTypes[len(extensionTypes)/2:] {
457		m.Clear(xt.TypeDescriptor())
458	}
459}
460
461func TestLegacyExtensionConvert(t *testing.T) {
462	for i := range extensionTypes {
463		i := i
464		t.Run("", func(t *testing.T) {
465			t.Parallel()
466
467			wantType := extensionTypes[i]
468			wantDesc := extensionDescs[i]
469			gotType := (pref.ExtensionType)(wantDesc)
470			gotDesc := wantType.(*pimpl.ExtensionInfo)
471
472			// Concurrently call accessors to trigger possible races.
473			for _, xt := range []pref.ExtensionType{wantType, wantDesc} {
474				xt := xt
475				go func() { xt.New() }()
476				go func() { xt.Zero() }()
477				go func() { xt.TypeDescriptor() }()
478			}
479
480			// TODO: We need a test package to compare descriptors.
481			type list interface {
482				Len() int
483				pragma.DoNotImplement
484			}
485			opts := cmp.Options{
486				cmp.Comparer(func(x, y reflect.Type) bool {
487					return x == y
488				}),
489				cmp.Transformer("", func(x list) []interface{} {
490					out := make([]interface{}, x.Len())
491					v := reflect.ValueOf(x)
492					for i := 0; i < x.Len(); i++ {
493						m := v.MethodByName("Get")
494						out[i] = m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
495					}
496					return out
497				}),
498				cmp.Transformer("", func(x pref.Descriptor) map[string]interface{} {
499					out := make(map[string]interface{})
500					v := reflect.ValueOf(x)
501					for i := 0; i < v.NumMethod(); i++ {
502						name := v.Type().Method(i).Name
503						if m := v.Method(i); m.Type().NumIn() == 0 && m.Type().NumOut() == 1 {
504							switch name {
505							case "ParentFile", "Parent":
506							// Ignore parents to avoid recursive cycle.
507							case "Options":
508								// Ignore descriptor options since protos are not cmperable.
509							case "ContainingOneof", "ContainingMessage", "Enum", "Message":
510								// Avoid descending into a dependency to avoid a cycle.
511								// Just record the full name if available.
512								//
513								// TODO: Cycle support in cmp would be useful here.
514								v := m.Call(nil)[0]
515								if !v.IsNil() {
516									out[name] = v.Interface().(pref.Descriptor).FullName()
517								}
518							case "Type":
519								// Ignore ExtensionTypeDescriptor.Type method to avoid cycle.
520							default:
521								out[name] = m.Call(nil)[0].Interface()
522							}
523						}
524					}
525					return out
526				}),
527				cmp.Transformer("", func(xt pref.ExtensionType) map[string]interface{} {
528					return map[string]interface{}{
529						"Descriptor": xt.TypeDescriptor(),
530					}
531				}),
532				cmp.Transformer("", func(v pref.Value) interface{} {
533					return v.Interface()
534				}),
535			}
536			if diff := cmp.Diff(&wantType, &gotType, opts); diff != "" {
537				t.Errorf("ExtensionType mismatch (-want, +got):\n%v", diff)
538			}
539
540			opts = cmp.Options{
541				cmpopts.IgnoreFields(pimpl.ExtensionInfo{}, "ExtensionType"),
542				cmpopts.IgnoreUnexported(pimpl.ExtensionInfo{}),
543			}
544			if diff := cmp.Diff(wantDesc, gotDesc, opts); diff != "" {
545				t.Errorf("ExtensionDesc mismatch (-want, +got):\n%v", diff)
546			}
547		})
548	}
549}
550
551type (
552	MessageA struct {
553		A1 *MessageA `protobuf:"bytes,1,req,name=a1"`
554		A2 *MessageB `protobuf:"bytes,2,req,name=a2"`
555		A3 Enum      `protobuf:"varint,3,opt,name=a3,enum=legacy.Enum"`
556	}
557	MessageB struct {
558		B1 *MessageA `protobuf:"bytes,1,req,name=b1"`
559		B2 *MessageB `protobuf:"bytes,2,req,name=b2"`
560		B3 Enum      `protobuf:"varint,3,opt,name=b3,enum=legacy.Enum"`
561	}
562	Enum int32
563)
564
565func (*MessageA) Reset()                      { panic("not implemented") }
566func (*MessageA) String() string              { panic("not implemented") }
567func (*MessageA) ProtoMessage()               { panic("not implemented") }
568func (*MessageA) Descriptor() ([]byte, []int) { return concurrentFD, []int{0} }
569
570func (*MessageB) Reset()                      { panic("not implemented") }
571func (*MessageB) String() string              { panic("not implemented") }
572func (*MessageB) ProtoMessage()               { panic("not implemented") }
573func (*MessageB) Descriptor() ([]byte, []int) { return concurrentFD, []int{1} }
574
575func (Enum) EnumDescriptor() ([]byte, []int) { return concurrentFD, []int{0} }
576
577var concurrentFD = func() []byte {
578	b, _ := proto.Marshal(pdesc.ToFileDescriptorProto(mustMakeFileDesc(`
579		name:    "concurrent.proto"
580		syntax:  "proto2"
581		package: "legacy"
582		message_type: [{
583			name: "MessageA"
584			field: [
585				{name:"a1" number:1 label:LABEL_REQUIRED type:TYPE_MESSAGE type_name:".legacy.MessageA"},
586				{name:"a2" number:2 label:LABEL_REQUIRED type:TYPE_MESSAGE type_name:".legacy.MessageB"},
587				{name:"a3" number:3 label:LABEL_OPTIONAL type:TYPE_ENUM    type_name:".legacy.Enum"}
588			]
589		}, {
590			name: "MessageB"
591			field: [
592				{name:"a1" number:1 label:LABEL_REQUIRED type:TYPE_MESSAGE type_name:".legacy.MessageA"},
593				{name:"a2" number:2 label:LABEL_REQUIRED type:TYPE_MESSAGE type_name:".legacy.MessageB"},
594				{name:"a3" number:3 label:LABEL_OPTIONAL type:TYPE_ENUM    type_name:".legacy.Enum"}
595			]
596		}]
597		enum_type: [{
598			name:  "Enum"
599			value: [{name:"FOO" number:500}]
600		}]
601	`, nil)))
602	return pimpl.Export{}.CompressGZIP(b)
603}()
604
605// TestLegacyConcurrentInit tests that concurrent wrapping of multiple legacy types
606// results in the exact same descriptor being created.
607func TestLegacyConcurrentInit(t *testing.T) {
608	const numParallel = 5
609	var messageATypes [numParallel]pref.MessageType
610	var messageBTypes [numParallel]pref.MessageType
611	var enumDescs [numParallel]pref.EnumDescriptor
612
613	// Concurrently load message and enum types.
614	var wg sync.WaitGroup
615	for i := 0; i < numParallel; i++ {
616		i := i
617		wg.Add(3)
618		go func() {
619			defer wg.Done()
620			messageATypes[i] = pimpl.Export{}.MessageTypeOf((*MessageA)(nil))
621		}()
622		go func() {
623			defer wg.Done()
624			messageBTypes[i] = pimpl.Export{}.MessageTypeOf((*MessageB)(nil))
625		}()
626		go func() {
627			defer wg.Done()
628			enumDescs[i] = pimpl.Export{}.EnumDescriptorOf(Enum(0))
629		}()
630	}
631	wg.Wait()
632
633	var (
634		wantMTA = messageATypes[0]
635		wantMDA = messageATypes[0].Descriptor().Fields().ByNumber(1).Message()
636		wantMTB = messageBTypes[0]
637		wantMDB = messageBTypes[0].Descriptor().Fields().ByNumber(2).Message()
638		wantED  = messageATypes[0].Descriptor().Fields().ByNumber(3).Enum()
639	)
640
641	for _, gotMT := range messageATypes[1:] {
642		if gotMT != wantMTA {
643			t.Error("MessageType(MessageA) mismatch")
644		}
645		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
646			t.Error("MessageDescriptor(MessageA) mismatch")
647		}
648		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
649			t.Error("MessageDescriptor(MessageB) mismatch")
650		}
651		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
652			t.Error("EnumDescriptor(Enum) mismatch")
653		}
654	}
655	for _, gotMT := range messageBTypes[1:] {
656		if gotMT != wantMTB {
657			t.Error("MessageType(MessageB) mismatch")
658		}
659		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
660			t.Error("MessageDescriptor(MessageA) mismatch")
661		}
662		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
663			t.Error("MessageDescriptor(MessageB) mismatch")
664		}
665		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
666			t.Error("EnumDescriptor(Enum) mismatch")
667		}
668	}
669	for _, gotED := range enumDescs[1:] {
670		if gotED != wantED {
671			t.Error("EnumType(Enum) mismatch")
672		}
673	}
674}
675
676type LegacyTestMessageName1 struct{}
677
678func (*LegacyTestMessageName1) Reset()         { panic("not implemented") }
679func (*LegacyTestMessageName1) String() string { panic("not implemented") }
680func (*LegacyTestMessageName1) ProtoMessage()  { panic("not implemented") }
681
682type LegacyTestMessageName2 struct{}
683
684func (*LegacyTestMessageName2) Reset()         { panic("not implemented") }
685func (*LegacyTestMessageName2) String() string { panic("not implemented") }
686func (*LegacyTestMessageName2) ProtoMessage()  { panic("not implemented") }
687func (*LegacyTestMessageName2) XXX_MessageName() string {
688	return "google.golang.org.LegacyTestMessageName2"
689}
690
691func TestLegacyMessageName(t *testing.T) {
692	tests := []struct {
693		in          piface.MessageV1
694		suggestName pref.FullName
695		wantName    pref.FullName
696	}{
697		{new(LegacyTestMessageName1), "google.golang.org.LegacyTestMessageName1", "google.golang.org.LegacyTestMessageName1"},
698		{new(LegacyTestMessageName2), "", "google.golang.org.LegacyTestMessageName2"},
699	}
700
701	for _, tt := range tests {
702		mt := pimpl.Export{}.LegacyMessageTypeOf(tt.in, tt.suggestName)
703		if got := mt.Descriptor().FullName(); got != tt.wantName {
704			t.Errorf("type: %T, name mismatch: got %v, want %v", tt.in, got, tt.wantName)
705		}
706	}
707}
708