• 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	prepend bool
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			prepend: true,
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			prepend: true,
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			prepend: true,
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			prepend: true,
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			prepend: true,
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			prepend: true,
290		},
291		{
292			// Append empty slice
293			in1: &struct{ S1, S2 []string }{
294				S1: []string{"string1"},
295				S2: []string{},
296			},
297			in2: &struct{ S1, S2 []string }{
298				S1: []string{},
299				S2: []string{"string2"},
300			},
301			out: &struct{ S1, S2 []string }{
302				S1: []string{"string1"},
303				S2: []string{"string2"},
304			},
305		},
306		{
307			// Prepend empty slice
308			in1: &struct{ S1, S2 []string }{
309				S1: []string{"string1"},
310				S2: []string{},
311			},
312			in2: &struct{ S1, S2 []string }{
313				S1: []string{},
314				S2: []string{"string2"},
315			},
316			out: &struct{ S1, S2 []string }{
317				S1: []string{"string1"},
318				S2: []string{"string2"},
319			},
320			prepend: true,
321		},
322		{
323			// Append nil slice
324			in1: &struct{ S1, S2, S3 []string }{
325				S1: []string{"string1"},
326			},
327			in2: &struct{ S1, S2, S3 []string }{
328				S2: []string{"string2"},
329			},
330			out: &struct{ S1, S2, S3 []string }{
331				S1: []string{"string1"},
332				S2: []string{"string2"},
333				S3: nil,
334			},
335		},
336		{
337			// Prepend nil slice
338			in1: &struct{ S1, S2, S3 []string }{
339				S1: []string{"string1"},
340			},
341			in2: &struct{ S1, S2, S3 []string }{
342				S2: []string{"string2"},
343			},
344			out: &struct{ S1, S2, S3 []string }{
345				S1: []string{"string1"},
346				S2: []string{"string2"},
347				S3: nil,
348			},
349			prepend: true,
350		},
351		{
352			// Append pointer
353			in1: &struct{ S *struct{ S string } }{
354				S: &struct{ S string }{
355					S: "string1",
356				},
357			},
358			in2: &struct{ S *struct{ S string } }{
359				S: &struct{ S string }{
360					S: "string2",
361				},
362			},
363			out: &struct{ S *struct{ S string } }{
364				S: &struct{ S string }{
365					S: "string1string2",
366				},
367			},
368		},
369		{
370			// Prepend pointer
371			in1: &struct{ S *struct{ S string } }{
372				S: &struct{ S string }{
373					S: "string1",
374				},
375			},
376			in2: &struct{ S *struct{ S string } }{
377				S: &struct{ S string }{
378					S: "string2",
379				},
380			},
381			out: &struct{ S *struct{ S string } }{
382				S: &struct{ S string }{
383					S: "string2string1",
384				},
385			},
386			prepend: true,
387		},
388		{
389			// Append interface
390			in1: &struct{ S interface{} }{
391				S: &struct{ S string }{
392					S: "string1",
393				},
394			},
395			in2: &struct{ S interface{} }{
396				S: &struct{ S string }{
397					S: "string2",
398				},
399			},
400			out: &struct{ S interface{} }{
401				S: &struct{ S string }{
402					S: "string1string2",
403				},
404			},
405		},
406		{
407			// Prepend interface
408			in1: &struct{ S interface{} }{
409				S: &struct{ S string }{
410					S: "string1",
411				},
412			},
413			in2: &struct{ S interface{} }{
414				S: &struct{ S string }{
415					S: "string2",
416				},
417			},
418			out: &struct{ S interface{} }{
419				S: &struct{ S string }{
420					S: "string2string1",
421				},
422			},
423			prepend: true,
424		},
425		{
426			// Unexported field
427			in1: &struct{ s string }{
428				s: "string1",
429			},
430			in2: &struct{ s string }{
431				s: "string2",
432			},
433			out: &struct{ s string }{
434				s: "string1",
435			},
436		},
437		{
438			// Unexported field
439			in1: &struct{ i *int64 }{
440				i: Int64Ptr(33),
441			},
442			in2: &struct{ i *int64 }{
443				i: Int64Ptr(5),
444			},
445			out: &struct{ i *int64 }{
446				i: Int64Ptr(33),
447			},
448		},
449		{
450			// Empty struct
451			in1: &struct{}{},
452			in2: &struct{}{},
453			out: &struct{}{},
454		},
455		{
456			// Interface nil
457			in1: &struct{ S interface{} }{
458				S: nil,
459			},
460			in2: &struct{ S interface{} }{
461				S: nil,
462			},
463			out: &struct{ S interface{} }{
464				S: nil,
465			},
466		},
467		{
468			// Pointer nil
469			in1: &struct{ S *struct{} }{
470				S: nil,
471			},
472			in2: &struct{ S *struct{} }{
473				S: nil,
474			},
475			out: &struct{ S *struct{} }{
476				S: nil,
477			},
478		},
479		{
480			// Anonymous struct
481			in1: &struct {
482				EmbeddedStruct
483				Nested struct{ EmbeddedStruct }
484			}{
485				EmbeddedStruct: EmbeddedStruct{
486					S: "string1",
487					I: Int64Ptr(55),
488				},
489				Nested: struct{ EmbeddedStruct }{
490					EmbeddedStruct: EmbeddedStruct{
491						S: "string2",
492						I: Int64Ptr(-4),
493					},
494				},
495			},
496			in2: &struct {
497				EmbeddedStruct
498				Nested struct{ EmbeddedStruct }
499			}{
500				EmbeddedStruct: EmbeddedStruct{
501					S: "string3",
502					I: Int64Ptr(66),
503				},
504				Nested: struct{ EmbeddedStruct }{
505					EmbeddedStruct: EmbeddedStruct{
506						S: "string4",
507						I: Int64Ptr(-8),
508					},
509				},
510			},
511			out: &struct {
512				EmbeddedStruct
513				Nested struct{ EmbeddedStruct }
514			}{
515				EmbeddedStruct: EmbeddedStruct{
516					S: "string1string3",
517					I: Int64Ptr(66),
518				},
519				Nested: struct{ EmbeddedStruct }{
520					EmbeddedStruct: EmbeddedStruct{
521						S: "string2string4",
522						I: Int64Ptr(-8),
523					},
524				},
525			},
526		},
527		{
528			// Anonymous interface
529			in1: &struct {
530				EmbeddedInterface
531				Nested struct{ EmbeddedInterface }
532			}{
533				EmbeddedInterface: &struct {
534					S string
535					I *int64
536				}{
537					S: "string1",
538					I: Int64Ptr(-8),
539				},
540				Nested: struct{ EmbeddedInterface }{
541					EmbeddedInterface: &struct {
542						S string
543						I *int64
544					}{
545						S: "string2",
546						I: Int64Ptr(55),
547					},
548				},
549			},
550			in2: &struct {
551				EmbeddedInterface
552				Nested struct{ EmbeddedInterface }
553			}{
554				EmbeddedInterface: &struct {
555					S string
556					I *int64
557				}{
558					S: "string3",
559					I: Int64Ptr(6),
560				},
561				Nested: struct{ EmbeddedInterface }{
562					EmbeddedInterface: &struct {
563						S string
564						I *int64
565					}{
566						S: "string4",
567						I: Int64Ptr(6),
568					},
569				},
570			},
571			out: &struct {
572				EmbeddedInterface
573				Nested struct{ EmbeddedInterface }
574			}{
575				EmbeddedInterface: &struct {
576					S string
577					I *int64
578				}{
579					S: "string1string3",
580					I: Int64Ptr(6),
581				},
582				Nested: struct{ EmbeddedInterface }{
583					EmbeddedInterface: &struct {
584						S string
585						I *int64
586					}{
587						S: "string2string4",
588						I: Int64Ptr(6),
589					},
590				},
591			},
592		},
593		{
594			// Nil pointer to a struct
595			in1: &struct {
596				Nested *struct {
597					S string
598				}
599			}{},
600			in2: &struct {
601				Nested *struct {
602					S string
603				}
604			}{
605				Nested: &struct {
606					S string
607				}{
608					S: "string",
609				},
610			},
611			out: &struct {
612				Nested *struct {
613					S string
614				}
615			}{
616				Nested: &struct {
617					S string
618				}{
619					S: "string",
620				},
621			},
622		},
623		{
624			// Nil pointer to a struct in an interface
625			in1: &struct {
626				Nested interface{}
627			}{
628				Nested: (*struct{ S string })(nil),
629			},
630			in2: &struct {
631				Nested interface{}
632			}{
633				Nested: &struct {
634					S string
635				}{
636					S: "string",
637				},
638			},
639			out: &struct {
640				Nested interface{}
641			}{
642				Nested: &struct {
643					S string
644				}{
645					S: "string",
646				},
647			},
648		},
649		{
650			// Interface src nil
651			in1: &struct{ S interface{} }{
652				S: &struct{ S string }{
653					S: "string1",
654				},
655			},
656			in2: &struct{ S interface{} }{
657				S: nil,
658			},
659			out: &struct{ S interface{} }{
660				S: &struct{ S string }{
661					S: "string1",
662				},
663			},
664		},
665
666		// Errors
667
668		{
669			// Non-pointer in1
670			in1: struct{}{},
671			in2: &struct{}{},
672			err: errors.New("expected pointer to struct, got struct {}"),
673			out: struct{}{},
674		},
675		{
676			// Non-pointer in2
677			in1: &struct{}{},
678			in2: struct{}{},
679			err: errors.New("expected pointer to struct, got struct {}"),
680			out: &struct{}{},
681		},
682		{
683			// Non-struct in1
684			in1: &[]string{"bad"},
685			in2: &struct{}{},
686			err: errors.New("expected pointer to struct, got *[]string"),
687			out: &[]string{"bad"},
688		},
689		{
690			// Non-struct in2
691			in1: &struct{}{},
692			in2: &[]string{"bad"},
693			err: errors.New("expected pointer to struct, got *[]string"),
694			out: &struct{}{},
695		},
696		{
697			// Mismatched types
698			in1: &struct{ A string }{
699				A: "string1",
700			},
701			in2: &struct{ B string }{
702				B: "string2",
703			},
704			out: &struct{ A string }{
705				A: "string1",
706			},
707			err: errors.New("expected matching types for dst and src, got *struct { A string } and *struct { B string }"),
708		},
709		{
710			// Unsupported kind
711			in1: &struct{ I int }{
712				I: 1,
713			},
714			in2: &struct{ I int }{
715				I: 2,
716			},
717			out: &struct{ I int }{
718				I: 1,
719			},
720			err: extendPropertyErrorf("i", "unsupported kind int"),
721		},
722		{
723			// Unsupported kind
724			in1: &struct{ I int64 }{
725				I: 1,
726			},
727			in2: &struct{ I int64 }{
728				I: 2,
729			},
730			out: &struct{ I int64 }{
731				I: 1,
732			},
733			err: extendPropertyErrorf("i", "unsupported kind int64"),
734		},
735		{
736			// Interface nilitude mismatch
737			in1: &struct{ S interface{} }{
738				S: nil,
739			},
740			in2: &struct{ S interface{} }{
741				S: &struct{ S string }{
742					S: "string1",
743				},
744			},
745			out: &struct{ S interface{} }{
746				S: nil,
747			},
748			err: extendPropertyErrorf("s", "nilitude mismatch"),
749		},
750		{
751			// Interface type mismatch
752			in1: &struct{ S interface{} }{
753				S: &struct{ A string }{
754					A: "string1",
755				},
756			},
757			in2: &struct{ S interface{} }{
758				S: &struct{ B string }{
759					B: "string2",
760				},
761			},
762			out: &struct{ S interface{} }{
763				S: &struct{ A string }{
764					A: "string1",
765				},
766			},
767			err: extendPropertyErrorf("s", "mismatched types struct { A string } and struct { B string }"),
768		},
769		{
770			// Interface not a pointer
771			in1: &struct{ S interface{} }{
772				S: struct{ S string }{
773					S: "string1",
774				},
775			},
776			in2: &struct{ S interface{} }{
777				S: struct{ S string }{
778					S: "string2",
779				},
780			},
781			out: &struct{ S interface{} }{
782				S: struct{ S string }{
783					S: "string1",
784				},
785			},
786			err: extendPropertyErrorf("s", "interface not a pointer"),
787		},
788		{
789			// Pointer not a struct
790			in1: &struct{ S *[]string }{
791				S: &[]string{"string1"},
792			},
793			in2: &struct{ S *[]string }{
794				S: &[]string{"string2"},
795			},
796			out: &struct{ S *[]string }{
797				S: &[]string{"string1"},
798			},
799			err: extendPropertyErrorf("s", "pointer is a slice"),
800		},
801		{
802			// Error in nested struct
803			in1: &struct{ S interface{} }{
804				S: &struct{ I int }{
805					I: 1,
806				},
807			},
808			in2: &struct{ S interface{} }{
809				S: &struct{ I int }{
810					I: 2,
811				},
812			},
813			out: &struct{ S interface{} }{
814				S: &struct{ I int }{
815					I: 1,
816				},
817			},
818			err: extendPropertyErrorf("s.i", "unsupported kind int"),
819		},
820
821		// Filters
822
823		{
824			// Filter true
825			in1: &struct{ S string }{
826				S: "string1",
827			},
828			in2: &struct{ S string }{
829				S: "string2",
830			},
831			out: &struct{ S string }{
832				S: "string1string2",
833			},
834			filter: func(property string,
835				dstField, srcField reflect.StructField,
836				dstValue, srcValue interface{}) (bool, error) {
837				return true, nil
838			},
839		},
840		{
841			// Filter false
842			in1: &struct{ S string }{
843				S: "string1",
844			},
845			in2: &struct{ S string }{
846				S: "string2",
847			},
848			out: &struct{ S string }{
849				S: "string1",
850			},
851			filter: func(property string,
852				dstField, srcField reflect.StructField,
853				dstValue, srcValue interface{}) (bool, error) {
854				return false, nil
855			},
856		},
857		{
858			// Filter check args
859			in1: &struct{ S string }{
860				S: "string1",
861			},
862			in2: &struct{ S string }{
863				S: "string2",
864			},
865			out: &struct{ S string }{
866				S: "string1string2",
867			},
868			filter: func(property string,
869				dstField, srcField reflect.StructField,
870				dstValue, srcValue interface{}) (bool, error) {
871				return property == "s" &&
872					dstField.Name == "S" && srcField.Name == "S" &&
873					dstValue.(string) == "string1" && srcValue.(string) == "string2", nil
874			},
875		},
876		{
877			// Filter mutated
878			in1: &struct {
879				S string `blueprint:"mutated"`
880			}{
881				S: "string1",
882			},
883			in2: &struct {
884				S string `blueprint:"mutated"`
885			}{
886				S: "string2",
887			},
888			out: &struct {
889				S string `blueprint:"mutated"`
890			}{
891				S: "string1",
892			},
893		},
894		{
895			// Filter mutated
896			in1: &struct {
897				S *int64 `blueprint:"mutated"`
898			}{
899				S: Int64Ptr(4),
900			},
901			in2: &struct {
902				S *int64 `blueprint:"mutated"`
903			}{
904				S: Int64Ptr(5),
905			},
906			out: &struct {
907				S *int64 `blueprint:"mutated"`
908			}{
909				S: Int64Ptr(4),
910			},
911		},
912		{
913			// Filter error
914			in1: &struct{ S string }{
915				S: "string1",
916			},
917			in2: &struct{ S string }{
918				S: "string2",
919			},
920			out: &struct{ S string }{
921				S: "string1",
922			},
923			filter: func(property string,
924				dstField, srcField reflect.StructField,
925				dstValue, srcValue interface{}) (bool, error) {
926				return true, fmt.Errorf("filter error")
927			},
928			err: extendPropertyErrorf("s", "filter error"),
929		},
930	}
931}
932
933func TestAppendProperties(t *testing.T) {
934	for _, testCase := range appendPropertiesTestCases() {
935		testString := fmt.Sprintf("%v, %v -> %v", testCase.in1, testCase.in2, testCase.out)
936
937		got := testCase.in1
938		var err error
939		var testType string
940
941		if testCase.prepend {
942			testType = "prepend"
943			err = PrependProperties(got, testCase.in2, testCase.filter)
944		} else {
945			testType = "append"
946			err = AppendProperties(got, testCase.in2, testCase.filter)
947		}
948
949		check(t, testType, testString, got, err, testCase.out, testCase.err)
950	}
951}
952
953func TestExtendProperties(t *testing.T) {
954	for _, testCase := range appendPropertiesTestCases() {
955		testString := fmt.Sprintf("%v, %v -> %v", testCase.in1, testCase.in2, testCase.out)
956
957		got := testCase.in1
958		var err error
959		var testType string
960
961		order := func(property string,
962			dstField, srcField reflect.StructField,
963			dstValue, srcValue interface{}) (Order, error) {
964			if testCase.prepend {
965				return Prepend, nil
966			} else {
967				return Append, nil
968			}
969		}
970
971		if testCase.prepend {
972			testType = "prepend"
973		} else {
974			testType = "append"
975		}
976
977		err = ExtendProperties(got, testCase.in2, testCase.filter, order)
978
979		check(t, testType, testString, got, err, testCase.out, testCase.err)
980	}
981}
982
983type appendMatchingPropertiesTestCase struct {
984	in1     []interface{}
985	in2     interface{}
986	out     []interface{}
987	prepend bool
988	filter  ExtendPropertyFilterFunc
989	err     error
990}
991
992func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
993	return []appendMatchingPropertiesTestCase{
994		{
995			// Append strings
996			in1: []interface{}{&struct{ S string }{
997				S: "string1",
998			}},
999			in2: &struct{ S string }{
1000				S: "string2",
1001			},
1002			out: []interface{}{&struct{ S string }{
1003				S: "string1string2",
1004			}},
1005		},
1006		{
1007			// Prepend strings
1008			in1: []interface{}{&struct{ S string }{
1009				S: "string1",
1010			}},
1011			in2: &struct{ S string }{
1012				S: "string2",
1013			},
1014			out: []interface{}{&struct{ S string }{
1015				S: "string2string1",
1016			}},
1017			prepend: true,
1018		},
1019		{
1020			// Append all
1021			in1: []interface{}{
1022				&struct{ S, A string }{
1023					S: "string1",
1024				},
1025				&struct{ S, B string }{
1026					S: "string2",
1027				},
1028			},
1029			in2: &struct{ S string }{
1030				S: "string3",
1031			},
1032			out: []interface{}{
1033				&struct{ S, A string }{
1034					S: "string1string3",
1035				},
1036				&struct{ S, B string }{
1037					S: "string2string3",
1038				},
1039			},
1040		},
1041		{
1042			// Append some
1043			in1: []interface{}{
1044				&struct{ S, A string }{
1045					S: "string1",
1046				},
1047				&struct{ B string }{},
1048			},
1049			in2: &struct{ S string }{
1050				S: "string2",
1051			},
1052			out: []interface{}{
1053				&struct{ S, A string }{
1054					S: "string1string2",
1055				},
1056				&struct{ B string }{},
1057			},
1058		},
1059		{
1060			// Append mismatched structs
1061			in1: []interface{}{&struct{ S, A string }{
1062				S: "string1",
1063			}},
1064			in2: &struct{ S string }{
1065				S: "string2",
1066			},
1067			out: []interface{}{&struct{ S, A string }{
1068				S: "string1string2",
1069			}},
1070		},
1071		{
1072			// Append mismatched pointer structs
1073			in1: []interface{}{&struct{ S *struct{ S, A string } }{
1074				S: &struct{ S, A string }{
1075					S: "string1",
1076				},
1077			}},
1078			in2: &struct{ S *struct{ S string } }{
1079				S: &struct{ S string }{
1080					S: "string2",
1081				},
1082			},
1083			out: []interface{}{&struct{ S *struct{ S, A string } }{
1084				S: &struct{ S, A string }{
1085					S: "string1string2",
1086				},
1087			}},
1088		},
1089		{
1090			// Append through mismatched types
1091			in1: []interface{}{
1092				&struct{ B string }{},
1093				&struct{ S interface{} }{
1094					S: &struct{ S, A string }{
1095						S: "string1",
1096					},
1097				},
1098			},
1099			in2: &struct{ S struct{ S string } }{
1100				S: struct{ S string }{
1101					S: "string2",
1102				},
1103			},
1104			out: []interface{}{
1105				&struct{ B string }{},
1106				&struct{ S interface{} }{
1107					S: &struct{ S, A string }{
1108						S: "string1string2",
1109					},
1110				},
1111			},
1112		},
1113		{
1114			// Append through mismatched types and nil
1115			in1: []interface{}{
1116				&struct{ B string }{},
1117				&struct{ S interface{} }{
1118					S: (*struct{ S, A string })(nil),
1119				},
1120			},
1121			in2: &struct{ S struct{ S string } }{
1122				S: struct{ S string }{
1123					S: "string2",
1124				},
1125			},
1126			out: []interface{}{
1127				&struct{ B string }{},
1128				&struct{ S interface{} }{
1129					S: &struct{ S, A string }{
1130						S: "string2",
1131					},
1132				},
1133			},
1134		},
1135		{
1136			// Append through multiple matches
1137			in1: []interface{}{
1138				&struct {
1139					S struct{ S, A string }
1140				}{
1141					S: struct{ S, A string }{
1142						S: "string1",
1143					},
1144				},
1145				&struct {
1146					S struct{ S, B string }
1147				}{
1148					S: struct{ S, B string }{
1149						S: "string2",
1150					},
1151				},
1152			},
1153			in2: &struct{ S struct{ B string } }{
1154				S: struct{ B string }{
1155					B: "string3",
1156				},
1157			},
1158			out: []interface{}{
1159				&struct {
1160					S struct{ S, A string }
1161				}{
1162					S: struct{ S, A string }{
1163						S: "string1",
1164					},
1165				},
1166				&struct {
1167					S struct{ S, B string }
1168				}{
1169					S: struct{ S, B string }{
1170						S: "string2",
1171						B: "string3",
1172					},
1173				},
1174			},
1175		},
1176
1177		// Errors
1178
1179		{
1180			// Non-pointer in1
1181			in1: []interface{}{struct{}{}},
1182			in2: &struct{}{},
1183			err: errors.New("expected pointer to struct, got struct {}"),
1184			out: []interface{}{struct{}{}},
1185		},
1186		{
1187			// Non-pointer in2
1188			in1: []interface{}{&struct{}{}},
1189			in2: struct{}{},
1190			err: errors.New("expected pointer to struct, got struct {}"),
1191			out: []interface{}{&struct{}{}},
1192		},
1193		{
1194			// Non-struct in1
1195			in1: []interface{}{&[]string{"bad"}},
1196			in2: &struct{}{},
1197			err: errors.New("expected pointer to struct, got *[]string"),
1198			out: []interface{}{&[]string{"bad"}},
1199		},
1200		{
1201			// Non-struct in2
1202			in1: []interface{}{&struct{}{}},
1203			in2: &[]string{"bad"},
1204			err: errors.New("expected pointer to struct, got *[]string"),
1205			out: []interface{}{&struct{}{}},
1206		},
1207		{
1208			// Append none
1209			in1: []interface{}{
1210				&struct{ A string }{},
1211				&struct{ B string }{},
1212			},
1213			in2: &struct{ S string }{
1214				S: "string1",
1215			},
1216			out: []interface{}{
1217				&struct{ A string }{},
1218				&struct{ B string }{},
1219			},
1220			err: extendPropertyErrorf("s", "failed to find property to extend"),
1221		},
1222		{
1223			// Append mismatched kinds
1224			in1: []interface{}{
1225				&struct{ S string }{
1226					S: "string1",
1227				},
1228			},
1229			in2: &struct{ S []string }{
1230				S: []string{"string2"},
1231			},
1232			out: []interface{}{
1233				&struct{ S string }{
1234					S: "string1",
1235				},
1236			},
1237			err: extendPropertyErrorf("s", "mismatched types string and []string"),
1238		},
1239		{
1240			// Append mismatched types
1241			in1: []interface{}{
1242				&struct{ S []int }{
1243					S: []int{1},
1244				},
1245			},
1246			in2: &struct{ S []string }{
1247				S: []string{"string2"},
1248			},
1249			out: []interface{}{
1250				&struct{ S []int }{
1251					S: []int{1},
1252				},
1253			},
1254			err: extendPropertyErrorf("s", "mismatched types []int and []string"),
1255		},
1256	}
1257}
1258
1259func TestAppendMatchingProperties(t *testing.T) {
1260	for _, testCase := range appendMatchingPropertiesTestCases() {
1261		testString := fmt.Sprintf("%s, %s -> %s", p(testCase.in1), p(testCase.in2), p(testCase.out))
1262
1263		got := testCase.in1
1264		var err error
1265		var testType string
1266
1267		if testCase.prepend {
1268			testType = "prepend matching"
1269			err = PrependMatchingProperties(got, testCase.in2, testCase.filter)
1270		} else {
1271			testType = "append matching"
1272			err = AppendMatchingProperties(got, testCase.in2, testCase.filter)
1273		}
1274
1275		check(t, testType, testString, got, err, testCase.out, testCase.err)
1276	}
1277}
1278
1279func TestExtendMatchingProperties(t *testing.T) {
1280	for _, testCase := range appendMatchingPropertiesTestCases() {
1281		testString := fmt.Sprintf("%s, %s -> %s", p(testCase.in1), p(testCase.in2), p(testCase.out))
1282
1283		got := testCase.in1
1284		var err error
1285		var testType string
1286
1287		order := func(property string,
1288			dstField, srcField reflect.StructField,
1289			dstValue, srcValue interface{}) (Order, error) {
1290			if testCase.prepend {
1291				return Prepend, nil
1292			} else {
1293				return Append, nil
1294			}
1295		}
1296
1297		if testCase.prepend {
1298			testType = "prepend matching"
1299		} else {
1300			testType = "append matching"
1301		}
1302
1303		err = ExtendMatchingProperties(got, testCase.in2, testCase.filter, order)
1304
1305		check(t, testType, testString, got, err, testCase.out, testCase.err)
1306	}
1307}
1308
1309func check(t *testing.T, testType, testString string,
1310	got interface{}, err error,
1311	expected interface{}, expectedErr error) {
1312
1313	printedTestCase := false
1314	e := func(s string, expected, got interface{}) {
1315		if !printedTestCase {
1316			t.Errorf("test case %s: %s", testType, testString)
1317			printedTestCase = true
1318		}
1319		t.Errorf("incorrect %s", s)
1320		t.Errorf("  expected: %s", p(expected))
1321		t.Errorf("       got: %s", p(got))
1322	}
1323
1324	if err != nil {
1325		if expectedErr != nil {
1326			if err.Error() != expectedErr.Error() {
1327				e("unexpected error", expectedErr.Error(), err.Error())
1328			}
1329		} else {
1330			e("unexpected error", nil, err.Error())
1331		}
1332	} else {
1333		if expectedErr != nil {
1334			e("missing error", expectedErr, nil)
1335		}
1336	}
1337
1338	if !reflect.DeepEqual(expected, got) {
1339		e("output:", expected, got)
1340	}
1341}
1342
1343func p(in interface{}) string {
1344	if v, ok := in.([]interface{}); ok {
1345		s := make([]string, len(v))
1346		for i := range v {
1347			s[i] = fmt.Sprintf("%#v", v[i])
1348		}
1349		return "[" + strings.Join(s, ", ") + "]"
1350	} else {
1351		return fmt.Sprintf("%#v", in)
1352	}
1353}
1354