command tests: plan and init (#28616)
* command/init: add test for reconfigure * command/plan: adding tests * command/apply: tests * command: show and refresh tests
This commit is contained in:
parent
1e3a60c7ac
commit
5f30efe857
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
|
@ -1023,6 +1024,56 @@ func TestApply_refresh(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestApply_refreshFalse(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("apply"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"ami":"bar"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
})
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
p := applyFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
"-auto-approve",
|
||||
"-refresh=false",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
if p.ReadResourceCalled {
|
||||
t.Fatal("should not call ReadResource when refresh=false")
|
||||
}
|
||||
}
|
||||
func TestApply_shutdown(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
|
@ -2102,6 +2153,84 @@ func TestApply_jsonGoldenReference(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestApply_warnings(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("apply"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = applyFixtureSchema()
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
Diagnostics: tfdiags.Diagnostics{
|
||||
tfdiags.SimpleWarning("warning 1"),
|
||||
tfdiags.SimpleWarning("warning 2"),
|
||||
},
|
||||
}
|
||||
}
|
||||
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||
return providers.ApplyResourceChangeResponse{
|
||||
NewState: cty.UnknownAsNull(req.PlannedState),
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("full warnings", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{"-auto-approve"}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("compact warnings", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
code := c.Run([]string{"-auto-approve", "-compact-warnings"})
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
// the output should contain 2 warnings and a message about -compact-warnings
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
"To see the full warning notes, run Terraform without -compact-warnings.",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// applyFixtureSchema returns a schema suitable for processing the
|
||||
// configuration in testdata/apply . This schema should be
|
||||
// assigned to a mock provider named "test".
|
||||
|
|
|
@ -485,6 +485,54 @@ func TestInit_backendConfigFilePowershellConfusion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInit_backendReconfigure(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("init-backend"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.2.3"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
ProviderSource: providerSource,
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
// create some state, so the backend has something to migrate.
|
||||
f, err := os.Create("foo") // this is the path" in the backend config
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err = writeStateForTesting(testState(), f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// now run init again, changing the path.
|
||||
// The -reconfigure flag prevents init from migrating
|
||||
// Without -reconfigure, the test fails since the backend asks for input on migrating state
|
||||
args = []string{"-reconfigure", "-backend-config", "path=changed"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_backendConfigFileChange(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
|
|
|
@ -2,6 +2,8 @@ package command
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -21,6 +23,7 @@ import (
|
|||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func TestPlan(t *testing.T) {
|
||||
|
@ -808,6 +811,22 @@ func TestPlan_detailedExitcode(t *testing.T) {
|
|||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
t.Run("return 1", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
// Running plan without setting testingOverrides is similar to plan without init
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
code := c.Run([]string{"-detailed-exitcode"})
|
||||
output := done(t)
|
||||
if code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("return 2", func(t *testing.T) {
|
||||
p := planFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
|
@ -817,12 +836,12 @@ func TestPlan_detailedExitcode(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
args := []string{"-detailed-exitcode"}
|
||||
code := c.Run(args)
|
||||
code := c.Run([]string{"-detailed-exitcode"})
|
||||
output := done(t)
|
||||
if code != 2 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
|
||||
|
@ -1102,7 +1121,155 @@ func TestPlan_replace(t *testing.T) {
|
|||
if got, want := stdout, "test_instance.a will be replaced, as requested"; !strings.Contains(got, want) {
|
||||
t.Errorf("missing replace explanation\ngot output:\n%s\n\nwant substring: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the parallelism flag allows no more than the desired number of
|
||||
// concurrent calls to PlanResourceChange.
|
||||
func TestPlan_parallelism(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("parallelism"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
par := 4
|
||||
|
||||
// started is a semaphore that we use to ensure that we never have more
|
||||
// than "par" plan operations happening concurrently
|
||||
started := make(chan struct{}, par)
|
||||
|
||||
// beginCtx is used as a starting gate to hold back PlanResourceChange
|
||||
// calls until we reach the desired concurrency. The cancel func "begin" is
|
||||
// called once we reach the desired concurrency, allowing all apply calls
|
||||
// to proceed in unison.
|
||||
beginCtx, begin := context.WithCancel(context.Background())
|
||||
|
||||
// Since our mock provider has its own mutex preventing concurrent calls
|
||||
// to ApplyResourceChange, we need to use a number of separate providers
|
||||
// here. They will all have the same mock implementation function assigned
|
||||
// but crucially they will each have their own mutex.
|
||||
providerFactories := map[addrs.Provider]providers.Factory{}
|
||||
for i := 0; i < 10; i++ {
|
||||
name := fmt.Sprintf("test%d", i)
|
||||
provider := &terraform.MockProvider{}
|
||||
provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
name + "_instance": {Block: &configschema.Block{}},
|
||||
},
|
||||
}
|
||||
provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
// If we ever have more than our intended parallelism number of
|
||||
// plan operations running concurrently, the semaphore will fail.
|
||||
select {
|
||||
case started <- struct{}{}:
|
||||
defer func() {
|
||||
<-started
|
||||
}()
|
||||
default:
|
||||
t.Fatal("too many concurrent apply operations")
|
||||
}
|
||||
|
||||
// If we never reach our intended parallelism, the context will
|
||||
// never be canceled and the test will time out.
|
||||
if len(started) >= par {
|
||||
begin()
|
||||
}
|
||||
<-beginCtx.Done()
|
||||
|
||||
// do some "work"
|
||||
// Not required for correctness, but makes it easier to spot a
|
||||
// failure when there is more overlap.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
providerFactories[addrs.NewDefaultProvider(name)] = providers.FactoryFixed(provider)
|
||||
}
|
||||
testingOverrides := &testingOverrides{
|
||||
Providers: providerFactories,
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: testingOverrides,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
fmt.Sprintf("-parallelism=%d", par),
|
||||
}
|
||||
|
||||
res := c.Run(args)
|
||||
output := done(t)
|
||||
if res != 0 {
|
||||
t.Fatal(output.Stdout())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlan_warnings(t *testing.T) {
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("plan"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
t.Run("full warnings", func(t *testing.T) {
|
||||
p := planWarningsFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
code := c.Run([]string{})
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
// the output should contain 3 warnings (returned by planWarningsFixtureProvider())
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
"warning 3",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("compact warnings", func(t *testing.T) {
|
||||
p := planWarningsFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
code := c.Run([]string{"-compact-warnings"})
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
// the output should contain 3 warnings (returned by planWarningsFixtureProvider())
|
||||
// and the message that plan was run with -compact-warnings
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
"warning 3",
|
||||
"To see the full warning notes, run Terraform without -compact-warnings.",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// planFixtureSchema returns a schema suitable for processing the
|
||||
|
@ -1182,6 +1349,25 @@ func planVarsFixtureProvider() *terraform.MockProvider {
|
|||
return p
|
||||
}
|
||||
|
||||
// planFixtureProvider returns a mock provider that is configured for basic
|
||||
// operation with the configuration in testdata/plan. This mock has
|
||||
// GetSchemaResponse and PlanResourceChangeFn populated, returning 3 warnings.
|
||||
func planWarningsFixtureProvider() *terraform.MockProvider {
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = planFixtureSchema()
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
Diagnostics: tfdiags.Diagnostics{
|
||||
tfdiags.SimpleWarning("warning 1"),
|
||||
tfdiags.SimpleWarning("warning 2"),
|
||||
tfdiags.SimpleWarning("warning 3"),
|
||||
},
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
const planVarFile = `
|
||||
foo = "bar"
|
||||
`
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
var equateEmpty = cmpopts.EquateEmpty()
|
||||
|
@ -859,6 +860,78 @@ func TestRefresh_targetFlagsDiags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRefresh_warnings(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
testCopyDir(t, testFixturePath("apply"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = refreshFixtureSchema()
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
Diagnostics: tfdiags.Diagnostics{
|
||||
tfdiags.SimpleWarning("warning 1"),
|
||||
tfdiags.SimpleWarning("warning 2"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("full warnings", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &RefreshCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
code := c.Run([]string{})
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("compact warnings", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &RefreshCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
code := c.Run([]string{"-compact-warnings"})
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
// the output should contain 2 warnings and a message about -compact-warnings
|
||||
wantWarnings := []string{
|
||||
"warning 1",
|
||||
"warning 2",
|
||||
"To see the full warning notes, run Terraform without -compact-warnings.",
|
||||
}
|
||||
for _, want := range wantWarnings {
|
||||
if !strings.Contains(output.Stdout(), want) {
|
||||
t.Errorf("missing warning %s", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// configuration in testdata/refresh . This schema should be
|
||||
// assigned to a mock provider named "test".
|
||||
func refreshFixtureSchema() *providers.GetProviderSchemaResponse {
|
||||
|
|
|
@ -41,11 +41,11 @@ func TestShow(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShow_noArgs(t *testing.T) {
|
||||
// Get a temp cwd
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
// Create the default state
|
||||
statePath := testStateFile(t, testState())
|
||||
stateDir := filepath.Dir(statePath)
|
||||
defer os.RemoveAll(stateDir)
|
||||
defer testChdir(t, stateDir)()
|
||||
testStateFileDefault(t, testState())
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
|
@ -57,10 +57,7 @@ func TestShow_noArgs(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
// the statefile created by testStateFile is named state.tfstate
|
||||
// so one arg is required
|
||||
args := []string{"state.tfstate"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
if code := c.Run([]string{}); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue