github.com/fabiomatavelli/terraform@v0.11.12-beta1/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  	td := testTempDir(t)
   200  	outPath := filepath.Join(td, "test.plan")
   201  
   202  	p := testProvider()
   203  	ui := new(cli.MockUi)
   204  	c := &PlanCommand{
   205  		Meta: Meta{
   206  			testingOverrides: metaOverridesForProvider(p),
   207  			Ui:               ui,
   208  		},
   209  	}
   210  
   211  	p.DiffReturn = &terraform.InstanceDiff{
   212  		Destroy: true,
   213  	}
   214  
   215  	args := []string{
   216  		"-out", outPath,
   217  		testFixturePath("plan"),
   218  	}
   219  	if code := c.Run(args); code != 0 {
   220  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   221  	}
   222  
   223  	f, err := os.Open(outPath)
   224  	if err != nil {
   225  		t.Fatalf("err: %s", err)
   226  	}
   227  	defer f.Close()
   228  
   229  	if _, err := terraform.ReadPlan(f); err != nil {
   230  		t.Fatalf("err: %s", err)
   231  	}
   232  }
   233  
   234  func TestPlan_outPathNoChange(t *testing.T) {
   235  	originalState := &terraform.State{
   236  		Modules: []*terraform.ModuleState{
   237  			&terraform.ModuleState{
   238  				Path: []string{"root"},
   239  				Resources: map[string]*terraform.ResourceState{
   240  					"test_instance.foo": &terraform.ResourceState{
   241  						Type: "test_instance",
   242  						Primary: &terraform.InstanceState{
   243  							ID: "bar",
   244  						},
   245  					},
   246  				},
   247  			},
   248  		},
   249  	}
   250  	statePath := testStateFile(t, originalState)
   251  
   252  	td := testTempDir(t)
   253  	outPath := filepath.Join(td, "test.plan")
   254  
   255  	p := testProvider()
   256  	ui := new(cli.MockUi)
   257  	c := &PlanCommand{
   258  		Meta: Meta{
   259  			testingOverrides: metaOverridesForProvider(p),
   260  			Ui:               ui,
   261  		},
   262  	}
   263  
   264  	args := []string{
   265  		"-out", outPath,
   266  		"-state", statePath,
   267  		testFixturePath("plan"),
   268  	}
   269  	if code := c.Run(args); code != 0 {
   270  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   271  	}
   272  
   273  	plan := testReadPlan(t, outPath)
   274  	if !plan.Diff.Empty() {
   275  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
   276  	}
   277  }
   278  
   279  // When using "-out" with a backend, the plan should encode the backend config
   280  func TestPlan_outBackend(t *testing.T) {
   281  	// Create a temporary working directory that is empty
   282  	td := tempDir(t)
   283  	copy.CopyDir(testFixturePath("plan-out-backend"), td)
   284  	defer os.RemoveAll(td)
   285  	defer testChdir(t, td)()
   286  
   287  	// Our state
   288  	originalState := &terraform.State{
   289  		Modules: []*terraform.ModuleState{
   290  			&terraform.ModuleState{
   291  				Path: []string{"root"},
   292  				Resources: map[string]*terraform.ResourceState{
   293  					"test_instance.foo": &terraform.ResourceState{
   294  						Type: "test_instance",
   295  						Primary: &terraform.InstanceState{
   296  							ID: "bar",
   297  						},
   298  					},
   299  				},
   300  			},
   301  		},
   302  	}
   303  	originalState.Init()
   304  
   305  	// Setup our backend state
   306  	dataState, srv := testBackendState(t, originalState, 200)
   307  	defer srv.Close()
   308  	testStateFileRemote(t, dataState)
   309  
   310  	outPath := "foo"
   311  	p := testProvider()
   312  	ui := new(cli.MockUi)
   313  	c := &PlanCommand{
   314  		Meta: Meta{
   315  			testingOverrides: metaOverridesForProvider(p),
   316  			Ui:               ui,
   317  		},
   318  	}
   319  
   320  	args := []string{
   321  		"-out", outPath,
   322  	}
   323  	if code := c.Run(args); code != 0 {
   324  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   325  	}
   326  
   327  	plan := testReadPlan(t, outPath)
   328  	if !plan.Diff.Empty() {
   329  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
   330  	}
   331  
   332  	if plan.Backend.Empty() {
   333  		t.Fatal("should have backend info")
   334  	}
   335  	if !reflect.DeepEqual(plan.Backend, dataState.Backend) {
   336  		t.Fatalf("bad: %#v", plan.Backend)
   337  	}
   338  }
   339  
   340  // When using "-out" with a legacy remote state, the plan should encode
   341  // the backend config
   342  func TestPlan_outBackendLegacy(t *testing.T) {
   343  	// Create a temporary working directory that is empty
   344  	td := tempDir(t)
   345  	copy.CopyDir(testFixturePath("plan-out-backend-legacy"), td)
   346  	defer os.RemoveAll(td)
   347  	defer testChdir(t, td)()
   348  
   349  	// Our state
   350  	originalState := &terraform.State{
   351  		Modules: []*terraform.ModuleState{
   352  			&terraform.ModuleState{
   353  				Path: []string{"root"},
   354  				Resources: map[string]*terraform.ResourceState{
   355  					"test_instance.foo": &terraform.ResourceState{
   356  						Type: "test_instance",
   357  						Primary: &terraform.InstanceState{
   358  							ID: "bar",
   359  						},
   360  					},
   361  				},
   362  			},
   363  		},
   364  	}
   365  	originalState.Init()
   366  
   367  	// Setup our legacy state
   368  	remoteState, srv := testRemoteState(t, originalState, 200)
   369  	defer srv.Close()
   370  	dataState := terraform.NewState()
   371  	dataState.Remote = remoteState
   372  	testStateFileRemote(t, dataState)
   373  
   374  	outPath := "foo"
   375  	p := testProvider()
   376  	ui := new(cli.MockUi)
   377  	c := &PlanCommand{
   378  		Meta: Meta{
   379  			testingOverrides: metaOverridesForProvider(p),
   380  			Ui:               ui,
   381  		},
   382  	}
   383  
   384  	args := []string{
   385  		"-out", outPath,
   386  	}
   387  	if code := c.Run(args); code != 0 {
   388  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   389  	}
   390  
   391  	plan := testReadPlan(t, outPath)
   392  	if !plan.Diff.Empty() {
   393  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
   394  	}
   395  
   396  	if plan.State.Remote.Empty() {
   397  		t.Fatal("should have remote info")
   398  	}
   399  }
   400  
   401  func TestPlan_refresh(t *testing.T) {
   402  	tmp, cwd := testCwd(t)
   403  	defer testFixCwd(t, tmp, cwd)
   404  
   405  	p := testProvider()
   406  	ui := new(cli.MockUi)
   407  	c := &PlanCommand{
   408  		Meta: Meta{
   409  			testingOverrides: metaOverridesForProvider(p),
   410  			Ui:               ui,
   411  		},
   412  	}
   413  
   414  	args := []string{
   415  		"-refresh=false",
   416  		testFixturePath("plan"),
   417  	}
   418  	if code := c.Run(args); code != 0 {
   419  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   420  	}
   421  
   422  	if p.RefreshCalled {
   423  		t.Fatal("refresh should not be called")
   424  	}
   425  }
   426  
   427  func TestPlan_state(t *testing.T) {
   428  	originalState := testState()
   429  	statePath := testStateFile(t, originalState)
   430  
   431  	p := testProvider()
   432  	ui := new(cli.MockUi)
   433  	c := &PlanCommand{
   434  		Meta: Meta{
   435  			testingOverrides: metaOverridesForProvider(p),
   436  			Ui:               ui,
   437  		},
   438  	}
   439  
   440  	args := []string{
   441  		"-state", statePath,
   442  		testFixturePath("plan"),
   443  	}
   444  	if code := c.Run(args); code != 0 {
   445  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   446  	}
   447  
   448  	// Verify that the provider was called with the existing state
   449  	actual := strings.TrimSpace(p.DiffState.String())
   450  	expected := strings.TrimSpace(testPlanStateStr)
   451  	if actual != expected {
   452  		t.Fatalf("bad:\n\n%s", actual)
   453  	}
   454  }
   455  
   456  func TestPlan_stateDefault(t *testing.T) {
   457  	originalState := testState()
   458  	statePath := testStateFile(t, originalState)
   459  
   460  	// Change to that directory
   461  	cwd, err := os.Getwd()
   462  	if err != nil {
   463  		t.Fatalf("err: %s", err)
   464  	}
   465  	if err := os.Chdir(filepath.Dir(statePath)); err != nil {
   466  		t.Fatalf("err: %s", err)
   467  	}
   468  	defer os.Chdir(cwd)
   469  
   470  	p := testProvider()
   471  	ui := new(cli.MockUi)
   472  	c := &PlanCommand{
   473  		Meta: Meta{
   474  			testingOverrides: metaOverridesForProvider(p),
   475  			Ui:               ui,
   476  		},
   477  	}
   478  
   479  	args := []string{
   480  		"-state", statePath,
   481  		testFixturePath("plan"),
   482  	}
   483  	if code := c.Run(args); code != 0 {
   484  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   485  	}
   486  
   487  	// Verify that the provider was called with the existing state
   488  	actual := strings.TrimSpace(p.DiffState.String())
   489  	expected := strings.TrimSpace(testPlanStateDefaultStr)
   490  	if actual != expected {
   491  		t.Fatalf("bad:\n\n%s", actual)
   492  	}
   493  }
   494  
   495  func TestPlan_stateFuture(t *testing.T) {
   496  	originalState := testState()
   497  	originalState.TFVersion = "99.99.99"
   498  	statePath := testStateFile(t, originalState)
   499  
   500  	p := testProvider()
   501  	ui := new(cli.MockUi)
   502  	c := &PlanCommand{
   503  		Meta: Meta{
   504  			testingOverrides: metaOverridesForProvider(p),
   505  			Ui:               ui,
   506  		},
   507  	}
   508  
   509  	args := []string{
   510  		"-state", statePath,
   511  		testFixturePath("plan"),
   512  	}
   513  	if code := c.Run(args); code == 0 {
   514  		t.Fatal("should fail")
   515  	}
   516  
   517  	f, err := os.Open(statePath)
   518  	if err != nil {
   519  		t.Fatalf("err: %s", err)
   520  	}
   521  
   522  	newState, err := terraform.ReadState(f)
   523  	f.Close()
   524  	if err != nil {
   525  		t.Fatalf("err: %s", err)
   526  	}
   527  
   528  	if !newState.Equal(originalState) {
   529  		t.Fatalf("bad: %#v", newState)
   530  	}
   531  	if newState.TFVersion != originalState.TFVersion {
   532  		t.Fatalf("bad: %#v", newState)
   533  	}
   534  }
   535  
   536  func TestPlan_statePast(t *testing.T) {
   537  	originalState := testState()
   538  	originalState.TFVersion = "0.1.0"
   539  	statePath := testStateFile(t, originalState)
   540  
   541  	p := testProvider()
   542  	ui := new(cli.MockUi)
   543  	c := &PlanCommand{
   544  		Meta: Meta{
   545  			testingOverrides: metaOverridesForProvider(p),
   546  			Ui:               ui,
   547  		},
   548  	}
   549  
   550  	args := []string{
   551  		"-state", statePath,
   552  		testFixturePath("plan"),
   553  	}
   554  	if code := c.Run(args); code != 0 {
   555  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   556  	}
   557  }
   558  
   559  func TestPlan_validate(t *testing.T) {
   560  	// This is triggered by not asking for input so we have to set this to false
   561  	test = false
   562  	defer func() { test = true }()
   563  
   564  	cwd, err := os.Getwd()
   565  	if err != nil {
   566  		t.Fatalf("err: %s", err)
   567  	}
   568  	if err := os.Chdir(testFixturePath("plan-invalid")); err != nil {
   569  		t.Fatalf("err: %s", err)
   570  	}
   571  	defer os.Chdir(cwd)
   572  
   573  	p := testProvider()
   574  	ui := new(cli.MockUi)
   575  	c := &PlanCommand{
   576  		Meta: Meta{
   577  			testingOverrides: metaOverridesForProvider(p),
   578  			Ui:               ui,
   579  		},
   580  	}
   581  
   582  	args := []string{}
   583  	if code := c.Run(args); code != 1 {
   584  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   585  	}
   586  
   587  	actual := ui.ErrorWriter.String()
   588  	if !strings.Contains(actual, "cannot be computed") {
   589  		t.Fatalf("bad: %s", actual)
   590  	}
   591  }
   592  
   593  func TestPlan_vars(t *testing.T) {
   594  	tmp, cwd := testCwd(t)
   595  	defer testFixCwd(t, tmp, cwd)
   596  
   597  	p := testProvider()
   598  	ui := new(cli.MockUi)
   599  	c := &PlanCommand{
   600  		Meta: Meta{
   601  			testingOverrides: metaOverridesForProvider(p),
   602  			Ui:               ui,
   603  		},
   604  	}
   605  
   606  	actual := ""
   607  	p.DiffFn = func(
   608  		info *terraform.InstanceInfo,
   609  		s *terraform.InstanceState,
   610  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   611  		if v, ok := c.Config["value"]; ok {
   612  			actual = v.(string)
   613  		}
   614  
   615  		return nil, nil
   616  	}
   617  
   618  	args := []string{
   619  		"-var", "foo=bar",
   620  		testFixturePath("plan-vars"),
   621  	}
   622  	if code := c.Run(args); code != 0 {
   623  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   624  	}
   625  
   626  	if actual != "bar" {
   627  		t.Fatal("didn't work")
   628  	}
   629  }
   630  
   631  func TestPlan_varsUnset(t *testing.T) {
   632  	tmp, cwd := testCwd(t)
   633  	defer testFixCwd(t, tmp, cwd)
   634  
   635  	// Disable test mode so input would be asked
   636  	test = false
   637  	defer func() { test = true }()
   638  
   639  	defaultInputReader = bytes.NewBufferString("bar\n")
   640  
   641  	p := testProvider()
   642  	ui := new(cli.MockUi)
   643  	c := &PlanCommand{
   644  		Meta: Meta{
   645  			testingOverrides: metaOverridesForProvider(p),
   646  			Ui:               ui,
   647  		},
   648  	}
   649  
   650  	args := []string{
   651  		testFixturePath("plan-vars"),
   652  	}
   653  	if code := c.Run(args); code != 0 {
   654  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   655  	}
   656  }
   657  
   658  func TestPlan_varFile(t *testing.T) {
   659  	tmp, cwd := testCwd(t)
   660  	defer testFixCwd(t, tmp, cwd)
   661  
   662  	varFilePath := testTempFile(t)
   663  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   664  		t.Fatalf("err: %s", err)
   665  	}
   666  
   667  	p := testProvider()
   668  	ui := new(cli.MockUi)
   669  	c := &PlanCommand{
   670  		Meta: Meta{
   671  			testingOverrides: metaOverridesForProvider(p),
   672  			Ui:               ui,
   673  		},
   674  	}
   675  
   676  	actual := ""
   677  	p.DiffFn = func(
   678  		info *terraform.InstanceInfo,
   679  		s *terraform.InstanceState,
   680  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   681  		if v, ok := c.Config["value"]; ok {
   682  			actual = v.(string)
   683  		}
   684  
   685  		return nil, nil
   686  	}
   687  
   688  	args := []string{
   689  		"-var-file", varFilePath,
   690  		testFixturePath("plan-vars"),
   691  	}
   692  	if code := c.Run(args); code != 0 {
   693  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   694  	}
   695  
   696  	if actual != "bar" {
   697  		t.Fatal("didn't work")
   698  	}
   699  }
   700  
   701  func TestPlan_varFileDefault(t *testing.T) {
   702  	varFileDir := testTempDir(t)
   703  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
   704  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   705  		t.Fatalf("err: %s", err)
   706  	}
   707  
   708  	cwd, err := os.Getwd()
   709  	if err != nil {
   710  		t.Fatalf("err: %s", err)
   711  	}
   712  	if err := os.Chdir(varFileDir); err != nil {
   713  		t.Fatalf("err: %s", err)
   714  	}
   715  	defer os.Chdir(cwd)
   716  
   717  	p := testProvider()
   718  	ui := new(cli.MockUi)
   719  	c := &PlanCommand{
   720  		Meta: Meta{
   721  			testingOverrides: metaOverridesForProvider(p),
   722  			Ui:               ui,
   723  		},
   724  	}
   725  
   726  	actual := ""
   727  	p.DiffFn = func(
   728  		info *terraform.InstanceInfo,
   729  		s *terraform.InstanceState,
   730  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   731  		if v, ok := c.Config["value"]; ok {
   732  			actual = v.(string)
   733  		}
   734  
   735  		return nil, nil
   736  	}
   737  
   738  	args := []string{
   739  		testFixturePath("plan-vars"),
   740  	}
   741  	if code := c.Run(args); code != 0 {
   742  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   743  	}
   744  
   745  	if actual != "bar" {
   746  		t.Fatal("didn't work")
   747  	}
   748  }
   749  
   750  func TestPlan_detailedExitcode(t *testing.T) {
   751  	cwd, err := os.Getwd()
   752  	if err != nil {
   753  		t.Fatalf("err: %s", err)
   754  	}
   755  	if err := os.Chdir(testFixturePath("plan")); err != nil {
   756  		t.Fatalf("err: %s", err)
   757  	}
   758  	defer os.Chdir(cwd)
   759  
   760  	p := testProvider()
   761  	ui := new(cli.MockUi)
   762  	c := &PlanCommand{
   763  		Meta: Meta{
   764  			testingOverrides: metaOverridesForProvider(p),
   765  			Ui:               ui,
   766  		},
   767  	}
   768  
   769  	args := []string{"-detailed-exitcode"}
   770  	if code := c.Run(args); code != 2 {
   771  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   772  	}
   773  }
   774  
   775  func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
   776  	cwd, err := os.Getwd()
   777  	if err != nil {
   778  		t.Fatalf("err: %s", err)
   779  	}
   780  	if err := os.Chdir(testFixturePath("plan-emptydiff")); err != nil {
   781  		t.Fatalf("err: %s", err)
   782  	}
   783  	defer os.Chdir(cwd)
   784  
   785  	p := testProvider()
   786  	ui := new(cli.MockUi)
   787  	c := &PlanCommand{
   788  		Meta: Meta{
   789  			testingOverrides: metaOverridesForProvider(p),
   790  			Ui:               ui,
   791  		},
   792  	}
   793  
   794  	args := []string{"-detailed-exitcode"}
   795  	if code := c.Run(args); code != 0 {
   796  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   797  	}
   798  }
   799  
   800  func TestPlan_shutdown(t *testing.T) {
   801  	cancelled := make(chan struct{})
   802  	shutdownCh := make(chan struct{})
   803  
   804  	p := testProvider()
   805  	ui := new(cli.MockUi)
   806  	c := &PlanCommand{
   807  		Meta: Meta{
   808  			testingOverrides: metaOverridesForProvider(p),
   809  			Ui:               ui,
   810  			ShutdownCh:       shutdownCh,
   811  		},
   812  	}
   813  
   814  	p.StopFn = func() error {
   815  		close(cancelled)
   816  		return nil
   817  	}
   818  
   819  	var once sync.Once
   820  
   821  	p.DiffFn = func(
   822  		*terraform.InstanceInfo,
   823  		*terraform.InstanceState,
   824  		*terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   825  
   826  		once.Do(func() {
   827  			shutdownCh <- struct{}{}
   828  		})
   829  
   830  		// Because of the internal lock in the MockProvider, we can't
   831  		// coordiante directly with the calling of Stop, and making the
   832  		// MockProvider concurrent is disruptive to a lot of existing tests.
   833  		// Wait here a moment to help make sure the main goroutine gets to the
   834  		// Stop call before we exit, or the plan may finish before it can be
   835  		// canceled.
   836  		time.Sleep(200 * time.Millisecond)
   837  
   838  		return &terraform.InstanceDiff{
   839  			Attributes: map[string]*terraform.ResourceAttrDiff{
   840  				"ami": &terraform.ResourceAttrDiff{
   841  					New: "bar",
   842  				},
   843  			},
   844  		}, nil
   845  	}
   846  
   847  	if code := c.Run([]string{testFixturePath("apply-shutdown")}); code != 1 {
   848  		// FIXME: we should be able to avoid the error during evaluation
   849  		// the early exit isn't caught before the interpolation is evaluated
   850  		t.Fatal(ui.OutputWriter.String())
   851  	}
   852  
   853  	select {
   854  	case <-cancelled:
   855  	default:
   856  		t.Fatal("command not cancelled")
   857  	}
   858  }
   859  
   860  const planVarFile = `
   861  foo = "bar"
   862  `
   863  
   864  const testPlanNoStateStr = `
   865  <not created>
   866  `
   867  
   868  const testPlanStateStr = `
   869  ID = bar
   870  Tainted = false
   871  `
   872  
   873  const testPlanStateDefaultStr = `
   874  ID = bar
   875  Tainted = false
   876  `