1// Copyright 2011 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 proto_test
6
7import (
8	"testing"
9
10	"github.com/golang/protobuf/proto"
11
12	pb2 "github.com/golang/protobuf/internal/testprotos/proto2_proto"
13	pb3 "github.com/golang/protobuf/internal/testprotos/proto3_proto"
14)
15
16var cloneTestMessage = &pb2.MyMessage{
17	Count: proto.Int32(42),
18	Name:  proto.String("Dave"),
19	Pet:   []string{"bunny", "kitty", "horsey"},
20	Inner: &pb2.InnerMessage{
21		Host:      proto.String("niles"),
22		Port:      proto.Int32(9099),
23		Connected: proto.Bool(true),
24	},
25	Others: []*pb2.OtherMessage{
26		{
27			Value: []byte("some bytes"),
28		},
29	},
30	Somegroup: &pb2.MyMessage_SomeGroup{
31		GroupField: proto.Int32(6),
32	},
33	RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
34}
35
36func init() {
37	ext := &pb2.Ext{
38		Data: proto.String("extension"),
39	}
40	if err := proto.SetExtension(cloneTestMessage, pb2.E_Ext_More, ext); err != nil {
41		panic("SetExtension: " + err.Error())
42	}
43	if err := proto.SetExtension(cloneTestMessage, pb2.E_Ext_Text, proto.String("hello")); err != nil {
44		panic("SetExtension: " + err.Error())
45	}
46	if err := proto.SetExtension(cloneTestMessage, pb2.E_Greeting, []string{"one", "two"}); err != nil {
47		panic("SetExtension: " + err.Error())
48	}
49}
50
51func TestClone(t *testing.T) {
52	// Create a clone using a marshal/unmarshal roundtrip.
53	vanilla := new(pb2.MyMessage)
54	b, err := proto.Marshal(cloneTestMessage)
55	if err != nil {
56		t.Errorf("unexpected Marshal error: %v", err)
57	}
58	if err := proto.Unmarshal(b, vanilla); err != nil {
59		t.Errorf("unexpected Unarshal error: %v", err)
60	}
61
62	// Create a clone using Clone and verify that it is equal to the original.
63	m := proto.Clone(cloneTestMessage).(*pb2.MyMessage)
64	if !proto.Equal(m, cloneTestMessage) {
65		t.Fatalf("Clone(%v) = %v", cloneTestMessage, m)
66	}
67
68	// Mutate the clone, which should not affect the original.
69	x1, err := proto.GetExtension(m, pb2.E_Ext_More)
70	if err != nil {
71		t.Errorf("unexpected GetExtension(%v) error: %v", pb2.E_Ext_More.Name, err)
72	}
73	x2, err := proto.GetExtension(m, pb2.E_Ext_Text)
74	if err != nil {
75		t.Errorf("unexpected GetExtension(%v) error: %v", pb2.E_Ext_Text.Name, err)
76	}
77	x3, err := proto.GetExtension(m, pb2.E_Greeting)
78	if err != nil {
79		t.Errorf("unexpected GetExtension(%v) error: %v", pb2.E_Greeting.Name, err)
80	}
81	*m.Inner.Port++
82	*(x1.(*pb2.Ext)).Data = "blah blah"
83	*(x2.(*string)) = "goodbye"
84	x3.([]string)[0] = "zero"
85	if !proto.Equal(cloneTestMessage, vanilla) {
86		t.Fatalf("mutation on original detected:\ngot  %v\nwant %v", cloneTestMessage, vanilla)
87	}
88}
89
90func TestCloneNil(t *testing.T) {
91	var m *pb2.MyMessage
92	if c := proto.Clone(m); !proto.Equal(m, c) {
93		t.Errorf("Clone(%v) = %v", m, c)
94	}
95}
96
97var mergeTests = []struct {
98	src, dst, want proto.Message
99}{
100	{
101		src: &pb2.MyMessage{
102			Count: proto.Int32(42),
103		},
104		dst: &pb2.MyMessage{
105			Name: proto.String("Dave"),
106		},
107		want: &pb2.MyMessage{
108			Count: proto.Int32(42),
109			Name:  proto.String("Dave"),
110		},
111	},
112	{
113		src: &pb2.MyMessage{
114			Inner: &pb2.InnerMessage{
115				Host:      proto.String("hey"),
116				Connected: proto.Bool(true),
117			},
118			Pet: []string{"horsey"},
119			Others: []*pb2.OtherMessage{
120				{
121					Value: []byte("some bytes"),
122				},
123			},
124		},
125		dst: &pb2.MyMessage{
126			Inner: &pb2.InnerMessage{
127				Host: proto.String("niles"),
128				Port: proto.Int32(9099),
129			},
130			Pet: []string{"bunny", "kitty"},
131			Others: []*pb2.OtherMessage{
132				{
133					Key: proto.Int64(31415926535),
134				},
135				{
136					// Explicitly test a src=nil field
137					Inner: nil,
138				},
139			},
140		},
141		want: &pb2.MyMessage{
142			Inner: &pb2.InnerMessage{
143				Host:      proto.String("hey"),
144				Connected: proto.Bool(true),
145				Port:      proto.Int32(9099),
146			},
147			Pet: []string{"bunny", "kitty", "horsey"},
148			Others: []*pb2.OtherMessage{
149				{
150					Key: proto.Int64(31415926535),
151				},
152				{},
153				{
154					Value: []byte("some bytes"),
155				},
156			},
157		},
158	},
159	{
160		src: &pb2.MyMessage{
161			RepBytes: [][]byte{[]byte("wow")},
162		},
163		dst: &pb2.MyMessage{
164			Somegroup: &pb2.MyMessage_SomeGroup{
165				GroupField: proto.Int32(6),
166			},
167			RepBytes: [][]byte{[]byte("sham")},
168		},
169		want: &pb2.MyMessage{
170			Somegroup: &pb2.MyMessage_SomeGroup{
171				GroupField: proto.Int32(6),
172			},
173			RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
174		},
175	},
176	// Check that a scalar bytes field replaces rather than appends.
177	{
178		src:  &pb2.OtherMessage{Value: []byte("foo")},
179		dst:  &pb2.OtherMessage{Value: []byte("bar")},
180		want: &pb2.OtherMessage{Value: []byte("foo")},
181	},
182	{
183		src: &pb2.MessageWithMap{
184			NameMapping: map[int32]string{6: "Nigel"},
185			MsgMapping: map[int64]*pb2.FloatingPoint{
186				0x4001: &pb2.FloatingPoint{F: proto.Float64(2.0)},
187				0x4002: &pb2.FloatingPoint{
188					F: proto.Float64(2.0),
189				},
190			},
191			ByteMapping: map[bool][]byte{true: []byte("wowsa")},
192		},
193		dst: &pb2.MessageWithMap{
194			NameMapping: map[int32]string{
195				6: "Bruce", // should be overwritten
196				7: "Andrew",
197			},
198			MsgMapping: map[int64]*pb2.FloatingPoint{
199				0x4002: &pb2.FloatingPoint{
200					F:     proto.Float64(3.0),
201					Exact: proto.Bool(true),
202				}, // the entire message should be overwritten
203			},
204		},
205		want: &pb2.MessageWithMap{
206			NameMapping: map[int32]string{
207				6: "Nigel",
208				7: "Andrew",
209			},
210			MsgMapping: map[int64]*pb2.FloatingPoint{
211				0x4001: &pb2.FloatingPoint{F: proto.Float64(2.0)},
212				0x4002: &pb2.FloatingPoint{
213					F: proto.Float64(2.0),
214				},
215			},
216			ByteMapping: map[bool][]byte{true: []byte("wowsa")},
217		},
218	},
219	// proto3 shouldn't merge zero values,
220	// in the same way that proto2 shouldn't merge nils.
221	{
222		src: &pb3.Message{
223			Name: "Aaron",
224			Data: []byte(""), // zero value, but not nil
225		},
226		dst: &pb3.Message{
227			HeightInCm: 176,
228			Data:       []byte("texas!"),
229		},
230		want: &pb3.Message{
231			Name:       "Aaron",
232			HeightInCm: 176,
233			Data:       []byte("texas!"),
234		},
235	},
236	{ // Oneof fields should merge by assignment.
237		src:  &pb2.Communique{Union: &pb2.Communique_Number{41}},
238		dst:  &pb2.Communique{Union: &pb2.Communique_Name{"Bobby Tables"}},
239		want: &pb2.Communique{Union: &pb2.Communique_Number{41}},
240	},
241	{ // Oneof nil is the same as not set.
242		src:  &pb2.Communique{},
243		dst:  &pb2.Communique{Union: &pb2.Communique_Name{"Bobby Tables"}},
244		want: &pb2.Communique{Union: &pb2.Communique_Name{"Bobby Tables"}},
245	},
246	{
247		src:  &pb2.Communique{Union: &pb2.Communique_Number{1337}},
248		dst:  &pb2.Communique{},
249		want: &pb2.Communique{Union: &pb2.Communique_Number{1337}},
250	},
251	{
252		src:  &pb2.Communique{Union: &pb2.Communique_Col{pb2.MyMessage_RED}},
253		dst:  &pb2.Communique{},
254		want: &pb2.Communique{Union: &pb2.Communique_Col{pb2.MyMessage_RED}},
255	},
256	{
257		src:  &pb2.Communique{Union: &pb2.Communique_Data{[]byte("hello")}},
258		dst:  &pb2.Communique{},
259		want: &pb2.Communique{Union: &pb2.Communique_Data{[]byte("hello")}},
260	},
261	{
262		src:  &pb2.Communique{Union: &pb2.Communique_Msg{&pb2.Strings{BytesField: []byte{1, 2, 3}}}},
263		dst:  &pb2.Communique{},
264		want: &pb2.Communique{Union: &pb2.Communique_Msg{&pb2.Strings{BytesField: []byte{1, 2, 3}}}},
265	},
266	{
267		src:  &pb2.Communique{Union: &pb2.Communique_Msg{}},
268		dst:  &pb2.Communique{},
269		want: &pb2.Communique{Union: &pb2.Communique_Msg{}},
270	},
271	{
272		src:  &pb2.Communique{Union: &pb2.Communique_Msg{&pb2.Strings{StringField: proto.String("123")}}},
273		dst:  &pb2.Communique{Union: &pb2.Communique_Msg{&pb2.Strings{BytesField: []byte{1, 2, 3}}}},
274		want: &pb2.Communique{Union: &pb2.Communique_Msg{&pb2.Strings{StringField: proto.String("123"), BytesField: []byte{1, 2, 3}}}},
275	},
276	{
277		src: &pb3.Message{
278			Terrain: map[string]*pb3.Nested{
279				"kay_a": &pb3.Nested{Cute: true},      // replace
280				"kay_b": &pb3.Nested{Bunny: "rabbit"}, // insert
281			},
282		},
283		dst: &pb3.Message{
284			Terrain: map[string]*pb3.Nested{
285				"kay_a": &pb3.Nested{Bunny: "lost"},  // replaced
286				"kay_c": &pb3.Nested{Bunny: "bunny"}, // keep
287			},
288		},
289		want: &pb3.Message{
290			Terrain: map[string]*pb3.Nested{
291				"kay_a": &pb3.Nested{Cute: true},
292				"kay_b": &pb3.Nested{Bunny: "rabbit"},
293				"kay_c": &pb3.Nested{Bunny: "bunny"},
294			},
295		},
296	},
297	{
298		src: &pb2.GoTest{
299			F_BoolRepeated:   []bool{},
300			F_Int32Repeated:  []int32{},
301			F_Int64Repeated:  []int64{},
302			F_Uint32Repeated: []uint32{},
303			F_Uint64Repeated: []uint64{},
304			F_FloatRepeated:  []float32{},
305			F_DoubleRepeated: []float64{},
306			F_StringRepeated: []string{},
307			F_BytesRepeated:  [][]byte{},
308		},
309		dst: &pb2.GoTest{},
310		want: &pb2.GoTest{
311			F_BoolRepeated:   []bool{},
312			F_Int32Repeated:  []int32{},
313			F_Int64Repeated:  []int64{},
314			F_Uint32Repeated: []uint32{},
315			F_Uint64Repeated: []uint64{},
316			F_FloatRepeated:  []float32{},
317			F_DoubleRepeated: []float64{},
318			F_StringRepeated: []string{},
319			F_BytesRepeated:  [][]byte{},
320		},
321	},
322	{
323		src: &pb2.GoTest{},
324		dst: &pb2.GoTest{
325			F_BoolRepeated:   []bool{},
326			F_Int32Repeated:  []int32{},
327			F_Int64Repeated:  []int64{},
328			F_Uint32Repeated: []uint32{},
329			F_Uint64Repeated: []uint64{},
330			F_FloatRepeated:  []float32{},
331			F_DoubleRepeated: []float64{},
332			F_StringRepeated: []string{},
333			F_BytesRepeated:  [][]byte{},
334		},
335		want: &pb2.GoTest{
336			F_BoolRepeated:   []bool{},
337			F_Int32Repeated:  []int32{},
338			F_Int64Repeated:  []int64{},
339			F_Uint32Repeated: []uint32{},
340			F_Uint64Repeated: []uint64{},
341			F_FloatRepeated:  []float32{},
342			F_DoubleRepeated: []float64{},
343			F_StringRepeated: []string{},
344			F_BytesRepeated:  [][]byte{},
345		},
346	},
347	{
348		src: &pb2.GoTest{
349			F_BytesRepeated: [][]byte{nil, []byte{}, []byte{0}},
350		},
351		dst: &pb2.GoTest{},
352		want: &pb2.GoTest{
353			F_BytesRepeated: [][]byte{nil, []byte{}, []byte{0}},
354		},
355	},
356	{
357		src: &pb2.MyMessage{
358			Others: []*pb2.OtherMessage{},
359		},
360		dst: &pb2.MyMessage{},
361		want: &pb2.MyMessage{
362			Others: []*pb2.OtherMessage{},
363		},
364	},
365}
366
367func TestMerge(t *testing.T) {
368	for _, m := range mergeTests {
369		got := proto.Clone(m.dst)
370		if !proto.Equal(got, m.dst) {
371			t.Errorf("Clone()\ngot  %v\nwant %v", got, m.dst)
372			continue
373		}
374		proto.Merge(got, m.src)
375		if !proto.Equal(got, m.want) {
376			t.Errorf("Merge(%v, %v)\ngot  %v\nwant %v", m.dst, m.src, got, m.want)
377		}
378	}
379}
380