github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/test_test.go (about)

     1  package command
     2  
     3  import (
     4  	"path"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/mitchellh/cli"
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  	testing_command "github.com/terramate-io/tf/command/testing"
    14  	"github.com/terramate-io/tf/command/views"
    15  	"github.com/terramate-io/tf/providers"
    16  	"github.com/terramate-io/tf/terminal"
    17  )
    18  
    19  func TestTest(t *testing.T) {
    20  	tcs := map[string]struct {
    21  		override string
    22  		args     []string
    23  		expected string
    24  		code     int
    25  		skip     bool
    26  	}{
    27  		"simple_pass": {
    28  			expected: "1 passed, 0 failed.",
    29  			code:     0,
    30  		},
    31  		"simple_pass_nested": {
    32  			expected: "1 passed, 0 failed.",
    33  			code:     0,
    34  		},
    35  		"simple_pass_nested_alternate": {
    36  			args:     []string{"-test-directory", "other"},
    37  			expected: "1 passed, 0 failed.",
    38  			code:     0,
    39  		},
    40  		"simple_pass_very_nested": {
    41  			args:     []string{"-test-directory", "tests/subdir"},
    42  			expected: "1 passed, 0 failed.",
    43  			code:     0,
    44  		},
    45  		"simple_pass_very_nested_alternate": {
    46  			override: "simple_pass_very_nested",
    47  			args:     []string{"-test-directory", "./tests/subdir"},
    48  			expected: "1 passed, 0 failed.",
    49  			code:     0,
    50  		},
    51  		"pass_with_locals": {
    52  			expected: "1 passed, 0 failed.",
    53  			code:     0,
    54  		},
    55  		"pass_with_outputs": {
    56  			expected: "1 passed, 0 failed.",
    57  			code:     0,
    58  		},
    59  		"pass_with_variables": {
    60  			expected: "2 passed, 0 failed.",
    61  			code:     0,
    62  		},
    63  		"plan_then_apply": {
    64  			expected: "2 passed, 0 failed.",
    65  			code:     0,
    66  		},
    67  		"expect_failures_checks": {
    68  			expected: "1 passed, 0 failed.",
    69  			code:     0,
    70  		},
    71  		"expect_failures_inputs": {
    72  			expected: "1 passed, 0 failed.",
    73  			code:     0,
    74  		},
    75  		"expect_failures_outputs": {
    76  			expected: "1 passed, 0 failed.",
    77  			code:     0,
    78  		},
    79  		"expect_failures_resources": {
    80  			expected: "1 passed, 0 failed.",
    81  			code:     0,
    82  		},
    83  		"multiple_files": {
    84  			expected: "2 passed, 0 failed",
    85  			code:     0,
    86  		},
    87  		"multiple_files_with_filter": {
    88  			override: "multiple_files",
    89  			args:     []string{"-filter=one.tftest.hcl"},
    90  			expected: "1 passed, 0 failed",
    91  			code:     0,
    92  		},
    93  		"variables": {
    94  			expected: "2 passed, 0 failed",
    95  			code:     0,
    96  		},
    97  		"variables_overridden": {
    98  			override: "variables",
    99  			args:     []string{"-var=input=foo"},
   100  			expected: "1 passed, 1 failed",
   101  			code:     1,
   102  		},
   103  		"simple_fail": {
   104  			expected: "0 passed, 1 failed.",
   105  			code:     1,
   106  		},
   107  		"custom_condition_checks": {
   108  			expected: "0 passed, 1 failed.",
   109  			code:     1,
   110  		},
   111  		"custom_condition_inputs": {
   112  			expected: "0 passed, 1 failed.",
   113  			code:     1,
   114  		},
   115  		"custom_condition_outputs": {
   116  			expected: "0 passed, 1 failed.",
   117  			code:     1,
   118  		},
   119  		"custom_condition_resources": {
   120  			expected: "0 passed, 1 failed.",
   121  			code:     1,
   122  		},
   123  		"no_providers_in_main": {
   124  			expected: "1 passed, 0 failed",
   125  			code:     0,
   126  		},
   127  		"default_variables": {
   128  			expected: "1 passed, 0 failed.",
   129  			code:     0,
   130  		},
   131  		"undefined_variables": {
   132  			expected: "1 passed, 0 failed.",
   133  			code:     0,
   134  		},
   135  	}
   136  	for name, tc := range tcs {
   137  		t.Run(name, func(t *testing.T) {
   138  			if tc.skip {
   139  				t.Skip()
   140  			}
   141  
   142  			file := name
   143  			if len(tc.override) > 0 {
   144  				file = tc.override
   145  			}
   146  
   147  			td := t.TempDir()
   148  			testCopyDir(t, testFixturePath(path.Join("test", file)), td)
   149  			defer testChdir(t, td)()
   150  
   151  			provider := testing_command.NewProvider(nil)
   152  			view, done := testView(t)
   153  
   154  			c := &TestCommand{
   155  				Meta: Meta{
   156  					testingOverrides: metaOverridesForProvider(provider.Provider),
   157  					View:             view,
   158  				},
   159  			}
   160  
   161  			code := c.Run(tc.args)
   162  			output := done(t)
   163  
   164  			if code != tc.code {
   165  				t.Errorf("expected status code %d but got %d", tc.code, code)
   166  			}
   167  
   168  			if !strings.Contains(output.Stdout(), tc.expected) {
   169  				t.Errorf("output didn't contain expected string:\n\n%s", output.All())
   170  			}
   171  
   172  			if provider.ResourceCount() > 0 {
   173  				t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestTest_Interrupt(t *testing.T) {
   180  	td := t.TempDir()
   181  	testCopyDir(t, testFixturePath(path.Join("test", "with_interrupt")), td)
   182  	defer testChdir(t, td)()
   183  
   184  	provider := testing_command.NewProvider(nil)
   185  	view, done := testView(t)
   186  
   187  	interrupt := make(chan struct{})
   188  	provider.Interrupt = interrupt
   189  
   190  	c := &TestCommand{
   191  		Meta: Meta{
   192  			testingOverrides: metaOverridesForProvider(provider.Provider),
   193  			View:             view,
   194  			ShutdownCh:       interrupt,
   195  		},
   196  	}
   197  
   198  	c.Run(nil)
   199  	output := done(t).All()
   200  
   201  	if !strings.Contains(output, "Interrupt received") {
   202  		t.Errorf("output didn't produce the right output:\n\n%s", output)
   203  	}
   204  
   205  	if provider.ResourceCount() > 0 {
   206  		// we asked for a nice stop in this one, so it should still have tidied everything up.
   207  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   208  	}
   209  }
   210  
   211  func TestTest_DoubleInterrupt(t *testing.T) {
   212  	td := t.TempDir()
   213  	testCopyDir(t, testFixturePath(path.Join("test", "with_double_interrupt")), td)
   214  	defer testChdir(t, td)()
   215  
   216  	provider := testing_command.NewProvider(nil)
   217  	view, done := testView(t)
   218  
   219  	interrupt := make(chan struct{})
   220  	provider.Interrupt = interrupt
   221  
   222  	c := &TestCommand{
   223  		Meta: Meta{
   224  			testingOverrides: metaOverridesForProvider(provider.Provider),
   225  			View:             view,
   226  			ShutdownCh:       interrupt,
   227  		},
   228  	}
   229  
   230  	c.Run(nil)
   231  	output := done(t).All()
   232  
   233  	if !strings.Contains(output, "Two interrupts received") {
   234  		t.Errorf("output didn't produce the right output:\n\n%s", output)
   235  	}
   236  
   237  	cleanupMessage := `Terraform was interrupted while executing main.tftest.hcl, and may not have
   238  performed the expected cleanup operations.
   239  
   240  Terraform has already created the following resources from the module under
   241  test:
   242    - test_resource.primary
   243    - test_resource.secondary
   244    - test_resource.tertiary`
   245  
   246  	// It's really important that the above message is printed, so we're testing
   247  	// for it specifically and making sure it contains all the resources.
   248  	if !strings.Contains(output, cleanupMessage) {
   249  		t.Errorf("output didn't produce the right output:\n\n%s", output)
   250  	}
   251  
   252  	// This time the test command shouldn't have cleaned up the resource because
   253  	// of the hard interrupt.
   254  	if provider.ResourceCount() != 3 {
   255  		// we asked for a nice stop in this one, so it should still have tidied everything up.
   256  		t.Errorf("should not have deleted all resources on completion but left %v", provider.ResourceString())
   257  	}
   258  }
   259  
   260  func TestTest_ProviderAlias(t *testing.T) {
   261  	td := t.TempDir()
   262  	testCopyDir(t, testFixturePath(path.Join("test", "with_provider_alias")), td)
   263  	defer testChdir(t, td)()
   264  
   265  	provider := testing_command.NewProvider(nil)
   266  
   267  	providerSource, close := newMockProviderSource(t, map[string][]string{
   268  		"test": {"1.0.0"},
   269  	})
   270  	defer close()
   271  
   272  	streams, done := terminal.StreamsForTesting(t)
   273  	view := views.NewView(streams)
   274  	ui := new(cli.MockUi)
   275  
   276  	meta := Meta{
   277  		testingOverrides: metaOverridesForProvider(provider.Provider),
   278  		Ui:               ui,
   279  		View:             view,
   280  		Streams:          streams,
   281  		ProviderSource:   providerSource,
   282  	}
   283  
   284  	init := &InitCommand{
   285  		Meta: meta,
   286  	}
   287  
   288  	if code := init.Run(nil); code != 0 {
   289  		t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   290  	}
   291  
   292  	command := &TestCommand{
   293  		Meta: meta,
   294  	}
   295  
   296  	code := command.Run(nil)
   297  	output := done(t)
   298  
   299  	printedOutput := false
   300  
   301  	if code != 0 {
   302  		printedOutput = true
   303  		t.Errorf("expected status code 0 but got %d: %s", code, output.All())
   304  	}
   305  
   306  	if provider.ResourceCount() > 0 {
   307  		if !printedOutput {
   308  			t.Errorf("should have deleted all resources on completion but left %s\n\n%s", provider.ResourceString(), output.All())
   309  		} else {
   310  			t.Errorf("should have deleted all resources on completion but left %s", provider.ResourceString())
   311  		}
   312  	}
   313  }
   314  
   315  func TestTest_ModuleDependencies(t *testing.T) {
   316  	td := t.TempDir()
   317  	testCopyDir(t, testFixturePath(path.Join("test", "with_setup_module")), td)
   318  	defer testChdir(t, td)()
   319  
   320  	// Our two providers will share a common set of values to make things
   321  	// easier.
   322  	store := &testing_command.ResourceStore{
   323  		Data: make(map[string]cty.Value),
   324  	}
   325  
   326  	// We set it up so the module provider will update the data sources
   327  	// available to the core mock provider.
   328  	test := testing_command.NewProvider(store)
   329  	setup := testing_command.NewProvider(store)
   330  
   331  	test.SetDataPrefix("data")
   332  	test.SetResourcePrefix("resource")
   333  
   334  	// Let's make the setup provider write into the data for test provider.
   335  	setup.SetResourcePrefix("data")
   336  
   337  	providerSource, close := newMockProviderSource(t, map[string][]string{
   338  		"test":  {"1.0.0"},
   339  		"setup": {"1.0.0"},
   340  	})
   341  	defer close()
   342  
   343  	streams, done := terminal.StreamsForTesting(t)
   344  	view := views.NewView(streams)
   345  	ui := new(cli.MockUi)
   346  
   347  	meta := Meta{
   348  		testingOverrides: &testingOverrides{
   349  			Providers: map[addrs.Provider]providers.Factory{
   350  				addrs.NewDefaultProvider("test"):  providers.FactoryFixed(test.Provider),
   351  				addrs.NewDefaultProvider("setup"): providers.FactoryFixed(setup.Provider),
   352  			},
   353  		},
   354  		Ui:             ui,
   355  		View:           view,
   356  		Streams:        streams,
   357  		ProviderSource: providerSource,
   358  	}
   359  
   360  	init := &InitCommand{
   361  		Meta: meta,
   362  	}
   363  
   364  	if code := init.Run(nil); code != 0 {
   365  		t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   366  	}
   367  
   368  	command := &TestCommand{
   369  		Meta: meta,
   370  	}
   371  
   372  	code := command.Run(nil)
   373  	output := done(t)
   374  
   375  	printedOutput := false
   376  
   377  	if code != 0 {
   378  		printedOutput = true
   379  		t.Errorf("expected status code 0 but got %d: %s", code, output.All())
   380  	}
   381  
   382  	if test.ResourceCount() > 0 {
   383  		if !printedOutput {
   384  			printedOutput = true
   385  			t.Errorf("should have deleted all resources on completion but left %s\n\n%s", test.ResourceString(), output.All())
   386  		} else {
   387  			t.Errorf("should have deleted all resources on completion but left %s", test.ResourceString())
   388  		}
   389  	}
   390  
   391  	if setup.ResourceCount() > 0 {
   392  		if !printedOutput {
   393  			t.Errorf("should have deleted all resources on completion but left %s\n\n%s", setup.ResourceString(), output.All())
   394  		} else {
   395  			t.Errorf("should have deleted all resources on completion but left %s", setup.ResourceString())
   396  		}
   397  	}
   398  }
   399  
   400  func TestTest_CatchesErrorsBeforeDestroy(t *testing.T) {
   401  	td := t.TempDir()
   402  	testCopyDir(t, testFixturePath(path.Join("test", "invalid_default_state")), td)
   403  	defer testChdir(t, td)()
   404  
   405  	provider := testing_command.NewProvider(nil)
   406  	view, done := testView(t)
   407  
   408  	c := &TestCommand{
   409  		Meta: Meta{
   410  			testingOverrides: metaOverridesForProvider(provider.Provider),
   411  			View:             view,
   412  		},
   413  	}
   414  
   415  	code := c.Run([]string{"-no-color"})
   416  	output := done(t)
   417  
   418  	if code != 1 {
   419  		t.Errorf("expected status code 0 but got %d", code)
   420  	}
   421  
   422  	expectedOut := `main.tftest.hcl... fail
   423    run "test"... fail
   424  
   425  Failure! 0 passed, 1 failed.
   426  `
   427  
   428  	expectedErr := `
   429  Error: No value for required variable
   430  
   431    on main.tf line 2:
   432     2: variable "input" {
   433  
   434  The root module input variable "input" is not set, and has no default value.
   435  Use a -var or -var-file command line argument to provide a value for this
   436  variable.
   437  `
   438  
   439  	actualOut := output.Stdout()
   440  	actualErr := output.Stderr()
   441  
   442  	if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 {
   443  		t.Errorf("std out didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff)
   444  	}
   445  
   446  	if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 {
   447  		t.Errorf("std err didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff)
   448  	}
   449  
   450  	if provider.ResourceCount() > 0 {
   451  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   452  	}
   453  }
   454  
   455  func TestTest_Verbose(t *testing.T) {
   456  	td := t.TempDir()
   457  	testCopyDir(t, testFixturePath(path.Join("test", "plan_then_apply")), td)
   458  	defer testChdir(t, td)()
   459  
   460  	provider := testing_command.NewProvider(nil)
   461  	view, done := testView(t)
   462  
   463  	c := &TestCommand{
   464  		Meta: Meta{
   465  			testingOverrides: metaOverridesForProvider(provider.Provider),
   466  			View:             view,
   467  		},
   468  	}
   469  
   470  	code := c.Run([]string{"-verbose", "-no-color"})
   471  	output := done(t)
   472  
   473  	if code != 0 {
   474  		t.Errorf("expected status code 0 but got %d", code)
   475  	}
   476  
   477  	expected := `main.tftest.hcl... pass
   478    run "validate_test_resource"... pass
   479  
   480  Terraform used the selected providers to generate the following execution
   481  plan. Resource actions are indicated with the following symbols:
   482    + create
   483  
   484  Terraform will perform the following actions:
   485  
   486    # test_resource.foo will be created
   487    + resource "test_resource" "foo" {
   488        + id    = "constant_value"
   489        + value = "bar"
   490      }
   491  
   492  Plan: 1 to add, 0 to change, 0 to destroy.
   493    run "validate_test_resource"... pass
   494  # test_resource.foo:
   495  resource "test_resource" "foo" {
   496      id    = "constant_value"
   497      value = "bar"
   498  }
   499  
   500  Success! 2 passed, 0 failed.
   501  `
   502  
   503  	actual := output.All()
   504  
   505  	if diff := cmp.Diff(actual, expected); len(diff) > 0 {
   506  		t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
   507  	}
   508  
   509  	if provider.ResourceCount() > 0 {
   510  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   511  	}
   512  }
   513  
   514  func TestTest_ValidatesBeforeExecution(t *testing.T) {
   515  	tcs := map[string]struct {
   516  		expectedOut string
   517  		expectedErr string
   518  	}{
   519  		"invalid": {
   520  			expectedOut: `main.tftest.hcl... fail
   521    run "invalid"... fail
   522  
   523  Failure! 0 passed, 1 failed.
   524  `,
   525  			expectedErr: `
   526  Error: Invalid ` + "`expect_failures`" + ` reference
   527  
   528    on main.tftest.hcl line 5, in run "invalid":
   529     5:         local.my_value,
   530  
   531  You cannot expect failures from local.my_value. You can only expect failures
   532  from checkable objects such as input variables, output values, check blocks,
   533  managed resources and data sources.
   534  `,
   535  		},
   536  		"invalid-module": {
   537  			expectedOut: `main.tftest.hcl... fail
   538    run "invalid"... fail
   539    run "test"... skip
   540  
   541  Failure! 0 passed, 1 failed, 1 skipped.
   542  `,
   543  			expectedErr: `
   544  Error: Reference to undeclared input variable
   545  
   546    on setup/main.tf line 3, in resource "test_resource" "setup":
   547     3:     value = var.not_real // Oh no!
   548  
   549  An input variable with the name "not_real" has not been declared. This
   550  variable can be declared with a variable "not_real" {} block.
   551  `,
   552  		},
   553  		"missing-provider": {
   554  			expectedOut: `main.tftest.hcl... fail
   555    run "passes_validation"... fail
   556  
   557  Failure! 0 passed, 1 failed.
   558  `,
   559  			expectedErr: `
   560  Error: Provider configuration not present
   561  
   562  To work with test_resource.secondary its original provider configuration at
   563  provider["registry.terraform.io/hashicorp/test"].secondary is required, but
   564  it has been removed. This occurs when a provider configuration is removed
   565  while objects created by that provider still exist in the state. Re-add the
   566  provider configuration to destroy test_resource.secondary, after which you
   567  can remove the provider configuration again.
   568  `,
   569  		},
   570  		"missing-provider-in-run-block": {
   571  			expectedOut: `main.tftest.hcl... fail
   572    run "passes_validation"... fail
   573  
   574  Failure! 0 passed, 1 failed.
   575  `,
   576  			expectedErr: `
   577  Error: Provider configuration not present
   578  
   579  To work with test_resource.secondary its original provider configuration at
   580  provider["registry.terraform.io/hashicorp/test"].secondary is required, but
   581  it has been removed. This occurs when a provider configuration is removed
   582  while objects created by that provider still exist in the state. Re-add the
   583  provider configuration to destroy test_resource.secondary, after which you
   584  can remove the provider configuration again.
   585  `,
   586  		},
   587  		"missing-provider-in-test-module": {
   588  			expectedOut: `main.tftest.hcl... fail
   589    run "passes_validation_primary"... pass
   590    run "passes_validation_secondary"... fail
   591  
   592  Failure! 1 passed, 1 failed.
   593  `,
   594  			expectedErr: `
   595  Error: Provider configuration not present
   596  
   597  To work with test_resource.secondary its original provider configuration at
   598  provider["registry.terraform.io/hashicorp/test"].secondary is required, but
   599  it has been removed. This occurs when a provider configuration is removed
   600  while objects created by that provider still exist in the state. Re-add the
   601  provider configuration to destroy test_resource.secondary, after which you
   602  can remove the provider configuration again.
   603  `,
   604  		},
   605  	}
   606  
   607  	for file, tc := range tcs {
   608  		t.Run(file, func(t *testing.T) {
   609  
   610  			td := t.TempDir()
   611  			testCopyDir(t, testFixturePath(path.Join("test", file)), td)
   612  			defer testChdir(t, td)()
   613  
   614  			provider := testing_command.NewProvider(nil)
   615  
   616  			providerSource, close := newMockProviderSource(t, map[string][]string{
   617  				"test": {"1.0.0"},
   618  			})
   619  			defer close()
   620  
   621  			streams, done := terminal.StreamsForTesting(t)
   622  			view := views.NewView(streams)
   623  			ui := new(cli.MockUi)
   624  
   625  			meta := Meta{
   626  				testingOverrides: metaOverridesForProvider(provider.Provider),
   627  				Ui:               ui,
   628  				View:             view,
   629  				Streams:          streams,
   630  				ProviderSource:   providerSource,
   631  			}
   632  
   633  			init := &InitCommand{
   634  				Meta: meta,
   635  			}
   636  
   637  			if code := init.Run(nil); code != 0 {
   638  				t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   639  			}
   640  
   641  			c := &TestCommand{
   642  				Meta: meta,
   643  			}
   644  
   645  			code := c.Run([]string{"-no-color"})
   646  			output := done(t)
   647  
   648  			if code != 1 {
   649  				t.Errorf("expected status code 1 but got %d", code)
   650  			}
   651  
   652  			actualOut, expectedOut := output.Stdout(), tc.expectedOut
   653  			actualErr, expectedErr := output.Stderr(), tc.expectedErr
   654  
   655  			if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 {
   656  				t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff)
   657  			}
   658  
   659  			if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 {
   660  				t.Errorf("error didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff)
   661  			}
   662  
   663  			if provider.ResourceCount() > 0 {
   664  				t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   665  			}
   666  		})
   667  	}
   668  }
   669  
   670  func TestTest_NestedSetupModules(t *testing.T) {
   671  	td := t.TempDir()
   672  	testCopyDir(t, testFixturePath(path.Join("test", "with_nested_setup_modules")), td)
   673  	defer testChdir(t, td)()
   674  
   675  	provider := testing_command.NewProvider(nil)
   676  
   677  	providerSource, close := newMockProviderSource(t, map[string][]string{
   678  		"test": {"1.0.0"},
   679  	})
   680  	defer close()
   681  
   682  	streams, done := terminal.StreamsForTesting(t)
   683  	view := views.NewView(streams)
   684  	ui := new(cli.MockUi)
   685  
   686  	meta := Meta{
   687  		testingOverrides: metaOverridesForProvider(provider.Provider),
   688  		Ui:               ui,
   689  		View:             view,
   690  		Streams:          streams,
   691  		ProviderSource:   providerSource,
   692  	}
   693  
   694  	init := &InitCommand{
   695  		Meta: meta,
   696  	}
   697  
   698  	if code := init.Run(nil); code != 0 {
   699  		t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   700  	}
   701  
   702  	command := &TestCommand{
   703  		Meta: meta,
   704  	}
   705  
   706  	code := command.Run(nil)
   707  	output := done(t)
   708  
   709  	printedOutput := false
   710  
   711  	if code != 0 {
   712  		printedOutput = true
   713  		t.Errorf("expected status code 0 but got %d: %s", code, output.All())
   714  	}
   715  
   716  	if provider.ResourceCount() > 0 {
   717  		if !printedOutput {
   718  			t.Errorf("should have deleted all resources on completion but left %s\n\n%s", provider.ResourceString(), output.All())
   719  		} else {
   720  			t.Errorf("should have deleted all resources on completion but left %s", provider.ResourceString())
   721  		}
   722  	}
   723  }
   724  
   725  func TestTest_StatePropagation(t *testing.T) {
   726  	td := t.TempDir()
   727  	testCopyDir(t, testFixturePath(path.Join("test", "state_propagation")), td)
   728  	defer testChdir(t, td)()
   729  
   730  	provider := testing_command.NewProvider(nil)
   731  
   732  	providerSource, close := newMockProviderSource(t, map[string][]string{
   733  		"test": {"1.0.0"},
   734  	})
   735  	defer close()
   736  
   737  	streams, done := terminal.StreamsForTesting(t)
   738  	view := views.NewView(streams)
   739  	ui := new(cli.MockUi)
   740  
   741  	meta := Meta{
   742  		testingOverrides: metaOverridesForProvider(provider.Provider),
   743  		Ui:               ui,
   744  		View:             view,
   745  		Streams:          streams,
   746  		ProviderSource:   providerSource,
   747  	}
   748  
   749  	init := &InitCommand{
   750  		Meta: meta,
   751  	}
   752  
   753  	if code := init.Run(nil); code != 0 {
   754  		t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   755  	}
   756  
   757  	c := &TestCommand{
   758  		Meta: meta,
   759  	}
   760  
   761  	code := c.Run([]string{"-verbose", "-no-color"})
   762  	output := done(t)
   763  
   764  	if code != 0 {
   765  		t.Errorf("expected status code 0 but got %d", code)
   766  	}
   767  
   768  	expected := `main.tftest.hcl... pass
   769    run "initial_apply_example"... pass
   770  # test_resource.module_resource:
   771  resource "test_resource" "module_resource" {
   772      id    = "df6h8as9"
   773      value = "start"
   774  }
   775    run "initial_apply"... pass
   776  # test_resource.resource:
   777  resource "test_resource" "resource" {
   778      id    = "598318e0"
   779      value = "start"
   780  }
   781    run "plan_second_example"... pass
   782  
   783  Terraform used the selected providers to generate the following execution
   784  plan. Resource actions are indicated with the following symbols:
   785    + create
   786  
   787  Terraform will perform the following actions:
   788  
   789    # test_resource.second_module_resource will be created
   790    + resource "test_resource" "second_module_resource" {
   791        + id    = "b6a1d8cb"
   792        + value = "start"
   793      }
   794  
   795  Plan: 1 to add, 0 to change, 0 to destroy.
   796    run "plan_update"... pass
   797  
   798  Terraform used the selected providers to generate the following execution
   799  plan. Resource actions are indicated with the following symbols:
   800    ~ update in-place
   801  
   802  Terraform will perform the following actions:
   803  
   804    # test_resource.resource will be updated in-place
   805    ~ resource "test_resource" "resource" {
   806          id    = "598318e0"
   807        ~ value = "start" -> "update"
   808      }
   809  
   810  Plan: 0 to add, 1 to change, 0 to destroy.
   811    run "plan_update_example"... pass
   812  
   813  Terraform used the selected providers to generate the following execution
   814  plan. Resource actions are indicated with the following symbols:
   815    ~ update in-place
   816  
   817  Terraform will perform the following actions:
   818  
   819    # test_resource.module_resource will be updated in-place
   820    ~ resource "test_resource" "module_resource" {
   821          id    = "df6h8as9"
   822        ~ value = "start" -> "update"
   823      }
   824  
   825  Plan: 0 to add, 1 to change, 0 to destroy.
   826  
   827  Success! 5 passed, 0 failed.
   828  `
   829  
   830  	actual := output.All()
   831  
   832  	if diff := cmp.Diff(actual, expected); len(diff) > 0 {
   833  		t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
   834  	}
   835  
   836  	if provider.ResourceCount() > 0 {
   837  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   838  	}
   839  }
   840  
   841  func TestTest_OnlyExternalModules(t *testing.T) {
   842  	td := t.TempDir()
   843  	testCopyDir(t, testFixturePath(path.Join("test", "only_modules")), td)
   844  	defer testChdir(t, td)()
   845  
   846  	provider := testing_command.NewProvider(nil)
   847  
   848  	providerSource, close := newMockProviderSource(t, map[string][]string{
   849  		"test": {"1.0.0"},
   850  	})
   851  	defer close()
   852  
   853  	streams, done := terminal.StreamsForTesting(t)
   854  	view := views.NewView(streams)
   855  	ui := new(cli.MockUi)
   856  
   857  	meta := Meta{
   858  		testingOverrides: metaOverridesForProvider(provider.Provider),
   859  		Ui:               ui,
   860  		View:             view,
   861  		Streams:          streams,
   862  		ProviderSource:   providerSource,
   863  	}
   864  
   865  	init := &InitCommand{
   866  		Meta: meta,
   867  	}
   868  
   869  	if code := init.Run(nil); code != 0 {
   870  		t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
   871  	}
   872  
   873  	c := &TestCommand{
   874  		Meta: meta,
   875  	}
   876  
   877  	code := c.Run([]string{"-no-color"})
   878  	output := done(t)
   879  
   880  	if code != 0 {
   881  		t.Errorf("expected status code 0 but got %d", code)
   882  	}
   883  
   884  	expected := `main.tftest.hcl... pass
   885    run "first"... pass
   886    run "second"... pass
   887  
   888  Success! 2 passed, 0 failed.
   889  `
   890  
   891  	actual := output.All()
   892  
   893  	if diff := cmp.Diff(actual, expected); len(diff) > 0 {
   894  		t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
   895  	}
   896  
   897  	if provider.ResourceCount() > 0 {
   898  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   899  	}
   900  }
   901  
   902  func TestTest_PartialUpdates(t *testing.T) {
   903  	td := t.TempDir()
   904  	testCopyDir(t, testFixturePath(path.Join("test", "partial_updates")), td)
   905  	defer testChdir(t, td)()
   906  
   907  	provider := testing_command.NewProvider(nil)
   908  	view, done := testView(t)
   909  
   910  	c := &TestCommand{
   911  		Meta: Meta{
   912  			testingOverrides: metaOverridesForProvider(provider.Provider),
   913  			View:             view,
   914  		},
   915  	}
   916  
   917  	code := c.Run([]string{"-no-color"})
   918  	output := done(t)
   919  
   920  	if code != 0 {
   921  		t.Errorf("expected status code 0 but got %d", code)
   922  	}
   923  
   924  	expected := `main.tftest.hcl... pass
   925    run "first"... pass
   926  
   927  Warning: Resource targeting is in effect
   928  
   929  You are creating a plan with the -target option, which means that the result
   930  of this plan may not represent all of the changes requested by the current
   931  configuration.
   932  
   933  The -target option is not for routine use, and is provided only for
   934  exceptional situations such as recovering from errors or mistakes, or when
   935  Terraform specifically suggests to use it as part of an error message.
   936  
   937  Warning: Applied changes may be incomplete
   938  
   939  The plan was created with the -target option in effect, so some changes
   940  requested in the configuration may have been ignored and the output values
   941  may not be fully updated. Run the following command to verify that no other
   942  changes are pending:
   943      terraform plan
   944  	
   945  Note that the -target option is not suitable for routine use, and is provided
   946  only for exceptional situations such as recovering from errors or mistakes,
   947  or when Terraform specifically suggests to use it as part of an error
   948  message.
   949    run "second"... pass
   950  
   951  Success! 2 passed, 0 failed.
   952  `
   953  
   954  	actual := output.All()
   955  
   956  	if diff := cmp.Diff(actual, expected); len(diff) > 0 {
   957  		t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
   958  	}
   959  
   960  	if provider.ResourceCount() > 0 {
   961  		t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
   962  	}
   963  }