github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/move_endpoint_module_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package addrs 5 6 import ( 7 "fmt" 8 "strings" 9 "testing" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/terramate-io/tf/tfdiags" 14 ) 15 16 func TestModuleInstanceMoveDestination(t *testing.T) { 17 tests := []struct { 18 DeclModule string 19 StmtFrom, StmtTo string 20 Receiver string 21 WantMatch bool 22 WantResult string 23 }{ 24 { 25 ``, 26 `module.foo`, 27 `module.bar`, 28 `module.foo`, 29 true, 30 `module.bar`, 31 }, 32 { 33 ``, 34 `module.foo`, 35 `module.bar`, 36 `module.foo[1]`, 37 true, 38 `module.bar[1]`, 39 }, 40 { 41 ``, 42 `module.foo`, 43 `module.bar`, 44 `module.foo["a"]`, 45 true, 46 `module.bar["a"]`, 47 }, 48 { 49 ``, 50 `module.foo`, 51 `module.bar.module.foo`, 52 `module.foo`, 53 true, 54 `module.bar.module.foo`, 55 }, 56 { 57 ``, 58 `module.foo.module.bar`, 59 `module.bar`, 60 `module.foo.module.bar`, 61 true, 62 `module.bar`, 63 }, 64 { 65 ``, 66 `module.foo[1]`, 67 `module.foo[2]`, 68 `module.foo[1]`, 69 true, 70 `module.foo[2]`, 71 }, 72 { 73 ``, 74 `module.foo[1]`, 75 `module.foo`, 76 `module.foo[1]`, 77 true, 78 `module.foo`, 79 }, 80 { 81 ``, 82 `module.foo`, 83 `module.foo[1]`, 84 `module.foo`, 85 true, 86 `module.foo[1]`, 87 }, 88 { 89 ``, 90 `module.foo`, 91 `module.foo[1]`, 92 `module.foo.module.bar`, 93 true, 94 `module.foo[1].module.bar`, 95 }, 96 { 97 ``, 98 `module.foo`, 99 `module.foo[1]`, 100 `module.foo.module.bar[0]`, 101 true, 102 `module.foo[1].module.bar[0]`, 103 }, 104 { 105 ``, 106 `module.foo`, 107 `module.bar.module.foo`, 108 `module.foo[0]`, 109 true, 110 `module.bar.module.foo[0]`, 111 }, 112 { 113 ``, 114 `module.foo.module.bar`, 115 `module.bar`, 116 `module.foo.module.bar[0]`, 117 true, 118 `module.bar[0]`, 119 }, 120 { 121 `foo`, 122 `module.bar`, 123 `module.baz`, 124 `module.foo.module.bar`, 125 true, 126 `module.foo.module.baz`, 127 }, 128 { 129 `foo`, 130 `module.bar`, 131 `module.baz`, 132 `module.foo[1].module.bar`, 133 true, 134 `module.foo[1].module.baz`, 135 }, 136 { 137 `foo`, 138 `module.bar`, 139 `module.bar[1]`, 140 `module.foo[1].module.bar`, 141 true, 142 `module.foo[1].module.bar[1]`, 143 }, 144 { 145 ``, 146 `module.foo[1]`, 147 `module.foo[2]`, 148 `module.foo`, 149 false, // the receiver has a non-matching instance key (NoKey) 150 ``, 151 }, 152 { 153 ``, 154 `module.foo[1]`, 155 `module.foo[2]`, 156 `module.foo[2]`, 157 false, // the receiver is already the "to" address 158 ``, 159 }, 160 { 161 ``, 162 `module.foo`, 163 `module.bar`, 164 ``, 165 false, // the root module can never be moved 166 ``, 167 }, 168 { 169 `foo`, 170 `module.bar`, 171 `module.bar[1]`, 172 `module.boz`, 173 false, // the receiver is outside the declaration module 174 ``, 175 }, 176 { 177 `foo.bar`, 178 `module.bar`, 179 `module.bar[1]`, 180 `module.boz`, 181 false, // the receiver is outside the declaration module 182 ``, 183 }, 184 { 185 `foo.bar`, 186 `module.a`, 187 `module.b`, 188 `module.boz`, 189 false, // the receiver is outside the declaration module 190 ``, 191 }, 192 { 193 ``, 194 `module.a1.module.a2`, 195 `module.b1.module.b2`, 196 `module.c`, 197 false, // the receiver is outside the declaration module 198 ``, 199 }, 200 { 201 ``, 202 `module.a1.module.a2[0]`, 203 `module.b1.module.b2[1]`, 204 `module.c`, 205 false, // the receiver is outside the declaration module 206 ``, 207 }, 208 { 209 ``, 210 `module.a1.module.a2`, 211 `module.b1.module.b2`, 212 `module.a1.module.b2`, 213 false, // the receiver is outside the declaration module 214 ``, 215 }, 216 { 217 ``, 218 `module.a1.module.a2`, 219 `module.b1.module.b2`, 220 `module.b1.module.a2`, 221 false, // the receiver is outside the declaration module 222 ``, 223 }, 224 { 225 ``, 226 `module.a1.module.a2[0]`, 227 `module.b1.module.b2[1]`, 228 `module.a1.module.b2[0]`, 229 false, // the receiver is outside the declaration module 230 ``, 231 }, 232 { 233 ``, 234 `foo_instance.bar`, 235 `foo_instance.baz`, 236 `module.foo`, 237 false, // a resource address can never match a module instance 238 ``, 239 }, 240 } 241 242 for _, test := range tests { 243 t.Run( 244 fmt.Sprintf( 245 "%s: %s to %s with %s", 246 test.DeclModule, 247 test.StmtFrom, test.StmtTo, 248 test.Receiver, 249 ), 250 func(t *testing.T) { 251 252 parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { 253 t.Helper() 254 255 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) 256 if hclDiags.HasErrors() { 257 // We're not trying to test the HCL parser here, so any 258 // failures at this point are likely to be bugs in the 259 // test case itself. 260 t.Fatalf("syntax error: %s", hclDiags.Error()) 261 } 262 263 moveEp, diags := ParseMoveEndpoint(traversal) 264 if diags.HasErrors() { 265 t.Fatalf("unexpected error: %s", diags.Err().Error()) 266 } 267 return moveEp 268 } 269 270 fromEPLocal := parseStmtEP(t, test.StmtFrom) 271 toEPLocal := parseStmtEP(t, test.StmtTo) 272 273 declModule := RootModule 274 if test.DeclModule != "" { 275 declModule = strings.Split(test.DeclModule, ".") 276 } 277 fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) 278 if fromEP == nil || toEP == nil { 279 t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) 280 } 281 282 receiverAddr := RootModuleInstance 283 if test.Receiver != "" { 284 var diags tfdiags.Diagnostics 285 receiverAddr, diags = ParseModuleInstanceStr(test.Receiver) 286 if diags.HasErrors() { 287 t.Fatalf("invalid reciever address: %s", diags.Err().Error()) 288 } 289 } 290 gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) 291 if !test.WantMatch { 292 if gotMatch { 293 t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr) 294 } 295 return 296 } 297 298 if !gotMatch { 299 t.Errorf("unexpected non-match\nreceiver: %s\nfrom: %s\nto: %s", test.Receiver, fromEP, toEP) 300 } 301 302 if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { 303 t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) 304 } 305 }, 306 ) 307 } 308 } 309 310 func TestAbsResourceInstanceMoveDestination(t *testing.T) { 311 tests := []struct { 312 DeclModule string 313 StmtFrom, StmtTo string 314 Receiver string 315 WantMatch bool 316 WantResult string 317 }{ 318 { 319 ``, 320 `test_object.beep`, 321 `test_object.boop`, 322 `test_object.beep`, 323 true, 324 `test_object.boop`, 325 }, 326 { 327 ``, 328 `test_object.beep`, 329 `test_object.beep[2]`, 330 `test_object.beep`, 331 true, 332 `test_object.beep[2]`, 333 }, 334 { 335 ``, 336 `test_object.beep`, 337 `module.foo.test_object.beep`, 338 `test_object.beep`, 339 true, 340 `module.foo.test_object.beep`, 341 }, 342 { 343 ``, 344 `test_object.beep[2]`, 345 `module.foo.test_object.beep["a"]`, 346 `test_object.beep[2]`, 347 true, 348 `module.foo.test_object.beep["a"]`, 349 }, 350 { 351 ``, 352 `test_object.beep`, 353 `module.foo[0].test_object.beep`, 354 `test_object.beep`, 355 true, 356 `module.foo[0].test_object.beep`, 357 }, 358 { 359 ``, 360 `module.foo.test_object.beep`, 361 `test_object.beep`, 362 `module.foo.test_object.beep`, 363 true, 364 `test_object.beep`, 365 }, 366 { 367 ``, 368 `module.foo[0].test_object.beep`, 369 `test_object.beep`, 370 `module.foo[0].test_object.beep`, 371 true, 372 `test_object.beep`, 373 }, 374 { 375 `foo`, 376 `test_object.beep`, 377 `test_object.boop`, 378 `module.foo[0].test_object.beep`, 379 true, 380 `module.foo[0].test_object.boop`, 381 }, 382 { 383 `foo`, 384 `test_object.beep`, 385 `test_object.beep[1]`, 386 `module.foo[0].test_object.beep`, 387 true, 388 `module.foo[0].test_object.beep[1]`, 389 }, 390 { 391 ``, 392 `test_object.beep`, 393 `test_object.boop`, 394 `test_object.boop`, 395 false, // the reciever is already the "to" address 396 ``, 397 }, 398 { 399 ``, 400 `test_object.beep[1]`, 401 `test_object.beep[2]`, 402 `test_object.beep[5]`, 403 false, // the receiver has a non-matching instance key 404 ``, 405 }, 406 { 407 `foo`, 408 `test_object.beep`, 409 `test_object.boop`, 410 `test_object.beep`, 411 false, // the receiver is not inside an instance of module "foo" 412 ``, 413 }, 414 { 415 `foo.bar`, 416 `test_object.beep`, 417 `test_object.boop`, 418 `test_object.beep`, 419 false, // the receiver is not inside an instance of module "foo.bar" 420 ``, 421 }, 422 { 423 ``, 424 `module.foo[0].test_object.beep`, 425 `test_object.beep`, 426 `module.foo[1].test_object.beep`, 427 false, // receiver is in a different instance of module.foo 428 ``, 429 }, 430 431 // Moving a module also moves all of the resources declared within it. 432 // The following tests all cover variations of that rule. 433 { 434 ``, 435 `module.foo`, 436 `module.bar`, 437 `module.foo.test_object.beep`, 438 true, 439 `module.bar.test_object.beep`, 440 }, 441 { 442 ``, 443 `module.foo`, 444 `module.bar`, 445 `module.foo[1].test_object.beep`, 446 true, 447 `module.bar[1].test_object.beep`, 448 }, 449 { 450 ``, 451 `module.foo`, 452 `module.bar`, 453 `module.foo["a"].test_object.beep`, 454 true, 455 `module.bar["a"].test_object.beep`, 456 }, 457 { 458 ``, 459 `module.foo`, 460 `module.bar.module.foo`, 461 `module.foo.test_object.beep`, 462 true, 463 `module.bar.module.foo.test_object.beep`, 464 }, 465 { 466 ``, 467 `module.foo.module.bar`, 468 `module.bar`, 469 `module.foo.module.bar.test_object.beep`, 470 true, 471 `module.bar.test_object.beep`, 472 }, 473 { 474 ``, 475 `module.foo[1]`, 476 `module.foo[2]`, 477 `module.foo[1].test_object.beep`, 478 true, 479 `module.foo[2].test_object.beep`, 480 }, 481 { 482 ``, 483 `module.foo[1]`, 484 `module.foo`, 485 `module.foo[1].test_object.beep`, 486 true, 487 `module.foo.test_object.beep`, 488 }, 489 { 490 ``, 491 `module.foo`, 492 `module.foo[1]`, 493 `module.foo.test_object.beep`, 494 true, 495 `module.foo[1].test_object.beep`, 496 }, 497 { 498 ``, 499 `module.foo`, 500 `module.foo[1]`, 501 `module.foo.module.bar.test_object.beep`, 502 true, 503 `module.foo[1].module.bar.test_object.beep`, 504 }, 505 { 506 ``, 507 `module.foo`, 508 `module.foo[1]`, 509 `module.foo.module.bar[0].test_object.beep`, 510 true, 511 `module.foo[1].module.bar[0].test_object.beep`, 512 }, 513 { 514 ``, 515 `module.foo`, 516 `module.bar.module.foo`, 517 `module.foo[0].test_object.beep`, 518 true, 519 `module.bar.module.foo[0].test_object.beep`, 520 }, 521 { 522 ``, 523 `module.foo.module.bar`, 524 `module.bar`, 525 `module.foo.module.bar[0].test_object.beep`, 526 true, 527 `module.bar[0].test_object.beep`, 528 }, 529 { 530 `foo`, 531 `module.bar`, 532 `module.baz`, 533 `module.foo.module.bar.test_object.beep`, 534 true, 535 `module.foo.module.baz.test_object.beep`, 536 }, 537 { 538 `foo`, 539 `module.bar`, 540 `module.baz`, 541 `module.foo[1].module.bar.test_object.beep`, 542 true, 543 `module.foo[1].module.baz.test_object.beep`, 544 }, 545 { 546 `foo`, 547 `module.bar`, 548 `module.bar[1]`, 549 `module.foo[1].module.bar.test_object.beep`, 550 true, 551 `module.foo[1].module.bar[1].test_object.beep`, 552 }, 553 { 554 ``, 555 `module.foo[1]`, 556 `module.foo[2]`, 557 `module.foo.test_object.beep`, 558 false, // the receiver module has a non-matching instance key (NoKey) 559 ``, 560 }, 561 { 562 ``, 563 `module.foo[1]`, 564 `module.foo[2]`, 565 `module.foo[2].test_object.beep`, 566 false, // the receiver is already at the "to" address 567 ``, 568 }, 569 { 570 `foo`, 571 `module.bar`, 572 `module.bar[1]`, 573 `module.boz.test_object.beep`, 574 false, // the receiver module is outside the declaration module 575 ``, 576 }, 577 { 578 `foo.bar`, 579 `module.bar`, 580 `module.bar[1]`, 581 `module.boz.test_object.beep`, 582 false, // the receiver module is outside the declaration module 583 ``, 584 }, 585 { 586 `foo.bar`, 587 `module.a`, 588 `module.b`, 589 `module.boz.test_object.beep`, 590 false, // the receiver module is outside the declaration module 591 ``, 592 }, 593 { 594 ``, 595 `module.a1.module.a2`, 596 `module.b1.module.b2`, 597 `module.c.test_object.beep`, 598 false, // the receiver module is outside the declaration module 599 ``, 600 }, 601 { 602 ``, 603 `module.a1.module.a2[0]`, 604 `module.b1.module.b2[1]`, 605 `module.c.test_object.beep`, 606 false, // the receiver module is outside the declaration module 607 ``, 608 }, 609 { 610 ``, 611 `module.a1.module.a2`, 612 `module.b1.module.b2`, 613 `module.a1.module.b2.test_object.beep`, 614 false, // the receiver module is outside the declaration module 615 ``, 616 }, 617 { 618 ``, 619 `module.a1.module.a2`, 620 `module.b1.module.b2`, 621 `module.b1.module.a2.test_object.beep`, 622 false, // the receiver module is outside the declaration module 623 ``, 624 }, 625 { 626 ``, 627 `module.a1.module.a2[0]`, 628 `module.b1.module.b2[1]`, 629 `module.a1.module.b2[0].test_object.beep`, 630 false, // the receiver module is outside the declaration module 631 ``, 632 }, 633 { 634 ``, 635 `foo_instance.bar`, 636 `foo_instance.baz`, 637 `module.foo.test_object.beep`, 638 false, // the resource address is unrelated to the move statements 639 ``, 640 }, 641 } 642 643 for _, test := range tests { 644 t.Run( 645 fmt.Sprintf( 646 "%s: %s to %s with %s", 647 test.DeclModule, 648 test.StmtFrom, test.StmtTo, 649 test.Receiver, 650 ), 651 func(t *testing.T) { 652 653 parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { 654 t.Helper() 655 656 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) 657 if hclDiags.HasErrors() { 658 // We're not trying to test the HCL parser here, so any 659 // failures at this point are likely to be bugs in the 660 // test case itself. 661 t.Fatalf("syntax error: %s", hclDiags.Error()) 662 } 663 664 moveEp, diags := ParseMoveEndpoint(traversal) 665 if diags.HasErrors() { 666 t.Fatalf("unexpected error: %s", diags.Err().Error()) 667 } 668 return moveEp 669 } 670 671 fromEPLocal := parseStmtEP(t, test.StmtFrom) 672 toEPLocal := parseStmtEP(t, test.StmtTo) 673 674 declModule := RootModule 675 if test.DeclModule != "" { 676 declModule = strings.Split(test.DeclModule, ".") 677 } 678 fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) 679 if fromEP == nil || toEP == nil { 680 t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) 681 } 682 683 receiverAddr, diags := ParseAbsResourceInstanceStr(test.Receiver) 684 if diags.HasErrors() { 685 t.Fatalf("invalid reciever address: %s", diags.Err().Error()) 686 } 687 gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) 688 if !test.WantMatch { 689 if gotMatch { 690 t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr) 691 } 692 return 693 } 694 695 if !gotMatch { 696 t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: (no match)\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult) 697 } 698 699 if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { 700 t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) 701 } 702 }, 703 ) 704 } 705 } 706 707 func TestAbsResourceMoveDestination(t *testing.T) { 708 tests := []struct { 709 DeclModule string 710 StmtFrom, StmtTo string 711 Receiver string 712 WantMatch bool 713 WantResult string 714 }{ 715 { 716 ``, 717 `test_object.beep`, 718 `test_object.boop`, 719 `test_object.beep`, 720 true, 721 `test_object.boop`, 722 }, 723 { 724 ``, 725 `test_object.beep`, 726 `module.foo.test_object.beep`, 727 `test_object.beep`, 728 true, 729 `module.foo.test_object.beep`, 730 }, 731 { 732 ``, 733 `test_object.beep`, 734 `module.foo[0].test_object.beep`, 735 `test_object.beep`, 736 true, 737 `module.foo[0].test_object.beep`, 738 }, 739 { 740 ``, 741 `module.foo.test_object.beep`, 742 `test_object.beep`, 743 `module.foo.test_object.beep`, 744 true, 745 `test_object.beep`, 746 }, 747 { 748 ``, 749 `module.foo[0].test_object.beep`, 750 `test_object.beep`, 751 `module.foo[0].test_object.beep`, 752 true, 753 `test_object.beep`, 754 }, 755 { 756 `foo`, 757 `test_object.beep`, 758 `test_object.boop`, 759 `module.foo[0].test_object.beep`, 760 true, 761 `module.foo[0].test_object.boop`, 762 }, 763 { 764 ``, 765 `test_object.beep`, 766 `test_object.boop`, 767 `test_object.boop`, 768 false, // the reciever is already the "to" address 769 ``, 770 }, 771 { 772 `foo`, 773 `test_object.beep`, 774 `test_object.boop`, 775 `test_object.beep`, 776 false, // the receiver is not inside an instance of module "foo" 777 ``, 778 }, 779 { 780 `foo.bar`, 781 `test_object.beep`, 782 `test_object.boop`, 783 `test_object.beep`, 784 false, // the receiver is not inside an instance of module "foo.bar" 785 ``, 786 }, 787 { 788 ``, 789 `module.foo[0].test_object.beep`, 790 `test_object.beep`, 791 `module.foo[1].test_object.beep`, 792 false, // receiver is in a different instance of module.foo 793 ``, 794 }, 795 796 // Moving a module also moves all of the resources declared within it. 797 // The following tests all cover variations of that rule. 798 { 799 ``, 800 `module.foo`, 801 `module.bar`, 802 `module.foo.test_object.beep`, 803 true, 804 `module.bar.test_object.beep`, 805 }, 806 { 807 ``, 808 `module.foo`, 809 `module.bar`, 810 `module.foo[1].test_object.beep`, 811 true, 812 `module.bar[1].test_object.beep`, 813 }, 814 { 815 ``, 816 `module.foo`, 817 `module.bar`, 818 `module.foo["a"].test_object.beep`, 819 true, 820 `module.bar["a"].test_object.beep`, 821 }, 822 { 823 ``, 824 `module.foo`, 825 `module.bar.module.foo`, 826 `module.foo.test_object.beep`, 827 true, 828 `module.bar.module.foo.test_object.beep`, 829 }, 830 { 831 ``, 832 `module.foo.module.bar`, 833 `module.bar`, 834 `module.foo.module.bar.test_object.beep`, 835 true, 836 `module.bar.test_object.beep`, 837 }, 838 { 839 ``, 840 `module.foo[1]`, 841 `module.foo[2]`, 842 `module.foo[1].test_object.beep`, 843 true, 844 `module.foo[2].test_object.beep`, 845 }, 846 { 847 ``, 848 `module.foo[1]`, 849 `module.foo`, 850 `module.foo[1].test_object.beep`, 851 true, 852 `module.foo.test_object.beep`, 853 }, 854 { 855 ``, 856 `module.foo`, 857 `module.foo[1]`, 858 `module.foo.test_object.beep`, 859 true, 860 `module.foo[1].test_object.beep`, 861 }, 862 { 863 ``, 864 `module.foo`, 865 `module.foo[1]`, 866 `module.foo.module.bar.test_object.beep`, 867 true, 868 `module.foo[1].module.bar.test_object.beep`, 869 }, 870 { 871 ``, 872 `module.foo`, 873 `module.foo[1]`, 874 `module.foo.module.bar[0].test_object.beep`, 875 true, 876 `module.foo[1].module.bar[0].test_object.beep`, 877 }, 878 { 879 ``, 880 `module.foo`, 881 `module.bar.module.foo`, 882 `module.foo[0].test_object.beep`, 883 true, 884 `module.bar.module.foo[0].test_object.beep`, 885 }, 886 { 887 ``, 888 `module.foo.module.bar`, 889 `module.bar`, 890 `module.foo.module.bar[0].test_object.beep`, 891 true, 892 `module.bar[0].test_object.beep`, 893 }, 894 { 895 `foo`, 896 `module.bar`, 897 `module.baz`, 898 `module.foo.module.bar.test_object.beep`, 899 true, 900 `module.foo.module.baz.test_object.beep`, 901 }, 902 { 903 `foo`, 904 `module.bar`, 905 `module.baz`, 906 `module.foo[1].module.bar.test_object.beep`, 907 true, 908 `module.foo[1].module.baz.test_object.beep`, 909 }, 910 { 911 `foo`, 912 `module.bar`, 913 `module.bar[1]`, 914 `module.foo[1].module.bar.test_object.beep`, 915 true, 916 `module.foo[1].module.bar[1].test_object.beep`, 917 }, 918 { 919 ``, 920 `module.foo[1]`, 921 `module.foo[2]`, 922 `module.foo.test_object.beep`, 923 false, // the receiver module has a non-matching instance key (NoKey) 924 ``, 925 }, 926 { 927 ``, 928 `module.foo[1]`, 929 `module.foo[2]`, 930 `module.foo[2].test_object.beep`, 931 false, // the receiver is already at the "to" address 932 ``, 933 }, 934 { 935 `foo`, 936 `module.bar`, 937 `module.bar[1]`, 938 `module.boz.test_object.beep`, 939 false, // the receiver module is outside the declaration module 940 ``, 941 }, 942 { 943 `foo.bar`, 944 `module.bar`, 945 `module.bar[1]`, 946 `module.boz.test_object.beep`, 947 false, // the receiver module is outside the declaration module 948 ``, 949 }, 950 { 951 `foo.bar`, 952 `module.a`, 953 `module.b`, 954 `module.boz.test_object.beep`, 955 false, // the receiver module is outside the declaration module 956 ``, 957 }, 958 { 959 ``, 960 `module.a1.module.a2`, 961 `module.b1.module.b2`, 962 `module.c.test_object.beep`, 963 false, // the receiver module is outside the declaration module 964 ``, 965 }, 966 { 967 ``, 968 `module.a1.module.a2[0]`, 969 `module.b1.module.b2[1]`, 970 `module.c.test_object.beep`, 971 false, // the receiver module is outside the declaration module 972 ``, 973 }, 974 { 975 ``, 976 `module.a1.module.a2`, 977 `module.b1.module.b2`, 978 `module.a1.module.b2.test_object.beep`, 979 false, // the receiver module is outside the declaration module 980 ``, 981 }, 982 { 983 ``, 984 `module.a1.module.a2`, 985 `module.b1.module.b2`, 986 `module.b1.module.a2.test_object.beep`, 987 false, // the receiver module is outside the declaration module 988 ``, 989 }, 990 { 991 ``, 992 `module.a1.module.a2[0]`, 993 `module.b1.module.b2[1]`, 994 `module.a1.module.b2[0].test_object.beep`, 995 false, // the receiver module is outside the declaration module 996 ``, 997 }, 998 { 999 ``, 1000 `foo_instance.bar`, 1001 `foo_instance.baz`, 1002 `module.foo.test_object.beep`, 1003 false, // the resource address is unrelated to the move statements 1004 ``, 1005 }, 1006 } 1007 1008 for i, test := range tests { 1009 t.Run( 1010 fmt.Sprintf( 1011 "[%02d] %s: %s to %s with %s", 1012 i, 1013 test.DeclModule, 1014 test.StmtFrom, test.StmtTo, 1015 test.Receiver, 1016 ), 1017 func(t *testing.T) { 1018 1019 parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { 1020 t.Helper() 1021 1022 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) 1023 if hclDiags.HasErrors() { 1024 // We're not trying to test the HCL parser here, so any 1025 // failures at this point are likely to be bugs in the 1026 // test case itself. 1027 t.Fatalf("syntax error: %s", hclDiags.Error()) 1028 } 1029 1030 moveEp, diags := ParseMoveEndpoint(traversal) 1031 if diags.HasErrors() { 1032 t.Fatalf("unexpected error: %s", diags.Err().Error()) 1033 } 1034 return moveEp 1035 } 1036 1037 fromEPLocal := parseStmtEP(t, test.StmtFrom) 1038 toEPLocal := parseStmtEP(t, test.StmtTo) 1039 1040 declModule := RootModule 1041 if test.DeclModule != "" { 1042 declModule = strings.Split(test.DeclModule, ".") 1043 } 1044 fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) 1045 if fromEP == nil || toEP == nil { 1046 t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) 1047 } 1048 1049 // We only have an AbsResourceInstance parser, not an 1050 // AbsResourceParser, and so we'll just cheat and parse this 1051 // as a resource instance but fail if it includes an instance 1052 // key. 1053 receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Receiver) 1054 if diags.HasErrors() { 1055 t.Fatalf("invalid reciever address: %s", diags.Err().Error()) 1056 } 1057 if receiverInstanceAddr.Resource.Key != NoKey { 1058 t.Fatalf("invalid reciever address: must be a resource, not a resource instance") 1059 } 1060 receiverAddr := receiverInstanceAddr.ContainingResource() 1061 gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) 1062 if !test.WantMatch { 1063 if gotMatch { 1064 t.Errorf("unexpected match\nreceiver: %s (%T)\nfrom: %s\nto: %s\nresult: %s", test.Receiver, receiverAddr, fromEP, toEP, gotAddr) 1065 } 1066 return 1067 } 1068 1069 if !gotMatch { 1070 t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: no match\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult) 1071 } 1072 1073 if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { 1074 t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) 1075 } 1076 }, 1077 ) 1078 } 1079 } 1080 1081 func TestMoveEndpointChainAndNested(t *testing.T) { 1082 tests := []struct { 1083 Endpoint, Other AbsMoveable 1084 EndpointMod, OtherMod Module 1085 CanChainFrom, NestedWithin bool 1086 }{ 1087 { 1088 Endpoint: AbsModuleCall{ 1089 Module: mustParseModuleInstanceStr("module.foo[2]"), 1090 Call: ModuleCall{Name: "bar"}, 1091 }, 1092 Other: AbsModuleCall{ 1093 Module: mustParseModuleInstanceStr("module.foo[2]"), 1094 Call: ModuleCall{Name: "bar"}, 1095 }, 1096 CanChainFrom: true, 1097 NestedWithin: false, 1098 }, 1099 1100 { 1101 Endpoint: mustParseModuleInstanceStr("module.foo[2]"), 1102 Other: AbsModuleCall{ 1103 Module: mustParseModuleInstanceStr("module.foo[2]"), 1104 Call: ModuleCall{Name: "bar"}, 1105 }, 1106 CanChainFrom: false, 1107 NestedWithin: false, 1108 }, 1109 1110 { 1111 Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"), 1112 Other: AbsModuleCall{ 1113 Module: RootModuleInstance, 1114 Call: ModuleCall{Name: "foo"}, 1115 }, 1116 CanChainFrom: false, 1117 NestedWithin: true, 1118 }, 1119 1120 { 1121 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(), 1122 Other: AbsModuleCall{ 1123 Module: mustParseModuleInstanceStr("module.foo[2]"), 1124 Call: ModuleCall{Name: "bar"}, 1125 }, 1126 CanChainFrom: false, 1127 NestedWithin: true, 1128 }, 1129 1130 { 1131 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"), 1132 Other: AbsModuleCall{ 1133 Module: mustParseModuleInstanceStr("module.foo[2]"), 1134 Call: ModuleCall{Name: "bar"}, 1135 }, 1136 CanChainFrom: false, 1137 NestedWithin: true, 1138 }, 1139 1140 { 1141 Endpoint: AbsModuleCall{ 1142 Module: mustParseModuleInstanceStr("module.foo[2]"), 1143 Call: ModuleCall{Name: "bar"}, 1144 }, 1145 Other: mustParseModuleInstanceStr("module.foo[2]"), 1146 CanChainFrom: false, 1147 NestedWithin: true, 1148 }, 1149 1150 { 1151 Endpoint: mustParseModuleInstanceStr("module.foo[2]"), 1152 Other: mustParseModuleInstanceStr("module.foo[2]"), 1153 CanChainFrom: true, 1154 NestedWithin: false, 1155 }, 1156 1157 { 1158 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1159 Other: mustParseModuleInstanceStr("module.foo[2]"), 1160 CanChainFrom: false, 1161 NestedWithin: true, 1162 }, 1163 1164 { 1165 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"), 1166 Other: mustParseModuleInstanceStr("module.foo[2]"), 1167 CanChainFrom: false, 1168 NestedWithin: true, 1169 }, 1170 1171 { 1172 Endpoint: AbsModuleCall{ 1173 Module: mustParseModuleInstanceStr("module.foo[2]"), 1174 Call: ModuleCall{Name: "bar"}, 1175 }, 1176 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1177 CanChainFrom: false, 1178 NestedWithin: false, 1179 }, 1180 1181 { 1182 Endpoint: mustParseModuleInstanceStr("module.foo[2]"), 1183 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1184 CanChainFrom: false, 1185 NestedWithin: false, 1186 }, 1187 1188 { 1189 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1190 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1191 CanChainFrom: true, 1192 NestedWithin: false, 1193 }, 1194 1195 { 1196 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1197 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(), 1198 CanChainFrom: false, 1199 NestedWithin: true, 1200 }, 1201 1202 { 1203 Endpoint: AbsModuleCall{ 1204 Module: mustParseModuleInstanceStr("module.foo[2]"), 1205 Call: ModuleCall{Name: "bar"}, 1206 }, 1207 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1208 CanChainFrom: false, 1209 }, 1210 1211 { 1212 Endpoint: mustParseModuleInstanceStr("module.foo[2]"), 1213 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1214 CanChainFrom: false, 1215 }, 1216 { 1217 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1218 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1219 CanChainFrom: false, 1220 }, 1221 1222 { 1223 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1224 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1225 CanChainFrom: true, 1226 }, 1227 1228 { 1229 Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), 1230 EndpointMod: Module{"foo"}, 1231 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1232 CanChainFrom: true, 1233 }, 1234 1235 { 1236 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1237 Other: mustParseAbsResourceInstanceStr("resource.baz"), 1238 OtherMod: Module{"foo"}, 1239 CanChainFrom: true, 1240 }, 1241 1242 { 1243 Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), 1244 EndpointMod: Module{"foo"}, 1245 Other: mustParseAbsResourceInstanceStr("resource.baz"), 1246 OtherMod: Module{"foo"}, 1247 CanChainFrom: true, 1248 }, 1249 1250 { 1251 Endpoint: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), 1252 EndpointMod: Module{"foo"}, 1253 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1254 CanChainFrom: true, 1255 }, 1256 1257 { 1258 Endpoint: mustParseModuleInstanceStr("module.foo[2].module.baz"), 1259 Other: mustParseModuleInstanceStr("module.baz"), 1260 OtherMod: Module{"foo"}, 1261 CanChainFrom: true, 1262 }, 1263 1264 { 1265 Endpoint: AbsModuleCall{ 1266 Call: ModuleCall{Name: "bing"}, 1267 }, 1268 EndpointMod: Module{"foo", "baz"}, 1269 Other: AbsModuleCall{ 1270 Module: mustParseModuleInstanceStr("module.baz"), 1271 Call: ModuleCall{Name: "bing"}, 1272 }, 1273 OtherMod: Module{"foo"}, 1274 CanChainFrom: true, 1275 }, 1276 1277 { 1278 Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), 1279 EndpointMod: Module{"foo"}, 1280 Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), 1281 NestedWithin: true, 1282 }, 1283 1284 { 1285 Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), 1286 Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), 1287 OtherMod: Module{"foo"}, 1288 NestedWithin: true, 1289 }, 1290 1291 { 1292 Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), 1293 EndpointMod: Module{"foo"}, 1294 Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), 1295 OtherMod: Module{"foo"}, 1296 NestedWithin: true, 1297 }, 1298 1299 { 1300 Endpoint: mustParseAbsResourceInstanceStr("ressurce.baz").ContainingResource(), 1301 EndpointMod: Module{"foo"}, 1302 Other: mustParseModuleInstanceStr("module.foo[2]"), 1303 NestedWithin: true, 1304 }, 1305 1306 { 1307 Endpoint: AbsModuleCall{ 1308 Call: ModuleCall{Name: "bang"}, 1309 }, 1310 EndpointMod: Module{"foo", "baz", "bing"}, 1311 Other: AbsModuleCall{ 1312 Module: mustParseModuleInstanceStr("module.baz"), 1313 Call: ModuleCall{Name: "bing"}, 1314 }, 1315 OtherMod: Module{"foo"}, 1316 NestedWithin: true, 1317 }, 1318 1319 { 1320 Endpoint: AbsModuleCall{ 1321 Module: mustParseModuleInstanceStr("module.bing"), 1322 Call: ModuleCall{Name: "bang"}, 1323 }, 1324 EndpointMod: Module{"foo", "baz"}, 1325 Other: AbsModuleCall{ 1326 Module: mustParseModuleInstanceStr("module.foo.module.baz"), 1327 Call: ModuleCall{Name: "bing"}, 1328 }, 1329 NestedWithin: true, 1330 }, 1331 } 1332 1333 for i, test := range tests { 1334 t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other), 1335 func(t *testing.T) { 1336 endpoint := &MoveEndpointInModule{ 1337 relSubject: test.Endpoint, 1338 module: test.EndpointMod, 1339 } 1340 1341 other := &MoveEndpointInModule{ 1342 relSubject: test.Other, 1343 module: test.OtherMod, 1344 } 1345 1346 if endpoint.CanChainFrom(other) != test.CanChainFrom { 1347 t.Errorf("expected %s CanChainFrom %s == %t", endpoint, other, test.CanChainFrom) 1348 } 1349 1350 if endpoint.NestedWithin(other) != test.NestedWithin { 1351 t.Errorf("expected %s NestedWithin %s == %t", endpoint, other, test.NestedWithin) 1352 } 1353 }, 1354 ) 1355 } 1356 } 1357 1358 func TestSelectsModule(t *testing.T) { 1359 tests := []struct { 1360 Endpoint *MoveEndpointInModule 1361 Addr ModuleInstance 1362 Selects bool 1363 }{ 1364 { 1365 Endpoint: &MoveEndpointInModule{ 1366 relSubject: AbsModuleCall{ 1367 Module: mustParseModuleInstanceStr("module.foo[2]"), 1368 Call: ModuleCall{Name: "bar"}, 1369 }, 1370 }, 1371 Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1]"), 1372 Selects: true, 1373 }, 1374 { 1375 Endpoint: &MoveEndpointInModule{ 1376 module: mustParseModuleInstanceStr("module.foo").Module(), 1377 relSubject: AbsModuleCall{ 1378 Module: mustParseModuleInstanceStr("module.bar[2]"), 1379 Call: ModuleCall{Name: "baz"}, 1380 }, 1381 }, 1382 Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[2].module.baz"), 1383 Selects: true, 1384 }, 1385 { 1386 Endpoint: &MoveEndpointInModule{ 1387 module: mustParseModuleInstanceStr("module.foo").Module(), 1388 relSubject: AbsModuleCall{ 1389 Module: mustParseModuleInstanceStr("module.bar[2]"), 1390 Call: ModuleCall{Name: "baz"}, 1391 }, 1392 }, 1393 Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1].module.baz"), 1394 Selects: false, 1395 }, 1396 { 1397 Endpoint: &MoveEndpointInModule{ 1398 relSubject: AbsModuleCall{ 1399 Module: mustParseModuleInstanceStr("module.bar"), 1400 Call: ModuleCall{Name: "baz"}, 1401 }, 1402 }, 1403 Addr: mustParseModuleInstanceStr("module.bar[1].module.baz"), 1404 Selects: false, 1405 }, 1406 { 1407 Endpoint: &MoveEndpointInModule{ 1408 module: mustParseModuleInstanceStr("module.foo").Module(), 1409 relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`), 1410 }, 1411 Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`), 1412 Selects: true, 1413 }, 1414 { 1415 Endpoint: &MoveEndpointInModule{ 1416 relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), 1417 }, 1418 Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), 1419 Selects: true, 1420 }, 1421 { 1422 Endpoint: &MoveEndpointInModule{ 1423 relSubject: mustParseAbsResourceInstanceStr(`module.bar.module.baz["key"].resource.name`).ContainingResource(), 1424 }, 1425 Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), 1426 Selects: true, 1427 }, 1428 { 1429 Endpoint: &MoveEndpointInModule{ 1430 module: mustParseModuleInstanceStr("module.nope").Module(), 1431 relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`), 1432 }, 1433 Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`), 1434 Selects: false, 1435 }, 1436 { 1437 Endpoint: &MoveEndpointInModule{ 1438 relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), 1439 }, 1440 Addr: mustParseModuleInstanceStr(`module.bar.module.baz["nope"]`), 1441 Selects: false, 1442 }, 1443 { 1444 Endpoint: &MoveEndpointInModule{ 1445 relSubject: mustParseAbsResourceInstanceStr(`module.nope.module.baz["key"].resource.name`).ContainingResource(), 1446 }, 1447 Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), 1448 Selects: false, 1449 }, 1450 } 1451 1452 for i, test := range tests { 1453 t.Run(fmt.Sprintf("[%02d]%s.SelectsModule(%s)", i, test.Endpoint, test.Addr), 1454 func(t *testing.T) { 1455 if test.Endpoint.SelectsModule(test.Addr) != test.Selects { 1456 t.Errorf("expected %s SelectsModule %s == %t", test.Endpoint, test.Addr, test.Selects) 1457 } 1458 }, 1459 ) 1460 } 1461 } 1462 1463 func TestSelectsResource(t *testing.T) { 1464 matchingResource := Resource{ 1465 Mode: ManagedResourceMode, 1466 Type: "foo", 1467 Name: "matching", 1468 } 1469 unmatchingResource := Resource{ 1470 Mode: ManagedResourceMode, 1471 Type: "foo", 1472 Name: "unmatching", 1473 } 1474 childMod := Module{ 1475 "child", 1476 } 1477 childModMatchingInst := ModuleInstance{ 1478 ModuleInstanceStep{Name: "child", InstanceKey: StringKey("matching")}, 1479 } 1480 childModUnmatchingInst := ModuleInstance{ 1481 ModuleInstanceStep{Name: "child", InstanceKey: StringKey("unmatching")}, 1482 } 1483 1484 tests := []struct { 1485 Endpoint *MoveEndpointInModule 1486 Addr AbsResource 1487 Selects bool 1488 }{ 1489 { 1490 Endpoint: &MoveEndpointInModule{ 1491 relSubject: matchingResource.Absolute(nil), 1492 }, 1493 Addr: matchingResource.Absolute(nil), 1494 Selects: true, // exact match 1495 }, 1496 { 1497 Endpoint: &MoveEndpointInModule{ 1498 relSubject: unmatchingResource.Absolute(nil), 1499 }, 1500 Addr: matchingResource.Absolute(nil), 1501 Selects: false, // wrong resource name 1502 }, 1503 { 1504 Endpoint: &MoveEndpointInModule{ 1505 relSubject: unmatchingResource.Instance(IntKey(1)).Absolute(nil), 1506 }, 1507 Addr: matchingResource.Absolute(nil), 1508 Selects: false, // wrong resource name 1509 }, 1510 { 1511 Endpoint: &MoveEndpointInModule{ 1512 relSubject: matchingResource.Instance(NoKey).Absolute(nil), 1513 }, 1514 Addr: matchingResource.Absolute(nil), 1515 Selects: true, // matches one instance 1516 }, 1517 { 1518 Endpoint: &MoveEndpointInModule{ 1519 relSubject: matchingResource.Instance(IntKey(0)).Absolute(nil), 1520 }, 1521 Addr: matchingResource.Absolute(nil), 1522 Selects: true, // matches one instance 1523 }, 1524 { 1525 Endpoint: &MoveEndpointInModule{ 1526 relSubject: matchingResource.Instance(StringKey("a")).Absolute(nil), 1527 }, 1528 Addr: matchingResource.Absolute(nil), 1529 Selects: true, // matches one instance 1530 }, 1531 { 1532 Endpoint: &MoveEndpointInModule{ 1533 module: childMod, 1534 relSubject: matchingResource.Absolute(nil), 1535 }, 1536 Addr: matchingResource.Absolute(childModMatchingInst), 1537 Selects: true, // in one of the instances of the module where the statement was written 1538 }, 1539 { 1540 Endpoint: &MoveEndpointInModule{ 1541 relSubject: matchingResource.Absolute(childModMatchingInst), 1542 }, 1543 Addr: matchingResource.Absolute(childModMatchingInst), 1544 Selects: true, // exact match 1545 }, 1546 { 1547 Endpoint: &MoveEndpointInModule{ 1548 relSubject: matchingResource.Instance(IntKey(2)).Absolute(childModMatchingInst), 1549 }, 1550 Addr: matchingResource.Absolute(childModMatchingInst), 1551 Selects: true, // matches one instance 1552 }, 1553 { 1554 Endpoint: &MoveEndpointInModule{ 1555 relSubject: matchingResource.Absolute(childModMatchingInst), 1556 }, 1557 Addr: matchingResource.Absolute(childModUnmatchingInst), 1558 Selects: false, // the containing module instance doesn't match 1559 }, 1560 { 1561 Endpoint: &MoveEndpointInModule{ 1562 relSubject: AbsModuleCall{ 1563 Module: mustParseModuleInstanceStr("module.foo[2]"), 1564 Call: ModuleCall{Name: "bar"}, 1565 }, 1566 }, 1567 Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")), 1568 Selects: false, // a module call can't match a resource 1569 }, 1570 { 1571 Endpoint: &MoveEndpointInModule{ 1572 relSubject: mustParseModuleInstanceStr("module.foo[2]"), 1573 }, 1574 Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")), 1575 Selects: false, // a module instance can't match a resource 1576 }, 1577 } 1578 1579 for i, test := range tests { 1580 t.Run(fmt.Sprintf("[%02d]%s SelectsResource(%s)", i, test.Endpoint, test.Addr), 1581 func(t *testing.T) { 1582 if got, want := test.Endpoint.SelectsResource(test.Addr), test.Selects; got != want { 1583 t.Errorf("wrong result\nReceiver: %s\nArgument: %s\ngot: %t\nwant: %t", test.Endpoint, test.Addr, got, want) 1584 } 1585 }, 1586 ) 1587 } 1588 } 1589 1590 func TestIsModuleMoveReIndex(t *testing.T) { 1591 tests := []struct { 1592 from, to AbsMoveable 1593 expect bool 1594 }{ 1595 { 1596 from: mustParseModuleInstanceStr(`module.bar`), 1597 to: mustParseModuleInstanceStr(`module.bar`), 1598 expect: true, 1599 }, 1600 { 1601 from: mustParseModuleInstanceStr(`module.bar`), 1602 to: mustParseModuleInstanceStr(`module.bar[0]`), 1603 expect: true, 1604 }, 1605 { 1606 from: AbsModuleCall{ 1607 Call: ModuleCall{Name: "bar"}, 1608 }, 1609 to: mustParseModuleInstanceStr(`module.bar[0]`), 1610 expect: true, 1611 }, 1612 { 1613 from: mustParseModuleInstanceStr(`module.bar["a"]`), 1614 to: AbsModuleCall{ 1615 Call: ModuleCall{Name: "bar"}, 1616 }, 1617 expect: true, 1618 }, 1619 { 1620 from: mustParseModuleInstanceStr(`module.foo`), 1621 to: mustParseModuleInstanceStr(`module.bar`), 1622 expect: false, 1623 }, 1624 { 1625 from: mustParseModuleInstanceStr(`module.bar`), 1626 to: mustParseModuleInstanceStr(`module.foo[0]`), 1627 expect: false, 1628 }, 1629 { 1630 from: AbsModuleCall{ 1631 Call: ModuleCall{Name: "bar"}, 1632 }, 1633 to: mustParseModuleInstanceStr(`module.foo[0]`), 1634 expect: false, 1635 }, 1636 { 1637 from: mustParseModuleInstanceStr(`module.bar["a"]`), 1638 to: AbsModuleCall{ 1639 Call: ModuleCall{Name: "foo"}, 1640 }, 1641 expect: false, 1642 }, 1643 { 1644 from: mustParseModuleInstanceStr(`module.bar.module.baz`), 1645 to: mustParseModuleInstanceStr(`module.bar.module.baz`), 1646 expect: true, 1647 }, 1648 { 1649 from: mustParseModuleInstanceStr(`module.bar.module.baz`), 1650 to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1651 expect: true, 1652 }, 1653 { 1654 from: mustParseModuleInstanceStr(`module.bar.module.baz`), 1655 to: mustParseModuleInstanceStr(`module.baz.module.baz`), 1656 expect: false, 1657 }, 1658 { 1659 from: mustParseModuleInstanceStr(`module.bar.module.baz`), 1660 to: mustParseModuleInstanceStr(`module.baz.module.baz[0]`), 1661 expect: false, 1662 }, 1663 { 1664 from: mustParseModuleInstanceStr(`module.bar.module.baz`), 1665 to: mustParseModuleInstanceStr(`module.bar[0].module.baz`), 1666 expect: true, 1667 }, 1668 { 1669 from: mustParseModuleInstanceStr(`module.bar[0].module.baz`), 1670 to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1671 expect: true, 1672 }, 1673 { 1674 from: mustParseModuleInstanceStr(`module.bar[0].module.baz`), 1675 to: mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`), 1676 expect: true, 1677 }, 1678 { 1679 from: AbsModuleCall{ 1680 Call: ModuleCall{Name: "baz"}, 1681 }, 1682 to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1683 expect: false, 1684 }, 1685 { 1686 from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1687 to: AbsModuleCall{ 1688 Call: ModuleCall{Name: "baz"}, 1689 }, 1690 expect: false, 1691 }, 1692 1693 { 1694 from: AbsModuleCall{ 1695 Module: mustParseModuleInstanceStr(`module.bar[0]`), 1696 Call: ModuleCall{Name: "baz"}, 1697 }, 1698 to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1699 expect: true, 1700 }, 1701 1702 { 1703 from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1704 to: AbsModuleCall{ 1705 Module: mustParseModuleInstanceStr(`module.bar[0]`), 1706 Call: ModuleCall{Name: "baz"}, 1707 }, 1708 expect: true, 1709 }, 1710 1711 { 1712 from: mustParseModuleInstanceStr(`module.baz`), 1713 to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1714 expect: false, 1715 }, 1716 { 1717 from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), 1718 to: mustParseModuleInstanceStr(`module.baz`), 1719 expect: false, 1720 }, 1721 } 1722 1723 for i, test := range tests { 1724 t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to), 1725 func(t *testing.T) { 1726 from := &MoveEndpointInModule{ 1727 relSubject: test.from, 1728 } 1729 1730 to := &MoveEndpointInModule{ 1731 relSubject: test.to, 1732 } 1733 1734 if got := from.IsModuleReIndex(to); got != test.expect { 1735 t.Errorf("expected %t, got %t", test.expect, got) 1736 } 1737 }, 1738 ) 1739 } 1740 } 1741 1742 func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance { 1743 r, diags := ParseAbsResourceInstanceStr(s) 1744 if diags.HasErrors() { 1745 panic(diags.ErrWithWarnings().Error()) 1746 } 1747 return r 1748 }