Home | History | Annotate | Download | only in proptools
      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 
     15 package proptools
     16 
     17 import (
     18 	"errors"
     19 	"fmt"
     20 	"reflect"
     21 	"strings"
     22 	"testing"
     23 )
     24 
     25 var 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 
    749 func 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 
    769 var 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 
    952 func 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 
    972 func 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 
   1006 func 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