mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
testing framework: introduce test command optional flags (#33504)
* testing framework: introduce test command optional flags * address consistency checks
This commit is contained in:
parent
2cc81cfec6
commit
6882dd9530
@ -364,7 +364,7 @@ func (s failingState) WriteState(state *states.State) error {
|
||||
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
||||
|
@ -32,7 +32,7 @@ func TestLocalRun(t *testing.T) {
|
||||
configDir := "./testdata/empty"
|
||||
b := TestLocal(t)
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
streams, _ := terminal.StreamsForTesting(t)
|
||||
@ -63,7 +63,7 @@ func TestLocalRun_error(t *testing.T) {
|
||||
// should then cause LocalRun to return with the state unlocked.
|
||||
b.Backend = backendWithStateStorageThatFailsRefresh{}
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
streams, _ := terminal.StreamsForTesting(t)
|
||||
@ -90,7 +90,7 @@ func TestLocalRun_stalePlan(t *testing.T) {
|
||||
configDir := "./testdata/apply"
|
||||
b := TestLocal(t)
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
// Write an empty state file with serial 3
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
@ -24,7 +26,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestLocal_planBasic(t *testing.T) {
|
||||
@ -716,7 +717,7 @@ func TestLocal_planOutPathNoChange(t *testing.T) {
|
||||
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
||||
|
@ -267,7 +267,7 @@ func TestLocal_refreshEmptyState(t *testing.T) {
|
||||
func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
||||
|
@ -15,6 +15,8 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/cloud"
|
||||
@ -29,7 +31,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
@ -41,7 +42,7 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun
|
||||
func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||
@ -20,7 +22,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/initwd"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestRemoteStoredVariableValue(t *testing.T) {
|
||||
@ -186,7 +187,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
|
||||
@ -409,7 +410,7 @@ func TestRemoteVariablesDoNotOverride(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
|
||||
|
@ -14,6 +14,8 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/cloud"
|
||||
@ -27,7 +29,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
@ -39,7 +40,7 @@ func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func
|
||||
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/initwd"
|
||||
@ -18,7 +19,7 @@ func TestChecksHappyPath(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, nil)
|
||||
_, instDiags := inst.InstallModules(context.Background(), fixtureDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), fixtureDir, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import (
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
mocks "github.com/hashicorp/go-tfe/mocks"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
@ -32,7 +34,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
@ -44,7 +45,7 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun
|
||||
func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||
@ -19,7 +21,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestRemoteStoredVariableValue(t *testing.T) {
|
||||
@ -185,7 +186,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), testBackendSingleWorkspaceName)
|
||||
@ -408,7 +409,7 @@ func TestRemoteVariablesDoNotOverride(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
defer configCleanup()
|
||||
|
||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), testBackendSingleWorkspaceName)
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
@ -29,7 +31,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
@ -41,7 +42,7 @@ func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func
|
||||
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||
@ -17,7 +19,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
@ -29,7 +30,7 @@ func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, f
|
||||
func testOperationRefreshWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
|
58
internal/command/arguments/test.go
Normal file
58
internal/command/arguments/test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package arguments
|
||||
|
||||
import "github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
||||
// Test represents the command-line arguments for the test command.
|
||||
type Test struct {
|
||||
// Filter contains a list of test files to execute. If empty, all test files
|
||||
// will be executed.
|
||||
Filter []string
|
||||
|
||||
// TestDirectory allows the user to override the directory that the test
|
||||
// command will use to discover test files, defaults to "tests". Regardless
|
||||
// of the value here, test files within the configuration directory will
|
||||
// always be discovered.
|
||||
TestDirectory string
|
||||
|
||||
// ViewType specifies which output format to use: human or JSON.
|
||||
ViewType ViewType
|
||||
|
||||
// You can specify common variables for all tests from the command line.
|
||||
Vars *Vars
|
||||
|
||||
// Verbose tells the test command to print out the plan either in
|
||||
// human-readable format or JSON for each run step depending on the
|
||||
// ViewType.
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func ParseTest(args []string) (*Test, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
test := Test{
|
||||
Vars: new(Vars),
|
||||
}
|
||||
|
||||
var jsonOutput bool
|
||||
cmdFlags := extendedFlagSet("test", nil, nil, test.Vars)
|
||||
cmdFlags.Var((*flagStringSlice)(&test.Filter), "filter", "filter")
|
||||
cmdFlags.StringVar(&test.TestDirectory, "test-directory", "tests", "test-directory")
|
||||
cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
|
||||
cmdFlags.BoolVar(&test.Verbose, "verbose", false, "verbose")
|
||||
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to parse command-line flags",
|
||||
err.Error()))
|
||||
}
|
||||
|
||||
switch {
|
||||
case jsonOutput:
|
||||
test.ViewType = ViewJSON
|
||||
default:
|
||||
test.ViewType = ViewHuman
|
||||
}
|
||||
|
||||
return &test, diags
|
||||
}
|
154
internal/command/arguments/test_test.go
Normal file
154
internal/command/arguments/test_test.go
Normal file
@ -0,0 +1,154 @@
|
||||
package arguments
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
func TestParseTest_Vars(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
args []string
|
||||
want []FlagNameValue
|
||||
}{
|
||||
"no var flags by default": {
|
||||
args: nil,
|
||||
want: nil,
|
||||
},
|
||||
"one var": {
|
||||
args: []string{"-var", "foo=bar"},
|
||||
want: []FlagNameValue{
|
||||
{Name: "-var", Value: "foo=bar"},
|
||||
},
|
||||
},
|
||||
"one var-file": {
|
||||
args: []string{"-var-file", "cool.tfvars"},
|
||||
want: []FlagNameValue{
|
||||
{Name: "-var-file", Value: "cool.tfvars"},
|
||||
},
|
||||
},
|
||||
"ordering preserved": {
|
||||
args: []string{
|
||||
"-var", "foo=bar",
|
||||
"-var-file", "cool.tfvars",
|
||||
"-var", "boop=beep",
|
||||
},
|
||||
want: []FlagNameValue{
|
||||
{Name: "-var", Value: "foo=bar"},
|
||||
{Name: "-var-file", Value: "cool.tfvars"},
|
||||
{Name: "-var", Value: "boop=beep"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseTest(tc.args)
|
||||
if len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
}
|
||||
if vars := got.Vars.All(); !cmp.Equal(vars, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(vars, tc.want))
|
||||
}
|
||||
if got, want := got.Vars.Empty(), len(tc.want) == 0; got != want {
|
||||
t.Fatalf("expected Empty() to return %t, but was %t", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTest(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
args []string
|
||||
want *Test
|
||||
wantDiags tfdiags.Diagnostics
|
||||
}{
|
||||
"defaults": {
|
||||
args: nil,
|
||||
want: &Test{
|
||||
Filter: nil,
|
||||
TestDirectory: "tests",
|
||||
ViewType: ViewHuman,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
wantDiags: nil,
|
||||
},
|
||||
"with-filters": {
|
||||
args: []string{"-filter=one.tftest", "-filter=two.tftest"},
|
||||
want: &Test{
|
||||
Filter: []string{"one.tftest", "two.tftest"},
|
||||
TestDirectory: "tests",
|
||||
ViewType: ViewHuman,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
wantDiags: nil,
|
||||
},
|
||||
"json": {
|
||||
args: []string{"-json"},
|
||||
want: &Test{
|
||||
Filter: nil,
|
||||
TestDirectory: "tests",
|
||||
ViewType: ViewJSON,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
wantDiags: nil,
|
||||
},
|
||||
"test-directory": {
|
||||
args: []string{"-test-directory=other"},
|
||||
want: &Test{
|
||||
Filter: nil,
|
||||
TestDirectory: "other",
|
||||
ViewType: ViewHuman,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
wantDiags: nil,
|
||||
},
|
||||
"verbose": {
|
||||
args: []string{"-verbose"},
|
||||
want: &Test{
|
||||
Filter: nil,
|
||||
TestDirectory: "tests",
|
||||
ViewType: ViewHuman,
|
||||
Verbose: true,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
},
|
||||
"unknown flag": {
|
||||
args: []string{"-boop"},
|
||||
want: &Test{
|
||||
Filter: nil,
|
||||
TestDirectory: "tests",
|
||||
ViewType: ViewHuman,
|
||||
Vars: &Vars{},
|
||||
},
|
||||
wantDiags: tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to parse command-line flags",
|
||||
"flag provided but not defined: -boop",
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cmpOpts := cmpopts.IgnoreUnexported(Operation{}, Vars{}, State{})
|
||||
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseTest(tc.args)
|
||||
|
||||
if diff := cmp.Diff(tc.want, got, cmpOpts); len(diff) > 0 {
|
||||
t.Errorf("diff:\n%s", diff)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(diags, tc.wantDiags) {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(diags), spew.Sdump(tc.wantDiags))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ import (
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/hashicorp/terraform-svchost/disco"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
backendInit "github.com/hashicorp/terraform/internal/backend/init"
|
||||
backendLocal "github.com/hashicorp/terraform/internal/backend/local"
|
||||
@ -50,7 +52,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// These are the directories for our test data and fixtures.
|
||||
@ -158,7 +159,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
}
|
||||
|
@ -19,10 +19,12 @@ type GetCommand struct {
|
||||
|
||||
func (c *GetCommand) Run(args []string) int {
|
||||
var update bool
|
||||
var testsDirectory string
|
||||
|
||||
args = c.Meta.process(args)
|
||||
cmdFlags := c.Meta.defaultFlagSet("get")
|
||||
cmdFlags.BoolVar(&update, "update", false, "update")
|
||||
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
|
||||
@ -41,7 +43,7 @@ func (c *GetCommand) Run(args []string) int {
|
||||
|
||||
path = c.normalizePath(path)
|
||||
|
||||
abort, diags := getModules(ctx, &c.Meta, path, update)
|
||||
abort, diags := getModules(ctx, &c.Meta, path, testsDirectory, update)
|
||||
c.showDiagnostics(diags)
|
||||
if abort || diags.HasErrors() {
|
||||
return 1
|
||||
@ -68,10 +70,12 @@ Usage: terraform [global options] get [options]
|
||||
|
||||
Options:
|
||||
|
||||
-update Check already-downloaded modules for available updates
|
||||
and install the newest versions available.
|
||||
-update Check already-downloaded modules for available updates
|
||||
and install the newest versions available.
|
||||
|
||||
-no-color Disable text coloring in the output.
|
||||
-no-color Disable text coloring in the output.
|
||||
|
||||
-test-directory=path Set the Terraform test directory, defaults to "tests".
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
@ -81,10 +85,10 @@ func (c *GetCommand) Synopsis() string {
|
||||
return "Install or upgrade remote Terraform modules"
|
||||
}
|
||||
|
||||
func getModules(ctx context.Context, m *Meta, path string, upgrade bool) (abort bool, diags tfdiags.Diagnostics) {
|
||||
func getModules(ctx context.Context, m *Meta, path string, testsDir string, upgrade bool) (abort bool, diags tfdiags.Diagnostics) {
|
||||
hooks := uiModuleInstallHooks{
|
||||
Ui: m.Ui,
|
||||
ShowLocalPaths: true,
|
||||
}
|
||||
return m.installModules(ctx, path, upgrade, hooks)
|
||||
return m.installModules(ctx, path, testsDir, upgrade, hooks)
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ type InitCommand struct {
|
||||
}
|
||||
|
||||
func (c *InitCommand) Run(args []string) int {
|
||||
var flagFromModule, flagLockfile string
|
||||
var flagFromModule, flagLockfile, testsDirectory string
|
||||
var flagBackend, flagCloud, flagGet, flagUpgrade bool
|
||||
var flagPluginPath FlagStringSlice
|
||||
flagConfigExtra := newRawFlags("-backend-config")
|
||||
@ -62,6 +62,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
|
||||
cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
|
||||
cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
|
||||
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
@ -172,7 +173,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
}
|
||||
|
||||
// Load just the root module to begin backend and module initialization
|
||||
rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, "tests")
|
||||
rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, testsDirectory)
|
||||
|
||||
// There may be parsing errors in config loading but these will be shown later _after_
|
||||
// checking for core version requirement errors. Not meeting the version requirement should
|
||||
@ -233,7 +234,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
}
|
||||
|
||||
if flagGet {
|
||||
modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, rootModEarly, flagUpgrade)
|
||||
modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, testsDirectory, rootModEarly, flagUpgrade)
|
||||
diags = diags.Append(modsDiags)
|
||||
if modsAbort || modsDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
@ -246,7 +247,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
|
||||
// With all of the modules (hopefully) installed, we can now try to load the
|
||||
// whole configuration tree.
|
||||
config, confDiags := c.loadConfigWithTests(path, "tests")
|
||||
config, confDiags := c.loadConfigWithTests(path, testsDirectory)
|
||||
// configDiags will be handled after the version constraint check, since an
|
||||
// incorrect version of terraform may be producing errors for configuration
|
||||
// constructs added in later versions.
|
||||
@ -342,7 +343,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *InitCommand) getModules(ctx context.Context, path string, earlyRoot *configs.Module, upgrade bool) (output bool, abort bool, diags tfdiags.Diagnostics) {
|
||||
func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool) (output bool, abort bool, diags tfdiags.Diagnostics) {
|
||||
testModules := false // We can also have modules buried in test files.
|
||||
for _, file := range earlyRoot.Tests {
|
||||
for _, run := range file.Runs {
|
||||
@ -373,7 +374,7 @@ func (c *InitCommand) getModules(ctx context.Context, path string, earlyRoot *co
|
||||
ShowLocalPaths: true,
|
||||
}
|
||||
|
||||
installAbort, installDiags := c.installModules(ctx, path, upgrade, hooks)
|
||||
installAbort, installDiags := c.installModules(ctx, path, testsDir, upgrade, hooks)
|
||||
diags = diags.Append(installDiags)
|
||||
|
||||
// At this point, installModules may have generated error diags or been
|
||||
@ -1175,6 +1176,8 @@ Options:
|
||||
See the documentation on configuring Terraform with
|
||||
Terraform Cloud for more information.
|
||||
|
||||
-test-directory=path Set the Terraform test directory, defaults to "tests".
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
@ -3,17 +3,17 @@
|
||||
|
||||
package jsonplan
|
||||
|
||||
// module is the representation of a module in state. This can be the root
|
||||
// Module is the representation of a module in state. This can be the root
|
||||
// module or a child module.
|
||||
type module struct {
|
||||
type Module struct {
|
||||
// Resources are sorted in a user-friendly order that is undefined at this
|
||||
// time, but consistent.
|
||||
Resources []resource `json:"resources,omitempty"`
|
||||
Resources []Resource `json:"resources,omitempty"`
|
||||
|
||||
// Address is the absolute module address, omitted for the root module
|
||||
Address string `json:"address,omitempty"`
|
||||
|
||||
// Each module object can optionally have its own nested "child_modules",
|
||||
// recursively describing the full module tree.
|
||||
ChildModules []module `json:"child_modules,omitempty"`
|
||||
ChildModules []Module `json:"child_modules,omitempty"`
|
||||
}
|
||||
|
@ -48,11 +48,11 @@ const (
|
||||
|
||||
// Plan is the top-level representation of the json format of a plan. It includes
|
||||
// the complete config and current state.
|
||||
type plan struct {
|
||||
type Plan struct {
|
||||
FormatVersion string `json:"format_version,omitempty"`
|
||||
TerraformVersion string `json:"terraform_version,omitempty"`
|
||||
Variables variables `json:"variables,omitempty"`
|
||||
PlannedValues stateValues `json:"planned_values,omitempty"`
|
||||
Variables Variables `json:"variables,omitempty"`
|
||||
PlannedValues StateValues `json:"planned_values,omitempty"`
|
||||
// ResourceDrift and ResourceChanges are sorted in a user-friendly order
|
||||
// that is undefined at this time, but consistent.
|
||||
ResourceDrift []ResourceChange `json:"resource_drift,omitempty"`
|
||||
@ -66,8 +66,8 @@ type plan struct {
|
||||
Errored bool `json:"errored"`
|
||||
}
|
||||
|
||||
func newPlan() *plan {
|
||||
return &plan{
|
||||
func newPlan() *Plan {
|
||||
return &Plan{
|
||||
FormatVersion: FormatVersion,
|
||||
}
|
||||
}
|
||||
@ -150,17 +150,17 @@ type Importing struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type output struct {
|
||||
type Output struct {
|
||||
Sensitive bool `json:"sensitive"`
|
||||
Type json.RawMessage `json:"type,omitempty"`
|
||||
Value json.RawMessage `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// variables is the JSON representation of the variables provided to the current
|
||||
// Variables is the JSON representation of the variables provided to the current
|
||||
// plan.
|
||||
type variables map[string]*variable
|
||||
type Variables map[string]*Variable
|
||||
|
||||
type variable struct {
|
||||
type Variable struct {
|
||||
Value json.RawMessage `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
@ -212,13 +212,14 @@ func MarshalForRenderer(
|
||||
return output.OutputChanges, output.ResourceChanges, output.ResourceDrift, output.RelevantAttributes, nil
|
||||
}
|
||||
|
||||
// Marshal returns the json encoding of a terraform plan.
|
||||
func Marshal(
|
||||
// MarshalForLog returns the original JSON compatible plan, ready for a logging
|
||||
// package to marshal further.
|
||||
func MarshalForLog(
|
||||
config *configs.Config,
|
||||
p *plans.Plan,
|
||||
sf *statefile.File,
|
||||
schemas *terraform.Schemas,
|
||||
) ([]byte, error) {
|
||||
) (*Plan, error) {
|
||||
output := newPlan()
|
||||
output.TerraformVersion = version.String()
|
||||
output.Timestamp = p.Timestamp.Format(time.RFC3339)
|
||||
@ -293,12 +294,26 @@ func Marshal(
|
||||
return nil, fmt.Errorf("error marshaling config: %s", err)
|
||||
}
|
||||
|
||||
ret, err := json.Marshal(output)
|
||||
return ret, err
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls map[string]*configs.Variable) error {
|
||||
p.Variables = make(variables, len(vars))
|
||||
// Marshal returns the json encoding of a terraform plan.
|
||||
func Marshal(
|
||||
config *configs.Config,
|
||||
p *plans.Plan,
|
||||
sf *statefile.File,
|
||||
schemas *terraform.Schemas,
|
||||
) ([]byte, error) {
|
||||
output, err := MarshalForLog(config, p, sf, schemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(output)
|
||||
}
|
||||
|
||||
func (p *Plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls map[string]*configs.Variable) error {
|
||||
p.Variables = make(Variables, len(vars))
|
||||
|
||||
for k, v := range vars {
|
||||
val, err := v.Decode(cty.DynamicPseudoType)
|
||||
@ -309,7 +324,7 @@ func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls ma
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Variables[k] = &variable{
|
||||
p.Variables[k] = &Variable{
|
||||
Value: valJSON,
|
||||
}
|
||||
}
|
||||
@ -338,7 +353,7 @@ func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls ma
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Variables[name] = &variable{
|
||||
p.Variables[name] = &Variable{
|
||||
Value: valJSON,
|
||||
}
|
||||
}
|
||||
@ -639,7 +654,7 @@ func MarshalOutputChanges(changes *plans.Changes) (map[string]Change, error) {
|
||||
return outputChanges, nil
|
||||
}
|
||||
|
||||
func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error {
|
||||
func (p *Plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error {
|
||||
// marshal the planned changes into a module
|
||||
plan, err := marshalPlannedValues(changes, schemas)
|
||||
if err != nil {
|
||||
@ -657,7 +672,7 @@ func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.S
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plan) marshalRelevantAttrs(plan *plans.Plan) error {
|
||||
func (p *Plan) marshalRelevantAttrs(plan *plans.Plan) error {
|
||||
for _, ra := range plan.RelevantAttributes {
|
||||
addr := ra.Resource.String()
|
||||
path, err := encodePath(ra.Attr)
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// Resource is the representation of a resource in the json plan
|
||||
type resource struct {
|
||||
type Resource struct {
|
||||
// Address is the absolute resource address
|
||||
Address string `json:"address,omitempty"`
|
||||
|
||||
@ -37,7 +37,7 @@ type resource struct {
|
||||
// resource, whose structure depends on the resource type schema. Any
|
||||
// unknown values are omitted or set to null, making them indistinguishable
|
||||
// from absent values.
|
||||
AttributeValues attributeValues `json:"values,omitempty"`
|
||||
AttributeValues AttributeValues `json:"values,omitempty"`
|
||||
|
||||
// SensitiveValues is similar to AttributeValues, but with all sensitive
|
||||
// values replaced with true, and all non-sensitive leaf values omitted.
|
||||
|
@ -19,22 +19,22 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
)
|
||||
|
||||
// stateValues is the common representation of resolved values for both the
|
||||
// StateValues is the common representation of resolved values for both the
|
||||
// prior state (which is always complete) and the planned new state.
|
||||
type stateValues struct {
|
||||
Outputs map[string]output `json:"outputs,omitempty"`
|
||||
RootModule module `json:"root_module,omitempty"`
|
||||
type StateValues struct {
|
||||
Outputs map[string]Output `json:"outputs,omitempty"`
|
||||
RootModule Module `json:"root_module,omitempty"`
|
||||
}
|
||||
|
||||
// attributeValues is the JSON representation of the attribute values of the
|
||||
// AttributeValues is the JSON representation of the attribute values of the
|
||||
// resource, whose structure depends on the resource type schema.
|
||||
type attributeValues map[string]interface{}
|
||||
type AttributeValues map[string]interface{}
|
||||
|
||||
func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues {
|
||||
func marshalAttributeValues(value cty.Value, schema *configschema.Block) AttributeValues {
|
||||
if value == cty.NilVal || value.IsNull() {
|
||||
return nil
|
||||
}
|
||||
ret := make(attributeValues)
|
||||
ret := make(AttributeValues)
|
||||
|
||||
it := value.ElementIterator()
|
||||
for it.Next() {
|
||||
@ -47,13 +47,13 @@ func marshalAttributeValues(value cty.Value, schema *configschema.Block) attribu
|
||||
|
||||
// marshalPlannedOutputs takes a list of changes and returns a map of output
|
||||
// values
|
||||
func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
|
||||
func marshalPlannedOutputs(changes *plans.Changes) (map[string]Output, error) {
|
||||
if changes.Outputs == nil {
|
||||
// No changes - we're done here!
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ret := make(map[string]output)
|
||||
ret := make(map[string]Output)
|
||||
|
||||
for _, oc := range changes.Outputs {
|
||||
if oc.ChangeSrc.Action == plans.Delete {
|
||||
@ -82,7 +82,7 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
|
||||
}
|
||||
}
|
||||
|
||||
ret[oc.Addr.OutputValue.Name] = output{
|
||||
ret[oc.Addr.OutputValue.Name] = Output{
|
||||
Value: json.RawMessage(after),
|
||||
Type: json.RawMessage(afterType),
|
||||
Sensitive: oc.Sensitive,
|
||||
@ -93,8 +93,8 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
|
||||
|
||||
}
|
||||
|
||||
func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (module, error) {
|
||||
var ret module
|
||||
func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (Module, error) {
|
||||
var ret Module
|
||||
|
||||
// build two maps:
|
||||
// module name -> [resource addresses]
|
||||
@ -166,8 +166,8 @@ func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (m
|
||||
}
|
||||
|
||||
// marshalPlanResources
|
||||
func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, error) {
|
||||
var ret []resource
|
||||
func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]Resource, error) {
|
||||
var ret []Resource
|
||||
|
||||
for _, ri := range ris {
|
||||
r := changes.ResourceInstance(ri)
|
||||
@ -175,7 +175,7 @@ func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstanc
|
||||
continue
|
||||
}
|
||||
|
||||
resource := resource{
|
||||
resource := Resource{
|
||||
Address: r.Addr.String(),
|
||||
Type: r.Addr.Resource.Resource.Type,
|
||||
Name: r.Addr.Resource.Resource.Name,
|
||||
@ -252,14 +252,14 @@ func marshalPlanModules(
|
||||
childModules []addrs.ModuleInstance,
|
||||
moduleMap map[string][]addrs.ModuleInstance,
|
||||
moduleResourceMap map[string][]addrs.AbsResourceInstance,
|
||||
) ([]module, error) {
|
||||
) ([]Module, error) {
|
||||
|
||||
var ret []module
|
||||
var ret []Module
|
||||
|
||||
for _, child := range childModules {
|
||||
moduleResources := moduleResourceMap[child.String()]
|
||||
// cm for child module, naming things is hard.
|
||||
var cm module
|
||||
var cm Module
|
||||
// don't populate the address for the root module
|
||||
if child.String() != "" {
|
||||
cm.Address = child.String()
|
||||
|
@ -8,19 +8,20 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestMarshalAttributeValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Attr cty.Value
|
||||
Schema *configschema.Block
|
||||
Want attributeValues
|
||||
Want AttributeValues
|
||||
}{
|
||||
{
|
||||
cty.NilVal,
|
||||
@ -58,7 +59,7 @@ func TestMarshalAttributeValues(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeValues{"foo": json.RawMessage(`"bar"`)},
|
||||
AttributeValues{"foo": json.RawMessage(`"bar"`)},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
@ -72,7 +73,7 @@ func TestMarshalAttributeValues(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeValues{"foo": json.RawMessage(`null`)},
|
||||
AttributeValues{"foo": json.RawMessage(`null`)},
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
@ -96,7 +97,7 @@ func TestMarshalAttributeValues(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeValues{
|
||||
AttributeValues{
|
||||
"bar": json.RawMessage(`{"hello":"world"}`),
|
||||
"baz": json.RawMessage(`["goodnight","moon"]`),
|
||||
},
|
||||
@ -117,7 +118,7 @@ func TestMarshalPlannedOutputs(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
Changes *plans.Changes
|
||||
Want map[string]output
|
||||
Want map[string]Output
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
@ -138,7 +139,7 @@ func TestMarshalPlannedOutputs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]output{
|
||||
map[string]Output{
|
||||
"bar": {
|
||||
Sensitive: false,
|
||||
Type: json.RawMessage(`"string"`),
|
||||
@ -159,7 +160,7 @@ func TestMarshalPlannedOutputs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]output{},
|
||||
map[string]Output{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
@ -187,7 +188,7 @@ func TestMarshalPlanResources(t *testing.T) {
|
||||
Action plans.Action
|
||||
Before cty.Value
|
||||
After cty.Value
|
||||
Want []resource
|
||||
Want []Resource
|
||||
Err bool
|
||||
}{
|
||||
"create with unknowns": {
|
||||
@ -197,7 +198,7 @@ func TestMarshalPlanResources(t *testing.T) {
|
||||
"woozles": cty.UnknownVal(cty.String),
|
||||
"foozles": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
Want: []resource{{
|
||||
Want: []Resource{{
|
||||
Address: "test_thing.example",
|
||||
Mode: "managed",
|
||||
Type: "test_thing",
|
||||
@ -205,7 +206,7 @@ func TestMarshalPlanResources(t *testing.T) {
|
||||
Index: addrs.InstanceKey(nil),
|
||||
ProviderName: "registry.terraform.io/hashicorp/test",
|
||||
SchemaVersion: 1,
|
||||
AttributeValues: attributeValues{},
|
||||
AttributeValues: AttributeValues{},
|
||||
SensitiveValues: json.RawMessage("{}"),
|
||||
}},
|
||||
Err: false,
|
||||
@ -240,7 +241,7 @@ func TestMarshalPlanResources(t *testing.T) {
|
||||
"woozles": cty.StringVal("baz"),
|
||||
"foozles": cty.StringVal("bat"),
|
||||
}),
|
||||
Want: []resource{{
|
||||
Want: []Resource{{
|
||||
Address: "test_thing.example",
|
||||
Mode: "managed",
|
||||
Type: "test_thing",
|
||||
@ -248,7 +249,7 @@ func TestMarshalPlanResources(t *testing.T) {
|
||||
Index: addrs.InstanceKey(nil),
|
||||
ProviderName: "registry.terraform.io/hashicorp/test",
|
||||
SchemaVersion: 1,
|
||||
AttributeValues: attributeValues{
|
||||
AttributeValues: AttributeValues{
|
||||
"woozles": json.RawMessage(`"baz"`),
|
||||
"foozles": json.RawMessage(`"bat"`),
|
||||
},
|
||||
|
@ -29,18 +29,18 @@ const (
|
||||
DataResourceMode = "data"
|
||||
)
|
||||
|
||||
// state is the top-level representation of the json format of a terraform
|
||||
// State is the top-level representation of the json format of a terraform
|
||||
// state.
|
||||
type state struct {
|
||||
type State struct {
|
||||
FormatVersion string `json:"format_version,omitempty"`
|
||||
TerraformVersion string `json:"terraform_version,omitempty"`
|
||||
Values *stateValues `json:"values,omitempty"`
|
||||
Values *StateValues `json:"values,omitempty"`
|
||||
Checks json.RawMessage `json:"checks,omitempty"`
|
||||
}
|
||||
|
||||
// stateValues is the common representation of resolved values for both the prior
|
||||
// StateValues is the common representation of resolved values for both the prior
|
||||
// state (which is always complete) and the planned new state.
|
||||
type stateValues struct {
|
||||
type StateValues struct {
|
||||
Outputs map[string]Output `json:"outputs,omitempty"`
|
||||
RootModule Module `json:"root_module,omitempty"`
|
||||
}
|
||||
@ -135,8 +135,8 @@ func marshalAttributeValues(value cty.Value) AttributeValues {
|
||||
}
|
||||
|
||||
// newState() returns a minimally-initialized state
|
||||
func newState() *state {
|
||||
return &state{
|
||||
func newState() *State {
|
||||
return &State{
|
||||
FormatVersion: FormatVersion,
|
||||
}
|
||||
}
|
||||
@ -162,13 +162,13 @@ func MarshalForRenderer(sf *statefile.File, schemas *terraform.Schemas) (Module,
|
||||
return root, outputs, err
|
||||
}
|
||||
|
||||
// Marshal returns the json encoding of a terraform state.
|
||||
func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
|
||||
// MarshalForLog returns the origin JSON compatible state, read for a logging
|
||||
// package to marshal further.
|
||||
func MarshalForLog(sf *statefile.File, schemas *terraform.Schemas) (*State, error) {
|
||||
output := newState()
|
||||
|
||||
if sf == nil || sf.State.Empty() {
|
||||
ret, err := json.Marshal(output)
|
||||
return ret, err
|
||||
return output, nil
|
||||
}
|
||||
|
||||
if sf.TerraformVersion != nil {
|
||||
@ -186,12 +186,22 @@ func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
|
||||
output.Checks = jsonchecks.MarshalCheckStates(sf.State.CheckResults)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Marshal returns the json encoding of a terraform state.
|
||||
func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
|
||||
output, err := MarshalForLog(sf, schemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err := json.Marshal(output)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.Schemas) error {
|
||||
var sv stateValues
|
||||
func (jsonstate *State) marshalStateValues(s *states.State, schemas *terraform.Schemas) error {
|
||||
var sv StateValues
|
||||
var err error
|
||||
|
||||
// only marshal the root module outputs
|
||||
|
@ -183,7 +183,7 @@ func (m *Meta) loadHCLFile(filename string) (hcl.Body, tfdiags.Diagnostics) {
|
||||
// can then be relayed to the end-user. The uiModuleInstallHooks type in
|
||||
// this package has a reasonable implementation for displaying notifications
|
||||
// via a provided cli.Ui.
|
||||
func (m *Meta) installModules(ctx context.Context, rootDir string, upgrade bool, hooks initwd.ModuleInstallHooks) (abort bool, diags tfdiags.Diagnostics) {
|
||||
func (m *Meta) installModules(ctx context.Context, rootDir, testsDir string, upgrade bool, hooks initwd.ModuleInstallHooks) (abort bool, diags tfdiags.Diagnostics) {
|
||||
ctx, span := tracer.Start(ctx, "install modules")
|
||||
defer span.End()
|
||||
|
||||
@ -203,7 +203,7 @@ func (m *Meta) installModules(ctx context.Context, rootDir string, upgrade bool,
|
||||
|
||||
inst := initwd.NewModuleInstaller(m.modulesDir(), loader, m.registryClient())
|
||||
|
||||
_, moreDiags := inst.InstallModules(ctx, rootDir, upgrade, hooks)
|
||||
_, moreDiags := inst.InstallModules(ctx, rootDir, testsDir, upgrade, hooks)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
if ctx.Err() == context.Canceled {
|
||||
|
@ -30,8 +30,11 @@ func (c *ProvidersCommand) Synopsis() string {
|
||||
}
|
||||
|
||||
func (c *ProvidersCommand) Run(args []string) int {
|
||||
var testsDirectory string
|
||||
|
||||
args = c.Meta.process(args)
|
||||
cmdFlags := c.Meta.defaultFlagSet("providers")
|
||||
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
|
||||
@ -70,7 +73,7 @@ func (c *ProvidersCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
config, configDiags := c.loadConfigWithTests(configPath, "tests")
|
||||
config, configDiags := c.loadConfigWithTests(configPath, testsDirectory)
|
||||
diags = diags.Append(configDiags)
|
||||
if configDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
@ -172,7 +175,7 @@ func (c *ProvidersCommand) populateTreeNode(tree treeprint.Tree, node *configs.M
|
||||
}
|
||||
|
||||
const providersCommandHelp = `
|
||||
Usage: terraform [global options] providers [DIR]
|
||||
Usage: terraform [global options] providers [options] [DIR]
|
||||
|
||||
Prints out a tree of modules in the referenced configuration annotated with
|
||||
their provider requirements.
|
||||
@ -180,4 +183,8 @@ Usage: terraform [global options] providers [DIR]
|
||||
This provides an overview of all of the provider requirements across all
|
||||
referenced modules, as an aid to understanding why particular provider
|
||||
plugins are needed and why particular versions are selected.
|
||||
|
||||
Options:
|
||||
|
||||
-test-directory=path Set the Terraform test directory, defaults to "tests".
|
||||
`
|
||||
|
@ -3,11 +3,10 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
@ -43,7 +42,26 @@ Usage: terraform [global options] test [options]
|
||||
|
||||
Options:
|
||||
|
||||
TODO: implement optional arguments.
|
||||
-filter=testfile If specified, Terraform will only execute the test files
|
||||
specified by this flag. You can use this option multiple
|
||||
times to execute more than one test file.
|
||||
|
||||
-json If specified, machine readable output will be printed in
|
||||
JSON format
|
||||
|
||||
-test-directory=path Set the Terraform test directory, defaults to "tests".
|
||||
|
||||
-var 'foo=bar' Set a value for one of the input variables in the root
|
||||
module of the configuration. Use this option more than
|
||||
once to set more than one variable.
|
||||
|
||||
-var-file=filename Load variable values from the given file, in addition
|
||||
to the default files terraform.tfvars and *.auto.tfvars.
|
||||
Use this option more than once to include more than one
|
||||
variables file.
|
||||
|
||||
-verbose Print the plan or state for each test run block as it
|
||||
executes.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
@ -55,26 +73,69 @@ func (c *TestCommand) Synopsis() string {
|
||||
func (c *TestCommand) Run(rawArgs []string) int {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
common, _ := arguments.ParseView(rawArgs)
|
||||
common, rawArgs := arguments.ParseView(rawArgs)
|
||||
c.View.Configure(common)
|
||||
|
||||
view := views.NewTest(arguments.ViewHuman, c.View)
|
||||
args, diags := arguments.ParseTest(rawArgs)
|
||||
if diags.HasErrors() {
|
||||
c.View.Diagnostics(diags)
|
||||
c.View.HelpPrompt("test")
|
||||
return 1
|
||||
}
|
||||
|
||||
config, configDiags := c.loadConfigWithTests(".", "tests")
|
||||
view := views.NewTest(args.ViewType, c.View)
|
||||
|
||||
config, configDiags := c.loadConfigWithTests(".", args.TestDirectory)
|
||||
diags = diags.Append(configDiags)
|
||||
if configDiags.HasErrors() {
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
var fileDiags tfdiags.Diagnostics
|
||||
suite := moduletest.Suite{
|
||||
Files: func() map[string]*moduletest.File {
|
||||
files := make(map[string]*moduletest.File)
|
||||
|
||||
if len(args.Filter) > 0 {
|
||||
for _, name := range args.Filter {
|
||||
file, ok := config.Module.Tests[name]
|
||||
if !ok {
|
||||
// If the filter is invalid, we'll simply skip this
|
||||
// entry and print a warning. But we could still execute
|
||||
// any other tests within the filter.
|
||||
fileDiags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Unknown test file",
|
||||
fmt.Sprintf("The specified test file, %s, could not be found.", name)))
|
||||
continue
|
||||
}
|
||||
|
||||
var runs []*moduletest.Run
|
||||
for ix, run := range file.Runs {
|
||||
runs = append(runs, &moduletest.Run{
|
||||
Config: run,
|
||||
Index: ix,
|
||||
Name: run.Name,
|
||||
})
|
||||
}
|
||||
files[name] = &moduletest.File{
|
||||
Config: file,
|
||||
Name: name,
|
||||
Runs: runs,
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
// Otherwise, we'll just do all the tests in the directory!
|
||||
for name, file := range config.Module.Tests {
|
||||
var runs []*moduletest.Run
|
||||
for _, run := range file.Runs {
|
||||
for ix, run := range file.Runs {
|
||||
runs = append(runs, &moduletest.Run{
|
||||
Config: run,
|
||||
Index: ix,
|
||||
Name: run.Name,
|
||||
})
|
||||
}
|
||||
@ -88,6 +149,30 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
}(),
|
||||
}
|
||||
|
||||
diags = diags.Append(fileDiags)
|
||||
if fileDiags.HasErrors() {
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Users can also specify variables via the command line, so we'll parse
|
||||
// all that here.
|
||||
var items []rawFlag
|
||||
for _, variable := range args.Vars.All() {
|
||||
items = append(items, rawFlag{
|
||||
Name: variable.Name,
|
||||
Value: variable.Value,
|
||||
})
|
||||
}
|
||||
c.variableArgs = rawFlags{items: &items}
|
||||
|
||||
variables, variableDiags := c.collectVariableValues()
|
||||
diags = diags.Append(variableDiags)
|
||||
if variableDiags.HasErrors() {
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
runningCtx, done := context.WithCancel(context.Background())
|
||||
stopCtx, stop := context.WithCancel(runningCtx)
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
@ -106,6 +191,8 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
// default to these values.
|
||||
Cancelled: false,
|
||||
Stopped: false,
|
||||
|
||||
Verbose: args.Verbose,
|
||||
}
|
||||
|
||||
view.Abstract(&suite)
|
||||
@ -116,7 +203,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
defer stop()
|
||||
defer cancel()
|
||||
|
||||
runner.Start()
|
||||
runner.Start(variables)
|
||||
}()
|
||||
|
||||
// Wait for the operation to complete, or for an interrupt to occur.
|
||||
@ -187,9 +274,12 @@ type TestRunner struct {
|
||||
// respond to external calls from the test command.
|
||||
StoppedCtx context.Context
|
||||
CancelledCtx context.Context
|
||||
|
||||
// Verbose tells the runner to print out plan files during each test run.
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (runner *TestRunner) Start() {
|
||||
func (runner *TestRunner) Start(globals map[string]backend.UnparsedVariableValue) {
|
||||
var files []string
|
||||
for name := range runner.Suite.Files {
|
||||
files = append(files, name)
|
||||
@ -203,16 +293,16 @@ func (runner *TestRunner) Start() {
|
||||
}
|
||||
|
||||
file := runner.Suite.Files[name]
|
||||
runner.ExecuteTestFile(file)
|
||||
runner.ExecuteTestFile(file, globals)
|
||||
runner.Suite.Status = runner.Suite.Status.Merge(file.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func (runner *TestRunner) ExecuteTestFile(file *moduletest.File) {
|
||||
func (runner *TestRunner) ExecuteTestFile(file *moduletest.File, globals map[string]backend.UnparsedVariableValue) {
|
||||
mgr := new(TestStateManager)
|
||||
mgr.runner = runner
|
||||
mgr.State = states.NewState()
|
||||
defer mgr.cleanupStates(file)
|
||||
defer mgr.cleanupStates(file, globals)
|
||||
|
||||
file.Status = file.Status.Merge(moduletest.Pass)
|
||||
for _, run := range file.Runs {
|
||||
@ -241,13 +331,13 @@ func (runner *TestRunner) ExecuteTestFile(file *moduletest.File) {
|
||||
if run.Config.ConfigUnderTest != nil {
|
||||
// Then we want to execute a different module under a kind of
|
||||
// sandbox.
|
||||
state := runner.ExecuteTestRun(run, file, states.NewState(), run.Config.ConfigUnderTest)
|
||||
state := runner.ExecuteTestRun(run, file, states.NewState(), run.Config.ConfigUnderTest, globals)
|
||||
mgr.States = append(mgr.States, &TestModuleState{
|
||||
State: state,
|
||||
Run: run,
|
||||
})
|
||||
} else {
|
||||
mgr.State = runner.ExecuteTestRun(run, file, mgr.State, runner.Config)
|
||||
mgr.State = runner.ExecuteTestRun(run, file, mgr.State, runner.Config, globals)
|
||||
}
|
||||
file.Status = file.Status.Merge(run.Status)
|
||||
}
|
||||
@ -258,7 +348,7 @@ func (runner *TestRunner) ExecuteTestFile(file *moduletest.File) {
|
||||
}
|
||||
}
|
||||
|
||||
func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.File, state *states.State, config *configs.Config) *states.State {
|
||||
func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.File, state *states.State, config *configs.Config, globals map[string]backend.UnparsedVariableValue) *states.State {
|
||||
if runner.Cancelled {
|
||||
// Don't do anything, just give up and return immediately.
|
||||
// The surrounding functions should stop this even being called, but in
|
||||
@ -299,7 +389,7 @@ func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.F
|
||||
ForceReplace: replaces,
|
||||
SkipRefresh: !run.Config.Options.Refresh,
|
||||
ExternalReferences: references,
|
||||
}, run.Config.Command)
|
||||
}, run.Config.Command, globals)
|
||||
diags = run.ValidateExpectedFailures(diags)
|
||||
run.Diagnostics = run.Diagnostics.Append(diags)
|
||||
|
||||
@ -321,7 +411,34 @@ func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.F
|
||||
return state
|
||||
}
|
||||
|
||||
variables, diags := buildInputVariablesForAssertions(run, file, config)
|
||||
// If the user wants to render the plans as part of the test output, we
|
||||
// track that here.
|
||||
if runner.Verbose {
|
||||
schemas, diags := ctx.Schemas(config, state)
|
||||
|
||||
// If we're going to fail to render the plan, let's not fail the overall
|
||||
// test. It can still have succeeded. So we'll add the diagnostics, but
|
||||
// still report the test status as a success.
|
||||
if diags.HasErrors() {
|
||||
// This is very unlikely.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to print verbose output",
|
||||
fmt.Sprintf("Terraform failed to print the verbose output for %s, other diagnostics will contain more details as to why.", path.Join(file.Name, run.Name))))
|
||||
} else {
|
||||
run.Verbose = &moduletest.Verbose{
|
||||
Plan: plan,
|
||||
State: state,
|
||||
Config: config,
|
||||
Providers: schemas.Providers,
|
||||
Provisioners: schemas.Provisioners,
|
||||
}
|
||||
}
|
||||
|
||||
run.Diagnostics = run.Diagnostics.Append(diags)
|
||||
}
|
||||
|
||||
variables, diags := buildInputVariablesForAssertions(run, file, config, globals)
|
||||
run.Diagnostics = run.Diagnostics.Append(diags)
|
||||
if diags.HasErrors() {
|
||||
run.Status = moduletest.Error
|
||||
@ -341,7 +458,7 @@ func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.F
|
||||
//
|
||||
// The command argument decides whether it executes only a plan or also applies
|
||||
// the plan it creates during the planning.
|
||||
func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, config *configs.Config, state *states.State, opts *terraform.PlanOpts, command configs.TestCommand) (*terraform.Context, *plans.Plan, *states.State, tfdiags.Diagnostics) {
|
||||
func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, config *configs.Config, state *states.State, opts *terraform.PlanOpts, command configs.TestCommand, globals map[string]backend.UnparsedVariableValue) (*terraform.Context, *plans.Plan, *states.State, tfdiags.Diagnostics) {
|
||||
if opts.Mode == plans.DestroyMode && state.Empty() {
|
||||
// Nothing to do!
|
||||
return nil, nil, state, nil
|
||||
@ -370,7 +487,7 @@ func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, co
|
||||
|
||||
// Second, gather any variables and give them to the plan options.
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config)
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, globals)
|
||||
diags = diags.Append(variableDiags)
|
||||
if variableDiags.HasErrors() {
|
||||
return nil, nil, state, diags
|
||||
@ -556,7 +673,7 @@ type TestModuleState struct {
|
||||
Run *moduletest.Run
|
||||
}
|
||||
|
||||
func (manager *TestStateManager) cleanupStates(file *moduletest.File) {
|
||||
func (manager *TestStateManager) cleanupStates(file *moduletest.File, globals map[string]backend.UnparsedVariableValue) {
|
||||
if manager.runner.Cancelled {
|
||||
|
||||
// We are still going to print out the resources that we have left
|
||||
@ -576,7 +693,7 @@ func (manager *TestStateManager) cleanupStates(file *moduletest.File) {
|
||||
// First, we'll clean up the main state.
|
||||
_, _, state, diags := manager.runner.execute(nil, file, manager.runner.Config, manager.State, &terraform.PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
}, configs.ApplyTestCommand)
|
||||
}, configs.ApplyTestCommand, globals)
|
||||
manager.runner.View.DestroySummary(diags, nil, file, state)
|
||||
|
||||
// Then we'll clean up the additional states for custom modules in reverse
|
||||
@ -593,7 +710,7 @@ func (manager *TestStateManager) cleanupStates(file *moduletest.File) {
|
||||
|
||||
_, _, state, diags := manager.runner.execute(module.Run, file, module.Run.Config.ConfigUnderTest, module.State, &terraform.PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
}, configs.ApplyTestCommand)
|
||||
}, configs.ApplyTestCommand, globals)
|
||||
manager.runner.View.DestroySummary(diags, module.Run, file, state)
|
||||
}
|
||||
}
|
||||
@ -606,37 +723,44 @@ func (manager *TestStateManager) cleanupStates(file *moduletest.File) {
|
||||
// Crucially, it differs from buildInputVariablesForAssertions in that it only
|
||||
// includes variables that are reference by the config and not everything that
|
||||
// is defined within the test run block and test file.
|
||||
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config) (terraform.InputValues, tfdiags.Diagnostics) {
|
||||
variables := make(map[string]hcl.Expression)
|
||||
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue) (terraform.InputValues, tfdiags.Diagnostics) {
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
for name := range config.Module.Variables {
|
||||
if run != nil {
|
||||
if expr, exists := run.Config.Variables[name]; exists {
|
||||
// Local variables take precedence.
|
||||
variables[name] = expr
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if file != nil {
|
||||
if expr, exists := file.Config.Variables[name]; exists {
|
||||
// If it's not set locally, it maybe set globally.
|
||||
variables[name] = expr
|
||||
// If it's not set locally, it maybe set for the entire file.
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if globals != nil {
|
||||
// If it's not set locally or at the file level, maybe it was
|
||||
// defined globally.
|
||||
if variable, exists := globals[name]; exists {
|
||||
variables[name] = variable
|
||||
}
|
||||
}
|
||||
|
||||
// If it's not set at all that might be okay if the variable is optional
|
||||
// so we'll just not add anything to the map.
|
||||
}
|
||||
|
||||
unparsed := make(map[string]backend.UnparsedVariableValue)
|
||||
for key, value := range variables {
|
||||
unparsed[key] = unparsedVariableValueExpression{
|
||||
expr: value,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
}
|
||||
}
|
||||
return backend.ParseVariableValues(unparsed, config.Module.Variables)
|
||||
return backend.ParseVariableValues(variables, config.Module.Variables)
|
||||
}
|
||||
|
||||
// buildInputVariablesForAssertions creates a terraform.InputValues mapping that
|
||||
@ -651,32 +775,41 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf
|
||||
// defined within the config. We might want to remove these warnings in the
|
||||
// future, since it is actually okay for test files to have variables defined
|
||||
// outside the configuration.
|
||||
func buildInputVariablesForAssertions(run *moduletest.Run, file *moduletest.File, config *configs.Config) (terraform.InputValues, tfdiags.Diagnostics) {
|
||||
merged := make(map[string]hcl.Expression)
|
||||
func buildInputVariablesForAssertions(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue) (terraform.InputValues, tfdiags.Diagnostics) {
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
|
||||
if run != nil {
|
||||
for name, expr := range run.Config.Variables {
|
||||
merged[name] = expr
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if file != nil {
|
||||
for name, expr := range file.Config.Variables {
|
||||
if _, exists := merged[name]; exists {
|
||||
if _, exists := variables[name]; exists {
|
||||
// Then this variable was defined at the run level and we want
|
||||
// that value to take precedence.
|
||||
continue
|
||||
}
|
||||
merged[name] = expr
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unparsed := make(map[string]backend.UnparsedVariableValue)
|
||||
for key, value := range merged {
|
||||
unparsed[key] = unparsedVariableValueExpression{
|
||||
expr: value,
|
||||
sourceType: terraform.ValueFromConfig,
|
||||
for name, variable := range globals {
|
||||
if _, exists := variables[name]; exists {
|
||||
// Then this value was already defined at either the run level
|
||||
// or the file level, and we want those values to take
|
||||
// precedence.
|
||||
continue
|
||||
}
|
||||
variables[name] = variable
|
||||
}
|
||||
return backend.ParseVariableValues(unparsed, config.Module.Variables)
|
||||
|
||||
return backend.ParseVariableValues(variables, config.Module.Variables)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
@ -17,6 +18,7 @@ import (
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
override string
|
||||
args []string
|
||||
expected string
|
||||
code int
|
||||
@ -30,6 +32,11 @@ func TestTest(t *testing.T) {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"simple_pass_nested_alternate": {
|
||||
args: []string{"-test-directory", "other"},
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"pass_with_locals": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
@ -62,6 +69,26 @@ func TestTest(t *testing.T) {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"multiple_files": {
|
||||
expected: "2 passed, 0 failed",
|
||||
code: 0,
|
||||
},
|
||||
"multiple_files_with_filter": {
|
||||
override: "multiple_files",
|
||||
args: []string{"-filter=one.tftest"},
|
||||
expected: "1 passed, 0 failed",
|
||||
code: 0,
|
||||
},
|
||||
"variables": {
|
||||
expected: "2 passed, 0 failed",
|
||||
code: 0,
|
||||
},
|
||||
"variables_overridden": {
|
||||
override: "variables",
|
||||
args: []string{"-var=input=foo"},
|
||||
expected: "1 passed, 1 failed",
|
||||
code: 1,
|
||||
},
|
||||
"simple_fail": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
@ -89,8 +116,13 @@ func TestTest(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
file := name
|
||||
if len(tc.override) > 0 {
|
||||
file = tc.override
|
||||
}
|
||||
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", name)), td)
|
||||
testCopyDir(t, testFixturePath(path.Join("test", file)), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
@ -326,3 +358,62 @@ func TestTest_ModuleDependencies(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_Verbose(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", "plan_then_apply")), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
view, done := testView(t)
|
||||
|
||||
c := &TestCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(provider.Provider),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
code := c.Run([]string{"-verbose", "-no-color"})
|
||||
output := done(t)
|
||||
|
||||
if code != 0 {
|
||||
t.Errorf("expected status code 0 but got %d", code)
|
||||
}
|
||||
|
||||
expected := `main.tftest... pass
|
||||
run "validate_test_resource"... pass
|
||||
|
||||
Terraform used the selected providers to generate the following execution
|
||||
plan. Resource actions are indicated with the following symbols:
|
||||
+ create
|
||||
|
||||
Terraform will perform the following actions:
|
||||
|
||||
# test_resource.foo will be created
|
||||
+ resource "test_resource" "foo" {
|
||||
+ id = "constant_value"
|
||||
+ value = "bar"
|
||||
}
|
||||
|
||||
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
run "validate_test_resource"... pass
|
||||
# test_resource.foo:
|
||||
resource "test_resource" "foo" {
|
||||
id = "constant_value"
|
||||
value = "bar"
|
||||
}
|
||||
|
||||
Success! 2 passed, 0 failed.
|
||||
`
|
||||
|
||||
actual := output.All()
|
||||
|
||||
if diff := cmp.Diff(actual, expected); len(diff) > 0 {
|
||||
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
|
||||
}
|
||||
|
||||
if provider.ResourceCount() > 0 {
|
||||
t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
|
||||
}
|
||||
}
|
||||
|
3
internal/command/testdata/test/multiple_files/main.tf
vendored
Normal file
3
internal/command/testdata/test/multiple_files/main.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
6
internal/command/testdata/test/multiple_files/one.tftest
vendored
Normal file
6
internal/command/testdata/test/multiple_files/one.tftest
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
6
internal/command/testdata/test/multiple_files/two.tftest
vendored
Normal file
6
internal/command/testdata/test/multiple_files/two.tftest
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
resource "test_resource" "foo" {
|
||||
id = "constant_value"
|
||||
value = "bar"
|
||||
}
|
||||
|
3
internal/command/testdata/test/simple_pass_nested_alternate/main.tf
vendored
Normal file
3
internal/command/testdata/test/simple_pass_nested_alternate/main.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
6
internal/command/testdata/test/simple_pass_nested_alternate/other/main.tftest
vendored
Normal file
6
internal/command/testdata/test/simple_pass_nested_alternate/other/main.tftest
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
8
internal/command/testdata/test/variables/main.tf
vendored
Normal file
8
internal/command/testdata/test/variables/main.tf
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
variable "input" {
|
||||
type = string
|
||||
default = "bar"
|
||||
}
|
||||
|
||||
resource "test_resource" "foo" {
|
||||
value = var.input
|
||||
}
|
6
internal/command/testdata/test/variables/main.tftest
vendored
Normal file
6
internal/command/testdata/test/variables/main.tftest
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
10
internal/command/testdata/test/variables/set_variables.tftest
vendored
Normal file
10
internal/command/testdata/test/variables/set_variables.tftest
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
run "validate_test_resource" {
|
||||
variables {
|
||||
input = "bar"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ const (
|
||||
MessageTestAbstract MessageType = "test_abstract"
|
||||
MessageTestFile MessageType = "test_file"
|
||||
MessageTestRun MessageType = "test_run"
|
||||
MessageTestPlan MessageType = "test_plan"
|
||||
MessageTestState MessageType = "test_state"
|
||||
MessageTestSummary MessageType = "test_summary"
|
||||
MessageTestCleanup MessageType = "test_cleanup"
|
||||
)
|
||||
|
@ -377,7 +377,7 @@ func TestJSONView_Outputs(t *testing.T) {
|
||||
// against a slice of structs representing the desired log messages. It
|
||||
// verifies that the output of JSONView is in JSON log format, one message per
|
||||
// line.
|
||||
func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string]interface{}) {
|
||||
func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string]interface{}, options ...cmp.Option) {
|
||||
t.Helper()
|
||||
|
||||
// Remove final trailing newline
|
||||
@ -415,7 +415,7 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string
|
||||
}
|
||||
}
|
||||
|
||||
if !cmp.Equal(wantStruct, gotStruct) {
|
||||
if !cmp.Equal(wantStruct, gotStruct, options...) {
|
||||
t.Errorf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct))
|
||||
}
|
||||
}
|
||||
@ -423,7 +423,7 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string
|
||||
|
||||
// testJSONViewOutputEquals skips the first line of output, since it ought to
|
||||
// be a version message that we don't care about for most of our tests.
|
||||
func testJSONViewOutputEquals(t *testing.T, output string, want []map[string]interface{}) {
|
||||
func testJSONViewOutputEquals(t *testing.T, output string, want []map[string]interface{}, options ...cmp.Option) {
|
||||
t.Helper()
|
||||
|
||||
// Remove up to the first newline
|
||||
@ -431,5 +431,5 @@ func testJSONViewOutputEquals(t *testing.T, output string, want []map[string]int
|
||||
if index >= 0 {
|
||||
output = output[index+1:]
|
||||
}
|
||||
testJSONViewOutputEqualsFull(t, output, want)
|
||||
testJSONViewOutputEqualsFull(t, output, want, options...)
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ func TestShowJSON(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
config, _, configCleanup := initwd.MustLoadConfigForTests(t, "./testdata/show")
|
||||
config, _, configCleanup := initwd.MustLoadConfigForTests(t, "./testdata/show", "tests")
|
||||
defer configCleanup()
|
||||
|
||||
for name, testCase := range testCases {
|
||||
|
@ -7,9 +7,16 @@ import (
|
||||
"github.com/mitchellh/colorstring"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/jsonformat"
|
||||
"github.com/hashicorp/terraform/internal/command/jsonplan"
|
||||
"github.com/hashicorp/terraform/internal/command/jsonprovider"
|
||||
"github.com/hashicorp/terraform/internal/command/jsonstate"
|
||||
"github.com/hashicorp/terraform/internal/command/views/json"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/moduletest"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
@ -116,6 +123,72 @@ func (t *TestHuman) File(file *moduletest.File) {
|
||||
func (t *TestHuman) Run(run *moduletest.Run, file *moduletest.File) {
|
||||
t.view.streams.Printf(" run %q... %s\n", run.Name, colorizeTestStatus(run.Status, t.view.colorize))
|
||||
|
||||
if run.Verbose != nil {
|
||||
// We're going to be more verbose about what we print, here's the plan
|
||||
// or the state depending on the type of run we did.
|
||||
|
||||
schemas := &terraform.Schemas{
|
||||
Providers: run.Verbose.Providers,
|
||||
Provisioners: run.Verbose.Provisioners,
|
||||
}
|
||||
|
||||
renderer := jsonformat.Renderer{
|
||||
Streams: t.view.streams,
|
||||
Colorize: t.view.colorize,
|
||||
RunningInAutomation: t.view.runningInAutomation,
|
||||
}
|
||||
|
||||
if run.Config.Command == configs.ApplyTestCommand {
|
||||
// Then we'll print the state.
|
||||
root, outputs, err := jsonstate.MarshalForRenderer(statefile.New(run.Verbose.State, file.Name, uint64(run.Index)), schemas)
|
||||
if err != nil {
|
||||
run.Diagnostics = run.Diagnostics.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to render test state",
|
||||
fmt.Sprintf("Terraform could not marshal the state for display: %v", err)))
|
||||
} else {
|
||||
state := jsonformat.State{
|
||||
StateFormatVersion: jsonstate.FormatVersion,
|
||||
ProviderFormatVersion: jsonprovider.FormatVersion,
|
||||
RootModule: root,
|
||||
RootModuleOutputs: outputs,
|
||||
ProviderSchemas: jsonprovider.MarshalForRenderer(schemas),
|
||||
}
|
||||
|
||||
renderer.RenderHumanState(state)
|
||||
}
|
||||
} else {
|
||||
// We'll print the plan.
|
||||
outputs, changed, drift, attrs, err := jsonplan.MarshalForRenderer(run.Verbose.Plan, schemas)
|
||||
if err != nil {
|
||||
run.Diagnostics = run.Diagnostics.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to render test plan",
|
||||
fmt.Sprintf("Terraform could not marshal the plan for display: %v", err)))
|
||||
} else {
|
||||
plan := jsonformat.Plan{
|
||||
PlanFormatVersion: jsonplan.FormatVersion,
|
||||
ProviderFormatVersion: jsonprovider.FormatVersion,
|
||||
OutputChanges: outputs,
|
||||
ResourceChanges: changed,
|
||||
ResourceDrift: drift,
|
||||
ProviderSchemas: jsonprovider.MarshalForRenderer(schemas),
|
||||
RelevantAttributes: attrs,
|
||||
}
|
||||
|
||||
var opts []jsonformat.PlanRendererOpt
|
||||
if !run.Verbose.Plan.CanApply() {
|
||||
opts = append(opts, jsonformat.CanNotApply)
|
||||
}
|
||||
if run.Verbose.Plan.Errored {
|
||||
opts = append(opts, jsonformat.Errored)
|
||||
}
|
||||
|
||||
renderer.RenderHumanPlan(plan, run.Verbose.Plan.UIMode, opts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally we'll print out a summary of the diagnostics from the run.
|
||||
t.Diagnostics(run, file, run.Diagnostics)
|
||||
}
|
||||
@ -257,6 +330,46 @@ func (t *TestJSON) Run(run *moduletest.Run, file *moduletest.File) {
|
||||
"@testfile", file.Name,
|
||||
"@testrun", run.Name)
|
||||
|
||||
if run.Verbose != nil {
|
||||
|
||||
schemas := &terraform.Schemas{
|
||||
Providers: run.Verbose.Providers,
|
||||
Provisioners: run.Verbose.Provisioners,
|
||||
}
|
||||
|
||||
if run.Config.Command == configs.ApplyTestCommand {
|
||||
state, err := jsonstate.MarshalForLog(statefile.New(run.Verbose.State, file.Name, uint64(run.Index)), schemas)
|
||||
if err != nil {
|
||||
run.Diagnostics = run.Diagnostics.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to render test state",
|
||||
fmt.Sprintf("Terraform could not marshal the state for display: %v", err)))
|
||||
} else {
|
||||
t.view.log.Info(
|
||||
"-verbose flag enabled, printing state",
|
||||
"type", json.MessageTestState,
|
||||
json.MessageTestState, state,
|
||||
"@testfile", file.Name,
|
||||
"@testrun", run.Name)
|
||||
}
|
||||
} else {
|
||||
plan, err := jsonplan.MarshalForLog(run.Verbose.Config, run.Verbose.Plan, nil, schemas)
|
||||
if err != nil {
|
||||
run.Diagnostics = run.Diagnostics.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to render test plan",
|
||||
fmt.Sprintf("Terraform could not marshal the plan for display: %v", err)))
|
||||
} else {
|
||||
t.view.log.Info(
|
||||
"-verbose flag enabled, printing plan",
|
||||
"type", json.MessageTestPlan,
|
||||
json.MessageTestPlan, plan,
|
||||
"@testfile", file.Name,
|
||||
"@testrun", run.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Diagnostics(run, file, run.Diagnostics)
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,19 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/moduletest"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
@ -524,6 +530,162 @@ other details
|
||||
Error: an error occurred
|
||||
|
||||
something bad happened during this test
|
||||
`,
|
||||
},
|
||||
"verbose_plan": {
|
||||
Run: &moduletest.Run{
|
||||
Name: "run_block",
|
||||
Status: moduletest.Pass,
|
||||
Config: &configs.TestRun{
|
||||
Command: configs.PlanTestCommand,
|
||||
},
|
||||
Verbose: &moduletest.Verbose{
|
||||
Plan: &plans.Plan{
|
||||
Changes: &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
PrevRunAddr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
ProviderAddr: addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
After: dynamicValue(
|
||||
t,
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
cty.Object(map[string]cty.Type{
|
||||
"value": cty.String,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.NewState(), // empty state
|
||||
Config: &configs.Config{},
|
||||
Providers: map[addrs.Provider]providers.ProviderSchema{
|
||||
addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
}: {
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_resource": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
StdOut: ` run "run_block"... pass
|
||||
|
||||
Terraform used the selected providers to generate the following execution
|
||||
plan. Resource actions are indicated with the following symbols:
|
||||
+ create
|
||||
|
||||
Terraform will perform the following actions:
|
||||
|
||||
# test_resource.creating will be created
|
||||
+ resource "test_resource" "creating" {
|
||||
+ value = "Hello, world!"
|
||||
}
|
||||
|
||||
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
`,
|
||||
},
|
||||
"verbose_apply": {
|
||||
Run: &moduletest.Run{
|
||||
Name: "run_block",
|
||||
Status: moduletest.Pass,
|
||||
Config: &configs.TestRun{
|
||||
Command: configs.ApplyTestCommand,
|
||||
},
|
||||
Verbose: &moduletest.Verbose{
|
||||
Plan: &plans.Plan{}, // empty plan
|
||||
State: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"value":"foobar"}`),
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
},
|
||||
})
|
||||
}),
|
||||
Config: &configs.Config{},
|
||||
Providers: map[addrs.Provider]providers.ProviderSchema{
|
||||
addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
}: {
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_resource": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
StdOut: ` run "run_block"... pass
|
||||
# test_resource.creating:
|
||||
resource "test_resource" "creating" {
|
||||
value = "foobar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
@ -2033,6 +2195,257 @@ func TestTestJSON_Run(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"verbose_plan": {
|
||||
run: &moduletest.Run{
|
||||
Name: "run_block",
|
||||
Status: moduletest.Pass,
|
||||
Config: &configs.TestRun{
|
||||
Command: configs.PlanTestCommand,
|
||||
},
|
||||
Verbose: &moduletest.Verbose{
|
||||
Plan: &plans.Plan{
|
||||
Changes: &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
PrevRunAddr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
ProviderAddr: addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
After: dynamicValue(
|
||||
t,
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.StringVal("foobar"),
|
||||
}),
|
||||
cty.Object(map[string]cty.Type{
|
||||
"value": cty.String,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.NewState(), // empty state
|
||||
Config: &configs.Config{
|
||||
Module: &configs.Module{
|
||||
ProviderRequirements: &configs.RequiredProviders{},
|
||||
},
|
||||
},
|
||||
Providers: map[addrs.Provider]providers.ProviderSchema{
|
||||
addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
}: {
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_resource": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": " \"run_block\"... pass",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"@testrun": "run_block",
|
||||
"test_run": map[string]interface{}{
|
||||
"path": "main.tftest",
|
||||
"run": "run_block",
|
||||
"status": "pass",
|
||||
},
|
||||
"type": "test_run",
|
||||
},
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "-verbose flag enabled, printing plan",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"@testrun": "run_block",
|
||||
"test_plan": map[string]interface{}{
|
||||
"configuration": map[string]interface{}{
|
||||
"root_module": map[string]interface{}{},
|
||||
},
|
||||
"errored": false,
|
||||
"planned_values": map[string]interface{}{
|
||||
"root_module": map[string]interface{}{
|
||||
"resources": []interface{}{
|
||||
map[string]interface{}{
|
||||
"address": "test_resource.creating",
|
||||
"mode": "managed",
|
||||
"name": "creating",
|
||||
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||
"schema_version": 0.0,
|
||||
"sensitive_values": map[string]interface{}{},
|
||||
"type": "test_resource",
|
||||
"values": map[string]interface{}{
|
||||
"value": "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"resource_changes": []interface{}{
|
||||
map[string]interface{}{
|
||||
"address": "test_resource.creating",
|
||||
"change": map[string]interface{}{
|
||||
"actions": []interface{}{"create"},
|
||||
"after": map[string]interface{}{
|
||||
"value": "foobar",
|
||||
},
|
||||
"after_sensitive": map[string]interface{}{},
|
||||
"after_unknown": map[string]interface{}{},
|
||||
"before": nil,
|
||||
"before_sensitive": false,
|
||||
},
|
||||
"mode": "managed",
|
||||
"name": "creating",
|
||||
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||
"type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "test_plan",
|
||||
},
|
||||
},
|
||||
},
|
||||
"verbose_apply": {
|
||||
run: &moduletest.Run{
|
||||
Name: "run_block",
|
||||
Status: moduletest.Pass,
|
||||
Config: &configs.TestRun{
|
||||
Command: configs.ApplyTestCommand,
|
||||
},
|
||||
Verbose: &moduletest.Verbose{
|
||||
Plan: &plans.Plan{}, // empty plan
|
||||
State: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_resource",
|
||||
Name: "creating",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"value":"foobar"}`),
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
},
|
||||
})
|
||||
}),
|
||||
Config: &configs.Config{
|
||||
Module: &configs.Module{},
|
||||
},
|
||||
Providers: map[addrs.Provider]providers.ProviderSchema{
|
||||
addrs.Provider{
|
||||
Hostname: addrs.DefaultProviderRegistryHost,
|
||||
Namespace: "hashicorp",
|
||||
Type: "test",
|
||||
}: {
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_resource": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": " \"run_block\"... pass",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"@testrun": "run_block",
|
||||
"test_run": map[string]interface{}{
|
||||
"path": "main.tftest",
|
||||
"run": "run_block",
|
||||
"status": "pass",
|
||||
},
|
||||
"type": "test_run",
|
||||
},
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "-verbose flag enabled, printing state",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"@testrun": "run_block",
|
||||
"test_state": map[string]interface{}{
|
||||
"values": map[string]interface{}{
|
||||
"root_module": map[string]interface{}{
|
||||
"resources": []interface{}{
|
||||
map[string]interface{}{
|
||||
"address": "test_resource.creating",
|
||||
"mode": "managed",
|
||||
"name": "creating",
|
||||
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||
"schema_version": 0.0,
|
||||
"sensitive_values": map[string]interface{}{},
|
||||
"type": "test_resource",
|
||||
"values": map[string]interface{}{
|
||||
"value": "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "test_state",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@ -2042,7 +2455,17 @@ func TestTestJSON_Run(t *testing.T) {
|
||||
file := &moduletest.File{Name: "main.tftest"}
|
||||
|
||||
view.Run(tc.run, file)
|
||||
testJSONViewOutputEquals(t, done(t).All(), tc.want)
|
||||
testJSONViewOutputEquals(t, done(t).All(), tc.want, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
return strings.Contains(path.Last().String(), "version") || strings.Contains(path.Last().String(), "timestamp")
|
||||
}, cmp.Ignore()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue {
|
||||
d, err := plans.NewDynamicValue(value, typ)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create dynamic value: %s", err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
@ -85,11 +85,11 @@ func NewModuleInstaller(modsDir string, loader *configload.Loader, reg *registry
|
||||
// If successful (the returned diagnostics contains no errors) then the
|
||||
// first return value is the early configuration tree that was constructed by
|
||||
// the installation process.
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, upgrade bool, hooks ModuleInstallHooks) (*configs.Config, tfdiags.Diagnostics) {
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir, testsDir string, upgrade bool, hooks ModuleInstallHooks) (*configs.Config, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] ModuleInstaller: installing child modules for %s into %s", rootDir, i.modsDir)
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
rootMod, mDiags := i.loader.Parser().LoadConfigDirWithTests(rootDir, "tests")
|
||||
rootMod, mDiags := i.loader.Parser().LoadConfigDirWithTests(rootDir, testsDir)
|
||||
if rootMod == nil {
|
||||
// We drop the diagnostics here because we only want to report module
|
||||
// loading errors after checking the core version constraints, which we
|
||||
|
@ -46,7 +46,7 @@ func TestModuleInstaller(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
wantCalls := []testInstallHookCall{
|
||||
@ -110,7 +110,7 @@ func TestModuleInstaller_error(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -131,7 +131,7 @@ func TestModuleInstaller_emptyModuleName(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -169,7 +169,7 @@ func TestModuleInstaller_packageEscapeError(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -207,7 +207,7 @@ func TestModuleInstaller_explicitPackageBoundary(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
@ -230,7 +230,7 @@ func TestModuleInstaller_ExactMatchPrerelease(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("found unexpected errors: %s", diags.Err())
|
||||
@ -257,7 +257,7 @@ func TestModuleInstaller_PartialMatchPrerelease(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("found unexpected errors: %s", diags.Err())
|
||||
@ -280,7 +280,7 @@ func TestModuleInstaller_invalid_version_constraint_error(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -306,7 +306,7 @@ func TestModuleInstaller_invalidVersionConstraintGetter(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -332,7 +332,7 @@ func TestModuleInstaller_invalidVersionConstraintLocal(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
@ -358,7 +358,7 @@ func TestModuleInstaller_symlink(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
wantCalls := []testInstallHookCall{
|
||||
@ -434,7 +434,7 @@ func TestLoaderInstallModules_registry(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), dir, "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
v := version.Must(version.NewVersion("0.0.1"))
|
||||
@ -597,7 +597,7 @@ func TestLoaderInstallModules_goGetter(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), dir, "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
wantCalls := []testInstallHookCall{
|
||||
@ -715,7 +715,7 @@ func TestModuleInstaller_fromTests(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
wantCalls := []testInstallHookCall{
|
||||
@ -772,7 +772,7 @@ func TestLoadInstallModules_registryFromTest(t *testing.T) {
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
_, diags := inst.InstallModules(context.Background(), dir, "tests", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
v := version.Must(version.NewVersion("0.0.1"))
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
// As with NewLoaderForTests, a cleanup function is returned which must be
|
||||
// called before the test completes in order to remove the temporary
|
||||
// modules directory.
|
||||
func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configload.Loader, func(), tfdiags.Diagnostics) {
|
||||
func LoadConfigForTests(t *testing.T, rootDir string, testsDir string) (*configs.Config, *configload.Loader, func(), tfdiags.Diagnostics) {
|
||||
t.Helper()
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
@ -39,7 +39,7 @@ func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configl
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
inst := NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
|
||||
_, moreDiags := inst.InstallModules(context.Background(), rootDir, true, ModuleInstallHooksImpl{})
|
||||
_, moreDiags := inst.InstallModules(context.Background(), rootDir, testsDir, true, ModuleInstallHooksImpl{})
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
cleanup()
|
||||
@ -65,10 +65,10 @@ func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configl
|
||||
// This is useful for concisely writing tests that don't expect errors at
|
||||
// all. For tests that expect errors and need to assert against them, use
|
||||
// LoadConfigForTests instead.
|
||||
func MustLoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configload.Loader, func()) {
|
||||
func MustLoadConfigForTests(t *testing.T, rootDir, testsDir string) (*configs.Config, *configload.Loader, func()) {
|
||||
t.Helper()
|
||||
|
||||
config, loader, cleanup, diags := LoadConfigForTests(t, rootDir)
|
||||
config, loader, cleanup, diags := LoadConfigForTests(t, rootDir, testsDir)
|
||||
if diags.HasErrors() {
|
||||
cleanup()
|
||||
t.Fatal(diags.Err())
|
||||
|
@ -8,13 +8,14 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/initwd"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/registry"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func testAnalyzer(t *testing.T, fixtureName string) *Analyzer {
|
||||
@ -24,7 +25,7 @@ func testAnalyzer(t *testing.T, fixtureName string) *Analyzer {
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), configDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), configDir, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatalf("unexpected module installation errors: %s", instDiags.Err().Error())
|
||||
}
|
||||
|
@ -7,18 +7,38 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
type Run struct {
|
||||
Config *configs.TestRun
|
||||
|
||||
Verbose *Verbose
|
||||
|
||||
Name string
|
||||
Index int
|
||||
Status Status
|
||||
|
||||
Diagnostics tfdiags.Diagnostics
|
||||
}
|
||||
|
||||
// Verbose is a utility struct that holds all the information required for a run
|
||||
// to render the results verbosely.
|
||||
//
|
||||
// At the moment, this basically means printing out the plan. To do that we need
|
||||
// all the information within this struct.
|
||||
type Verbose struct {
|
||||
Plan *plans.Plan
|
||||
State *states.State
|
||||
Config *configs.Config
|
||||
Providers map[addrs.Provider]providers.ProviderSchema
|
||||
Provisioners map[string]*configschema.Block
|
||||
}
|
||||
|
||||
func (run *Run) GetTargets() ([]addrs.Targetable, tfdiags.Diagnostics) {
|
||||
var diagnostics tfdiags.Diagnostics
|
||||
var targets []addrs.Targetable
|
||||
|
@ -538,7 +538,7 @@ func loadRefactoringFixture(t *testing.T, dir string) (*configs.Config, instance
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ func testSession(t *testing.T, test testSessionTest) {
|
||||
},
|
||||
}
|
||||
|
||||
config, _, cleanup, configDiags := initwd.LoadConfigForTests(t, "testdata/config-fixture")
|
||||
config, _, cleanup, configDiags := initwd.LoadConfigForTests(t, "testdata/config-fixture", "tests")
|
||||
defer cleanup()
|
||||
if configDiags.HasErrors() {
|
||||
t.Fatalf("unexpected problems loading config: %s", configDiags.Err())
|
||||
|
@ -67,7 +67,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
}
|
||||
@ -124,7 +124,7 @@ func testModuleInline(t *testing.T, sources map[string]string) *configs.Config {
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), cfgPath, true, initwd.ModuleInstallHooksImpl{})
|
||||
_, instDiags := inst.InstallModules(context.Background(), cfgPath, "tests", true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user