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