github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/plan_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "reflect" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/terraform/helper/copy" 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mitchellh/cli" 17 ) 18 19 func TestPlan(t *testing.T) { 20 cwd, err := os.Getwd() 21 if err != nil { 22 t.Fatalf("err: %s", err) 23 } 24 if err := os.Chdir(testFixturePath("plan")); err != nil { 25 t.Fatalf("err: %s", err) 26 } 27 defer os.Chdir(cwd) 28 29 p := testProvider() 30 ui := new(cli.MockUi) 31 c := &PlanCommand{ 32 Meta: Meta{ 33 testingOverrides: metaOverridesForProvider(p), 34 Ui: ui, 35 }, 36 } 37 38 args := []string{} 39 if code := c.Run(args); code != 0 { 40 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 41 } 42 } 43 44 func TestPlan_lockedState(t *testing.T) { 45 cwd, err := os.Getwd() 46 if err != nil { 47 t.Fatalf("err: %s", err) 48 } 49 50 testPath := testFixturePath("plan") 51 unlock, err := testLockState("./testdata", filepath.Join(testPath, DefaultStateFilename)) 52 if err != nil { 53 t.Fatal(err) 54 } 55 defer unlock() 56 57 if err := os.Chdir(testPath); err != nil { 58 t.Fatalf("err: %s", err) 59 } 60 defer os.Chdir(cwd) 61 62 p := testProvider() 63 ui := new(cli.MockUi) 64 c := &PlanCommand{ 65 Meta: Meta{ 66 testingOverrides: metaOverridesForProvider(p), 67 Ui: ui, 68 }, 69 } 70 71 args := []string{} 72 if code := c.Run(args); code == 0 { 73 t.Fatal("expected error") 74 } 75 76 output := ui.ErrorWriter.String() 77 if !strings.Contains(output, "lock") { 78 t.Fatal("command output does not look like a lock error:", output) 79 } 80 } 81 82 func TestPlan_plan(t *testing.T) { 83 tmp, cwd := testCwd(t) 84 defer testFixCwd(t, tmp, cwd) 85 86 planPath := testPlanFile(t, &terraform.Plan{ 87 Module: testModule(t, "apply"), 88 }) 89 90 p := testProvider() 91 ui := new(cli.MockUi) 92 c := &PlanCommand{ 93 Meta: Meta{ 94 testingOverrides: metaOverridesForProvider(p), 95 Ui: ui, 96 }, 97 } 98 99 args := []string{planPath} 100 if code := c.Run(args); code != 0 { 101 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 102 } 103 104 if p.RefreshCalled { 105 t.Fatal("refresh should not be called") 106 } 107 } 108 109 func TestPlan_destroy(t *testing.T) { 110 originalState := &terraform.State{ 111 Modules: []*terraform.ModuleState{ 112 &terraform.ModuleState{ 113 Path: []string{"root"}, 114 Resources: map[string]*terraform.ResourceState{ 115 "test_instance.foo": &terraform.ResourceState{ 116 Type: "test_instance", 117 Primary: &terraform.InstanceState{ 118 ID: "bar", 119 }, 120 }, 121 }, 122 }, 123 }, 124 } 125 126 outPath := testTempFile(t) 127 statePath := testStateFile(t, originalState) 128 129 p := testProvider() 130 ui := new(cli.MockUi) 131 c := &PlanCommand{ 132 Meta: Meta{ 133 testingOverrides: metaOverridesForProvider(p), 134 Ui: ui, 135 }, 136 } 137 138 args := []string{ 139 "-destroy", 140 "-out", outPath, 141 "-state", statePath, 142 testFixturePath("plan"), 143 } 144 if code := c.Run(args); code != 0 { 145 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 146 } 147 148 if !p.RefreshCalled { 149 t.Fatal("refresh should be called") 150 } 151 152 plan := testReadPlan(t, outPath) 153 for _, m := range plan.Diff.Modules { 154 for _, r := range m.Resources { 155 if !r.Destroy { 156 t.Fatalf("bad: %#v", r) 157 } 158 } 159 } 160 } 161 162 func TestPlan_noState(t *testing.T) { 163 tmp, cwd := testCwd(t) 164 defer testFixCwd(t, tmp, cwd) 165 166 p := testProvider() 167 ui := new(cli.MockUi) 168 c := &PlanCommand{ 169 Meta: Meta{ 170 testingOverrides: metaOverridesForProvider(p), 171 Ui: ui, 172 }, 173 } 174 175 args := []string{ 176 testFixturePath("plan"), 177 } 178 if code := c.Run(args); code != 0 { 179 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 180 } 181 182 // Verify that refresh was called 183 if p.RefreshCalled { 184 t.Fatal("refresh should not be called") 185 } 186 187 // Verify that the provider was called with the existing state 188 actual := strings.TrimSpace(p.DiffState.String()) 189 expected := strings.TrimSpace(testPlanNoStateStr) 190 if actual != expected { 191 t.Fatalf("bad:\n\n%s", actual) 192 } 193 } 194 195 func TestPlan_outPath(t *testing.T) { 196 tmp, cwd := testCwd(t) 197 defer testFixCwd(t, tmp, cwd) 198 199 tf, err := ioutil.TempFile("", "tf") 200 if err != nil { 201 t.Fatalf("err: %s", err) 202 } 203 outPath := tf.Name() 204 os.Remove(tf.Name()) 205 206 p := testProvider() 207 ui := new(cli.MockUi) 208 c := &PlanCommand{ 209 Meta: Meta{ 210 testingOverrides: metaOverridesForProvider(p), 211 Ui: ui, 212 }, 213 } 214 215 p.DiffReturn = &terraform.InstanceDiff{ 216 Destroy: true, 217 } 218 219 args := []string{ 220 "-out", outPath, 221 testFixturePath("plan"), 222 } 223 if code := c.Run(args); code != 0 { 224 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 225 } 226 227 f, err := os.Open(outPath) 228 if err != nil { 229 t.Fatalf("err: %s", err) 230 } 231 defer f.Close() 232 233 if _, err := terraform.ReadPlan(f); err != nil { 234 t.Fatalf("err: %s", err) 235 } 236 } 237 238 func TestPlan_outPathNoChange(t *testing.T) { 239 originalState := &terraform.State{ 240 Modules: []*terraform.ModuleState{ 241 &terraform.ModuleState{ 242 Path: []string{"root"}, 243 Resources: map[string]*terraform.ResourceState{ 244 "test_instance.foo": &terraform.ResourceState{ 245 Type: "test_instance", 246 Primary: &terraform.InstanceState{ 247 ID: "bar", 248 }, 249 }, 250 }, 251 }, 252 }, 253 } 254 statePath := testStateFile(t, originalState) 255 256 tf, err := ioutil.TempFile("", "tf") 257 if err != nil { 258 t.Fatalf("err: %s", err) 259 } 260 outPath := tf.Name() 261 os.Remove(tf.Name()) 262 263 p := testProvider() 264 ui := new(cli.MockUi) 265 c := &PlanCommand{ 266 Meta: Meta{ 267 testingOverrides: metaOverridesForProvider(p), 268 Ui: ui, 269 }, 270 } 271 272 args := []string{ 273 "-out", outPath, 274 "-state", statePath, 275 testFixturePath("plan"), 276 } 277 if code := c.Run(args); code != 0 { 278 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 279 } 280 281 plan := testReadPlan(t, outPath) 282 if !plan.Diff.Empty() { 283 t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan) 284 } 285 } 286 287 // When using "-out" with a backend, the plan should encode the backend config 288 func TestPlan_outBackend(t *testing.T) { 289 // Create a temporary working directory that is empty 290 td := tempDir(t) 291 copy.CopyDir(testFixturePath("plan-out-backend"), td) 292 defer os.RemoveAll(td) 293 defer testChdir(t, td)() 294 295 // Our state 296 originalState := &terraform.State{ 297 Modules: []*terraform.ModuleState{ 298 &terraform.ModuleState{ 299 Path: []string{"root"}, 300 Resources: map[string]*terraform.ResourceState{ 301 "test_instance.foo": &terraform.ResourceState{ 302 Type: "test_instance", 303 Primary: &terraform.InstanceState{ 304 ID: "bar", 305 }, 306 }, 307 }, 308 }, 309 }, 310 } 311 originalState.Init() 312 313 // Setup our backend state 314 dataState, srv := testBackendState(t, originalState, 200) 315 defer srv.Close() 316 testStateFileRemote(t, dataState) 317 318 outPath := "foo" 319 p := testProvider() 320 ui := new(cli.MockUi) 321 c := &PlanCommand{ 322 Meta: Meta{ 323 testingOverrides: metaOverridesForProvider(p), 324 Ui: ui, 325 }, 326 } 327 328 args := []string{ 329 "-out", outPath, 330 } 331 if code := c.Run(args); code != 0 { 332 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 333 } 334 335 plan := testReadPlan(t, outPath) 336 if !plan.Diff.Empty() { 337 t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan) 338 } 339 340 if plan.Backend.Empty() { 341 t.Fatal("should have backend info") 342 } 343 if !reflect.DeepEqual(plan.Backend, dataState.Backend) { 344 t.Fatalf("bad: %#v", plan.Backend) 345 } 346 } 347 348 // When using "-out" with a legacy remote state, the plan should encode 349 // the backend config 350 func TestPlan_outBackendLegacy(t *testing.T) { 351 // Create a temporary working directory that is empty 352 td := tempDir(t) 353 copy.CopyDir(testFixturePath("plan-out-backend-legacy"), td) 354 defer os.RemoveAll(td) 355 defer testChdir(t, td)() 356 357 // Our state 358 originalState := &terraform.State{ 359 Modules: []*terraform.ModuleState{ 360 &terraform.ModuleState{ 361 Path: []string{"root"}, 362 Resources: map[string]*terraform.ResourceState{ 363 "test_instance.foo": &terraform.ResourceState{ 364 Type: "test_instance", 365 Primary: &terraform.InstanceState{ 366 ID: "bar", 367 }, 368 }, 369 }, 370 }, 371 }, 372 } 373 originalState.Init() 374 375 // Setup our legacy state 376 remoteState, srv := testRemoteState(t, originalState, 200) 377 defer srv.Close() 378 dataState := terraform.NewState() 379 dataState.Remote = remoteState 380 testStateFileRemote(t, dataState) 381 382 outPath := "foo" 383 p := testProvider() 384 ui := new(cli.MockUi) 385 c := &PlanCommand{ 386 Meta: Meta{ 387 testingOverrides: metaOverridesForProvider(p), 388 Ui: ui, 389 }, 390 } 391 392 args := []string{ 393 "-out", outPath, 394 } 395 if code := c.Run(args); code != 0 { 396 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 397 } 398 399 plan := testReadPlan(t, outPath) 400 if !plan.Diff.Empty() { 401 t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan) 402 } 403 404 if plan.State.Remote.Empty() { 405 t.Fatal("should have remote info") 406 } 407 } 408 409 func TestPlan_refresh(t *testing.T) { 410 tmp, cwd := testCwd(t) 411 defer testFixCwd(t, tmp, cwd) 412 413 p := testProvider() 414 ui := new(cli.MockUi) 415 c := &PlanCommand{ 416 Meta: Meta{ 417 testingOverrides: metaOverridesForProvider(p), 418 Ui: ui, 419 }, 420 } 421 422 args := []string{ 423 "-refresh=false", 424 testFixturePath("plan"), 425 } 426 if code := c.Run(args); code != 0 { 427 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 428 } 429 430 if p.RefreshCalled { 431 t.Fatal("refresh should not be called") 432 } 433 } 434 435 func TestPlan_state(t *testing.T) { 436 // Write out some prior state 437 tf, err := ioutil.TempFile("", "tf") 438 if err != nil { 439 t.Fatalf("err: %s", err) 440 } 441 statePath := tf.Name() 442 defer os.Remove(tf.Name()) 443 444 originalState := testState() 445 err = terraform.WriteState(originalState, tf) 446 tf.Close() 447 if err != nil { 448 t.Fatalf("err: %s", err) 449 } 450 451 p := testProvider() 452 ui := new(cli.MockUi) 453 c := &PlanCommand{ 454 Meta: Meta{ 455 testingOverrides: metaOverridesForProvider(p), 456 Ui: ui, 457 }, 458 } 459 460 args := []string{ 461 "-state", statePath, 462 testFixturePath("plan"), 463 } 464 if code := c.Run(args); code != 0 { 465 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 466 } 467 468 // Verify that the provider was called with the existing state 469 actual := strings.TrimSpace(p.DiffState.String()) 470 expected := strings.TrimSpace(testPlanStateStr) 471 if actual != expected { 472 t.Fatalf("bad:\n\n%s", actual) 473 } 474 } 475 476 func TestPlan_stateDefault(t *testing.T) { 477 originalState := testState() 478 479 // Write the state file in a temporary directory with the 480 // default filename. 481 td, err := ioutil.TempDir("", "tf") 482 if err != nil { 483 t.Fatalf("err: %s", err) 484 } 485 statePath := filepath.Join(td, DefaultStateFilename) 486 487 f, err := os.Create(statePath) 488 if err != nil { 489 t.Fatalf("err: %s", err) 490 } 491 err = terraform.WriteState(originalState, f) 492 f.Close() 493 if err != nil { 494 t.Fatalf("err: %s", err) 495 } 496 497 // Change to that directory 498 cwd, err := os.Getwd() 499 if err != nil { 500 t.Fatalf("err: %s", err) 501 } 502 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 503 t.Fatalf("err: %s", err) 504 } 505 defer os.Chdir(cwd) 506 507 p := testProvider() 508 ui := new(cli.MockUi) 509 c := &PlanCommand{ 510 Meta: Meta{ 511 testingOverrides: metaOverridesForProvider(p), 512 Ui: ui, 513 }, 514 } 515 516 args := []string{ 517 testFixturePath("plan"), 518 } 519 if code := c.Run(args); code != 0 { 520 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 521 } 522 523 // Verify that the provider was called with the existing state 524 actual := strings.TrimSpace(p.DiffState.String()) 525 expected := strings.TrimSpace(testPlanStateDefaultStr) 526 if actual != expected { 527 t.Fatalf("bad:\n\n%s", actual) 528 } 529 } 530 531 func TestPlan_stateFuture(t *testing.T) { 532 originalState := testState() 533 originalState.TFVersion = "99.99.99" 534 statePath := testStateFile(t, originalState) 535 536 p := testProvider() 537 ui := new(cli.MockUi) 538 c := &PlanCommand{ 539 Meta: Meta{ 540 testingOverrides: metaOverridesForProvider(p), 541 Ui: ui, 542 }, 543 } 544 545 args := []string{ 546 "-state", statePath, 547 testFixturePath("plan"), 548 } 549 if code := c.Run(args); code == 0 { 550 t.Fatal("should fail") 551 } 552 553 f, err := os.Open(statePath) 554 if err != nil { 555 t.Fatalf("err: %s", err) 556 } 557 558 newState, err := terraform.ReadState(f) 559 f.Close() 560 if err != nil { 561 t.Fatalf("err: %s", err) 562 } 563 564 if !newState.Equal(originalState) { 565 t.Fatalf("bad: %#v", newState) 566 } 567 if newState.TFVersion != originalState.TFVersion { 568 t.Fatalf("bad: %#v", newState) 569 } 570 } 571 572 func TestPlan_statePast(t *testing.T) { 573 originalState := testState() 574 originalState.TFVersion = "0.1.0" 575 statePath := testStateFile(t, originalState) 576 577 p := testProvider() 578 ui := new(cli.MockUi) 579 c := &PlanCommand{ 580 Meta: Meta{ 581 testingOverrides: metaOverridesForProvider(p), 582 Ui: ui, 583 }, 584 } 585 586 args := []string{ 587 "-state", statePath, 588 testFixturePath("plan"), 589 } 590 if code := c.Run(args); code != 0 { 591 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 592 } 593 } 594 595 func TestPlan_validate(t *testing.T) { 596 // This is triggered by not asking for input so we have to set this to false 597 test = false 598 defer func() { test = true }() 599 600 cwd, err := os.Getwd() 601 if err != nil { 602 t.Fatalf("err: %s", err) 603 } 604 if err := os.Chdir(testFixturePath("plan-invalid")); err != nil { 605 t.Fatalf("err: %s", err) 606 } 607 defer os.Chdir(cwd) 608 609 p := testProvider() 610 ui := new(cli.MockUi) 611 c := &PlanCommand{ 612 Meta: Meta{ 613 testingOverrides: metaOverridesForProvider(p), 614 Ui: ui, 615 }, 616 } 617 618 args := []string{} 619 if code := c.Run(args); code != 1 { 620 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 621 } 622 623 actual := ui.ErrorWriter.String() 624 if !strings.Contains(actual, "cannot be computed") { 625 t.Fatalf("bad: %s", actual) 626 } 627 } 628 629 func TestPlan_vars(t *testing.T) { 630 tmp, cwd := testCwd(t) 631 defer testFixCwd(t, tmp, cwd) 632 633 p := testProvider() 634 ui := new(cli.MockUi) 635 c := &PlanCommand{ 636 Meta: Meta{ 637 testingOverrides: metaOverridesForProvider(p), 638 Ui: ui, 639 }, 640 } 641 642 actual := "" 643 p.DiffFn = func( 644 info *terraform.InstanceInfo, 645 s *terraform.InstanceState, 646 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 647 if v, ok := c.Config["value"]; ok { 648 actual = v.(string) 649 } 650 651 return nil, nil 652 } 653 654 args := []string{ 655 "-var", "foo=bar", 656 testFixturePath("plan-vars"), 657 } 658 if code := c.Run(args); code != 0 { 659 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 660 } 661 662 if actual != "bar" { 663 t.Fatal("didn't work") 664 } 665 } 666 667 func TestPlan_varsUnset(t *testing.T) { 668 tmp, cwd := testCwd(t) 669 defer testFixCwd(t, tmp, cwd) 670 671 // Disable test mode so input would be asked 672 test = false 673 defer func() { test = true }() 674 675 defaultInputReader = bytes.NewBufferString("bar\n") 676 677 p := testProvider() 678 ui := new(cli.MockUi) 679 c := &PlanCommand{ 680 Meta: Meta{ 681 testingOverrides: metaOverridesForProvider(p), 682 Ui: ui, 683 }, 684 } 685 686 args := []string{ 687 testFixturePath("plan-vars"), 688 } 689 if code := c.Run(args); code != 0 { 690 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 691 } 692 } 693 694 func TestPlan_varFile(t *testing.T) { 695 tmp, cwd := testCwd(t) 696 defer testFixCwd(t, tmp, cwd) 697 698 varFilePath := testTempFile(t) 699 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 700 t.Fatalf("err: %s", err) 701 } 702 703 p := testProvider() 704 ui := new(cli.MockUi) 705 c := &PlanCommand{ 706 Meta: Meta{ 707 testingOverrides: metaOverridesForProvider(p), 708 Ui: ui, 709 }, 710 } 711 712 actual := "" 713 p.DiffFn = func( 714 info *terraform.InstanceInfo, 715 s *terraform.InstanceState, 716 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 717 if v, ok := c.Config["value"]; ok { 718 actual = v.(string) 719 } 720 721 return nil, nil 722 } 723 724 args := []string{ 725 "-var-file", varFilePath, 726 testFixturePath("plan-vars"), 727 } 728 if code := c.Run(args); code != 0 { 729 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 730 } 731 732 if actual != "bar" { 733 t.Fatal("didn't work") 734 } 735 } 736 737 func TestPlan_varFileDefault(t *testing.T) { 738 varFileDir := testTempDir(t) 739 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 740 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 741 t.Fatalf("err: %s", err) 742 } 743 744 cwd, err := os.Getwd() 745 if err != nil { 746 t.Fatalf("err: %s", err) 747 } 748 if err := os.Chdir(varFileDir); err != nil { 749 t.Fatalf("err: %s", err) 750 } 751 defer os.Chdir(cwd) 752 753 p := testProvider() 754 ui := new(cli.MockUi) 755 c := &PlanCommand{ 756 Meta: Meta{ 757 testingOverrides: metaOverridesForProvider(p), 758 Ui: ui, 759 }, 760 } 761 762 actual := "" 763 p.DiffFn = func( 764 info *terraform.InstanceInfo, 765 s *terraform.InstanceState, 766 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 767 if v, ok := c.Config["value"]; ok { 768 actual = v.(string) 769 } 770 771 return nil, nil 772 } 773 774 args := []string{ 775 testFixturePath("plan-vars"), 776 } 777 if code := c.Run(args); code != 0 { 778 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 779 } 780 781 if actual != "bar" { 782 t.Fatal("didn't work") 783 } 784 } 785 786 func TestPlan_detailedExitcode(t *testing.T) { 787 cwd, err := os.Getwd() 788 if err != nil { 789 t.Fatalf("err: %s", err) 790 } 791 if err := os.Chdir(testFixturePath("plan")); err != nil { 792 t.Fatalf("err: %s", err) 793 } 794 defer os.Chdir(cwd) 795 796 p := testProvider() 797 ui := new(cli.MockUi) 798 c := &PlanCommand{ 799 Meta: Meta{ 800 testingOverrides: metaOverridesForProvider(p), 801 Ui: ui, 802 }, 803 } 804 805 args := []string{"-detailed-exitcode"} 806 if code := c.Run(args); code != 2 { 807 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 808 } 809 } 810 811 func TestPlan_detailedExitcode_emptyDiff(t *testing.T) { 812 cwd, err := os.Getwd() 813 if err != nil { 814 t.Fatalf("err: %s", err) 815 } 816 if err := os.Chdir(testFixturePath("plan-emptydiff")); err != nil { 817 t.Fatalf("err: %s", err) 818 } 819 defer os.Chdir(cwd) 820 821 p := testProvider() 822 ui := new(cli.MockUi) 823 c := &PlanCommand{ 824 Meta: Meta{ 825 testingOverrides: metaOverridesForProvider(p), 826 Ui: ui, 827 }, 828 } 829 830 args := []string{"-detailed-exitcode"} 831 if code := c.Run(args); code != 0 { 832 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 833 } 834 } 835 836 func TestPlan_shutdown(t *testing.T) { 837 cancelled := make(chan struct{}) 838 shutdownCh := make(chan struct{}) 839 840 p := testProvider() 841 ui := new(cli.MockUi) 842 c := &PlanCommand{ 843 Meta: Meta{ 844 testingOverrides: metaOverridesForProvider(p), 845 Ui: ui, 846 ShutdownCh: shutdownCh, 847 }, 848 } 849 850 p.StopFn = func() error { 851 close(cancelled) 852 return nil 853 } 854 855 var once sync.Once 856 857 p.DiffFn = func( 858 *terraform.InstanceInfo, 859 *terraform.InstanceState, 860 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 861 862 once.Do(func() { 863 shutdownCh <- struct{}{} 864 }) 865 866 // Because of the internal lock in the MockProvider, we can't 867 // coordiante directly with the calling of Stop, and making the 868 // MockProvider concurrent is disruptive to a lot of existing tests. 869 // Wait here a moment to help make sure the main goroutine gets to the 870 // Stop call before we exit, or the plan may finish before it can be 871 // canceled. 872 time.Sleep(200 * time.Millisecond) 873 874 return &terraform.InstanceDiff{ 875 Attributes: map[string]*terraform.ResourceAttrDiff{ 876 "ami": &terraform.ResourceAttrDiff{ 877 New: "bar", 878 }, 879 }, 880 }, nil 881 } 882 883 if code := c.Run([]string{testFixturePath("apply-shutdown")}); code != 1 { 884 // FIXME: we should be able to avoid the error during evaluation 885 // the early exit isn't caught before the interpolation is evaluated 886 t.Fatal(ui.OutputWriter.String()) 887 } 888 889 select { 890 case <-cancelled: 891 default: 892 t.Fatal("command not cancelled") 893 } 894 } 895 896 const planVarFile = ` 897 foo = "bar" 898 ` 899 900 const testPlanNoStateStr = ` 901 <not created> 902 ` 903 904 const testPlanStateStr = ` 905 ID = bar 906 Tainted = false 907 ` 908 909 const testPlanStateDefaultStr = ` 910 ID = bar 911 Tainted = false 912 `