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