• 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
25var appendPropertiesTestCases = []struct {
26	in1     interface{}
27	in2     interface{}
28	out     interface{}
29	prepend bool
30	filter  ExtendPropertyFilterFunc
31	err     error
32}{
33	// Valid inputs
34
35	{
36		// Append bool
37		in1: &struct{ B1, B2, B3, B4 bool }{
38			B1: true,
39			B2: false,
40			B3: true,
41			B4: false,
42		},
43		in2: &struct{ B1, B2, B3, B4 bool }{
44			B1: true,
45			B2: true,
46			B3: false,
47			B4: false,
48		},
49		out: &struct{ B1, B2, B3, B4 bool }{
50			B1: true,
51			B2: true,
52			B3: true,
53			B4: false,
54		},
55	},
56	{
57		// Prepend bool
58		in1: &struct{ B1, B2, B3, B4 bool }{
59			B1: true,
60			B2: false,
61			B3: true,
62			B4: false,
63		},
64		in2: &struct{ B1, B2, B3, B4 bool }{
65			B1: true,
66			B2: true,
67			B3: false,
68			B4: false,
69		},
70		out: &struct{ B1, B2, B3, B4 bool }{
71			B1: true,
72			B2: true,
73			B3: true,
74			B4: false,
75		},
76		prepend: true,
77	},
78	{
79		// Append strings
80		in1: &struct{ S string }{
81			S: "string1",
82		},
83		in2: &struct{ S string }{
84			S: "string2",
85		},
86		out: &struct{ S string }{
87			S: "string1string2",
88		},
89	},
90	{
91		// Prepend strings
92		in1: &struct{ S string }{
93			S: "string1",
94		},
95		in2: &struct{ S string }{
96			S: "string2",
97		},
98		out: &struct{ S string }{
99			S: "string2string1",
100		},
101		prepend: true,
102	},
103	{
104		// Append pointer to bool
105		in1: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
106			B1: BoolPtr(true),
107			B2: BoolPtr(false),
108			B3: nil,
109			B4: BoolPtr(true),
110			B5: BoolPtr(false),
111			B6: nil,
112			B7: BoolPtr(true),
113			B8: BoolPtr(false),
114			B9: nil,
115		},
116		in2: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
117			B1: nil,
118			B2: nil,
119			B3: nil,
120			B4: BoolPtr(true),
121			B5: BoolPtr(true),
122			B6: BoolPtr(true),
123			B7: BoolPtr(false),
124			B8: BoolPtr(false),
125			B9: BoolPtr(false),
126		},
127		out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
128			B1: BoolPtr(true),
129			B2: BoolPtr(false),
130			B3: nil,
131			B4: BoolPtr(true),
132			B5: BoolPtr(true),
133			B6: BoolPtr(true),
134			B7: BoolPtr(false),
135			B8: BoolPtr(false),
136			B9: BoolPtr(false),
137		},
138	},
139	{
140		// Prepend pointer to bool
141		in1: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
142			B1: BoolPtr(true),
143			B2: BoolPtr(false),
144			B3: nil,
145			B4: BoolPtr(true),
146			B5: BoolPtr(false),
147			B6: nil,
148			B7: BoolPtr(true),
149			B8: BoolPtr(false),
150			B9: nil,
151		},
152		in2: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
153			B1: nil,
154			B2: nil,
155			B3: nil,
156			B4: BoolPtr(true),
157			B5: BoolPtr(true),
158			B6: BoolPtr(true),
159			B7: BoolPtr(false),
160			B8: BoolPtr(false),
161			B9: BoolPtr(false),
162		},
163		out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{
164			B1: BoolPtr(true),
165			B2: BoolPtr(false),
166			B3: nil,
167			B4: BoolPtr(true),
168			B5: BoolPtr(false),
169			B6: BoolPtr(true),
170			B7: BoolPtr(true),
171			B8: BoolPtr(false),
172			B9: BoolPtr(false),
173		},
174		prepend: true,
175	},
176	{
177		// Append pointer to strings
178		in1: &struct{ S1, S2, S3, S4 *string }{
179			S1: StringPtr("string1"),
180			S2: StringPtr("string2"),
181		},
182		in2: &struct{ S1, S2, S3, S4 *string }{
183			S1: StringPtr("string3"),
184			S3: StringPtr("string4"),
185		},
186		out: &struct{ S1, S2, S3, S4 *string }{
187			S1: StringPtr("string3"),
188			S2: StringPtr("string2"),
189			S3: StringPtr("string4"),
190			S4: nil,
191		},
192	},
193	{
194		// Prepend pointer to strings
195		in1: &struct{ S1, S2, S3, S4 *string }{
196			S1: StringPtr("string1"),
197			S2: StringPtr("string2"),
198		},
199		in2: &struct{ S1, S2, S3, S4 *string }{
200			S1: StringPtr("string3"),
201			S3: StringPtr("string4"),
202		},
203		out: &struct{ S1, S2, S3, S4 *string }{
204			S1: StringPtr("string1"),
205			S2: StringPtr("string2"),
206			S3: StringPtr("string4"),
207			S4: nil,
208		},
209		prepend: true,
210	},
211	{
212		// Append slice
213		in1: &struct{ S []string }{
214			S: []string{"string1"},
215		},
216		in2: &struct{ S []string }{
217			S: []string{"string2"},
218		},
219		out: &struct{ S []string }{
220			S: []string{"string1", "string2"},
221		},
222	},
223	{
224		// Prepend slice
225		in1: &struct{ S []string }{
226			S: []string{"string1"},
227		},
228		in2: &struct{ S []string }{
229			S: []string{"string2"},
230		},
231		out: &struct{ S []string }{
232			S: []string{"string2", "string1"},
233		},
234		prepend: true,
235	},
236	{
237		// Append empty slice
238		in1: &struct{ S1, S2 []string }{
239			S1: []string{"string1"},
240			S2: []string{},
241		},
242		in2: &struct{ S1, S2 []string }{
243			S1: []string{},
244			S2: []string{"string2"},
245		},
246		out: &struct{ S1, S2 []string }{
247			S1: []string{"string1"},
248			S2: []string{"string2"},
249		},
250	},
251	{
252		// Prepend empty slice
253		in1: &struct{ S1, S2 []string }{
254			S1: []string{"string1"},
255			S2: []string{},
256		},
257		in2: &struct{ S1, S2 []string }{
258			S1: []string{},
259			S2: []string{"string2"},
260		},
261		out: &struct{ S1, S2 []string }{
262			S1: []string{"string1"},
263			S2: []string{"string2"},
264		},
265		prepend: true,
266	},
267	{
268		// Append nil slice
269		in1: &struct{ S1, S2, S3 []string }{
270			S1: []string{"string1"},
271		},
272		in2: &struct{ S1, S2, S3 []string }{
273			S2: []string{"string2"},
274		},
275		out: &struct{ S1, S2, S3 []string }{
276			S1: []string{"string1"},
277			S2: []string{"string2"},
278			S3: nil,
279		},
280	},
281	{
282		// Prepend nil slice
283		in1: &struct{ S1, S2, S3 []string }{
284			S1: []string{"string1"},
285		},
286		in2: &struct{ S1, S2, S3 []string }{
287			S2: []string{"string2"},
288		},
289		out: &struct{ S1, S2, S3 []string }{
290			S1: []string{"string1"},
291			S2: []string{"string2"},
292			S3: nil,
293		},
294		prepend: true,
295	},
296	{
297		// Append pointer
298		in1: &struct{ S *struct{ S string } }{
299			S: &struct{ S string }{
300				S: "string1",
301			},
302		},
303		in2: &struct{ S *struct{ S string } }{
304			S: &struct{ S string }{
305				S: "string2",
306			},
307		},
308		out: &struct{ S *struct{ S string } }{
309			S: &struct{ S string }{
310				S: "string1string2",
311			},
312		},
313	},
314	{
315		// Prepend pointer
316		in1: &struct{ S *struct{ S string } }{
317			S: &struct{ S string }{
318				S: "string1",
319			},
320		},
321		in2: &struct{ S *struct{ S string } }{
322			S: &struct{ S string }{
323				S: "string2",
324			},
325		},
326		out: &struct{ S *struct{ S string } }{
327			S: &struct{ S string }{
328				S: "string2string1",
329			},
330		},
331		prepend: true,
332	},
333	{
334		// Append interface
335		in1: &struct{ S interface{} }{
336			S: &struct{ S string }{
337				S: "string1",
338			},
339		},
340		in2: &struct{ S interface{} }{
341			S: &struct{ S string }{
342				S: "string2",
343			},
344		},
345		out: &struct{ S interface{} }{
346			S: &struct{ S string }{
347				S: "string1string2",
348			},
349		},
350	},
351	{
352		// Prepend interface
353		in1: &struct{ S interface{} }{
354			S: &struct{ S string }{
355				S: "string1",
356			},
357		},
358		in2: &struct{ S interface{} }{
359			S: &struct{ S string }{
360				S: "string2",
361			},
362		},
363		out: &struct{ S interface{} }{
364			S: &struct{ S string }{
365				S: "string2string1",
366			},
367		},
368		prepend: true,
369	},
370	{
371		// Unexported field
372		in1: &struct{ s string }{
373			s: "string1",
374		},
375		in2: &struct{ s string }{
376			s: "string2",
377		},
378		out: &struct{ s string }{
379			s: "string1",
380		},
381	},
382	{
383		// Empty struct
384		in1: &struct{}{},
385		in2: &struct{}{},
386		out: &struct{}{},
387	},
388	{
389		// Interface nil
390		in1: &struct{ S interface{} }{
391			S: nil,
392		},
393		in2: &struct{ S interface{} }{
394			S: nil,
395		},
396		out: &struct{ S interface{} }{
397			S: nil,
398		},
399	},
400	{
401		// Pointer nil
402		in1: &struct{ S *struct{} }{
403			S: nil,
404		},
405		in2: &struct{ S *struct{} }{
406			S: nil,
407		},
408		out: &struct{ S *struct{} }{
409			S: nil,
410		},
411	},
412	{
413		// Anonymous struct
414		in1: &struct {
415			EmbeddedStruct
416			Nested struct{ EmbeddedStruct }
417		}{
418			EmbeddedStruct: EmbeddedStruct{
419				S: "string1",
420			},
421			Nested: struct{ EmbeddedStruct }{
422				EmbeddedStruct: EmbeddedStruct{
423					S: "string2",
424				},
425			},
426		},
427		in2: &struct {
428			EmbeddedStruct
429			Nested struct{ EmbeddedStruct }
430		}{
431			EmbeddedStruct: EmbeddedStruct{
432				S: "string3",
433			},
434			Nested: struct{ EmbeddedStruct }{
435				EmbeddedStruct: EmbeddedStruct{
436					S: "string4",
437				},
438			},
439		},
440		out: &struct {
441			EmbeddedStruct
442			Nested struct{ EmbeddedStruct }
443		}{
444			EmbeddedStruct: EmbeddedStruct{
445				S: "string1string3",
446			},
447			Nested: struct{ EmbeddedStruct }{
448				EmbeddedStruct: EmbeddedStruct{
449					S: "string2string4",
450				},
451			},
452		},
453	},
454	{
455		// Anonymous interface
456		in1: &struct {
457			EmbeddedInterface
458			Nested struct{ EmbeddedInterface }
459		}{
460			EmbeddedInterface: &struct{ S string }{
461				S: "string1",
462			},
463			Nested: struct{ EmbeddedInterface }{
464				EmbeddedInterface: &struct{ S string }{
465					S: "string2",
466				},
467			},
468		},
469		in2: &struct {
470			EmbeddedInterface
471			Nested struct{ EmbeddedInterface }
472		}{
473			EmbeddedInterface: &struct{ S string }{
474				S: "string3",
475			},
476			Nested: struct{ EmbeddedInterface }{
477				EmbeddedInterface: &struct{ S string }{
478					S: "string4",
479				},
480			},
481		},
482		out: &struct {
483			EmbeddedInterface
484			Nested struct{ EmbeddedInterface }
485		}{
486			EmbeddedInterface: &struct{ S string }{
487				S: "string1string3",
488			},
489			Nested: struct{ EmbeddedInterface }{
490				EmbeddedInterface: &struct{ S string }{
491					S: "string2string4",
492				},
493			},
494		},
495	},
496
497	// Errors
498
499	{
500		// Non-pointer in1
501		in1: struct{}{},
502		err: errors.New("expected pointer to struct, got struct {}"),
503		out: struct{}{},
504	},
505	{
506		// Non-pointer in2
507		in1: &struct{}{},
508		in2: struct{}{},
509		err: errors.New("expected pointer to struct, got struct {}"),
510		out: &struct{}{},
511	},
512	{
513		// Non-struct in1
514		in1: &[]string{"bad"},
515		err: errors.New("expected pointer to struct, got *[]string"),
516		out: &[]string{"bad"},
517	},
518	{
519		// Non-struct in2
520		in1: &struct{}{},
521		in2: &[]string{"bad"},
522		err: errors.New("expected pointer to struct, got *[]string"),
523		out: &struct{}{},
524	},
525	{
526		// Mismatched types
527		in1: &struct{ A string }{
528			A: "string1",
529		},
530		in2: &struct{ B string }{
531			B: "string2",
532		},
533		out: &struct{ A string }{
534			A: "string1",
535		},
536		err: errors.New("expected matching types for dst and src, got *struct { A string } and *struct { B string }"),
537	},
538	{
539		// Unsupported kind
540		in1: &struct{ I int }{
541			I: 1,
542		},
543		in2: &struct{ I int }{
544			I: 2,
545		},
546		out: &struct{ I int }{
547			I: 1,
548		},
549		err: extendPropertyErrorf("i", "unsupported kind int"),
550	},
551	{
552		// Interface nilitude mismatch
553		in1: &struct{ S interface{} }{
554			S: &struct{ S string }{
555				S: "string1",
556			},
557		},
558		in2: &struct{ S interface{} }{
559			S: nil,
560		},
561		out: &struct{ S interface{} }{
562			S: &struct{ S string }{
563				S: "string1",
564			},
565		},
566		err: extendPropertyErrorf("s", "nilitude mismatch"),
567	},
568	{
569		// Interface type mismatch
570		in1: &struct{ S interface{} }{
571			S: &struct{ A string }{
572				A: "string1",
573			},
574		},
575		in2: &struct{ S interface{} }{
576			S: &struct{ B string }{
577				B: "string2",
578			},
579		},
580		out: &struct{ S interface{} }{
581			S: &struct{ A string }{
582				A: "string1",
583			},
584		},
585		err: extendPropertyErrorf("s", "mismatched types struct { A string } and struct { B string }"),
586	},
587	{
588		// Interface not a pointer
589		in1: &struct{ S interface{} }{
590			S: struct{ S string }{
591				S: "string1",
592			},
593		},
594		in2: &struct{ S interface{} }{
595			S: struct{ S string }{
596				S: "string2",
597			},
598		},
599		out: &struct{ S interface{} }{
600			S: struct{ S string }{
601				S: "string1",
602			},
603		},
604		err: extendPropertyErrorf("s", "interface not a pointer"),
605	},
606	{
607		// Pointer nilitude mismatch
608		in1: &struct{ S *struct{ S string } }{
609			S: &struct{ S string }{
610				S: "string1",
611			},
612		},
613		in2: &struct{ S *struct{ S string } }{
614			S: nil,
615		},
616		out: &struct{ S *struct{ S string } }{
617			S: &struct{ S string }{
618				S: "string1",
619			},
620		},
621		err: extendPropertyErrorf("s", "nilitude mismatch"),
622	},
623	{
624		// Pointer not a struct
625		in1: &struct{ S *[]string }{
626			S: &[]string{"string1"},
627		},
628		in2: &struct{ S *[]string }{
629			S: &[]string{"string2"},
630		},
631		out: &struct{ S *[]string }{
632			S: &[]string{"string1"},
633		},
634		err: extendPropertyErrorf("s", "pointer is a slice"),
635	},
636	{
637		// Error in nested struct
638		in1: &struct{ S interface{} }{
639			S: &struct{ I int }{
640				I: 1,
641			},
642		},
643		in2: &struct{ S interface{} }{
644			S: &struct{ I int }{
645				I: 2,
646			},
647		},
648		out: &struct{ S interface{} }{
649			S: &struct{ I int }{
650				I: 1,
651			},
652		},
653		err: extendPropertyErrorf("s.i", "unsupported kind int"),
654	},
655
656	// Filters
657
658	{
659		// Filter true
660		in1: &struct{ S string }{
661			S: "string1",
662		},
663		in2: &struct{ S string }{
664			S: "string2",
665		},
666		out: &struct{ S string }{
667			S: "string1string2",
668		},
669		filter: func(property string,
670			dstField, srcField reflect.StructField,
671			dstValue, srcValue interface{}) (bool, error) {
672			return true, nil
673		},
674	},
675	{
676		// Filter false
677		in1: &struct{ S string }{
678			S: "string1",
679		},
680		in2: &struct{ S string }{
681			S: "string2",
682		},
683		out: &struct{ S string }{
684			S: "string1",
685		},
686		filter: func(property string,
687			dstField, srcField reflect.StructField,
688			dstValue, srcValue interface{}) (bool, error) {
689			return false, nil
690		},
691	},
692	{
693		// Filter check args
694		in1: &struct{ S string }{
695			S: "string1",
696		},
697		in2: &struct{ S string }{
698			S: "string2",
699		},
700		out: &struct{ S string }{
701			S: "string1string2",
702		},
703		filter: func(property string,
704			dstField, srcField reflect.StructField,
705			dstValue, srcValue interface{}) (bool, error) {
706			return property == "s" &&
707				dstField.Name == "S" && srcField.Name == "S" &&
708				dstValue.(string) == "string1" && srcValue.(string) == "string2", nil
709		},
710	},
711	{
712		// Filter mutated
713		in1: &struct {
714			S string `blueprint:"mutated"`
715		}{
716			S: "string1",
717		},
718		in2: &struct {
719			S string `blueprint:"mutated"`
720		}{
721			S: "string2",
722		},
723		out: &struct {
724			S string `blueprint:"mutated"`
725		}{
726			S: "string1",
727		},
728	},
729	{
730		// Filter error
731		in1: &struct{ S string }{
732			S: "string1",
733		},
734		in2: &struct{ S string }{
735			S: "string2",
736		},
737		out: &struct{ S string }{
738			S: "string1",
739		},
740		filter: func(property string,
741			dstField, srcField reflect.StructField,
742			dstValue, srcValue interface{}) (bool, error) {
743			return true, fmt.Errorf("filter error")
744		},
745		err: extendPropertyErrorf("s", "filter error"),
746	},
747}
748
749func TestAppendProperties(t *testing.T) {
750	for _, testCase := range appendPropertiesTestCases {
751		testString := fmt.Sprintf("%v, %v -> %v", testCase.in1, testCase.in2, testCase.out)
752
753		got := testCase.in1
754		var err error
755		var testType string
756
757		if testCase.prepend {
758			testType = "prepend"
759			err = PrependProperties(got, testCase.in2, testCase.filter)
760		} else {
761			testType = "append"
762			err = AppendProperties(got, testCase.in2, testCase.filter)
763		}
764
765		check(t, testType, testString, got, err, testCase.out, testCase.err)
766	}
767}
768
769var appendMatchingPropertiesTestCases = []struct {
770	in1     []interface{}
771	in2     interface{}
772	out     []interface{}
773	prepend bool
774	filter  ExtendPropertyFilterFunc
775	err     error
776}{
777	{
778		// Append strings
779		in1: []interface{}{&struct{ S string }{
780			S: "string1",
781		}},
782		in2: &struct{ S string }{
783			S: "string2",
784		},
785		out: []interface{}{&struct{ S string }{
786			S: "string1string2",
787		}},
788	},
789	{
790		// Prepend strings
791		in1: []interface{}{&struct{ S string }{
792			S: "string1",
793		}},
794		in2: &struct{ S string }{
795			S: "string2",
796		},
797		out: []interface{}{&struct{ S string }{
798			S: "string2string1",
799		}},
800		prepend: true,
801	},
802	{
803		// Append all
804		in1: []interface{}{
805			&struct{ S, A string }{
806				S: "string1",
807			},
808			&struct{ S, B string }{
809				S: "string2",
810			},
811		},
812		in2: &struct{ S string }{
813			S: "string3",
814		},
815		out: []interface{}{
816			&struct{ S, A string }{
817				S: "string1string3",
818			},
819			&struct{ S, B string }{
820				S: "string2string3",
821			},
822		},
823	},
824	{
825		// Append some
826		in1: []interface{}{
827			&struct{ S, A string }{
828				S: "string1",
829			},
830			&struct{ B string }{},
831		},
832		in2: &struct{ S string }{
833			S: "string2",
834		},
835		out: []interface{}{
836			&struct{ S, A string }{
837				S: "string1string2",
838			},
839			&struct{ B string }{},
840		},
841	},
842	{
843		// Append mismatched structs
844		in1: []interface{}{&struct{ S, A string }{
845			S: "string1",
846		}},
847		in2: &struct{ S string }{
848			S: "string2",
849		},
850		out: []interface{}{&struct{ S, A string }{
851			S: "string1string2",
852		}},
853	},
854	{
855		// Append mismatched pointer structs
856		in1: []interface{}{&struct{ S *struct{ S, A string } }{
857			S: &struct{ S, A string }{
858				S: "string1",
859			},
860		}},
861		in2: &struct{ S *struct{ S string } }{
862			S: &struct{ S string }{
863				S: "string2",
864			},
865		},
866		out: []interface{}{&struct{ S *struct{ S, A string } }{
867			S: &struct{ S, A string }{
868				S: "string1string2",
869			},
870		}},
871	},
872
873	// Errors
874
875	{
876		// Non-pointer in1
877		in1: []interface{}{struct{}{}},
878		err: errors.New("expected pointer to struct, got struct {}"),
879		out: []interface{}{struct{}{}},
880	},
881	{
882		// Non-pointer in2
883		in1: []interface{}{&struct{}{}},
884		in2: struct{}{},
885		err: errors.New("expected pointer to struct, got struct {}"),
886		out: []interface{}{&struct{}{}},
887	},
888	{
889		// Non-struct in1
890		in1: []interface{}{&[]string{"bad"}},
891		err: errors.New("expected pointer to struct, got *[]string"),
892		out: []interface{}{&[]string{"bad"}},
893	},
894	{
895		// Non-struct in2
896		in1: []interface{}{&struct{}{}},
897		in2: &[]string{"bad"},
898		err: errors.New("expected pointer to struct, got *[]string"),
899		out: []interface{}{&struct{}{}},
900	},
901	{
902		// Append none
903		in1: []interface{}{
904			&struct{ A string }{},
905			&struct{ B string }{},
906		},
907		in2: &struct{ S string }{
908			S: "string1",
909		},
910		out: []interface{}{
911			&struct{ A string }{},
912			&struct{ B string }{},
913		},
914		err: extendPropertyErrorf("s", "failed to find property to extend"),
915	},
916	{
917		// Append mismatched kinds
918		in1: []interface{}{
919			&struct{ S string }{
920				S: "string1",
921			},
922		},
923		in2: &struct{ S []string }{
924			S: []string{"string2"},
925		},
926		out: []interface{}{
927			&struct{ S string }{
928				S: "string1",
929			},
930		},
931		err: extendPropertyErrorf("s", "mismatched types string and []string"),
932	},
933	{
934		// Append mismatched types
935		in1: []interface{}{
936			&struct{ S []int }{
937				S: []int{1},
938			},
939		},
940		in2: &struct{ S []string }{
941			S: []string{"string2"},
942		},
943		out: []interface{}{
944			&struct{ S []int }{
945				S: []int{1},
946			},
947		},
948		err: extendPropertyErrorf("s", "mismatched types []int and []string"),
949	},
950}
951
952func TestAppendMatchingProperties(t *testing.T) {
953	for _, testCase := range appendMatchingPropertiesTestCases {
954		testString := fmt.Sprintf("%s, %s -> %s", p(testCase.in1), p(testCase.in2), p(testCase.out))
955
956		got := testCase.in1
957		var err error
958		var testType string
959
960		if testCase.prepend {
961			testType = "prepend matching"
962			err = PrependMatchingProperties(got, testCase.in2, testCase.filter)
963		} else {
964			testType = "append matching"
965			err = AppendMatchingProperties(got, testCase.in2, testCase.filter)
966		}
967
968		check(t, testType, testString, got, err, testCase.out, testCase.err)
969	}
970}
971
972func check(t *testing.T, testType, testString string,
973	got interface{}, err error,
974	expected interface{}, expectedErr error) {
975
976	printedTestCase := false
977	e := func(s string, expected, got interface{}) {
978		if !printedTestCase {
979			t.Errorf("test case %s: %s", testType, testString)
980			printedTestCase = true
981		}
982		t.Errorf("incorrect %s", s)
983		t.Errorf("  expected: %s", p(expected))
984		t.Errorf("       got: %s", p(got))
985	}
986
987	if err != nil {
988		if expectedErr != nil {
989			if err.Error() != expectedErr.Error() {
990				e("unexpected error", expectedErr.Error(), err.Error())
991			}
992		} else {
993			e("unexpected error", nil, err.Error())
994		}
995	} else {
996		if expectedErr != nil {
997			e("missing error", expectedErr, nil)
998		}
999	}
1000
1001	if !reflect.DeepEqual(expected, got) {
1002		e("output:", expected, got)
1003	}
1004}
1005
1006func p(in interface{}) string {
1007	if v, ok := in.([]interface{}); ok {
1008		s := make([]string, len(v))
1009		for i := range v {
1010			s[i] = fmt.Sprintf("%#v", v[i])
1011		}
1012		return "[" + strings.Join(s, ", ") + "]"
1013	} else {
1014		return fmt.Sprintf("%#v", in)
1015	}
1016}
1017