github.com/opentofu/opentofu@v1.7.1/internal/command/test_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 command 7 8 import ( 9 "path" 10 "strings" 11 "testing" 12 13 "github.com/google/go-cmp/cmp" 14 "github.com/mitchellh/cli" 15 "github.com/zclconf/go-cty/cty" 16 17 "github.com/opentofu/opentofu/internal/addrs" 18 testing_command "github.com/opentofu/opentofu/internal/command/testing" 19 "github.com/opentofu/opentofu/internal/command/views" 20 "github.com/opentofu/opentofu/internal/providers" 21 "github.com/opentofu/opentofu/internal/terminal" 22 ) 23 24 func TestTest(t *testing.T) { 25 tcs := map[string]struct { 26 override string 27 args []string 28 expected string 29 code int 30 skip bool 31 }{ 32 "simple_pass": { 33 expected: "1 passed, 0 failed.", 34 code: 0, 35 }, 36 "simple_pass_nested": { 37 expected: "1 passed, 0 failed.", 38 code: 0, 39 }, 40 "simple_pass_nested_alternate": { 41 args: []string{"-test-directory", "other"}, 42 expected: "1 passed, 0 failed.", 43 code: 0, 44 }, 45 "simple_pass_very_nested": { 46 args: []string{"-test-directory", "tests/subdir"}, 47 expected: "1 passed, 0 failed.", 48 code: 0, 49 }, 50 "simple_pass_very_nested_alternate": { 51 override: "simple_pass_very_nested", 52 args: []string{"-test-directory", "./tests/subdir"}, 53 expected: "1 passed, 0 failed.", 54 code: 0, 55 }, 56 "pass_with_locals": { 57 expected: "1 passed, 0 failed.", 58 code: 0, 59 }, 60 "pass_with_outputs": { 61 expected: "1 passed, 0 failed.", 62 code: 0, 63 }, 64 "pass_with_variables": { 65 expected: "2 passed, 0 failed.", 66 code: 0, 67 }, 68 "plan_then_apply": { 69 expected: "2 passed, 0 failed.", 70 code: 0, 71 }, 72 "expect_failures_checks": { 73 expected: "1 passed, 0 failed.", 74 code: 0, 75 }, 76 "expect_failures_inputs": { 77 expected: "1 passed, 0 failed.", 78 code: 0, 79 }, 80 "expect_failures_outputs": { 81 expected: "1 passed, 0 failed.", 82 code: 0, 83 }, 84 "expect_runtime_check_fail": { 85 expected: "0 passed, 1 failed.", 86 code: 1, 87 }, 88 "expect_runtime_check_pass_with_expect": { 89 expected: "1 passed, 0 failed.", 90 code: 0, 91 }, 92 "expect_runtime_check_pass_command_plan_expected": { 93 expected: "1 passed, 0 failed.", 94 code: 0, 95 }, 96 "expect_runtime_check_fail_command_plan": { 97 expected: "0 passed, 1 failed.", 98 code: 1, 99 }, 100 "expect_failures_resources": { 101 expected: "1 passed, 0 failed.", 102 code: 0, 103 }, 104 "multiple_files": { 105 expected: "2 passed, 0 failed", 106 code: 0, 107 }, 108 "multiple_files_with_filter": { 109 override: "multiple_files", 110 args: []string{"-filter=one.tftest.hcl"}, 111 expected: "1 passed, 0 failed", 112 code: 0, 113 }, 114 "variables": { 115 expected: "2 passed, 0 failed", 116 code: 0, 117 }, 118 "variables_overridden": { 119 override: "variables", 120 args: []string{"-var=input=foo"}, 121 expected: "1 passed, 1 failed", 122 code: 1, 123 }, 124 "simple_fail": { 125 expected: "0 passed, 1 failed.", 126 code: 1, 127 }, 128 "custom_condition_checks": { 129 expected: "0 passed, 1 failed.", 130 code: 1, 131 }, 132 "custom_condition_inputs": { 133 expected: "0 passed, 1 failed.", 134 code: 1, 135 }, 136 "custom_condition_outputs": { 137 expected: "0 passed, 1 failed.", 138 code: 1, 139 }, 140 "custom_condition_resources": { 141 expected: "0 passed, 1 failed.", 142 code: 1, 143 }, 144 "no_providers_in_main": { 145 expected: "1 passed, 0 failed", 146 code: 0, 147 }, 148 "default_variables": { 149 expected: "1 passed, 0 failed.", 150 code: 0, 151 }, 152 "undefined_variables": { 153 expected: "1 passed, 0 failed.", 154 code: 0, 155 }, 156 "refresh_only": { 157 expected: "3 passed, 0 failed.", 158 code: 0, 159 }, 160 "null_output": { 161 expected: "1 passed, 0 failed.", 162 code: 0, 163 }, 164 "pass_with_tests_dir_variables": { 165 expected: "1 passed, 0 failed.", 166 code: 0, 167 }, 168 "override_with_tests_dir_variables": { 169 expected: "1 passed, 0 failed.", 170 code: 0, 171 }, 172 } 173 for name, tc := range tcs { 174 t.Run(name, func(t *testing.T) { 175 if tc.skip { 176 t.Skip() 177 } 178 179 file := name 180 if len(tc.override) > 0 { 181 file = tc.override 182 } 183 184 td := t.TempDir() 185 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 186 defer testChdir(t, td)() 187 188 provider := testing_command.NewProvider(nil) 189 view, done := testView(t) 190 191 c := &TestCommand{ 192 Meta: Meta{ 193 testingOverrides: metaOverridesForProvider(provider.Provider), 194 View: view, 195 }, 196 } 197 198 code := c.Run(tc.args) 199 output := done(t) 200 201 if code != tc.code { 202 t.Errorf("expected status code %d but got %d", tc.code, code) 203 } 204 205 if !strings.Contains(output.Stdout(), tc.expected) { 206 t.Errorf("output didn't contain expected string:\n\n%s", output.All()) 207 } 208 209 if provider.ResourceCount() > 0 { 210 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 211 } 212 }) 213 } 214 } 215 func TestTest_Full_Output(t *testing.T) { 216 tcs := map[string]struct { 217 override string 218 args []string 219 expected string 220 code int 221 skip bool 222 }{ 223 "broken_no_valid_hcl": { 224 expected: "Unsupported block type", 225 code: 1, 226 }, 227 "expect_runtime_check_fail_command_plan": { 228 expected: "Check block assertion known after apply", 229 code: 1, 230 }, 231 "broken_wrong_block_resource": { 232 expected: "Blocks of type \"resource\" are not expected here.", 233 code: 1, 234 }, 235 "broken_wrong_block_data": { 236 expected: "Blocks of type \"data\" are not expected here.", 237 code: 1, 238 }, 239 "broken_wrong_block_output": { 240 expected: "Blocks of type \"output\" are not expected here.", 241 code: 1, 242 }, 243 "broken_wrong_block_check": { 244 expected: "Blocks of type \"check\" are not expected here.", 245 code: 1, 246 }, 247 "not_exists_output": { 248 expected: "Error: Reference to undeclared output value", 249 args: []string{"-no-color"}, 250 code: 1, 251 }, 252 "refresh_conflicting_config": { 253 expected: "Incompatible plan options", 254 code: 1, 255 }, 256 "is_sorted": { 257 expected: "1.tftest.hcl... pass\n run \"a\"... pass\n2.tftest.hcl... pass\n run \"b\"... pass\n3.tftest.hcl... pass\n run \"c\"... pass", 258 code: 0, 259 args: []string{"-no-color"}, 260 }, 261 } 262 for name, tc := range tcs { 263 t.Run(name, func(t *testing.T) { 264 if tc.skip { 265 t.Skip() 266 } 267 268 file := name 269 if len(tc.override) > 0 { 270 file = tc.override 271 } 272 273 td := t.TempDir() 274 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 275 defer testChdir(t, td)() 276 277 provider := testing_command.NewProvider(nil) 278 view, done := testView(t) 279 280 c := &TestCommand{ 281 Meta: Meta{ 282 testingOverrides: metaOverridesForProvider(provider.Provider), 283 View: view, 284 }, 285 } 286 287 code := c.Run(tc.args) 288 output := done(t) 289 290 if code != tc.code { 291 t.Errorf("expected status code %d but got %d", tc.code, code) 292 } 293 294 if !strings.Contains(output.All(), tc.expected) { 295 t.Errorf("output didn't contain expected string:\n\n%s \n\n----\n\nexpected: %s", output.All(), tc.expected) 296 } 297 298 if provider.ResourceCount() > 0 { 299 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 300 } 301 }) 302 } 303 } 304 305 func TestTest_Interrupt(t *testing.T) { 306 td := t.TempDir() 307 testCopyDir(t, testFixturePath(path.Join("test", "with_interrupt")), td) 308 defer testChdir(t, td)() 309 310 provider := testing_command.NewProvider(nil) 311 view, done := testView(t) 312 313 interrupt := make(chan struct{}) 314 provider.Interrupt = interrupt 315 316 c := &TestCommand{ 317 Meta: Meta{ 318 testingOverrides: metaOverridesForProvider(provider.Provider), 319 View: view, 320 ShutdownCh: interrupt, 321 }, 322 } 323 324 c.Run(nil) 325 output := done(t).All() 326 327 if !strings.Contains(output, "Interrupt received") { 328 t.Errorf("output didn't produce the right output:\n\n%s", output) 329 } 330 331 if provider.ResourceCount() > 0 { 332 // we asked for a nice stop in this one, so it should still have tidied everything up. 333 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 334 } 335 } 336 337 func TestTest_DoubleInterrupt(t *testing.T) { 338 td := t.TempDir() 339 testCopyDir(t, testFixturePath(path.Join("test", "with_double_interrupt")), td) 340 defer testChdir(t, td)() 341 342 provider := testing_command.NewProvider(nil) 343 view, done := testView(t) 344 345 interrupt := make(chan struct{}) 346 provider.Interrupt = interrupt 347 348 c := &TestCommand{ 349 Meta: Meta{ 350 testingOverrides: metaOverridesForProvider(provider.Provider), 351 View: view, 352 ShutdownCh: interrupt, 353 }, 354 } 355 356 c.Run(nil) 357 output := done(t).All() 358 359 if !strings.Contains(output, "Two interrupts received") { 360 t.Errorf("output didn't produce the right output:\n\n%s", output) 361 } 362 363 cleanupMessage := `OpenTofu was interrupted while executing main.tftest.hcl, and may not have 364 performed the expected cleanup operations. 365 366 OpenTofu has already created the following resources from the module under 367 test: 368 - test_resource.primary 369 - test_resource.secondary 370 - test_resource.tertiary` 371 372 // It's really important that the above message is printed, so we're testing 373 // for it specifically and making sure it contains all the resources. 374 if !strings.Contains(output, cleanupMessage) { 375 t.Errorf("output didn't produce the right output:\n\n%s", output) 376 } 377 378 // This time the test command shouldn't have cleaned up the resource because 379 // of the hard interrupt. 380 if provider.ResourceCount() != 3 { 381 // we asked for a nice stop in this one, so it should still have tidied everything up. 382 t.Errorf("should not have deleted all resources on completion but left %v", provider.ResourceString()) 383 } 384 } 385 386 func TestTest_ProviderAlias(t *testing.T) { 387 td := t.TempDir() 388 testCopyDir(t, testFixturePath(path.Join("test", "with_provider_alias")), td) 389 defer testChdir(t, td)() 390 391 provider := testing_command.NewProvider(nil) 392 393 providerSource, close := newMockProviderSource(t, map[string][]string{ 394 "test": {"1.0.0"}, 395 }) 396 defer close() 397 398 streams, done := terminal.StreamsForTesting(t) 399 view := views.NewView(streams) 400 ui := new(cli.MockUi) 401 402 meta := Meta{ 403 testingOverrides: metaOverridesForProvider(provider.Provider), 404 Ui: ui, 405 View: view, 406 Streams: streams, 407 ProviderSource: providerSource, 408 } 409 410 init := &InitCommand{ 411 Meta: meta, 412 } 413 414 if code := init.Run(nil); code != 0 { 415 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 416 } 417 418 command := &TestCommand{ 419 Meta: meta, 420 } 421 422 code := command.Run(nil) 423 output := done(t) 424 425 printedOutput := false 426 427 if code != 0 { 428 printedOutput = true 429 t.Errorf("expected status code 0 but got %d: %s", code, output.All()) 430 } 431 432 if provider.ResourceCount() > 0 { 433 if !printedOutput { 434 t.Errorf("should have deleted all resources on completion but left %s\n\n%s", provider.ResourceString(), output.All()) 435 } else { 436 t.Errorf("should have deleted all resources on completion but left %s", provider.ResourceString()) 437 } 438 } 439 } 440 441 func TestTest_ModuleDependencies(t *testing.T) { 442 td := t.TempDir() 443 testCopyDir(t, testFixturePath(path.Join("test", "with_setup_module")), td) 444 defer testChdir(t, td)() 445 446 // Our two providers will share a common set of values to make things 447 // easier. 448 store := &testing_command.ResourceStore{ 449 Data: make(map[string]cty.Value), 450 } 451 452 // We set it up so the module provider will update the data sources 453 // available to the core mock provider. 454 test := testing_command.NewProvider(store) 455 setup := testing_command.NewProvider(store) 456 457 test.SetDataPrefix("data") 458 test.SetResourcePrefix("resource") 459 460 // Let's make the setup provider write into the data for test provider. 461 setup.SetResourcePrefix("data") 462 463 providerSource, close := newMockProviderSource(t, map[string][]string{ 464 "test": {"1.0.0"}, 465 "setup": {"1.0.0"}, 466 }) 467 defer close() 468 469 streams, done := terminal.StreamsForTesting(t) 470 view := views.NewView(streams) 471 ui := new(cli.MockUi) 472 473 meta := Meta{ 474 testingOverrides: &testingOverrides{ 475 Providers: map[addrs.Provider]providers.Factory{ 476 addrs.NewDefaultProvider("test"): providers.FactoryFixed(test.Provider), 477 addrs.NewDefaultProvider("setup"): providers.FactoryFixed(setup.Provider), 478 }, 479 }, 480 Ui: ui, 481 View: view, 482 Streams: streams, 483 ProviderSource: providerSource, 484 } 485 486 init := &InitCommand{ 487 Meta: meta, 488 } 489 490 if code := init.Run(nil); code != 0 { 491 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 492 } 493 494 command := &TestCommand{ 495 Meta: meta, 496 } 497 498 code := command.Run(nil) 499 output := done(t) 500 501 printedOutput := false 502 503 if code != 0 { 504 printedOutput = true 505 t.Errorf("expected status code 0 but got %d: %s", code, output.All()) 506 } 507 508 if test.ResourceCount() > 0 { 509 if !printedOutput { 510 printedOutput = true 511 t.Errorf("should have deleted all resources on completion but left %s\n\n%s", test.ResourceString(), output.All()) 512 } else { 513 t.Errorf("should have deleted all resources on completion but left %s", test.ResourceString()) 514 } 515 } 516 517 if setup.ResourceCount() > 0 { 518 if !printedOutput { 519 t.Errorf("should have deleted all resources on completion but left %s\n\n%s", setup.ResourceString(), output.All()) 520 } else { 521 t.Errorf("should have deleted all resources on completion but left %s", setup.ResourceString()) 522 } 523 } 524 } 525 526 func TestTest_CatchesErrorsBeforeDestroy(t *testing.T) { 527 td := t.TempDir() 528 testCopyDir(t, testFixturePath(path.Join("test", "invalid_default_state")), td) 529 defer testChdir(t, td)() 530 531 provider := testing_command.NewProvider(nil) 532 view, done := testView(t) 533 534 c := &TestCommand{ 535 Meta: Meta{ 536 testingOverrides: metaOverridesForProvider(provider.Provider), 537 View: view, 538 }, 539 } 540 541 code := c.Run([]string{"-no-color"}) 542 output := done(t) 543 544 if code != 1 { 545 t.Errorf("expected status code 0 but got %d", code) 546 } 547 548 expectedOut := `main.tftest.hcl... fail 549 run "test"... fail 550 551 Failure! 0 passed, 1 failed. 552 ` 553 554 expectedErr := ` 555 Error: No value for required variable 556 557 on main.tf line 2: 558 2: variable "input" { 559 560 The root module input variable "input" is not set, and has no default value. 561 Use a -var or -var-file command line argument to provide a value for this 562 variable. 563 ` 564 565 actualOut := output.Stdout() 566 actualErr := output.Stderr() 567 568 if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 { 569 t.Errorf("std out didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff) 570 } 571 572 if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 { 573 t.Errorf("std err didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff) 574 } 575 576 if provider.ResourceCount() > 0 { 577 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 578 } 579 } 580 581 func TestTest_Verbose(t *testing.T) { 582 td := t.TempDir() 583 testCopyDir(t, testFixturePath(path.Join("test", "plan_then_apply")), td) 584 defer testChdir(t, td)() 585 586 provider := testing_command.NewProvider(nil) 587 view, done := testView(t) 588 589 c := &TestCommand{ 590 Meta: Meta{ 591 testingOverrides: metaOverridesForProvider(provider.Provider), 592 View: view, 593 }, 594 } 595 596 code := c.Run([]string{"-verbose", "-no-color"}) 597 output := done(t) 598 599 if code != 0 { 600 t.Errorf("expected status code 0 but got %d", code) 601 } 602 603 expected := `main.tftest.hcl... pass 604 run "validate_test_resource"... pass 605 606 OpenTofu used the selected providers to generate the following execution 607 plan. Resource actions are indicated with the following symbols: 608 + create 609 610 OpenTofu will perform the following actions: 611 612 # test_resource.foo will be created 613 + resource "test_resource" "foo" { 614 + id = "constant_value" 615 + value = "bar" 616 } 617 618 Plan: 1 to add, 0 to change, 0 to destroy. 619 run "validate_test_resource"... pass 620 # test_resource.foo: 621 resource "test_resource" "foo" { 622 id = "constant_value" 623 value = "bar" 624 } 625 626 Success! 2 passed, 0 failed. 627 ` 628 629 actual := output.All() 630 631 if diff := cmp.Diff(actual, expected); len(diff) > 0 { 632 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) 633 } 634 635 if provider.ResourceCount() > 0 { 636 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 637 } 638 } 639 640 func TestTest_ValidatesBeforeExecution(t *testing.T) { 641 tcs := map[string]struct { 642 expectedOut string 643 expectedErr string 644 }{ 645 "invalid": { 646 expectedOut: `main.tftest.hcl... fail 647 run "invalid"... fail 648 649 Failure! 0 passed, 1 failed. 650 `, 651 expectedErr: ` 652 Error: Invalid ` + "`expect_failures`" + ` reference 653 654 on main.tftest.hcl line 5, in run "invalid": 655 5: local.my_value, 656 657 You cannot expect failures from local.my_value. You can only expect failures 658 from checkable objects such as input variables, output values, check blocks, 659 managed resources and data sources. 660 `, 661 }, 662 "invalid-module": { 663 expectedOut: `main.tftest.hcl... fail 664 run "invalid"... fail 665 run "test"... skip 666 667 Failure! 0 passed, 1 failed, 1 skipped. 668 `, 669 expectedErr: ` 670 Error: Reference to undeclared input variable 671 672 on setup/main.tf line 3, in resource "test_resource" "setup": 673 3: value = var.not_real // Oh no! 674 675 An input variable with the name "not_real" has not been declared. This 676 variable can be declared with a variable "not_real" {} block. 677 `, 678 }, 679 "missing-provider": { 680 expectedOut: `main.tftest.hcl... fail 681 run "passes_validation"... fail 682 683 Failure! 0 passed, 1 failed. 684 `, 685 expectedErr: ` 686 Error: Provider configuration not present 687 688 To work with test_resource.secondary its original provider configuration at 689 provider["registry.opentofu.org/hashicorp/test"].secondary is required, but 690 it has been removed. This occurs when a provider configuration is removed 691 while objects created by that provider still exist in the state. Re-add the 692 provider configuration to destroy test_resource.secondary, after which you 693 can remove the provider configuration again. 694 `, 695 }, 696 "missing-provider-in-run-block": { 697 expectedOut: `main.tftest.hcl... fail 698 run "passes_validation"... fail 699 700 Failure! 0 passed, 1 failed. 701 `, 702 expectedErr: ` 703 Error: Provider configuration not present 704 705 To work with test_resource.secondary its original provider configuration at 706 provider["registry.opentofu.org/hashicorp/test"].secondary is required, but 707 it has been removed. This occurs when a provider configuration is removed 708 while objects created by that provider still exist in the state. Re-add the 709 provider configuration to destroy test_resource.secondary, after which you 710 can remove the provider configuration again. 711 `, 712 }, 713 "missing-provider-in-test-module": { 714 expectedOut: `main.tftest.hcl... fail 715 run "passes_validation_primary"... pass 716 run "passes_validation_secondary"... fail 717 718 Failure! 1 passed, 1 failed. 719 `, 720 expectedErr: ` 721 Error: Provider configuration not present 722 723 To work with test_resource.secondary its original provider configuration at 724 provider["registry.opentofu.org/hashicorp/test"].secondary is required, but 725 it has been removed. This occurs when a provider configuration is removed 726 while objects created by that provider still exist in the state. Re-add the 727 provider configuration to destroy test_resource.secondary, after which you 728 can remove the provider configuration again. 729 `, 730 }, 731 } 732 733 for file, tc := range tcs { 734 t.Run(file, func(t *testing.T) { 735 736 td := t.TempDir() 737 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 738 defer testChdir(t, td)() 739 740 provider := testing_command.NewProvider(nil) 741 742 providerSource, close := newMockProviderSource(t, map[string][]string{ 743 "test": {"1.0.0"}, 744 }) 745 defer close() 746 747 streams, done := terminal.StreamsForTesting(t) 748 view := views.NewView(streams) 749 ui := new(cli.MockUi) 750 751 meta := Meta{ 752 testingOverrides: metaOverridesForProvider(provider.Provider), 753 Ui: ui, 754 View: view, 755 Streams: streams, 756 ProviderSource: providerSource, 757 } 758 759 init := &InitCommand{ 760 Meta: meta, 761 } 762 763 if code := init.Run(nil); code != 0 { 764 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 765 } 766 767 c := &TestCommand{ 768 Meta: meta, 769 } 770 771 code := c.Run([]string{"-no-color"}) 772 output := done(t) 773 774 if code != 1 { 775 t.Errorf("expected status code 1 but got %d", code) 776 } 777 778 actualOut, expectedOut := output.Stdout(), tc.expectedOut 779 actualErr, expectedErr := output.Stderr(), tc.expectedErr 780 781 if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 { 782 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff) 783 } 784 785 if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 { 786 t.Errorf("error didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff) 787 } 788 789 if provider.ResourceCount() > 0 { 790 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 791 } 792 }) 793 } 794 } 795 796 func TestTest_Modules(t *testing.T) { 797 tcs := map[string]struct { 798 expected string 799 code int 800 skip bool 801 }{ 802 "pass_module_with_no_resource": { 803 expected: "main.tftest.hcl... pass\n run \"run\"... pass\n\nSuccess! 1 passed, 0 failed.\n", 804 code: 0, 805 }, 806 "with_nested_setup_modules": { 807 expected: "main.tftest.hcl... pass\n run \"load_module\"... pass\n\nSuccess! 1 passed, 0 failed.\n", 808 code: 0, 809 }, 810 "with_verify_module": { 811 expected: "main.tftest.hcl... pass\n run \"test\"... pass\n run \"verify\"... pass\n\nSuccess! 2 passed, 0 failed.\n", 812 code: 0, 813 }, 814 "only_modules": { 815 expected: "main.tftest.hcl... pass\n run \"first\"... pass\n run \"second\"... pass\n\nSuccess! 2 passed, 0 failed.\n", 816 code: 0, 817 }, 818 "variables_reference": { 819 expected: "main.tftest.hcl... pass\n run \"setup\"... pass\n run \"test\"... pass\n\nSuccess! 2 passed, 0 failed.\n", 820 code: 0, 821 }, 822 } 823 824 for name, tc := range tcs { 825 t.Run(name, func(t *testing.T) { 826 if tc.skip { 827 t.Skip() 828 } 829 830 file := name 831 832 td := t.TempDir() 833 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 834 defer testChdir(t, td)() 835 836 provider := testing_command.NewProvider(nil) 837 providerSource, close := newMockProviderSource(t, map[string][]string{ 838 "test": {"1.0.0"}, 839 }) 840 defer close() 841 842 streams, done := terminal.StreamsForTesting(t) 843 view := views.NewView(streams) 844 ui := new(cli.MockUi) 845 meta := Meta{ 846 testingOverrides: metaOverridesForProvider(provider.Provider), 847 Ui: ui, 848 View: view, 849 Streams: streams, 850 ProviderSource: providerSource, 851 } 852 853 init := &InitCommand{ 854 Meta: meta, 855 } 856 857 if code := init.Run(nil); code != 0 { 858 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 859 } 860 861 command := &TestCommand{ 862 Meta: meta, 863 } 864 865 code := command.Run([]string{"-no-color"}) 866 output := done(t) 867 printedOutput := false 868 869 if code != tc.code { 870 printedOutput = true 871 t.Errorf("expected status code %d but got %d: %s", tc.code, code, output.All()) 872 } 873 874 actual := output.All() 875 876 if diff := cmp.Diff(actual, tc.expected); len(diff) > 0 { 877 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", tc.expected, actual, diff) 878 } 879 880 if provider.ResourceCount() > 0 { 881 if !printedOutput { 882 t.Errorf("should have deleted all resources on completion but left %s\n\n%s", provider.ResourceString(), output.All()) 883 } else { 884 t.Errorf("should have deleted all resources on completion but left %s", provider.ResourceString()) 885 } 886 } 887 888 if provider.DataSourceCount() > 0 { 889 if !printedOutput { 890 t.Errorf("should have deleted all data sources on completion but left %s\n\n%s", provider.DataSourceString(), output.All()) 891 } else { 892 t.Errorf("should have deleted all data sources on completion but left %s", provider.DataSourceString()) 893 } 894 } 895 }) 896 } 897 } 898 899 func TestTest_StatePropagation(t *testing.T) { 900 td := t.TempDir() 901 testCopyDir(t, testFixturePath(path.Join("test", "state_propagation")), td) 902 defer testChdir(t, td)() 903 904 provider := testing_command.NewProvider(nil) 905 906 providerSource, close := newMockProviderSource(t, map[string][]string{ 907 "test": {"1.0.0"}, 908 }) 909 defer close() 910 911 streams, done := terminal.StreamsForTesting(t) 912 view := views.NewView(streams) 913 ui := new(cli.MockUi) 914 915 meta := Meta{ 916 testingOverrides: metaOverridesForProvider(provider.Provider), 917 Ui: ui, 918 View: view, 919 Streams: streams, 920 ProviderSource: providerSource, 921 } 922 923 init := &InitCommand{ 924 Meta: meta, 925 } 926 927 if code := init.Run(nil); code != 0 { 928 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 929 } 930 931 c := &TestCommand{ 932 Meta: meta, 933 } 934 935 code := c.Run([]string{"-verbose", "-no-color"}) 936 output := done(t) 937 938 if code != 0 { 939 t.Errorf("expected status code 0 but got %d", code) 940 } 941 942 expected := `main.tftest.hcl... pass 943 run "initial_apply_example"... pass 944 # test_resource.module_resource: 945 resource "test_resource" "module_resource" { 946 id = "df6h8as9" 947 value = "start" 948 } 949 run "initial_apply"... pass 950 # test_resource.resource: 951 resource "test_resource" "resource" { 952 id = "598318e0" 953 value = "start" 954 } 955 run "plan_second_example"... pass 956 957 OpenTofu used the selected providers to generate the following execution 958 plan. Resource actions are indicated with the following symbols: 959 + create 960 961 OpenTofu will perform the following actions: 962 963 # test_resource.second_module_resource will be created 964 + resource "test_resource" "second_module_resource" { 965 + id = "b6a1d8cb" 966 + value = "start" 967 } 968 969 Plan: 1 to add, 0 to change, 0 to destroy. 970 run "plan_update"... pass 971 972 OpenTofu used the selected providers to generate the following execution 973 plan. Resource actions are indicated with the following symbols: 974 ~ update in-place 975 976 OpenTofu will perform the following actions: 977 978 # test_resource.resource will be updated in-place 979 ~ resource "test_resource" "resource" { 980 id = "598318e0" 981 ~ value = "start" -> "update" 982 } 983 984 Plan: 0 to add, 1 to change, 0 to destroy. 985 run "plan_update_example"... pass 986 987 OpenTofu used the selected providers to generate the following execution 988 plan. Resource actions are indicated with the following symbols: 989 ~ update in-place 990 991 OpenTofu will perform the following actions: 992 993 # test_resource.module_resource will be updated in-place 994 ~ resource "test_resource" "module_resource" { 995 id = "df6h8as9" 996 ~ value = "start" -> "update" 997 } 998 999 Plan: 0 to add, 1 to change, 0 to destroy. 1000 1001 Success! 5 passed, 0 failed. 1002 ` 1003 1004 actual := output.All() 1005 1006 if diff := cmp.Diff(actual, expected); len(diff) > 0 { 1007 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) 1008 } 1009 1010 if provider.ResourceCount() > 0 { 1011 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 1012 } 1013 } 1014 1015 func TestTest_PartialUpdates(t *testing.T) { 1016 tcs := map[string]struct { 1017 expectedOut string 1018 expectedErr string 1019 expectedCode int 1020 }{ 1021 "partial_updates": { 1022 expectedOut: `main.tftest.hcl... pass 1023 run "first"... pass 1024 1025 Warning: Resource targeting is in effect 1026 1027 You are creating a plan with the -target option, which means that the result 1028 of this plan may not represent all of the changes requested by the current 1029 configuration. 1030 1031 The -target option is not for routine use, and is provided only for 1032 exceptional situations such as recovering from errors or mistakes, or when 1033 OpenTofu specifically suggests to use it as part of an error message. 1034 1035 Warning: Applied changes may be incomplete 1036 1037 The plan was created with the -target option in effect, so some changes 1038 requested in the configuration may have been ignored and the output values 1039 may not be fully updated. Run the following command to verify that no other 1040 changes are pending: 1041 tofu plan 1042 1043 Note that the -target option is not suitable for routine use, and is provided 1044 only for exceptional situations such as recovering from errors or mistakes, 1045 or when OpenTofu specifically suggests to use it as part of an error message. 1046 run "second"... pass 1047 1048 Success! 2 passed, 0 failed. 1049 `, 1050 expectedCode: 0, 1051 }, 1052 "partial_update_failure": { 1053 expectedOut: `main.tftest.hcl... fail 1054 run "partial"... fail 1055 1056 Warning: Resource targeting is in effect 1057 1058 You are creating a plan with the -target option, which means that the result 1059 of this plan may not represent all of the changes requested by the current 1060 configuration. 1061 1062 The -target option is not for routine use, and is provided only for 1063 exceptional situations such as recovering from errors or mistakes, or when 1064 OpenTofu specifically suggests to use it as part of an error message. 1065 1066 Warning: Applied changes may be incomplete 1067 1068 The plan was created with the -target option in effect, so some changes 1069 requested in the configuration may have been ignored and the output values 1070 may not be fully updated. Run the following command to verify that no other 1071 changes are pending: 1072 tofu plan 1073 1074 Note that the -target option is not suitable for routine use, and is provided 1075 only for exceptional situations such as recovering from errors or mistakes, 1076 or when OpenTofu specifically suggests to use it as part of an error message. 1077 1078 Failure! 0 passed, 1 failed. 1079 `, 1080 expectedErr: ` 1081 Error: Unknown condition run 1082 1083 on main.tftest.hcl line 7, in run "partial": 1084 7: condition = test_resource.bar.value == "bar" 1085 1086 Condition expression could not be evaluated at this time. 1087 `, 1088 expectedCode: 1, 1089 }, 1090 } 1091 1092 for file, tc := range tcs { 1093 t.Run(file, func(t *testing.T) { 1094 td := t.TempDir() 1095 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 1096 defer testChdir(t, td)() 1097 1098 provider := testing_command.NewProvider(nil) 1099 view, done := testView(t) 1100 1101 c := &TestCommand{ 1102 Meta: Meta{ 1103 testingOverrides: metaOverridesForProvider(provider.Provider), 1104 View: view, 1105 }, 1106 } 1107 1108 code := c.Run([]string{"-no-color"}) 1109 output := done(t) 1110 1111 actualOut, expectedOut := output.Stdout(), tc.expectedOut 1112 actualErr, expectedErr := output.Stderr(), tc.expectedErr 1113 expectedCode := tc.expectedCode 1114 1115 if code != expectedCode { 1116 t.Errorf("expected status code %d but got %d", expectedCode, code) 1117 } 1118 1119 if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 { 1120 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff) 1121 } 1122 1123 if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 { 1124 t.Errorf("error didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff) 1125 } 1126 1127 if provider.ResourceCount() > 0 { 1128 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 1129 } 1130 }) 1131 } 1132 } 1133 1134 func TestTest_LocalVariables(t *testing.T) { 1135 td := t.TempDir() 1136 testCopyDir(t, testFixturePath(path.Join("test", "pass_with_local_variable")), td) 1137 defer testChdir(t, td)() 1138 1139 provider := testing_command.NewProvider(nil) 1140 1141 providerSource, close := newMockProviderSource(t, map[string][]string{ 1142 "test": {"1.0.0"}, 1143 }) 1144 defer close() 1145 1146 streams, done := terminal.StreamsForTesting(t) 1147 view := views.NewView(streams) 1148 ui := new(cli.MockUi) 1149 1150 meta := Meta{ 1151 testingOverrides: metaOverridesForProvider(provider.Provider), 1152 Ui: ui, 1153 View: view, 1154 Streams: streams, 1155 ProviderSource: providerSource, 1156 } 1157 1158 init := &InitCommand{ 1159 Meta: meta, 1160 } 1161 1162 if code := init.Run(nil); code != 0 { 1163 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 1164 } 1165 1166 c := &TestCommand{ 1167 Meta: meta, 1168 } 1169 code := c.Run([]string{"-verbose", "-no-color"}) 1170 output := done(t) 1171 1172 if code != 0 { 1173 t.Errorf("expected status code 0 but got %d", code) 1174 } 1175 1176 expected := `tests/test.tftest.hcl... pass 1177 run "first"... pass 1178 1179 1180 Outputs: 1181 1182 foo = "bar" 1183 run "second"... pass 1184 1185 No changes. Your infrastructure matches the configuration. 1186 1187 OpenTofu has compared your real infrastructure against your configuration and 1188 found no differences, so no changes are needed. 1189 1190 Success! 2 passed, 0 failed. 1191 ` 1192 1193 actual := output.All() 1194 1195 if diff := cmp.Diff(actual, expected); len(diff) > 0 { 1196 t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) 1197 } 1198 1199 if provider.ResourceCount() > 0 { 1200 t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString()) 1201 } 1202 } 1203 1204 func TestTest_RunBlock(t *testing.T) { 1205 tcs := map[string]struct { 1206 expected string 1207 code int 1208 skip bool 1209 }{ 1210 "invalid_run_block_name": { 1211 expected: ` 1212 Error: Invalid run block name 1213 1214 on tests/main.tftest.hcl line 1, in run "sample run": 1215 1: run "sample run" { 1216 1217 A name must start with a letter or underscore and may contain only letters, 1218 digits, underscores, and dashes. 1219 `, 1220 code: 1, 1221 }, 1222 } 1223 1224 for name, tc := range tcs { 1225 t.Run(name, func(t *testing.T) { 1226 if tc.skip { 1227 t.Skip() 1228 } 1229 1230 file := name 1231 1232 td := t.TempDir() 1233 testCopyDir(t, testFixturePath(path.Join("test", file)), td) 1234 defer testChdir(t, td)() 1235 1236 provider := testing_command.NewProvider(nil) 1237 providerSource, close := newMockProviderSource(t, map[string][]string{ 1238 "test": {"1.0.0"}, 1239 }) 1240 defer close() 1241 1242 streams, _ := terminal.StreamsForTesting(t) 1243 view := views.NewView(streams) 1244 ui := new(cli.MockUi) 1245 meta := Meta{ 1246 testingOverrides: metaOverridesForProvider(provider.Provider), 1247 Ui: ui, 1248 View: view, 1249 Streams: streams, 1250 ProviderSource: providerSource, 1251 } 1252 1253 init := &InitCommand{ 1254 Meta: meta, 1255 } 1256 1257 if code := init.Run(nil); code != tc.code { 1258 t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) 1259 } 1260 }) 1261 } 1262 }