1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package proptools 16 17import ( 18 "errors" 19 "fmt" 20 "reflect" 21 "strings" 22 "testing" 23) 24 25type appendPropertyTestCase struct { 26 name string 27 dst interface{} 28 src interface{} 29 out interface{} 30 order Order // default is Append 31 filter ExtendPropertyFilterFunc 32 err error 33} 34 35func appendPropertiesTestCases() []appendPropertyTestCase { 36 return []appendPropertyTestCase{ 37 // Valid inputs 38 39 { 40 name: "Append bool", 41 dst: &struct{ B1, B2, B3, B4 bool }{ 42 B1: true, 43 B2: false, 44 B3: true, 45 B4: false, 46 }, 47 src: &struct{ B1, B2, B3, B4 bool }{ 48 B1: true, 49 B2: true, 50 B3: false, 51 B4: false, 52 }, 53 out: &struct{ B1, B2, B3, B4 bool }{ 54 B1: true, 55 B2: true, 56 B3: true, 57 B4: false, 58 }, 59 }, 60 { 61 name: "Prepend bool", 62 dst: &struct{ B1, B2, B3, B4 bool }{ 63 B1: true, 64 B2: false, 65 B3: true, 66 B4: false, 67 }, 68 src: &struct{ B1, B2, B3, B4 bool }{ 69 B1: true, 70 B2: true, 71 B3: false, 72 B4: false, 73 }, 74 out: &struct{ B1, B2, B3, B4 bool }{ 75 B1: true, 76 B2: true, 77 B3: true, 78 B4: false, 79 }, 80 order: Prepend, 81 }, 82 { 83 name: "Append strings", 84 dst: &struct{ S string }{ 85 S: "string1", 86 }, 87 src: &struct{ S string }{ 88 S: "string2", 89 }, 90 out: &struct{ S string }{ 91 S: "string1string2", 92 }, 93 }, 94 { 95 name: "Prepend strings", 96 dst: &struct{ S string }{ 97 S: "string1", 98 }, 99 src: &struct{ S string }{ 100 S: "string2", 101 }, 102 out: &struct{ S string }{ 103 S: "string2string1", 104 }, 105 order: Prepend, 106 }, 107 { 108 name: "Append pointer to bool", 109 dst: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 110 B1: BoolPtr(true), 111 B2: BoolPtr(false), 112 B3: nil, 113 B4: BoolPtr(true), 114 B5: BoolPtr(false), 115 B6: nil, 116 B7: BoolPtr(true), 117 B8: BoolPtr(false), 118 B9: nil, 119 }, 120 src: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 121 B1: nil, 122 B2: nil, 123 B3: nil, 124 B4: BoolPtr(true), 125 B5: BoolPtr(true), 126 B6: BoolPtr(true), 127 B7: BoolPtr(false), 128 B8: BoolPtr(false), 129 B9: BoolPtr(false), 130 }, 131 out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 132 B1: BoolPtr(true), 133 B2: BoolPtr(false), 134 B3: nil, 135 B4: BoolPtr(true), 136 B5: BoolPtr(true), 137 B6: BoolPtr(true), 138 B7: BoolPtr(false), 139 B8: BoolPtr(false), 140 B9: BoolPtr(false), 141 }, 142 }, 143 { 144 name: "Prepend pointer to bool", 145 dst: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 146 B1: BoolPtr(true), 147 B2: BoolPtr(false), 148 B3: nil, 149 B4: BoolPtr(true), 150 B5: BoolPtr(false), 151 B6: nil, 152 B7: BoolPtr(true), 153 B8: BoolPtr(false), 154 B9: nil, 155 }, 156 src: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 157 B1: nil, 158 B2: nil, 159 B3: nil, 160 B4: BoolPtr(true), 161 B5: BoolPtr(true), 162 B6: BoolPtr(true), 163 B7: BoolPtr(false), 164 B8: BoolPtr(false), 165 B9: BoolPtr(false), 166 }, 167 out: &struct{ B1, B2, B3, B4, B5, B6, B7, B8, B9 *bool }{ 168 B1: BoolPtr(true), 169 B2: BoolPtr(false), 170 B3: nil, 171 B4: BoolPtr(true), 172 B5: BoolPtr(false), 173 B6: BoolPtr(true), 174 B7: BoolPtr(true), 175 B8: BoolPtr(false), 176 B9: BoolPtr(false), 177 }, 178 order: Prepend, 179 }, 180 { 181 name: "Append pointer to integer", 182 dst: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{ 183 I1: Int64Ptr(55), 184 I2: Int64Ptr(-3), 185 I3: nil, 186 I4: Int64Ptr(100), 187 I5: Int64Ptr(33), 188 I6: nil, 189 I7: Int64Ptr(77), 190 I8: Int64Ptr(0), 191 I9: nil, 192 }, 193 src: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{ 194 I1: nil, 195 I2: nil, 196 I3: nil, 197 I4: Int64Ptr(1), 198 I5: Int64Ptr(-2), 199 I6: Int64Ptr(8), 200 I7: Int64Ptr(9), 201 I8: Int64Ptr(10), 202 I9: Int64Ptr(11), 203 }, 204 out: &struct{ I1, I2, I3, I4, I5, I6, I7, I8, I9 *int64 }{ 205 I1: Int64Ptr(55), 206 I2: Int64Ptr(-3), 207 I3: nil, 208 I4: Int64Ptr(1), 209 I5: Int64Ptr(-2), 210 I6: Int64Ptr(8), 211 I7: Int64Ptr(9), 212 I8: Int64Ptr(10), 213 I9: Int64Ptr(11), 214 }, 215 }, 216 { 217 name: "Prepend pointer to integer", 218 dst: &struct{ I1, I2, I3 *int64 }{ 219 I1: Int64Ptr(55), 220 I3: nil, 221 }, 222 src: &struct{ I1, I2, I3 *int64 }{ 223 I2: Int64Ptr(33), 224 }, 225 out: &struct{ I1, I2, I3 *int64 }{ 226 I1: Int64Ptr(55), 227 I2: Int64Ptr(33), 228 I3: nil, 229 }, 230 order: Prepend, 231 }, 232 { 233 name: "Append pointer to strings", 234 dst: &struct{ S1, S2, S3, S4 *string }{ 235 S1: StringPtr("string1"), 236 S2: StringPtr("string2"), 237 }, 238 src: &struct{ S1, S2, S3, S4 *string }{ 239 S1: StringPtr("string3"), 240 S3: StringPtr("string4"), 241 }, 242 out: &struct{ S1, S2, S3, S4 *string }{ 243 S1: StringPtr("string3"), 244 S2: StringPtr("string2"), 245 S3: StringPtr("string4"), 246 S4: nil, 247 }, 248 }, 249 { 250 name: "Prepend pointer to strings", 251 dst: &struct{ S1, S2, S3, S4 *string }{ 252 S1: StringPtr("string1"), 253 S2: StringPtr("string2"), 254 }, 255 src: &struct{ S1, S2, S3, S4 *string }{ 256 S1: StringPtr("string3"), 257 S3: StringPtr("string4"), 258 }, 259 out: &struct{ S1, S2, S3, S4 *string }{ 260 S1: StringPtr("string1"), 261 S2: StringPtr("string2"), 262 S3: StringPtr("string4"), 263 S4: nil, 264 }, 265 order: Prepend, 266 }, 267 { 268 name: "Append slice", 269 dst: &struct{ S []string }{ 270 S: []string{"string1"}, 271 }, 272 src: &struct{ S []string }{ 273 S: []string{"string2"}, 274 }, 275 out: &struct{ S []string }{ 276 S: []string{"string1", "string2"}, 277 }, 278 }, 279 { 280 name: "Prepend slice", 281 dst: &struct{ S []string }{ 282 S: []string{"string1"}, 283 }, 284 src: &struct{ S []string }{ 285 S: []string{"string2"}, 286 }, 287 out: &struct{ S []string }{ 288 S: []string{"string2", "string1"}, 289 }, 290 order: Prepend, 291 }, 292 { 293 name: "Replace slice", 294 dst: &struct{ S []string }{ 295 S: []string{"string1"}, 296 }, 297 src: &struct{ S []string }{ 298 S: []string{"string2"}, 299 }, 300 out: &struct{ S []string }{ 301 S: []string{"string2"}, 302 }, 303 order: Replace, 304 }, 305 { 306 name: "Append empty slice", 307 dst: &struct{ S1, S2 []string }{ 308 S1: []string{"string1"}, 309 S2: []string{}, 310 }, 311 src: &struct{ S1, S2 []string }{ 312 S1: []string{}, 313 S2: []string{"string2"}, 314 }, 315 out: &struct{ S1, S2 []string }{ 316 S1: []string{"string1"}, 317 S2: []string{"string2"}, 318 }, 319 }, 320 { 321 name: "Prepend empty slice", 322 dst: &struct{ S1, S2 []string }{ 323 S1: []string{"string1"}, 324 S2: []string{}, 325 }, 326 src: &struct{ S1, S2 []string }{ 327 S1: []string{}, 328 S2: []string{"string2"}, 329 }, 330 out: &struct{ S1, S2 []string }{ 331 S1: []string{"string1"}, 332 S2: []string{"string2"}, 333 }, 334 order: Prepend, 335 }, 336 { 337 name: "Replace empty slice", 338 dst: &struct{ S1, S2 []string }{ 339 S1: []string{"string1"}, 340 S2: []string{}, 341 }, 342 src: &struct{ S1, S2 []string }{ 343 S1: []string{}, 344 S2: []string{"string2"}, 345 }, 346 out: &struct{ S1, S2 []string }{ 347 S1: []string{}, 348 S2: []string{"string2"}, 349 }, 350 order: Replace, 351 }, 352 { 353 name: "Append nil slice", 354 dst: &struct{ S1, S2, S3 []string }{ 355 S1: []string{"string1"}, 356 }, 357 src: &struct{ S1, S2, S3 []string }{ 358 S2: []string{"string2"}, 359 }, 360 out: &struct{ S1, S2, S3 []string }{ 361 S1: []string{"string1"}, 362 S2: []string{"string2"}, 363 S3: nil, 364 }, 365 }, 366 { 367 name: "Prepend nil slice", 368 dst: &struct{ S1, S2, S3 []string }{ 369 S1: []string{"string1"}, 370 }, 371 src: &struct{ S1, S2, S3 []string }{ 372 S2: []string{"string2"}, 373 }, 374 out: &struct{ S1, S2, S3 []string }{ 375 S1: []string{"string1"}, 376 S2: []string{"string2"}, 377 S3: nil, 378 }, 379 order: Prepend, 380 }, 381 { 382 name: "Replace nil slice", 383 dst: &struct{ S1, S2, S3 []string }{ 384 S1: []string{"string1"}, 385 }, 386 src: &struct{ S1, S2, S3 []string }{ 387 S2: []string{"string2"}, 388 }, 389 out: &struct{ S1, S2, S3 []string }{ 390 S1: []string{"string1"}, 391 S2: []string{"string2"}, 392 S3: nil, 393 }, 394 order: Replace, 395 }, 396 { 397 name: "Replace embedded slice", 398 dst: &struct{ S *struct{ S1 []string } }{ 399 S: &struct{ S1 []string }{ 400 S1: []string{"string1"}, 401 }, 402 }, 403 src: &struct{ S *struct{ S1 []string } }{ 404 S: &struct{ S1 []string }{ 405 S1: []string{"string2"}, 406 }, 407 }, 408 out: &struct{ S *struct{ S1 []string } }{ 409 S: &struct{ S1 []string }{ 410 S1: []string{"string2"}, 411 }, 412 }, 413 order: Replace, 414 }, 415 { 416 name: "Append slice of structs", 417 dst: &struct{ S []struct{ F string } }{ 418 S: []struct{ F string }{ 419 {F: "foo"}, {F: "bar"}, 420 }, 421 }, 422 src: &struct{ S []struct{ F string } }{ 423 S: []struct{ F string }{ 424 {F: "baz"}, 425 }, 426 }, 427 out: &struct{ S []struct{ F string } }{ 428 S: []struct{ F string }{ 429 {F: "foo"}, {F: "bar"}, {F: "baz"}, 430 }, 431 }, 432 order: Append, 433 }, 434 { 435 name: "Prepend slice of structs", 436 dst: &struct{ S []struct{ F string } }{ 437 S: []struct{ F string }{ 438 {F: "foo"}, {F: "bar"}, 439 }, 440 }, 441 src: &struct{ S []struct{ F string } }{ 442 S: []struct{ F string }{ 443 {F: "baz"}, 444 }, 445 }, 446 out: &struct{ S []struct{ F string } }{ 447 S: []struct{ F string }{ 448 {F: "baz"}, {F: "foo"}, {F: "bar"}, 449 }, 450 }, 451 order: Prepend, 452 }, 453 { 454 name: "Append map", 455 dst: &struct{ S map[string]string }{ 456 S: map[string]string{ 457 "key0": "", 458 "key1": "dst_value1", 459 "key2": "dst_value2", 460 }, 461 }, 462 src: &struct{ S map[string]string }{ 463 S: map[string]string{ 464 "key0": "src_value0", 465 "key1": "src_value1", 466 "key3": "src_value3", 467 }, 468 }, 469 out: &struct{ S map[string]string }{ 470 S: map[string]string{ 471 "key0": "src_value0", 472 "key1": "src_value1", 473 "key2": "dst_value2", 474 "key3": "src_value3", 475 }, 476 }, 477 order: Append, 478 }, 479 { 480 name: "Prepend map", 481 dst: &struct{ S map[string]string }{ 482 S: map[string]string{ 483 "key0": "", 484 "key1": "dst_value1", 485 "key2": "dst_value2", 486 }, 487 }, 488 src: &struct{ S map[string]string }{ 489 S: map[string]string{ 490 "key0": "src_value0", 491 "key1": "src_value1", 492 "key3": "src_value3", 493 }, 494 }, 495 out: &struct{ S map[string]string }{ 496 S: map[string]string{ 497 "key0": "", 498 "key1": "dst_value1", 499 "key2": "dst_value2", 500 "key3": "src_value3", 501 }, 502 }, 503 order: Prepend, 504 }, 505 { 506 name: "Replace map", 507 dst: &struct{ S map[string]string }{ 508 S: map[string]string{ 509 "key0": "", 510 "key1": "dst_value1", 511 "key2": "dst_value2", 512 }, 513 }, 514 src: &struct{ S map[string]string }{ 515 S: map[string]string{ 516 "key0": "src_value0", 517 "key1": "src_value1", 518 "key3": "src_value3", 519 }, 520 }, 521 out: &struct{ S map[string]string }{ 522 S: map[string]string{ 523 "key0": "src_value0", 524 "key1": "src_value1", 525 "key3": "src_value3", 526 }, 527 }, 528 order: Replace, 529 }, 530 { 531 name: "Append empty map", 532 dst: &struct{ S1, S2 map[string]string }{ 533 S1: map[string]string{"key0": "dst_value0"}, 534 S2: map[string]string{}, 535 }, 536 src: &struct{ S1, S2 map[string]string }{ 537 S1: map[string]string{}, 538 S2: map[string]string{"key0": "src_value0"}, 539 }, 540 out: &struct{ S1, S2 map[string]string }{ 541 S1: map[string]string{"key0": "dst_value0"}, 542 S2: map[string]string{"key0": "src_value0"}, 543 }, 544 order: Append, 545 }, 546 { 547 name: "Prepend empty map", 548 dst: &struct{ S1, S2 map[string]string }{ 549 S1: map[string]string{"key0": "dst_value0"}, 550 S2: map[string]string{}, 551 }, 552 src: &struct{ S1, S2 map[string]string }{ 553 S1: map[string]string{}, 554 S2: map[string]string{"key0": "src_value0"}, 555 }, 556 out: &struct{ S1, S2 map[string]string }{ 557 S1: map[string]string{"key0": "dst_value0"}, 558 S2: map[string]string{"key0": "src_value0"}, 559 }, 560 order: Prepend, 561 }, 562 { 563 name: "Replace empty map", 564 dst: &struct{ S1, S2 map[string]string }{ 565 S1: map[string]string{"key0": "dst_value0"}, 566 S2: map[string]string{}, 567 }, 568 src: &struct{ S1, S2 map[string]string }{ 569 S1: map[string]string{}, 570 S2: map[string]string{"key0": "src_value0"}, 571 }, 572 out: &struct{ S1, S2 map[string]string }{ 573 S1: map[string]string{}, 574 S2: map[string]string{"key0": "src_value0"}, 575 }, 576 order: Replace, 577 }, 578 { 579 name: "Append nil map", 580 dst: &struct{ S1, S2, S3 map[string]string }{ 581 S1: map[string]string{"key0": "dst_value0"}, 582 }, 583 src: &struct{ S1, S2, S3 map[string]string }{ 584 S2: map[string]string{"key0": "src_value0"}, 585 }, 586 out: &struct{ S1, S2, S3 map[string]string }{ 587 S1: map[string]string{"key0": "dst_value0"}, 588 S2: map[string]string{"key0": "src_value0"}, 589 }, 590 order: Append, 591 }, 592 { 593 name: "Prepend nil map", 594 dst: &struct{ S1, S2, S3 map[string]string }{ 595 S1: map[string]string{"key0": "dst_value0"}, 596 }, 597 src: &struct{ S1, S2, S3 map[string]string }{ 598 S2: map[string]string{"key0": "src_value0"}, 599 }, 600 out: &struct{ S1, S2, S3 map[string]string }{ 601 S1: map[string]string{"key0": "dst_value0"}, 602 S2: map[string]string{"key0": "src_value0"}, 603 }, 604 order: Prepend, 605 }, 606 { 607 name: "Replace nil map", 608 dst: &struct{ S1, S2, S3 map[string]string }{ 609 S1: map[string]string{"key0": "dst_value0"}, 610 }, 611 src: &struct{ S1, S2, S3 map[string]string }{ 612 S2: map[string]string{"key0": "src_value0"}, 613 }, 614 out: &struct{ S1, S2, S3 map[string]string }{ 615 S1: map[string]string{"key0": "dst_value0"}, 616 S2: map[string]string{"key0": "src_value0"}, 617 S3: nil, 618 }, 619 order: Replace, 620 }, 621 { 622 name: "Replace slice of structs", 623 dst: &struct{ S []struct{ F string } }{ 624 S: []struct{ F string }{ 625 {F: "foo"}, {F: "bar"}, 626 }, 627 }, 628 src: &struct{ S []struct{ F string } }{ 629 S: []struct{ F string }{ 630 {F: "baz"}, 631 }, 632 }, 633 out: &struct{ S []struct{ F string } }{ 634 S: []struct{ F string }{ 635 {F: "baz"}, 636 }, 637 }, 638 order: Replace, 639 }, 640 { 641 name: "Append pointer", 642 dst: &struct{ S *struct{ S string } }{ 643 S: &struct{ S string }{ 644 S: "string1", 645 }, 646 }, 647 src: &struct{ S *struct{ S string } }{ 648 S: &struct{ S string }{ 649 S: "string2", 650 }, 651 }, 652 out: &struct{ S *struct{ S string } }{ 653 S: &struct{ S string }{ 654 S: "string1string2", 655 }, 656 }, 657 }, 658 { 659 name: "Prepend pointer", 660 dst: &struct{ S *struct{ S string } }{ 661 S: &struct{ S string }{ 662 S: "string1", 663 }, 664 }, 665 src: &struct{ S *struct{ S string } }{ 666 S: &struct{ S string }{ 667 S: "string2", 668 }, 669 }, 670 out: &struct{ S *struct{ S string } }{ 671 S: &struct{ S string }{ 672 S: "string2string1", 673 }, 674 }, 675 order: Prepend, 676 }, 677 { 678 name: "Append interface", 679 dst: &struct{ S interface{} }{ 680 S: &struct{ S string }{ 681 S: "string1", 682 }, 683 }, 684 src: &struct{ S interface{} }{ 685 S: &struct{ S string }{ 686 S: "string2", 687 }, 688 }, 689 out: &struct{ S interface{} }{ 690 S: &struct{ S string }{ 691 S: "string1string2", 692 }, 693 }, 694 }, 695 { 696 name: "Prepend interface", 697 dst: &struct{ S interface{} }{ 698 S: &struct{ S string }{ 699 S: "string1", 700 }, 701 }, 702 src: &struct{ S interface{} }{ 703 S: &struct{ S string }{ 704 S: "string2", 705 }, 706 }, 707 out: &struct{ S interface{} }{ 708 S: &struct{ S string }{ 709 S: "string2string1", 710 }, 711 }, 712 order: Prepend, 713 }, 714 { 715 name: "Unexported field", 716 dst: &struct{ s string }{ 717 s: "string1", 718 }, 719 src: &struct{ s string }{ 720 s: "string2", 721 }, 722 out: &struct{ s string }{ 723 s: "string1", 724 }, 725 }, 726 { 727 name: "Unexported field", 728 dst: &struct{ i *int64 }{ 729 i: Int64Ptr(33), 730 }, 731 src: &struct{ i *int64 }{ 732 i: Int64Ptr(5), 733 }, 734 out: &struct{ i *int64 }{ 735 i: Int64Ptr(33), 736 }, 737 }, 738 { 739 name: "Empty struct", 740 dst: &struct{}{}, 741 src: &struct{}{}, 742 out: &struct{}{}, 743 }, 744 { 745 name: "Interface nil", 746 dst: &struct{ S interface{} }{ 747 S: nil, 748 }, 749 src: &struct{ S interface{} }{ 750 S: nil, 751 }, 752 out: &struct{ S interface{} }{ 753 S: nil, 754 }, 755 }, 756 { 757 name: "Pointer nil", 758 dst: &struct{ S *struct{} }{ 759 S: nil, 760 }, 761 src: &struct{ S *struct{} }{ 762 S: nil, 763 }, 764 out: &struct{ S *struct{} }{ 765 S: nil, 766 }, 767 }, 768 { 769 name: "Anonymous struct", 770 dst: &struct { 771 EmbeddedStruct 772 Nested struct{ EmbeddedStruct } 773 }{ 774 EmbeddedStruct: EmbeddedStruct{ 775 S: "string1", 776 I: Int64Ptr(55), 777 }, 778 Nested: struct{ EmbeddedStruct }{ 779 EmbeddedStruct: EmbeddedStruct{ 780 S: "string2", 781 I: Int64Ptr(-4), 782 }, 783 }, 784 }, 785 src: &struct { 786 EmbeddedStruct 787 Nested struct{ EmbeddedStruct } 788 }{ 789 EmbeddedStruct: EmbeddedStruct{ 790 S: "string3", 791 I: Int64Ptr(66), 792 }, 793 Nested: struct{ EmbeddedStruct }{ 794 EmbeddedStruct: EmbeddedStruct{ 795 S: "string4", 796 I: Int64Ptr(-8), 797 }, 798 }, 799 }, 800 out: &struct { 801 EmbeddedStruct 802 Nested struct{ EmbeddedStruct } 803 }{ 804 EmbeddedStruct: EmbeddedStruct{ 805 S: "string1string3", 806 I: Int64Ptr(66), 807 }, 808 Nested: struct{ EmbeddedStruct }{ 809 EmbeddedStruct: EmbeddedStruct{ 810 S: "string2string4", 811 I: Int64Ptr(-8), 812 }, 813 }, 814 }, 815 }, 816 { 817 name: "BlueprintEmbed struct", 818 dst: &struct { 819 BlueprintEmbed EmbeddedStruct 820 Nested struct{ BlueprintEmbed EmbeddedStruct } 821 }{ 822 BlueprintEmbed: EmbeddedStruct{ 823 S: "string1", 824 I: Int64Ptr(55), 825 }, 826 Nested: struct{ BlueprintEmbed EmbeddedStruct }{ 827 BlueprintEmbed: EmbeddedStruct{ 828 S: "string2", 829 I: Int64Ptr(-4), 830 }, 831 }, 832 }, 833 src: &struct { 834 BlueprintEmbed EmbeddedStruct 835 Nested struct{ BlueprintEmbed EmbeddedStruct } 836 }{ 837 BlueprintEmbed: EmbeddedStruct{ 838 S: "string3", 839 I: Int64Ptr(66), 840 }, 841 Nested: struct{ BlueprintEmbed EmbeddedStruct }{ 842 BlueprintEmbed: EmbeddedStruct{ 843 S: "string4", 844 I: Int64Ptr(-8), 845 }, 846 }, 847 }, 848 out: &struct { 849 BlueprintEmbed EmbeddedStruct 850 Nested struct{ BlueprintEmbed EmbeddedStruct } 851 }{ 852 BlueprintEmbed: EmbeddedStruct{ 853 S: "string1string3", 854 I: Int64Ptr(66), 855 }, 856 Nested: struct{ BlueprintEmbed EmbeddedStruct }{ 857 BlueprintEmbed: EmbeddedStruct{ 858 S: "string2string4", 859 I: Int64Ptr(-8), 860 }, 861 }, 862 }, 863 }, 864 { 865 name: "Anonymous interface", 866 dst: &struct { 867 EmbeddedInterface 868 Nested struct{ EmbeddedInterface } 869 }{ 870 EmbeddedInterface: &struct { 871 S string 872 I *int64 873 }{ 874 S: "string1", 875 I: Int64Ptr(-8), 876 }, 877 Nested: struct{ EmbeddedInterface }{ 878 EmbeddedInterface: &struct { 879 S string 880 I *int64 881 }{ 882 S: "string2", 883 I: Int64Ptr(55), 884 }, 885 }, 886 }, 887 src: &struct { 888 EmbeddedInterface 889 Nested struct{ EmbeddedInterface } 890 }{ 891 EmbeddedInterface: &struct { 892 S string 893 I *int64 894 }{ 895 S: "string3", 896 I: Int64Ptr(6), 897 }, 898 Nested: struct{ EmbeddedInterface }{ 899 EmbeddedInterface: &struct { 900 S string 901 I *int64 902 }{ 903 S: "string4", 904 I: Int64Ptr(6), 905 }, 906 }, 907 }, 908 out: &struct { 909 EmbeddedInterface 910 Nested struct{ EmbeddedInterface } 911 }{ 912 EmbeddedInterface: &struct { 913 S string 914 I *int64 915 }{ 916 S: "string1string3", 917 I: Int64Ptr(6), 918 }, 919 Nested: struct{ EmbeddedInterface }{ 920 EmbeddedInterface: &struct { 921 S string 922 I *int64 923 }{ 924 S: "string2string4", 925 I: Int64Ptr(6), 926 }, 927 }, 928 }, 929 }, 930 { 931 name: "Nil pointer to a struct", 932 dst: &struct { 933 Nested *struct { 934 S string 935 } 936 }{}, 937 src: &struct { 938 Nested *struct { 939 S string 940 } 941 }{ 942 Nested: &struct { 943 S string 944 }{ 945 S: "string", 946 }, 947 }, 948 out: &struct { 949 Nested *struct { 950 S string 951 } 952 }{ 953 Nested: &struct { 954 S string 955 }{ 956 S: "string", 957 }, 958 }, 959 }, 960 { 961 name: "Nil pointer to a struct in an interface", 962 dst: &struct { 963 Nested interface{} 964 }{ 965 Nested: (*struct{ S string })(nil), 966 }, 967 src: &struct { 968 Nested interface{} 969 }{ 970 Nested: &struct { 971 S string 972 }{ 973 S: "string", 974 }, 975 }, 976 out: &struct { 977 Nested interface{} 978 }{ 979 Nested: &struct { 980 S string 981 }{ 982 S: "string", 983 }, 984 }, 985 }, 986 { 987 name: "Interface src nil", 988 dst: &struct{ S interface{} }{ 989 S: &struct{ S string }{ 990 S: "string1", 991 }, 992 }, 993 src: &struct{ S interface{} }{ 994 S: nil, 995 }, 996 out: &struct{ S interface{} }{ 997 S: &struct{ S string }{ 998 S: "string1", 999 }, 1000 }, 1001 }, 1002 1003 // Errors 1004 1005 { 1006 name: "Non-pointer dst", 1007 dst: struct{}{}, 1008 src: &struct{}{}, 1009 err: errors.New("expected pointer to struct, got struct {}"), 1010 out: struct{}{}, 1011 }, 1012 { 1013 name: "Non-pointer src", 1014 dst: &struct{}{}, 1015 src: struct{}{}, 1016 err: errors.New("expected pointer to struct, got struct {}"), 1017 out: &struct{}{}, 1018 }, 1019 { 1020 name: "Non-struct dst", 1021 dst: &[]string{"bad"}, 1022 src: &struct{}{}, 1023 err: errors.New("expected pointer to struct, got *[]string"), 1024 out: &[]string{"bad"}, 1025 }, 1026 { 1027 name: "Non-struct src", 1028 dst: &struct{}{}, 1029 src: &[]string{"bad"}, 1030 err: errors.New("expected pointer to struct, got *[]string"), 1031 out: &struct{}{}, 1032 }, 1033 { 1034 name: "Mismatched types", 1035 dst: &struct{ A string }{ 1036 A: "string1", 1037 }, 1038 src: &struct{ B string }{ 1039 B: "string2", 1040 }, 1041 out: &struct{ A string }{ 1042 A: "string1", 1043 }, 1044 err: errors.New("expected matching types for dst and src, got *struct { A string } and *struct { B string }"), 1045 }, 1046 { 1047 name: "Unsupported kind", 1048 dst: &struct{ I int }{ 1049 I: 1, 1050 }, 1051 src: &struct{ I int }{ 1052 I: 2, 1053 }, 1054 out: &struct{ I int }{ 1055 I: 1, 1056 }, 1057 err: extendPropertyErrorf("i", "unsupported kind int"), 1058 }, 1059 { 1060 name: "Unsupported kind", 1061 dst: &struct{ I int64 }{ 1062 I: 1, 1063 }, 1064 src: &struct{ I int64 }{ 1065 I: 2, 1066 }, 1067 out: &struct{ I int64 }{ 1068 I: 1, 1069 }, 1070 err: extendPropertyErrorf("i", "unsupported kind int64"), 1071 }, 1072 { 1073 name: "Interface nilitude mismatch", 1074 dst: &struct{ S interface{} }{ 1075 S: nil, 1076 }, 1077 src: &struct{ S interface{} }{ 1078 S: &struct{ S string }{ 1079 S: "string1", 1080 }, 1081 }, 1082 out: &struct{ S interface{} }{ 1083 S: nil, 1084 }, 1085 err: extendPropertyErrorf("s", "nilitude mismatch"), 1086 }, 1087 { 1088 name: "Interface type mismatch", 1089 dst: &struct{ S interface{} }{ 1090 S: &struct{ A string }{ 1091 A: "string1", 1092 }, 1093 }, 1094 src: &struct{ S interface{} }{ 1095 S: &struct{ B string }{ 1096 B: "string2", 1097 }, 1098 }, 1099 out: &struct{ S interface{} }{ 1100 S: &struct{ A string }{ 1101 A: "string1", 1102 }, 1103 }, 1104 err: extendPropertyErrorf("s", "mismatched types struct { A string } and struct { B string }"), 1105 }, 1106 { 1107 name: "Interface not a pointer", 1108 dst: &struct{ S interface{} }{ 1109 S: struct{ S string }{ 1110 S: "string1", 1111 }, 1112 }, 1113 src: &struct{ S interface{} }{ 1114 S: struct{ S string }{ 1115 S: "string2", 1116 }, 1117 }, 1118 out: &struct{ S interface{} }{ 1119 S: struct{ S string }{ 1120 S: "string1", 1121 }, 1122 }, 1123 err: extendPropertyErrorf("s", "interface not a pointer"), 1124 }, 1125 { 1126 name: "Pointer not a struct", 1127 dst: &struct{ S *[]string }{ 1128 S: &[]string{"string1"}, 1129 }, 1130 src: &struct{ S *[]string }{ 1131 S: &[]string{"string2"}, 1132 }, 1133 out: &struct{ S *[]string }{ 1134 S: &[]string{"string1"}, 1135 }, 1136 err: extendPropertyErrorf("s", "pointer is a slice"), 1137 }, 1138 { 1139 name: "Error in nested struct", 1140 dst: &struct{ S interface{} }{ 1141 S: &struct{ I int }{ 1142 I: 1, 1143 }, 1144 }, 1145 src: &struct{ S interface{} }{ 1146 S: &struct{ I int }{ 1147 I: 2, 1148 }, 1149 }, 1150 out: &struct{ S interface{} }{ 1151 S: &struct{ I int }{ 1152 I: 1, 1153 }, 1154 }, 1155 err: extendPropertyErrorf("s.i", "unsupported kind int"), 1156 }, 1157 1158 // Filters 1159 1160 { 1161 name: "Filter true", 1162 dst: &struct{ S string }{ 1163 S: "string1", 1164 }, 1165 src: &struct{ S string }{ 1166 S: "string2", 1167 }, 1168 out: &struct{ S string }{ 1169 S: "string1string2", 1170 }, 1171 filter: func(property string, 1172 dstField, srcField reflect.StructField, 1173 dstValue, srcValue interface{}) (bool, error) { 1174 return true, nil 1175 }, 1176 }, 1177 { 1178 name: "Filter false", 1179 dst: &struct{ S string }{ 1180 S: "string1", 1181 }, 1182 src: &struct{ S string }{ 1183 S: "string2", 1184 }, 1185 out: &struct{ S string }{ 1186 S: "string1", 1187 }, 1188 filter: func(property string, 1189 dstField, srcField reflect.StructField, 1190 dstValue, srcValue interface{}) (bool, error) { 1191 return false, nil 1192 }, 1193 }, 1194 { 1195 name: "Filter check args", 1196 dst: &struct{ S string }{ 1197 S: "string1", 1198 }, 1199 src: &struct{ S string }{ 1200 S: "string2", 1201 }, 1202 out: &struct{ S string }{ 1203 S: "string1string2", 1204 }, 1205 filter: func(property string, 1206 dstField, srcField reflect.StructField, 1207 dstValue, srcValue interface{}) (bool, error) { 1208 return property == "s" && 1209 dstField.Name == "S" && srcField.Name == "S" && 1210 dstValue.(string) == "string1" && srcValue.(string) == "string2", nil 1211 }, 1212 }, 1213 { 1214 name: "Filter mutated", 1215 dst: &struct { 1216 S string `blueprint:"mutated"` 1217 }{ 1218 S: "string1", 1219 }, 1220 src: &struct { 1221 S string `blueprint:"mutated"` 1222 }{ 1223 S: "string2", 1224 }, 1225 out: &struct { 1226 S string `blueprint:"mutated"` 1227 }{ 1228 S: "string1", 1229 }, 1230 }, 1231 { 1232 name: "Filter mutated", 1233 dst: &struct { 1234 S *int64 `blueprint:"mutated"` 1235 }{ 1236 S: Int64Ptr(4), 1237 }, 1238 src: &struct { 1239 S *int64 `blueprint:"mutated"` 1240 }{ 1241 S: Int64Ptr(5), 1242 }, 1243 out: &struct { 1244 S *int64 `blueprint:"mutated"` 1245 }{ 1246 S: Int64Ptr(4), 1247 }, 1248 }, 1249 { 1250 name: "Filter error", 1251 dst: &struct{ S string }{ 1252 S: "string1", 1253 }, 1254 src: &struct{ S string }{ 1255 S: "string2", 1256 }, 1257 out: &struct{ S string }{ 1258 S: "string1", 1259 }, 1260 filter: func(property string, 1261 dstField, srcField reflect.StructField, 1262 dstValue, srcValue interface{}) (bool, error) { 1263 return true, fmt.Errorf("filter error") 1264 }, 1265 err: extendPropertyErrorf("s", "filter error"), 1266 }, 1267 } 1268} 1269 1270func TestAppendProperties(t *testing.T) { 1271 for _, testCase := range appendPropertiesTestCases() { 1272 t.Run(testCase.name, func(t *testing.T) { 1273 1274 got := testCase.dst 1275 var err error 1276 var testType string 1277 1278 switch testCase.order { 1279 case Append: 1280 testType = "append" 1281 err = AppendProperties(got, testCase.src, testCase.filter) 1282 case Prepend: 1283 testType = "prepend" 1284 err = PrependProperties(got, testCase.src, testCase.filter) 1285 case Replace: 1286 testType = "replace" 1287 err = ExtendProperties(got, testCase.src, testCase.filter, OrderReplace) 1288 } 1289 1290 check(t, testType, testCase.name, got, err, testCase.out, testCase.err) 1291 }) 1292 } 1293} 1294 1295func TestExtendProperties(t *testing.T) { 1296 for _, testCase := range appendPropertiesTestCases() { 1297 t.Run(testCase.name, func(t *testing.T) { 1298 1299 got := testCase.dst 1300 var err error 1301 var testType string 1302 1303 order := func(property string, 1304 dstField, srcField reflect.StructField, 1305 dstValue, srcValue interface{}) (Order, error) { 1306 switch testCase.order { 1307 case Append: 1308 return Append, nil 1309 case Prepend: 1310 return Prepend, nil 1311 case Replace: 1312 return Replace, nil 1313 } 1314 return Append, errors.New("unknown order") 1315 } 1316 1317 switch testCase.order { 1318 case Append: 1319 testType = "prepend" 1320 case Prepend: 1321 testType = "append" 1322 case Replace: 1323 testType = "replace" 1324 } 1325 1326 err = ExtendProperties(got, testCase.src, testCase.filter, order) 1327 1328 check(t, testType, testCase.name, got, err, testCase.out, testCase.err) 1329 }) 1330 } 1331} 1332 1333type appendMatchingPropertiesTestCase struct { 1334 name string 1335 dst []interface{} 1336 src interface{} 1337 out []interface{} 1338 order Order // default is Append 1339 filter ExtendPropertyFilterFunc 1340 err error 1341} 1342 1343func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { 1344 return []appendMatchingPropertiesTestCase{ 1345 { 1346 name: "Append strings", 1347 dst: []interface{}{&struct{ S string }{ 1348 S: "string1", 1349 }}, 1350 src: &struct{ S string }{ 1351 S: "string2", 1352 }, 1353 out: []interface{}{&struct{ S string }{ 1354 S: "string1string2", 1355 }}, 1356 }, 1357 { 1358 name: "Prepend strings", 1359 dst: []interface{}{&struct{ S string }{ 1360 S: "string1", 1361 }}, 1362 src: &struct{ S string }{ 1363 S: "string2", 1364 }, 1365 out: []interface{}{&struct{ S string }{ 1366 S: "string2string1", 1367 }}, 1368 order: Prepend, 1369 }, 1370 { 1371 name: "Append all", 1372 dst: []interface{}{ 1373 &struct{ S, A string }{ 1374 S: "string1", 1375 }, 1376 &struct{ S, B string }{ 1377 S: "string2", 1378 }, 1379 }, 1380 src: &struct{ S string }{ 1381 S: "string3", 1382 }, 1383 out: []interface{}{ 1384 &struct{ S, A string }{ 1385 S: "string1string3", 1386 }, 1387 &struct{ S, B string }{ 1388 S: "string2string3", 1389 }, 1390 }, 1391 }, 1392 { 1393 name: "Append some", 1394 dst: []interface{}{ 1395 &struct{ S, A string }{ 1396 S: "string1", 1397 }, 1398 &struct{ B string }{}, 1399 }, 1400 src: &struct{ S string }{ 1401 S: "string2", 1402 }, 1403 out: []interface{}{ 1404 &struct{ S, A string }{ 1405 S: "string1string2", 1406 }, 1407 &struct{ B string }{}, 1408 }, 1409 }, 1410 { 1411 name: "Append mismatched structs", 1412 dst: []interface{}{&struct{ S, A string }{ 1413 S: "string1", 1414 }}, 1415 src: &struct{ S string }{ 1416 S: "string2", 1417 }, 1418 out: []interface{}{&struct{ S, A string }{ 1419 S: "string1string2", 1420 }}, 1421 }, 1422 { 1423 name: "Append mismatched pointer structs", 1424 dst: []interface{}{&struct{ S *struct{ S, A string } }{ 1425 S: &struct{ S, A string }{ 1426 S: "string1", 1427 }, 1428 }}, 1429 src: &struct{ S *struct{ S string } }{ 1430 S: &struct{ S string }{ 1431 S: "string2", 1432 }, 1433 }, 1434 out: []interface{}{&struct{ S *struct{ S, A string } }{ 1435 S: &struct{ S, A string }{ 1436 S: "string1string2", 1437 }, 1438 }}, 1439 }, 1440 { 1441 name: "Append through mismatched types", 1442 dst: []interface{}{ 1443 &struct{ B string }{}, 1444 &struct{ S interface{} }{ 1445 S: &struct{ S, A string }{ 1446 S: "string1", 1447 }, 1448 }, 1449 }, 1450 src: &struct{ S struct{ S string } }{ 1451 S: struct{ S string }{ 1452 S: "string2", 1453 }, 1454 }, 1455 out: []interface{}{ 1456 &struct{ B string }{}, 1457 &struct{ S interface{} }{ 1458 S: &struct{ S, A string }{ 1459 S: "string1string2", 1460 }, 1461 }, 1462 }, 1463 }, 1464 { 1465 name: "Append through mismatched types and nil", 1466 dst: []interface{}{ 1467 &struct{ B string }{}, 1468 &struct{ S interface{} }{ 1469 S: (*struct{ S, A string })(nil), 1470 }, 1471 }, 1472 src: &struct{ S struct{ S string } }{ 1473 S: struct{ S string }{ 1474 S: "string2", 1475 }, 1476 }, 1477 out: []interface{}{ 1478 &struct{ B string }{}, 1479 &struct{ S interface{} }{ 1480 S: &struct{ S, A string }{ 1481 S: "string2", 1482 }, 1483 }, 1484 }, 1485 }, 1486 { 1487 name: "Append through multiple matches", 1488 dst: []interface{}{ 1489 &struct { 1490 S struct{ S, A string } 1491 }{ 1492 S: struct{ S, A string }{ 1493 S: "string1", 1494 }, 1495 }, 1496 &struct { 1497 S struct{ S, B string } 1498 }{ 1499 S: struct{ S, B string }{ 1500 S: "string2", 1501 }, 1502 }, 1503 }, 1504 src: &struct{ S struct{ B string } }{ 1505 S: struct{ B string }{ 1506 B: "string3", 1507 }, 1508 }, 1509 out: []interface{}{ 1510 &struct { 1511 S struct{ S, A string } 1512 }{ 1513 S: struct{ S, A string }{ 1514 S: "string1", 1515 }, 1516 }, 1517 &struct { 1518 S struct{ S, B string } 1519 }{ 1520 S: struct{ S, B string }{ 1521 S: "string2", 1522 B: "string3", 1523 }, 1524 }, 1525 }, 1526 }, 1527 { 1528 name: "Append through embedded struct", 1529 dst: []interface{}{ 1530 &struct{ B string }{}, 1531 &struct{ EmbeddedStruct }{ 1532 EmbeddedStruct: EmbeddedStruct{ 1533 S: "string1", 1534 }, 1535 }, 1536 }, 1537 src: &struct{ S string }{ 1538 S: "string2", 1539 }, 1540 out: []interface{}{ 1541 &struct{ B string }{}, 1542 &struct{ EmbeddedStruct }{ 1543 EmbeddedStruct: EmbeddedStruct{ 1544 S: "string1string2", 1545 }, 1546 }, 1547 }, 1548 }, 1549 { 1550 name: "Append through BlueprintEmbed struct", 1551 dst: []interface{}{ 1552 &struct{ B string }{}, 1553 &struct{ BlueprintEmbed EmbeddedStruct }{ 1554 BlueprintEmbed: EmbeddedStruct{ 1555 S: "string1", 1556 }, 1557 }, 1558 }, 1559 src: &struct{ S string }{ 1560 S: "string2", 1561 }, 1562 out: []interface{}{ 1563 &struct{ B string }{}, 1564 &struct{ BlueprintEmbed EmbeddedStruct }{ 1565 BlueprintEmbed: EmbeddedStruct{ 1566 S: "string1string2", 1567 }, 1568 }, 1569 }, 1570 }, 1571 { 1572 name: "Append through embedded pointer to struct", 1573 dst: []interface{}{ 1574 &struct{ B string }{}, 1575 &struct{ *EmbeddedStruct }{ 1576 EmbeddedStruct: &EmbeddedStruct{ 1577 S: "string1", 1578 }, 1579 }, 1580 }, 1581 src: &struct{ S string }{ 1582 S: "string2", 1583 }, 1584 out: []interface{}{ 1585 &struct{ B string }{}, 1586 &struct{ *EmbeddedStruct }{ 1587 EmbeddedStruct: &EmbeddedStruct{ 1588 S: "string1string2", 1589 }, 1590 }, 1591 }, 1592 }, 1593 { 1594 name: "Append through BlueprintEmbed pointer to struct", 1595 dst: []interface{}{ 1596 &struct{ B string }{}, 1597 &struct{ BlueprintEmbed *EmbeddedStruct }{ 1598 BlueprintEmbed: &EmbeddedStruct{ 1599 S: "string1", 1600 }, 1601 }, 1602 }, 1603 src: &struct{ S string }{ 1604 S: "string2", 1605 }, 1606 out: []interface{}{ 1607 &struct{ B string }{}, 1608 &struct{ BlueprintEmbed *EmbeddedStruct }{ 1609 BlueprintEmbed: &EmbeddedStruct{ 1610 S: "string1string2", 1611 }, 1612 }, 1613 }, 1614 }, 1615 { 1616 name: "Append through embedded nil pointer to struct", 1617 dst: []interface{}{ 1618 &struct{ B string }{}, 1619 &struct{ *EmbeddedStruct }{}, 1620 }, 1621 src: &struct{ S string }{ 1622 S: "string2", 1623 }, 1624 out: []interface{}{ 1625 &struct{ B string }{}, 1626 &struct{ *EmbeddedStruct }{ 1627 EmbeddedStruct: &EmbeddedStruct{ 1628 S: "string2", 1629 }, 1630 }, 1631 }, 1632 }, 1633 { 1634 name: "Append through BlueprintEmbed nil pointer to struct", 1635 dst: []interface{}{ 1636 &struct{ B string }{}, 1637 &struct{ BlueprintEmbed *EmbeddedStruct }{}, 1638 }, 1639 src: &struct{ S string }{ 1640 S: "string2", 1641 }, 1642 out: []interface{}{ 1643 &struct{ B string }{}, 1644 &struct{ BlueprintEmbed *EmbeddedStruct }{ 1645 BlueprintEmbed: &EmbeddedStruct{ 1646 S: "string2", 1647 }, 1648 }, 1649 }, 1650 }, 1651 1652 // Errors 1653 1654 { 1655 name: "Non-pointer dst", 1656 dst: []interface{}{struct{}{}}, 1657 src: &struct{}{}, 1658 err: errors.New("expected pointer to struct, got struct {}"), 1659 out: []interface{}{struct{}{}}, 1660 }, 1661 { 1662 name: "Non-pointer src", 1663 dst: []interface{}{&struct{}{}}, 1664 src: struct{}{}, 1665 err: errors.New("expected pointer to struct, got struct {}"), 1666 out: []interface{}{&struct{}{}}, 1667 }, 1668 { 1669 name: "Non-struct dst", 1670 dst: []interface{}{&[]string{"bad"}}, 1671 src: &struct{}{}, 1672 err: errors.New("expected pointer to struct, got *[]string"), 1673 out: []interface{}{&[]string{"bad"}}, 1674 }, 1675 { 1676 name: "Non-struct src", 1677 dst: []interface{}{&struct{}{}}, 1678 src: &[]string{"bad"}, 1679 err: errors.New("expected pointer to struct, got *[]string"), 1680 out: []interface{}{&struct{}{}}, 1681 }, 1682 { 1683 name: "Append none", 1684 dst: []interface{}{ 1685 &struct{ A string }{}, 1686 &struct{ B string }{}, 1687 }, 1688 src: &struct{ S string }{ 1689 S: "string1", 1690 }, 1691 out: []interface{}{ 1692 &struct{ A string }{}, 1693 &struct{ B string }{}, 1694 }, 1695 err: extendPropertyErrorf("s", "failed to find property to extend"), 1696 }, 1697 { 1698 name: "Append mismatched kinds", 1699 dst: []interface{}{ 1700 &struct{ S string }{ 1701 S: "string1", 1702 }, 1703 }, 1704 src: &struct{ S []string }{ 1705 S: []string{"string2"}, 1706 }, 1707 out: []interface{}{ 1708 &struct{ S string }{ 1709 S: "string1", 1710 }, 1711 }, 1712 err: extendPropertyErrorf("s", "mismatched types string and []string"), 1713 }, 1714 { 1715 name: "Append mismatched types", 1716 dst: []interface{}{ 1717 &struct{ S []int }{ 1718 S: []int{1}, 1719 }, 1720 }, 1721 src: &struct{ S []string }{ 1722 S: []string{"string2"}, 1723 }, 1724 out: []interface{}{ 1725 &struct{ S []int }{ 1726 S: []int{1}, 1727 }, 1728 }, 1729 err: extendPropertyErrorf("s", "mismatched types []int and []string"), 1730 }, 1731 } 1732} 1733 1734func TestAppendMatchingProperties(t *testing.T) { 1735 for _, testCase := range appendMatchingPropertiesTestCases() { 1736 t.Run(testCase.name, func(t *testing.T) { 1737 1738 got := testCase.dst 1739 var err error 1740 var testType string 1741 1742 switch testCase.order { 1743 case Append: 1744 testType = "append" 1745 err = AppendMatchingProperties(got, testCase.src, testCase.filter) 1746 case Prepend: 1747 testType = "prepend" 1748 err = PrependMatchingProperties(got, testCase.src, testCase.filter) 1749 case Replace: 1750 testType = "replace" 1751 err = ExtendMatchingProperties(got, testCase.src, testCase.filter, OrderReplace) 1752 } 1753 1754 check(t, testType, testCase.name, got, err, testCase.out, testCase.err) 1755 }) 1756 } 1757} 1758 1759func TestExtendMatchingProperties(t *testing.T) { 1760 for _, testCase := range appendMatchingPropertiesTestCases() { 1761 t.Run(testCase.name, func(t *testing.T) { 1762 1763 got := testCase.dst 1764 var err error 1765 var testType string 1766 1767 order := func(property string, 1768 dstField, srcField reflect.StructField, 1769 dstValue, srcValue interface{}) (Order, error) { 1770 switch testCase.order { 1771 case Append: 1772 return Append, nil 1773 case Prepend: 1774 return Prepend, nil 1775 case Replace: 1776 return Replace, nil 1777 } 1778 return Append, errors.New("unknown order") 1779 } 1780 1781 switch testCase.order { 1782 case Append: 1783 testType = "prepend matching" 1784 case Prepend: 1785 testType = "append matching" 1786 case Replace: 1787 testType = "replace matching" 1788 } 1789 1790 err = ExtendMatchingProperties(got, testCase.src, testCase.filter, order) 1791 1792 check(t, testType, testCase.name, got, err, testCase.out, testCase.err) 1793 }) 1794 } 1795} 1796 1797func check(t *testing.T, testType, testString string, 1798 got interface{}, err error, 1799 expected interface{}, expectedErr error) { 1800 1801 printedTestCase := false 1802 e := func(s string, expected, got interface{}) { 1803 if !printedTestCase { 1804 t.Errorf("test case %s: %s", testType, testString) 1805 printedTestCase = true 1806 } 1807 t.Errorf("incorrect %s", s) 1808 t.Errorf(" expected: %s", p(expected)) 1809 t.Errorf(" got: %s", p(got)) 1810 } 1811 1812 if err != nil { 1813 if expectedErr != nil { 1814 if err.Error() != expectedErr.Error() { 1815 e("unexpected error", expectedErr.Error(), err.Error()) 1816 } 1817 } else { 1818 e("unexpected error", nil, err.Error()) 1819 } 1820 } else { 1821 if expectedErr != nil { 1822 e("missing error", expectedErr, nil) 1823 } 1824 } 1825 1826 if !reflect.DeepEqual(expected, got) { 1827 e("output:", expected, got) 1828 } 1829} 1830 1831func p(in interface{}) string { 1832 if v, ok := in.([]interface{}); ok { 1833 s := make([]string, len(v)) 1834 for i := range v { 1835 s[i] = fmt.Sprintf("%#v", v[i]) 1836 } 1837 return "[" + strings.Join(s, ", ") + "]" 1838 } else { 1839 return fmt.Sprintf("%#v", in) 1840 } 1841} 1842