• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"errors"
19	"fmt"
20	"reflect"
21	"strings"
22	"testing"
23)
24
25type appendPropertyTestCase struct {
26	in1    interface{}
27	in2    interface{}
28	out    interface{}
29	order  Order // default is Append
30	filter ExtendPropertyFilterFunc
31	err    error
32}
33
34func appendPropertiesTestCases() []appendPropertyTestCase {
35	return []appendPropertyTestCase{
36		// Valid inputs
37
38		{
39			// Append bool
40			in1: &struct{ B1, B2, B3, B4 bool }{
41				B1: true,
42				B2: false,
43				B3: true,
44				B4: false,
45			},
46			in2: &struct{ B1, B2, B3, B4 bool }{
47				B1: true,
48				B2: true,
49				B3: false,
50				B4: false,
51			},
52			out: &struct{ B1, B2, B3, B4 bool }{
53				B1: true,
54				B2: true,
55				B3: true,
56				B4: false,
57			},
58		},
59		{
60			// Prepend bool
61			in1: &struct{ B1, B2, B3, B4 bool }{
62				B1: true,
63				B2: false,
64				B3: true,
65				B4: false,
66			},
67			in2: &struct{ B1, B2, B3, B4 bool }{
68				B1: true,
69				B2: true,
70				B3: false,
71				B4: false,
72			},
73			out: &struct{ B1, B2, B3, B4 bool }{
74				B1: true,
75				B2: true,
76				B3: true,
77				B4: false,
78			},
79			order: Prepend,
80		},
81		{
82			// Append strings
83			in1: &struct{ S string }{
84				S: "string1",
85			},
86			in2: &struct{ S string }{
87				S: "string2",
88			},
89			out: &struct{ S string }{
90				S: "string1string2",
91			},
92		},
93		{
94			// Prepend strings
95			in1: &struct{ S string }{
96				S: "string1",
97			},
98			in2: &struct{ S string }{
99				S: "string2",
100			},
101			out: &struct{ S string }{
102				S: "string2string1",
103			},
104			order: Prepend,
105		},
106		{
107			// Append pointer to bool
108			in1: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
109				B1: BoolPtr(true),
110				B2: BoolPtr(false),
111				B3: nil,
112				B4: BoolPtr(true),
113				B5: BoolPtr(false),
114				B6: nil,
115				B7: BoolPtr(true),
116				B8: BoolPtr(false),
117				B9: nil,
118			},
119			in2: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
120				B1: nil,
121				B2: nil,
122				B3: nil,
123				B4: BoolPtr(true),
124				B5: BoolPtr(true),
125				B6: BoolPtr(true),
126				B7: BoolPtr(false),
127				B8: BoolPtr(false),
128				B9: BoolPtr(false),
129			},
130			out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
131				B1: BoolPtr(true),
132				B2: BoolPtr(false),
133				B3: nil,
134				B4: BoolPtr(true),
135				B5: BoolPtr(true),
136				B6: BoolPtr(true),
137				B7: BoolPtr(false),
138				B8: BoolPtr(false),
139				B9: BoolPtr(false),
140			},
141		},
142		{
143			// Prepend pointer to bool
144			in1: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
145				B1: BoolPtr(true),
146				B2: BoolPtr(false),
147				B3: nil,
148				B4: BoolPtr(true),
149				B5: BoolPtr(false),
150				B6: nil,
151				B7: BoolPtr(true),
152				B8: BoolPtr(false),
153				B9: nil,
154			},
155			in2: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
156				B1: nil,
157				B2: nil,
158				B3: nil,
159				B4: BoolPtr(true),
160				B5: BoolPtr(true),
161				B6: BoolPtr(true),
162				B7: BoolPtr(false),
163				B8: BoolPtr(false),
164				B9: BoolPtr(false),
165			},
166			out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
167				B1: BoolPtr(true),
168				B2: BoolPtr(false),
169				B3: nil,
170				B4: BoolPtr(true),
171				B5: BoolPtr(false),
172				B6: BoolPtr(true),
173				B7: BoolPtr(true),
174				B8: BoolPtr(false),
175				B9: BoolPtr(false),
176			},
177			order: Prepend,
178		},
179		{
180			// Append pointer to integer
181			in1: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{
182				I1: Int64Ptr(55),
183				I2: Int64Ptr(-3),
184				I3: nil,
185				I4: Int64Ptr(100),
186				I5: Int64Ptr(33),
187				I6: nil,
188				I7: Int64Ptr(77),
189				I8: Int64Ptr(0),
190				I9: nil,
191			},
192			in2: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{
193				I1: nil,
194				I2: nil,
195				I3: nil,
196				I4: Int64Ptr(1),
197				I5: Int64Ptr(-2),
198				I6: Int64Ptr(8),
199				I7: Int64Ptr(9),
200				I8: Int64Ptr(10),
201				I9: Int64Ptr(11),
202			},
203			out: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{
204				I1: Int64Ptr(55),
205				I2: Int64Ptr(-3),
206				I3: nil,
207				I4: Int64Ptr(1),
208				I5: Int64Ptr(-2),
209				I6: Int64Ptr(8),
210				I7: Int64Ptr(9),
211				I8: Int64Ptr(10),
212				I9: Int64Ptr(11),
213			},
214		},
215		{
216			// Prepend pointer to integer
217			in1: &struct{ I1, I2, I3 *int64 }{
218				I1: Int64Ptr(55),
219				I3: nil,
220			},
221			in2: &struct{ I1, I2, I3 *int64 }{
222				I2: Int64Ptr(33),
223			},
224			out: &struct{ I1, I2, I3 *int64 }{
225				I1: Int64Ptr(55),
226				I2: Int64Ptr(33),
227				I3: nil,
228			},
229			order: Prepend,
230		},
231		{
232			// Append pointer to strings
233			in1: &struct{ S1, S2, S3, S4 *string }{
234				S1: StringPtr("string1"),
235				S2: StringPtr("string2"),
236			},
237			in2: &struct{ S1, S2, S3, S4 *string }{
238				S1: StringPtr("string3"),
239				S3: StringPtr("string4"),
240			},
241			out: &struct{ S1, S2, S3, S4 *string }{
242				S1: StringPtr("string3"),
243				S2: StringPtr("string2"),
244				S3: StringPtr("string4"),
245				S4: nil,
246			},
247		},
248		{
249			// Prepend pointer to strings
250			in1: &struct{ S1, S2, S3, S4 *string }{
251				S1: StringPtr("string1"),
252				S2: StringPtr("string2"),
253			},
254			in2: &struct{ S1, S2, S3, S4 *string }{
255				S1: StringPtr("string3"),
256				S3: StringPtr("string4"),
257			},
258			out: &struct{ S1, S2, S3, S4 *string }{
259				S1: StringPtr("string1"),
260				S2: StringPtr("string2"),
261				S3: StringPtr("string4"),
262				S4: nil,
263			},
264			order: Prepend,
265		},
266		{
267			// Append slice
268			in1: &struct{ S []string }{
269				S: []string{"string1"},
270			},
271			in2: &struct{ S []string }{
272				S: []string{"string2"},
273			},
274			out: &struct{ S []string }{
275				S: []string{"string1", "string2"},
276			},
277		},
278		{
279			// Prepend slice
280			in1: &struct{ S []string }{
281				S: []string{"string1"},
282			},
283			in2: &struct{ S []string }{
284				S: []string{"string2"},
285			},
286			out: &struct{ S []string }{
287				S: []string{"string2", "string1"},
288			},
289			order: Prepend,
290		},
291		{
292			// Replace slice
293			in1: &struct{ S []string }{
294				S: []string{"string1"},
295			},
296			in2: &struct{ S []string }{
297				S: []string{"string2"},
298			},
299			out: &struct{ S []string }{
300				S: []string{"string2"},
301			},
302			order: Replace,
303		},
304		{
305			// Append empty slice
306			in1: &struct{ S1, S2 []string }{
307				S1: []string{"string1"},
308				S2: []string{},
309			},
310			in2: &struct{ S1, S2 []string }{
311				S1: []string{},
312				S2: []string{"string2"},
313			},
314			out: &struct{ S1, S2 []string }{
315				S1: []string{"string1"},
316				S2: []string{"string2"},
317			},
318		},
319		{
320			// Prepend empty slice
321			in1: &struct{ S1, S2 []string }{
322				S1: []string{"string1"},
323				S2: []string{},
324			},
325			in2: &struct{ S1, S2 []string }{
326				S1: []string{},
327				S2: []string{"string2"},
328			},
329			out: &struct{ S1, S2 []string }{
330				S1: []string{"string1"},
331				S2: []string{"string2"},
332			},
333			order: Prepend,
334		},
335		{
336			// Replace empty slice
337			in1: &struct{ S1, S2 []string }{
338				S1: []string{"string1"},
339				S2: []string{},
340			},
341			in2: &struct{ S1, S2 []string }{
342				S1: []string{},
343				S2: []string{"string2"},
344			},
345			out: &struct{ S1, S2 []string }{
346				S1: []string{},
347				S2: []string{"string2"},
348			},
349			order: Replace,
350		},
351		{
352			// Append nil slice
353			in1: &struct{ S1, S2, S3 []string }{
354				S1: []string{"string1"},
355			},
356			in2: &struct{ S1, S2, S3 []string }{
357				S2: []string{"string2"},
358			},
359			out: &struct{ S1, S2, S3 []string }{
360				S1: []string{"string1"},
361				S2: []string{"string2"},
362				S3: nil,
363			},
364		},
365		{
366			// Prepend nil slice
367			in1: &struct{ S1, S2, S3 []string }{
368				S1: []string{"string1"},
369			},
370			in2: &struct{ S1, S2, S3 []string }{
371				S2: []string{"string2"},
372			},
373			out: &struct{ S1, S2, S3 []string }{
374				S1: []string{"string1"},
375				S2: []string{"string2"},
376				S3: nil,
377			},
378			order: Prepend,
379		},
380		{
381			// Replace nil slice
382			in1: &struct{ S1, S2, S3 []string }{
383				S1: []string{"string1"},
384			},
385			in2: &struct{ S1, S2, S3 []string }{
386				S2: []string{"string2"},
387			},
388			out: &struct{ S1, S2, S3 []string }{
389				S1: []string{"string1"},
390				S2: []string{"string2"},
391				S3: nil,
392			},
393			order: Replace,
394		},
395		{
396			// Replace embedded slice
397			in1: &struct{ S *struct{ S1 []string } }{
398				S: &struct{ S1 []string }{
399					S1: []string{"string1"},
400				},
401			},
402			in2: &struct{ S *struct{ S1 []string } }{
403				S: &struct{ S1 []string }{
404					S1: []string{"string2"},
405				},
406			},
407			out: &struct{ S *struct{ S1 []string } }{
408				S: &struct{ S1 []string }{
409					S1: []string{"string2"},
410				},
411			},
412			order: Replace,
413		},
414		{
415			// Append slice of structs
416			in1: &struct{ S []struct{ F string } }{
417				S: []struct{ F string }{
418					{F: "foo"}, {F: "bar"},
419				},
420			},
421			in2: &struct{ S []struct{ F string } }{
422				S: []struct{ F string }{
423					{F: "baz"},
424				},
425			},
426			out: &struct{ S []struct{ F string } }{
427				S: []struct{ F string }{
428					{F: "foo"}, {F: "bar"}, {F: "baz"},
429				},
430			},
431			order: Append,
432		},
433		{
434			// Prepend slice of structs
435			in1: &struct{ S []struct{ F string } }{
436				S: []struct{ F string }{
437					{F: "foo"}, {F: "bar"},
438				},
439			},
440			in2: &struct{ S []struct{ F string } }{
441				S: []struct{ F string }{
442					{F: "baz"},
443				},
444			},
445			out: &struct{ S []struct{ F string } }{
446				S: []struct{ F string }{
447					{F: "baz"}, {F: "foo"}, {F: "bar"},
448				},
449			},
450			order: Prepend,
451		},
452		{
453			// Replace slice of structs
454			in1: &struct{ S []struct{ F string } }{
455				S: []struct{ F string }{
456					{F: "foo"}, {F: "bar"},
457				},
458			},
459			in2: &struct{ S []struct{ F string } }{
460				S: []struct{ F string }{
461					{F: "baz"},
462				},
463			},
464			out: &struct{ S []struct{ F string } }{
465				S: []struct{ F string }{
466					{F: "baz"},
467				},
468			},
469			order: Replace,
470		},
471		{
472			// Append pointer
473			in1: &struct{ S *struct{ S string } }{
474				S: &struct{ S string }{
475					S: "string1",
476				},
477			},
478			in2: &struct{ S *struct{ S string } }{
479				S: &struct{ S string }{
480					S: "string2",
481				},
482			},
483			out: &struct{ S *struct{ S string } }{
484				S: &struct{ S string }{
485					S: "string1string2",
486				},
487			},
488		},
489		{
490			// Prepend pointer
491			in1: &struct{ S *struct{ S string } }{
492				S: &struct{ S string }{
493					S: "string1",
494				},
495			},
496			in2: &struct{ S *struct{ S string } }{
497				S: &struct{ S string }{
498					S: "string2",
499				},
500			},
501			out: &struct{ S *struct{ S string } }{
502				S: &struct{ S string }{
503					S: "string2string1",
504				},
505			},
506			order: Prepend,
507		},
508		{
509			// Append interface
510			in1: &struct{ S interface{} }{
511				S: &struct{ S string }{
512					S: "string1",
513				},
514			},
515			in2: &struct{ S interface{} }{
516				S: &struct{ S string }{
517					S: "string2",
518				},
519			},
520			out: &struct{ S interface{} }{
521				S: &struct{ S string }{
522					S: "string1string2",
523				},
524			},
525		},
526		{
527			// Prepend interface
528			in1: &struct{ S interface{} }{
529				S: &struct{ S string }{
530					S: "string1",
531				},
532			},
533			in2: &struct{ S interface{} }{
534				S: &struct{ S string }{
535					S: "string2",
536				},
537			},
538			out: &struct{ S interface{} }{
539				S: &struct{ S string }{
540					S: "string2string1",
541				},
542			},
543			order: Prepend,
544		},
545		{
546			// Unexported field
547			in1: &struct{ s string }{
548				s: "string1",
549			},
550			in2: &struct{ s string }{
551				s: "string2",
552			},
553			out: &struct{ s string }{
554				s: "string1",
555			},
556		},
557		{
558			// Unexported field
559			in1: &struct{ i *int64 }{
560				i: Int64Ptr(33),
561			},
562			in2: &struct{ i *int64 }{
563				i: Int64Ptr(5),
564			},
565			out: &struct{ i *int64 }{
566				i: Int64Ptr(33),
567			},
568		},
569		{
570			// Empty struct
571			in1: &struct{}{},
572			in2: &struct{}{},
573			out: &struct{}{},
574		},
575		{
576			// Interface nil
577			in1: &struct{ S interface{} }{
578				S: nil,
579			},
580			in2: &struct{ S interface{} }{
581				S: nil,
582			},
583			out: &struct{ S interface{} }{
584				S: nil,
585			},
586		},
587		{
588			// Pointer nil
589			in1: &struct{ S *struct{} }{
590				S: nil,
591			},
592			in2: &struct{ S *struct{} }{
593				S: nil,
594			},
595			out: &struct{ S *struct{} }{
596				S: nil,
597			},
598		},
599		{
600			// Anonymous struct
601			in1: &struct {
602				EmbeddedStruct
603				Nested struct{ EmbeddedStruct }
604			}{
605				EmbeddedStruct: EmbeddedStruct{
606					S: "string1",
607					I: Int64Ptr(55),
608				},
609				Nested: struct{ EmbeddedStruct }{
610					EmbeddedStruct: EmbeddedStruct{
611						S: "string2",
612						I: Int64Ptr(-4),
613					},
614				},
615			},
616			in2: &struct {
617				EmbeddedStruct
618				Nested struct{ EmbeddedStruct }
619			}{
620				EmbeddedStruct: EmbeddedStruct{
621					S: "string3",
622					I: Int64Ptr(66),
623				},
624				Nested: struct{ EmbeddedStruct }{
625					EmbeddedStruct: EmbeddedStruct{
626						S: "string4",
627						I: Int64Ptr(-8),
628					},
629				},
630			},
631			out: &struct {
632				EmbeddedStruct
633				Nested struct{ EmbeddedStruct }
634			}{
635				EmbeddedStruct: EmbeddedStruct{
636					S: "string1string3",
637					I: Int64Ptr(66),
638				},
639				Nested: struct{ EmbeddedStruct }{
640					EmbeddedStruct: EmbeddedStruct{
641						S: "string2string4",
642						I: Int64Ptr(-8),
643					},
644				},
645			},
646		},
647		{
648			// Anonymous interface
649			in1: &struct {
650				EmbeddedInterface
651				Nested struct{ EmbeddedInterface }
652			}{
653				EmbeddedInterface: &struct {
654					S string
655					I *int64
656				}{
657					S: "string1",
658					I: Int64Ptr(-8),
659				},
660				Nested: struct{ EmbeddedInterface }{
661					EmbeddedInterface: &struct {
662						S string
663						I *int64
664					}{
665						S: "string2",
666						I: Int64Ptr(55),
667					},
668				},
669			},
670			in2: &struct {
671				EmbeddedInterface
672				Nested struct{ EmbeddedInterface }
673			}{
674				EmbeddedInterface: &struct {
675					S string
676					I *int64
677				}{
678					S: "string3",
679					I: Int64Ptr(6),
680				},
681				Nested: struct{ EmbeddedInterface }{
682					EmbeddedInterface: &struct {
683						S string
684						I *int64
685					}{
686						S: "string4",
687						I: Int64Ptr(6),
688					},
689				},
690			},
691			out: &struct {
692				EmbeddedInterface
693				Nested struct{ EmbeddedInterface }
694			}{
695				EmbeddedInterface: &struct {
696					S string
697					I *int64
698				}{
699					S: "string1string3",
700					I: Int64Ptr(6),
701				},
702				Nested: struct{ EmbeddedInterface }{
703					EmbeddedInterface: &struct {
704						S string
705						I *int64
706					}{
707						S: "string2string4",
708						I: Int64Ptr(6),
709					},
710				},
711			},
712		},
713		{
714			// Nil pointer to a struct
715			in1: &struct {
716				Nested *struct {
717					S string
718				}
719			}{},
720			in2: &struct {
721				Nested *struct {
722					S string
723				}
724			}{
725				Nested: &struct {
726					S string
727				}{
728					S: "string",
729				},
730			},
731			out: &struct {
732				Nested *struct {
733					S string
734				}
735			}{
736				Nested: &struct {
737					S string
738				}{
739					S: "string",
740				},
741			},
742		},
743		{
744			// Nil pointer to a struct in an interface
745			in1: &struct {
746				Nested interface{}
747			}{
748				Nested: (*struct{ S string })(nil),
749			},
750			in2: &struct {
751				Nested interface{}
752			}{
753				Nested: &struct {
754					S string
755				}{
756					S: "string",
757				},
758			},
759			out: &struct {
760				Nested interface{}
761			}{
762				Nested: &struct {
763					S string
764				}{
765					S: "string",
766				},
767			},
768		},
769		{
770			// Interface src nil
771			in1: &struct{ S interface{} }{
772				S: &struct{ S string }{
773					S: "string1",
774				},
775			},
776			in2: &struct{ S interface{} }{
777				S: nil,
778			},
779			out: &struct{ S interface{} }{
780				S: &struct{ S string }{
781					S: "string1",
782				},
783			},
784		},
785
786		// Errors
787
788		{
789			// Non-pointer in1
790			in1: struct{}{},
791			in2: &struct{}{},
792			err: errors.New("expected pointer to struct, got struct {}"),
793			out: struct{}{},
794		},
795		{
796			// Non-pointer in2
797			in1: &struct{}{},
798			in2: struct{}{},
799			err: errors.New("expected pointer to struct, got struct {}"),
800			out: &struct{}{},
801		},
802		{
803			// Non-struct in1
804			in1: &[]string{"bad"},
805			in2: &struct{}{},
806			err: errors.New("expected pointer to struct, got *[]string"),
807			out: &[]string{"bad"},
808		},
809		{
810			// Non-struct in2
811			in1: &struct{}{},
812			in2: &[]string{"bad"},
813			err: errors.New("expected pointer to struct, got *[]string"),
814			out: &struct{}{},
815		},
816		{
817			// Mismatched types
818			in1: &struct{ A string }{
819				A: "string1",
820			},
821			in2: &struct{ B string }{
822				B: "string2",
823			},
824			out: &struct{ A string }{
825				A: "string1",
826			},
827			err: errors.New("expected matching types for dst and src, got *struct { A string } and *struct { B string }"),
828		},
829		{
830			// Unsupported kind
831			in1: &struct{ I int }{
832				I: 1,
833			},
834			in2: &struct{ I int }{
835				I: 2,
836			},
837			out: &struct{ I int }{
838				I: 1,
839			},
840			err: extendPropertyErrorf("i", "unsupported kind int"),
841		},
842		{
843			// Unsupported kind
844			in1: &struct{ I int64 }{
845				I: 1,
846			},
847			in2: &struct{ I int64 }{
848				I: 2,
849			},
850			out: &struct{ I int64 }{
851				I: 1,
852			},
853			err: extendPropertyErrorf("i", "unsupported kind int64"),
854		},
855		{
856			// Interface nilitude mismatch
857			in1: &struct{ S interface{} }{
858				S: nil,
859			},
860			in2: &struct{ S interface{} }{
861				S: &struct{ S string }{
862					S: "string1",
863				},
864			},
865			out: &struct{ S interface{} }{
866				S: nil,
867			},
868			err: extendPropertyErrorf("s", "nilitude mismatch"),
869		},
870		{
871			// Interface type mismatch
872			in1: &struct{ S interface{} }{
873				S: &struct{ A string }{
874					A: "string1",
875				},
876			},
877			in2: &struct{ S interface{} }{
878				S: &struct{ B string }{
879					B: "string2",
880				},
881			},
882			out: &struct{ S interface{} }{
883				S: &struct{ A string }{
884					A: "string1",
885				},
886			},
887			err: extendPropertyErrorf("s", "mismatched types struct { A string } and struct { B string }"),
888		},
889		{
890			// Interface not a pointer
891			in1: &struct{ S interface{} }{
892				S: struct{ S string }{
893					S: "string1",
894				},
895			},
896			in2: &struct{ S interface{} }{
897				S: struct{ S string }{
898					S: "string2",
899				},
900			},
901			out: &struct{ S interface{} }{
902				S: struct{ S string }{
903					S: "string1",
904				},
905			},
906			err: extendPropertyErrorf("s", "interface not a pointer"),
907		},
908		{
909			// Pointer not a struct
910			in1: &struct{ S *[]string }{
911				S: &[]string{"string1"},
912			},
913			in2: &struct{ S *[]string }{
914				S: &[]string{"string2"},
915			},
916			out: &struct{ S *[]string }{
917				S: &[]string{"string1"},
918			},
919			err: extendPropertyErrorf("s", "pointer is a slice"),
920		},
921		{
922			// Error in nested struct
923			in1: &struct{ S interface{} }{
924				S: &struct{ I int }{
925					I: 1,
926				},
927			},
928			in2: &struct{ S interface{} }{
929				S: &struct{ I int }{
930					I: 2,
931				},
932			},
933			out: &struct{ S interface{} }{
934				S: &struct{ I int }{
935					I: 1,
936				},
937			},
938			err: extendPropertyErrorf("s.i", "unsupported kind int"),
939		},
940
941		// Filters
942
943		{
944			// Filter true
945			in1: &struct{ S string }{
946				S: "string1",
947			},
948			in2: &struct{ S string }{
949				S: "string2",
950			},
951			out: &struct{ S string }{
952				S: "string1string2",
953			},
954			filter: func(property string,
955				dstField, srcField reflect.StructField,
956				dstValue, srcValue interface{}) (bool, error) {
957				return true, nil
958			},
959		},
960		{
961			// Filter false
962			in1: &struct{ S string }{
963				S: "string1",
964			},
965			in2: &struct{ S string }{
966				S: "string2",
967			},
968			out: &struct{ S string }{
969				S: "string1",
970			},
971			filter: func(property string,
972				dstField, srcField reflect.StructField,
973				dstValue, srcValue interface{}) (bool, error) {
974				return false, nil
975			},
976		},
977		{
978			// Filter check args
979			in1: &struct{ S string }{
980				S: "string1",
981			},
982			in2: &struct{ S string }{
983				S: "string2",
984			},
985			out: &struct{ S string }{
986				S: "string1string2",
987			},
988			filter: func(property string,
989				dstField, srcField reflect.StructField,
990				dstValue, srcValue interface{}) (bool, error) {
991				return property == "s" &&
992					dstField.Name == "S" && srcField.Name == "S" &&
993					dstValue.(string) == "string1" && srcValue.(string) == "string2", nil
994			},
995		},
996		{
997			// Filter mutated
998			in1: &struct {
999				S string `blueprint:"mutated"`
1000			}{
1001				S: "string1",
1002			},
1003			in2: &struct {
1004				S string `blueprint:"mutated"`
1005			}{
1006				S: "string2",
1007			},
1008			out: &struct {
1009				S string `blueprint:"mutated"`
1010			}{
1011				S: "string1",
1012			},
1013		},
1014		{
1015			// Filter mutated
1016			in1: &struct {
1017				S *int64 `blueprint:"mutated"`
1018			}{
1019				S: Int64Ptr(4),
1020			},
1021			in2: &struct {
1022				S *int64 `blueprint:"mutated"`
1023			}{
1024				S: Int64Ptr(5),
1025			},
1026			out: &struct {
1027				S *int64 `blueprint:"mutated"`
1028			}{
1029				S: Int64Ptr(4),
1030			},
1031		},
1032		{
1033			// Filter error
1034			in1: &struct{ S string }{
1035				S: "string1",
1036			},
1037			in2: &struct{ S string }{
1038				S: "string2",
1039			},
1040			out: &struct{ S string }{
1041				S: "string1",
1042			},
1043			filter: func(property string,
1044				dstField, srcField reflect.StructField,
1045				dstValue, srcValue interface{}) (bool, error) {
1046				return true, fmt.Errorf("filter error")
1047			},
1048			err: extendPropertyErrorf("s", "filter error"),
1049		},
1050	}
1051}
1052
1053func TestAppendProperties(t *testing.T) {
1054	for _, testCase := range appendPropertiesTestCases() {
1055		testString := fmt.Sprintf("%v, %v -> %v", testCase.in1, testCase.in2, testCase.out)
1056
1057		got := testCase.in1
1058		var err error
1059		var testType string
1060
1061		switch testCase.order {
1062		case Append:
1063			testType = "append"
1064			err = AppendProperties(got, testCase.in2, testCase.filter)
1065		case Prepend:
1066			testType = "prepend"
1067			err = PrependProperties(got, testCase.in2, testCase.filter)
1068		case Replace:
1069			testType = "replace"
1070			err = ExtendProperties(got, testCase.in2, testCase.filter, OrderReplace)
1071		}
1072
1073		check(t, testType, testString, got, err, testCase.out, testCase.err)
1074	}
1075}
1076
1077func TestExtendProperties(t *testing.T) {
1078	for _, testCase := range appendPropertiesTestCases() {
1079		testString := fmt.Sprintf("%v, %v -> %v", testCase.in1, testCase.in2, testCase.out)
1080
1081		got := testCase.in1
1082		var err error
1083		var testType string
1084
1085		order := func(property string,
1086			dstField, srcField reflect.StructField,
1087			dstValue, srcValue interface{}) (Order, error) {
1088			switch testCase.order {
1089			case Append:
1090				return Append, nil
1091			case Prepend:
1092				return Prepend, nil
1093			case Replace:
1094				return Replace, nil
1095			}
1096			return Append, errors.New("unknown order")
1097		}
1098
1099		switch testCase.order {
1100		case Append:
1101			testType = "prepend"
1102		case Prepend:
1103			testType = "append"
1104		case Replace:
1105			testType = "replace"
1106		}
1107
1108		err = ExtendProperties(got, testCase.in2, testCase.filter, order)
1109
1110		check(t, testType, testString, got, err, testCase.out, testCase.err)
1111	}
1112}
1113
1114type appendMatchingPropertiesTestCase struct {
1115	in1    []interface{}
1116	in2    interface{}
1117	out    []interface{}
1118	order  Order // default is Append
1119	filter ExtendPropertyFilterFunc
1120	err    error
1121}
1122
1123func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
1124	return []appendMatchingPropertiesTestCase{
1125		{
1126			// Append strings
1127			in1: []interface{}{&struct{ S string }{
1128				S: "string1",
1129			}},
1130			in2: &struct{ S string }{
1131				S: "string2",
1132			},
1133			out: []interface{}{&struct{ S string }{
1134				S: "string1string2",
1135			}},
1136		},
1137		{
1138			// Prepend strings
1139			in1: []interface{}{&struct{ S string }{
1140				S: "string1",
1141			}},
1142			in2: &struct{ S string }{
1143				S: "string2",
1144			},
1145			out: []interface{}{&struct{ S string }{
1146				S: "string2string1",
1147			}},
1148			order: Prepend,
1149		},
1150		{
1151			// Append all
1152			in1: []interface{}{
1153				&struct{ S, A string }{
1154					S: "string1",
1155				},
1156				&struct{ S, B string }{
1157					S: "string2",
1158				},
1159			},
1160			in2: &struct{ S string }{
1161				S: "string3",
1162			},
1163			out: []interface{}{
1164				&struct{ S, A string }{
1165					S: "string1string3",
1166				},
1167				&struct{ S, B string }{
1168					S: "string2string3",
1169				},
1170			},
1171		},
1172		{
1173			// Append some
1174			in1: []interface{}{
1175				&struct{ S, A string }{
1176					S: "string1",
1177				},
1178				&struct{ B string }{},
1179			},
1180			in2: &struct{ S string }{
1181				S: "string2",
1182			},
1183			out: []interface{}{
1184				&struct{ S, A string }{
1185					S: "string1string2",
1186				},
1187				&struct{ B string }{},
1188			},
1189		},
1190		{
1191			// Append mismatched structs
1192			in1: []interface{}{&struct{ S, A string }{
1193				S: "string1",
1194			}},
1195			in2: &struct{ S string }{
1196				S: "string2",
1197			},
1198			out: []interface{}{&struct{ S, A string }{
1199				S: "string1string2",
1200			}},
1201		},
1202		{
1203			// Append mismatched pointer structs
1204			in1: []interface{}{&struct{ S *struct{ S, A string } }{
1205				S: &struct{ S, A string }{
1206					S: "string1",
1207				},
1208			}},
1209			in2: &struct{ S *struct{ S string } }{
1210				S: &struct{ S string }{
1211					S: "string2",
1212				},
1213			},
1214			out: []interface{}{&struct{ S *struct{ S, A string } }{
1215				S: &struct{ S, A string }{
1216					S: "string1string2",
1217				},
1218			}},
1219		},
1220		{
1221			// Append through mismatched types
1222			in1: []interface{}{
1223				&struct{ B string }{},
1224				&struct{ S interface{} }{
1225					S: &struct{ S, A string }{
1226						S: "string1",
1227					},
1228				},
1229			},
1230			in2: &struct{ S struct{ S string } }{
1231				S: struct{ S string }{
1232					S: "string2",
1233				},
1234			},
1235			out: []interface{}{
1236				&struct{ B string }{},
1237				&struct{ S interface{} }{
1238					S: &struct{ S, A string }{
1239						S: "string1string2",
1240					},
1241				},
1242			},
1243		},
1244		{
1245			// Append through mismatched types and nil
1246			in1: []interface{}{
1247				&struct{ B string }{},
1248				&struct{ S interface{} }{
1249					S: (*struct{ S, A string })(nil),
1250				},
1251			},
1252			in2: &struct{ S struct{ S string } }{
1253				S: struct{ S string }{
1254					S: "string2",
1255				},
1256			},
1257			out: []interface{}{
1258				&struct{ B string }{},
1259				&struct{ S interface{} }{
1260					S: &struct{ S, A string }{
1261						S: "string2",
1262					},
1263				},
1264			},
1265		},
1266		{
1267			// Append through multiple matches
1268			in1: []interface{}{
1269				&struct {
1270					S struct{ S, A string }
1271				}{
1272					S: struct{ S, A string }{
1273						S: "string1",
1274					},
1275				},
1276				&struct {
1277					S struct{ S, B string }
1278				}{
1279					S: struct{ S, B string }{
1280						S: "string2",
1281					},
1282				},
1283			},
1284			in2: &struct{ S struct{ B string } }{
1285				S: struct{ B string }{
1286					B: "string3",
1287				},
1288			},
1289			out: []interface{}{
1290				&struct {
1291					S struct{ S, A string }
1292				}{
1293					S: struct{ S, A string }{
1294						S: "string1",
1295					},
1296				},
1297				&struct {
1298					S struct{ S, B string }
1299				}{
1300					S: struct{ S, B string }{
1301						S: "string2",
1302						B: "string3",
1303					},
1304				},
1305			},
1306		},
1307
1308		// Errors
1309
1310		{
1311			// Non-pointer in1
1312			in1: []interface{}{struct{}{}},
1313			in2: &struct{}{},
1314			err: errors.New("expected pointer to struct, got struct {}"),
1315			out: []interface{}{struct{}{}},
1316		},
1317		{
1318			// Non-pointer in2
1319			in1: []interface{}{&struct{}{}},
1320			in2: struct{}{},
1321			err: errors.New("expected pointer to struct, got struct {}"),
1322			out: []interface{}{&struct{}{}},
1323		},
1324		{
1325			// Non-struct in1
1326			in1: []interface{}{&[]string{"bad"}},
1327			in2: &struct{}{},
1328			err: errors.New("expected pointer to struct, got *[]string"),
1329			out: []interface{}{&[]string{"bad"}},
1330		},
1331		{
1332			// Non-struct in2
1333			in1: []interface{}{&struct{}{}},
1334			in2: &[]string{"bad"},
1335			err: errors.New("expected pointer to struct, got *[]string"),
1336			out: []interface{}{&struct{}{}},
1337		},
1338		{
1339			// Append none
1340			in1: []interface{}{
1341				&struct{ A string }{},
1342				&struct{ B string }{},
1343			},
1344			in2: &struct{ S string }{
1345				S: "string1",
1346			},
1347			out: []interface{}{
1348				&struct{ A string }{},
1349				&struct{ B string }{},
1350			},
1351			err: extendPropertyErrorf("s", "failed to find property to extend"),
1352		},
1353		{
1354			// Append mismatched kinds
1355			in1: []interface{}{
1356				&struct{ S string }{
1357					S: "string1",
1358				},
1359			},
1360			in2: &struct{ S []string }{
1361				S: []string{"string2"},
1362			},
1363			out: []interface{}{
1364				&struct{ S string }{
1365					S: "string1",
1366				},
1367			},
1368			err: extendPropertyErrorf("s", "mismatched types string and []string"),
1369		},
1370		{
1371			// Append mismatched types
1372			in1: []interface{}{
1373				&struct{ S []int }{
1374					S: []int{1},
1375				},
1376			},
1377			in2: &struct{ S []string }{
1378				S: []string{"string2"},
1379			},
1380			out: []interface{}{
1381				&struct{ S []int }{
1382					S: []int{1},
1383				},
1384			},
1385			err: extendPropertyErrorf("s", "mismatched types []int and []string"),
1386		},
1387	}
1388}
1389
1390func TestAppendMatchingProperties(t *testing.T) {
1391	for _, testCase := range appendMatchingPropertiesTestCases() {
1392		testString := fmt.Sprintf("%s, %s -> %s", p(testCase.in1), p(testCase.in2), p(testCase.out))
1393
1394		got := testCase.in1
1395		var err error
1396		var testType string
1397
1398		switch testCase.order {
1399		case Append:
1400			testType = "append"
1401			err = AppendMatchingProperties(got, testCase.in2, testCase.filter)
1402		case Prepend:
1403			testType = "prepend"
1404			err = PrependMatchingProperties(got, testCase.in2, testCase.filter)
1405		case Replace:
1406			testType = "replace"
1407			err = ExtendMatchingProperties(got, testCase.in2, testCase.filter, OrderReplace)
1408		}
1409
1410		check(t, testType, testString, got, err, testCase.out, testCase.err)
1411	}
1412}
1413
1414func TestExtendMatchingProperties(t *testing.T) {
1415	for _, testCase := range appendMatchingPropertiesTestCases() {
1416		testString := fmt.Sprintf("%s, %s -> %s", p(testCase.in1), p(testCase.in2), p(testCase.out))
1417
1418		got := testCase.in1
1419		var err error
1420		var testType string
1421
1422		order := func(property string,
1423			dstField, srcField reflect.StructField,
1424			dstValue, srcValue interface{}) (Order, error) {
1425			switch testCase.order {
1426			case Append:
1427				return Append, nil
1428			case Prepend:
1429				return Prepend, nil
1430			case Replace:
1431				return Replace, nil
1432			}
1433			return Append, errors.New("unknown order")
1434		}
1435
1436		switch testCase.order {
1437		case Append:
1438			testType = "prepend matching"
1439		case Prepend:
1440			testType = "append matching"
1441		case Replace:
1442			testType = "replace matching"
1443		}
1444
1445		err = ExtendMatchingProperties(got, testCase.in2, testCase.filter, order)
1446
1447		check(t, testType, testString, got, err, testCase.out, testCase.err)
1448	}
1449}
1450
1451func check(t *testing.T, testType, testString string,
1452	got interface{}, err error,
1453	expected interface{}, expectedErr error) {
1454
1455	printedTestCase := false
1456	e := func(s string, expected, got interface{}) {
1457		if !printedTestCase {
1458			t.Errorf("test case %s: %s", testType, testString)
1459			printedTestCase = true
1460		}
1461		t.Errorf("incorrect %s", s)
1462		t.Errorf("  expected: %s", p(expected))
1463		t.Errorf("       got: %s", p(got))
1464	}
1465
1466	if err != nil {
1467		if expectedErr != nil {
1468			if err.Error() != expectedErr.Error() {
1469				e("unexpected error", expectedErr.Error(), err.Error())
1470			}
1471		} else {
1472			e("unexpected error", nil, err.Error())
1473		}
1474	} else {
1475		if expectedErr != nil {
1476			e("missing error", expectedErr, nil)
1477		}
1478	}
1479
1480	if !reflect.DeepEqual(expected, got) {
1481		e("output:", expected, got)
1482	}
1483}
1484
1485func p(in interface{}) string {
1486	if v, ok := in.([]interface{}); ok {
1487		s := make([]string, len(v))
1488		for i := range v {
1489			s[i] = fmt.Sprintf("%#v", v[i])
1490		}
1491		return "[" + strings.Join(s, ", ") + "]"
1492	} else {
1493		return fmt.Sprintf("%#v", in)
1494	}
1495}
1496