1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package proptools
16
17import (
18	"fmt"
19	"reflect"
20	"testing"
21)
22
23var clonePropertiesTestCases = []struct {
24	in  interface{}
25	out interface{}
26	err error
27}{
28	// Valid inputs
29
30	{
31		// Clone bool
32		in: &struct{ B1, B2 bool }{
33			B1: true,
34			B2: false,
35		},
36		out: &struct{ B1, B2 bool }{
37			B1: true,
38			B2: false,
39		},
40	},
41	{
42		// Clone strings
43		in: &struct{ S string }{
44			S: "string1",
45		},
46		out: &struct{ S string }{
47			S: "string1",
48		},
49	},
50	{
51		// Clone slice
52		in: &struct{ S []string }{
53			S: []string{"string1"},
54		},
55		out: &struct{ S []string }{
56			S: []string{"string1"},
57		},
58	},
59	{
60		// Clone empty slice
61		in: &struct{ S []string }{
62			S: []string{},
63		},
64		out: &struct{ S []string }{
65			S: []string{},
66		},
67	},
68	{
69		// Clone nil slice
70		in:  &struct{ S []string }{},
71		out: &struct{ S []string }{},
72	},
73	{
74		// Clone slice of structs
75		in: &struct{ S []struct{ T string } }{
76			S: []struct{ T string }{
77				{"string1"}, {"string2"},
78			},
79		},
80		out: &struct{ S []struct{ T string } }{
81			S: []struct{ T string }{
82				{"string1"}, {"string2"},
83			},
84		},
85	},
86	{
87		// Clone map
88		in: &struct{ S map[string]string }{
89			S: map[string]string{"key": "string1"},
90		},
91		out: &struct{ S map[string]string }{
92			S: map[string]string{"key": "string1"},
93		},
94	},
95	{
96		// Clone empty map
97		in: &struct{ S map[string]string }{
98			S: map[string]string{},
99		},
100		out: &struct{ S map[string]string }{
101			S: map[string]string{},
102		},
103	},
104	{
105		// Clone nil map
106		in:  &struct{ S map[string]string }{},
107		out: &struct{ S map[string]string }{},
108	},
109	{
110		// Clone pointer to bool
111		in: &struct{ B1, B2 *bool }{
112			B1: BoolPtr(true),
113			B2: BoolPtr(false),
114		},
115		out: &struct{ B1, B2 *bool }{
116			B1: BoolPtr(true),
117			B2: BoolPtr(false),
118		},
119	},
120	{
121		// Clone pointer to string
122		in: &struct{ S *string }{
123			S: StringPtr("string1"),
124		},
125		out: &struct{ S *string }{
126			S: StringPtr("string1"),
127		},
128	},
129	{
130		// Clone pointer to int64
131		in: &struct{ S *int64 }{
132			S: Int64Ptr(5),
133		},
134		out: &struct{ S *int64 }{
135			S: Int64Ptr(5),
136		},
137	},
138	{
139		// Clone struct
140		in: &struct{ S struct{ S string } }{
141			S: struct{ S string }{
142				S: "string1",
143			},
144		},
145		out: &struct{ S struct{ S string } }{
146			S: struct{ S string }{
147				S: "string1",
148			},
149		},
150	},
151	{
152		// Clone struct pointer
153		in: &struct{ S *struct{ S string } }{
154			S: &struct{ S string }{
155				S: "string1",
156			},
157		},
158		out: &struct{ S *struct{ S string } }{
159			S: &struct{ S string }{
160				S: "string1",
161			},
162		},
163	},
164	{
165		// Clone interface
166		in: &struct{ S interface{} }{
167			S: &struct{ S string }{
168				S: "string1",
169			},
170		},
171		out: &struct{ S interface{} }{
172			S: &struct{ S string }{
173				S: "string1",
174			},
175		},
176	},
177	{
178		// Clone nested interface
179		in: &struct {
180			Nested struct{ S interface{} }
181		}{
182			Nested: struct{ S interface{} }{
183				S: &struct{ S string }{
184					S: "string1",
185				},
186			},
187		},
188		out: &struct {
189			Nested struct{ S interface{} }
190		}{
191			Nested: struct{ S interface{} }{
192				S: &struct{ S string }{
193					S: "string1",
194				},
195			},
196		},
197	}, {
198		// Empty struct
199		in:  &struct{}{},
200		out: &struct{}{},
201	},
202	{
203		// Interface nil
204		in: &struct{ S interface{} }{
205			S: nil,
206		},
207		out: &struct{ S interface{} }{
208			S: nil,
209		},
210	},
211	{
212		// Interface pointer to nil
213		in: &struct{ S interface{} }{
214			S: (*struct{ S string })(nil),
215		},
216		out: &struct{ S interface{} }{
217			S: (*struct{ S string })(nil),
218		},
219	},
220	{
221		// Pointer nil
222		in: &struct{ S *struct{} }{
223			S: nil,
224		},
225		out: &struct{ S *struct{} }{
226			S: nil,
227		},
228	},
229	{
230		// Anonymous struct
231		in: &struct {
232			EmbeddedStruct
233			Nested struct{ EmbeddedStruct }
234		}{
235			EmbeddedStruct: EmbeddedStruct{
236				S: "string1",
237				I: Int64Ptr(55),
238			},
239			Nested: struct{ EmbeddedStruct }{
240				EmbeddedStruct: EmbeddedStruct{
241					S: "string2",
242					I: Int64Ptr(5),
243				},
244			},
245		},
246		out: &struct {
247			EmbeddedStruct
248			Nested struct{ EmbeddedStruct }
249		}{
250			EmbeddedStruct: EmbeddedStruct{
251				S: "string1",
252				I: Int64Ptr(55),
253			},
254			Nested: struct{ EmbeddedStruct }{
255				EmbeddedStruct: EmbeddedStruct{
256					S: "string2",
257					I: Int64Ptr(5),
258				},
259			},
260		},
261	},
262	{
263		// Anonymous interface
264		in: &struct {
265			EmbeddedInterface
266			Nested struct{ EmbeddedInterface }
267		}{
268			EmbeddedInterface: &struct{ S string }{
269				S: "string1",
270			},
271			Nested: struct{ EmbeddedInterface }{
272				EmbeddedInterface: &struct{ S string }{
273					S: "string2",
274				},
275			},
276		},
277		out: &struct {
278			EmbeddedInterface
279			Nested struct{ EmbeddedInterface }
280		}{
281			EmbeddedInterface: &struct{ S string }{
282				S: "string1",
283			},
284			Nested: struct{ EmbeddedInterface }{
285				EmbeddedInterface: &struct{ S string }{
286					S: "string2",
287				},
288			},
289		},
290	},
291}
292
293type EmbeddedStruct struct {
294	S string
295	I *int64
296}
297type EmbeddedInterface interface{}
298
299func isPointerToEmptyStruct(v any) bool {
300	t := reflect.TypeOf(v)
301	if t.Kind() != reflect.Ptr {
302		return false
303	}
304	t = t.Elem()
305	if t.Kind() != reflect.Struct {
306		return false
307	}
308	if t.NumField() > 0 {
309		return false
310	}
311	return true
312}
313
314func TestCloneProperties(t *testing.T) {
315	for _, testCase := range clonePropertiesTestCases {
316		testString := fmt.Sprintf("%s", testCase.in)
317
318		got := CloneProperties(reflect.ValueOf(testCase.in)).Interface()
319
320		if !reflect.DeepEqual(testCase.out, got) {
321			t.Errorf("test case %s", testString)
322			t.Errorf("incorrect output")
323			t.Errorf("  expected: %#v", testCase.out)
324			t.Errorf("       got: %#v", got)
325		}
326		if testCase.out == got && !isPointerToEmptyStruct(testCase.out) {
327			t.Errorf("test case %s", testString)
328			t.Errorf("items should be cloned, not the original")
329			t.Errorf("  expected: %s", testCase.out)
330			t.Errorf("       got: %s", got)
331		}
332	}
333}
334
335var cloneEmptyPropertiesTestCases = []struct {
336	in  interface{}
337	out interface{}
338	err error
339}{
340	// Valid inputs
341
342	{
343		// Clone bool
344		in: &struct{ B1, B2 bool }{
345			B1: true,
346			B2: false,
347		},
348		out: &struct{ B1, B2 bool }{},
349	},
350	{
351		// Clone strings
352		in: &struct{ S string }{
353			S: "string1",
354		},
355		out: &struct{ S string }{},
356	},
357	{
358		// Clone slice
359		in: &struct{ S []string }{
360			S: []string{"string1"},
361		},
362		out: &struct{ S []string }{},
363	},
364	{
365		// Clone empty slice
366		in: &struct{ S []string }{
367			S: []string{},
368		},
369		out: &struct{ S []string }{},
370	},
371	{
372		// Clone nil slice
373		in:  &struct{ S []string }{},
374		out: &struct{ S []string }{},
375	},
376	{
377		// Clone slice of structs
378		in: &struct{ S []struct{ T string } }{
379			S: []struct{ T string }{
380				{"string1"}, {"string2"},
381			},
382		},
383		out: &struct{ S []struct{ T string } }{
384			S: []struct{ T string }(nil),
385		},
386	},
387	{
388		// Clone pointer to bool
389		in: &struct{ B1, B2 *bool }{
390			B1: BoolPtr(true),
391			B2: BoolPtr(false),
392		},
393		out: &struct{ B1, B2 *bool }{},
394	},
395	{
396		// Clone pointer to int64
397		in: &struct{ B1, B2 *int64 }{
398			B1: Int64Ptr(5),
399			B2: Int64Ptr(4),
400		},
401		out: &struct{ B1, B2 *int64 }{},
402	},
403	{
404		// Clone pointer to string
405		in: &struct{ S *string }{
406			S: StringPtr("string1"),
407		},
408		out: &struct{ S *string }{},
409	},
410	{
411		// Clone struct
412		in: &struct{ S struct{ S string } }{
413			S: struct{ S string }{
414				S: "string1",
415			},
416		},
417		out: &struct{ S struct{ S string } }{
418			S: struct{ S string }{},
419		},
420	},
421	{
422		// Clone struct pointer
423		in: &struct{ S *struct{ S string } }{
424			S: &struct{ S string }{
425				S: "string1",
426			},
427		},
428		out: &struct{ S *struct{ S string } }{
429			S: &struct{ S string }{},
430		},
431	},
432	{
433		// Clone interface
434		in: &struct{ S interface{} }{
435			S: &struct{ S string }{
436				S: "string1",
437			},
438		},
439		out: &struct{ S interface{} }{
440			S: &struct{ S string }{},
441		},
442	},
443	{
444		// Clone nested interface
445		in: &struct {
446			Nested struct{ S interface{} }
447		}{
448			Nested: struct{ S interface{} }{
449				S: &struct{ S string }{
450					S: "string1",
451				},
452			},
453		},
454		out: &struct {
455			Nested struct{ S interface{} }
456		}{
457			Nested: struct{ S interface{} }{
458				S: &struct{ S string }{},
459			},
460		},
461	},
462	{
463		// Empty struct
464		in:  &struct{}{},
465		out: &struct{}{},
466	},
467	{
468		// Interface nil
469		in: &struct{ S interface{} }{
470			S: nil,
471		},
472		out: &struct{ S interface{} }{},
473	},
474	{
475		// Interface pointer to nil
476		in: &struct{ S interface{} }{
477			S: (*struct{ S string })(nil),
478		},
479		out: &struct{ S interface{} }{
480			S: (*struct{ S string })(nil),
481		},
482	},
483	{
484		// Pointer nil
485		in: &struct{ S *struct{} }{
486			S: nil,
487		},
488		out: &struct{ S *struct{} }{},
489	},
490	{
491		// Anonymous struct
492		in: &struct {
493			EmbeddedStruct
494			Nested struct{ EmbeddedStruct }
495		}{
496			EmbeddedStruct: EmbeddedStruct{
497				S: "string1",
498			},
499			Nested: struct{ EmbeddedStruct }{
500				EmbeddedStruct: EmbeddedStruct{
501					S: "string2",
502				},
503			},
504		},
505		out: &struct {
506			EmbeddedStruct
507			Nested struct{ EmbeddedStruct }
508		}{
509			EmbeddedStruct: EmbeddedStruct{},
510			Nested: struct{ EmbeddedStruct }{
511				EmbeddedStruct: EmbeddedStruct{},
512			},
513		},
514	},
515	{
516		// Anonymous interface
517		in: &struct {
518			EmbeddedInterface
519			Nested struct{ EmbeddedInterface }
520		}{
521			EmbeddedInterface: &struct{ S string }{
522				S: "string1",
523			},
524			Nested: struct{ EmbeddedInterface }{
525				EmbeddedInterface: &struct{ S string }{
526					S: "string2",
527				},
528			},
529		},
530		out: &struct {
531			EmbeddedInterface
532			Nested struct{ EmbeddedInterface }
533		}{
534			EmbeddedInterface: &struct{ S string }{},
535			Nested: struct{ EmbeddedInterface }{
536				EmbeddedInterface: &struct{ S string }{},
537			},
538		},
539	},
540}
541
542func TestCloneEmptyProperties(t *testing.T) {
543	for _, testCase := range cloneEmptyPropertiesTestCases {
544		testString := fmt.Sprintf("%#v", testCase.in)
545
546		got := CloneEmptyProperties(reflect.ValueOf(testCase.in)).Interface()
547
548		if !reflect.DeepEqual(testCase.out, got) {
549			t.Errorf("test case %s", testString)
550			t.Errorf("incorrect output")
551			t.Errorf("  expected: %#v", testCase.out)
552			t.Errorf("       got: %#v", got)
553		}
554	}
555}
556
557func TestZeroProperties(t *testing.T) {
558	for _, testCase := range cloneEmptyPropertiesTestCases {
559		testString := fmt.Sprintf("%#v", testCase.in)
560
561		got := CloneProperties(reflect.ValueOf(testCase.in)).Interface()
562		ZeroProperties(reflect.ValueOf(got))
563
564		if !reflect.DeepEqual(testCase.out, got) {
565			t.Errorf("test case %s", testString)
566			t.Errorf("incorrect output")
567			t.Errorf("  expected: %#v", testCase.out)
568			t.Errorf("       got: %#v", got)
569		}
570	}
571}
572