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