Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions internal/backend/local/backend_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,20 +508,23 @@ func (v unparsedUnknownVariableValue) ParseVariableValue(mode configs.VariablePa
}

type unparsedTestVariableValue struct {
expr hcl.Expression
ctx *hcl.EvalContext
Expr hcl.Expression
}

var _ backend.UnparsedVariableValue = unparsedTestVariableValue{}

func (v unparsedTestVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
val, hclDiags := v.expr.Value(v.ctx) // nil because no function calls or variable references are allowed here
diags = diags.Append(hclDiags)

rng := tfdiags.SourceRangeFromHCL(v.expr.Range())
value, valueDiags := v.Expr.Value(nil)
diags = diags.Append(valueDiags)
if valueDiags.HasErrors() {
return nil, diags
}

return &terraform.InputValue{
Value: val,
SourceType: terraform.ValueFromConfig, // Test variables always come from config.
SourceRange: rng,
Value: value,
SourceType: terraform.ValueFromConfig,
SourceRange: tfdiags.SourceRangeFromHCL(v.Expr.Range()),
}, diags
}
684 changes: 187 additions & 497 deletions internal/backend/local/test.go

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ func TestTest(t *testing.T) {
args: []string{"-var=global=\"triple\""},
code: 0,
},
"unreferenced_global_variable": {
override: "variable_references",
expected: "2 passed, 0 failed.",
// The other variable shouldn't pass validation, but it won't be
// referenced anywhere so should just be ignored.
args: []string{"-var=global=\"triple\"", "-var=other=bad"},
code: 0,
},
"variables_types": {
expected: "1 passed, 0 failed.",
args: []string{"-var=number_input=0", "-var=string_input=Hello, world!", "-var=list_input=[\"Hello\",\"world\"]"},
Expand Down Expand Up @@ -1184,6 +1192,98 @@ the current run block.
}
}

func TestTest_UndefinedVariables(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "variables_undefined_in_config")), 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{"-no-color"})
output := done(t)

if code == 0 {
t.Errorf("expected status code 0 but got %d", code)
}

expectedOut := `main.tftest.hcl... in progress
run "test"... fail
main.tftest.hcl... tearing down
main.tftest.hcl... fail

Failure! 0 passed, 1 failed.
`
actualOut := output.Stdout()
if diff := cmp.Diff(actualOut, expectedOut); len(diff) > 0 {
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff)
}

expectedErr := `
Error: Reference to undeclared input variable

on main.tf line 2, in resource "test_resource" "foo":
2: value = var.input

An input variable with the name "input" has not been declared. This variable
can be declared with a variable "input" {} block.
`
actualErr := output.Stderr()
if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 {
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff)
}

if provider.ResourceCount() > 0 {
t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
}
}

func TestTest_VariablesInProviders(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "provider_vars")), 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{"-no-color"})
output := done(t)

if code != 0 {
t.Errorf("expected status code 0 but got %d", code)
}

expected := `main.tftest.hcl... in progress
run "test"... pass
main.tftest.hcl... tearing down
main.tftest.hcl... pass

Success! 1 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())
}
}

func TestTest_ExpectedFailuresDuringPlanning(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "expected_failures_during_planning")), td)
Expand Down
9 changes: 9 additions & 0 deletions internal/command/testdata/test/provider_vars/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}

resource "test_resource" "foo" {}
10 changes: 10 additions & 0 deletions internal/command/testdata/test/provider_vars/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

variables {
resource_directory = "my-resource-dir"
}

provider "test" {
resource_prefix = var.resource_directory
}

run "test" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "test_resource" "foo" {
value = var.input
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
run "test" {
variables {
input = "value"
}

assert {
condition = test_resource.foo.value == "value"
error_message = "bad value"
}
}
91 changes: 0 additions & 91 deletions internal/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,94 +854,3 @@ func (c *Config) CheckCoreVersionRequirements() hcl.Diagnostics {

return diags
}

// TransformForTest prepares the config to execute the given test.
//
// This function directly edits the config that is to be tested, and returns a
// function that will reset the config back to its original state.
//
// Tests will call this before they execute, and then call the deferred function
// to reset the config before the next test.
func (c *Config) TransformForTest(run *TestRun, file *TestFile) (func(), hcl.Diagnostics) {
var diags hcl.Diagnostics

// Currently, we only need to override the provider settings.
//
// We can have a set of providers defined within the config, we can also
// have a set of providers defined within the test file. Then the run can
// also specify a set of overrides that tell Terraform exactly which
// providers from the test file to apply into the config.
//
// The process here is as follows:
// 1. Take all the providers in the original config keyed by name.alias,
// we call this `previous`
// 2. Copy them all into a new map, we call this `next`.
// 3a. If the run has configuration specifying provider overrides, we copy
// only the specified providers from the test file into `next`. While
// doing this we ensure to preserve the name and alias from the
// original config.
// 3b. If the run has no override configuration, we copy all the providers
// from the test file into `next`, overriding all providers with name
// collisions from the original config.
// 4. We then modify the original configuration so that the providers it
// holds are the combination specified by the original config, the test
// file and the run file.
// 5. We then return a function that resets the original config back to
// its original state. This can be called by the surrounding test once
// completed so future run blocks can safely execute.

// First, initialise `previous` and `next`. `previous` contains a backup of
// the providers from the original config. `next` contains the set of
// providers that will be used by the test. `next` starts with the set of
// providers from the original config.
previous := c.Module.ProviderConfigs
next := make(map[string]*Provider)
for key, value := range previous {
next[key] = value
}

if run != nil && len(run.Providers) > 0 {
// Then we'll only copy over and overwrite the specific providers asked
// for by this run block.

for _, ref := range run.Providers {

testProvider, ok := file.Providers[ref.InParent.String()]
if !ok {
// Then this reference was invalid as we didn't have the
// specified provider in the parent. This should have been
// caught earlier in validation anyway so is unlikely to happen.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Missing provider definition for %s", ref.InParent.String()),
Detail: "This provider block references a provider definition that does not exist.",
Subject: ref.InParent.NameRange.Ptr(),
})
continue
}

next[ref.InChild.String()] = &Provider{
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
}

}
} else {
// Otherwise, let's copy over and overwrite all providers specified by
// the test file itself.
for key, provider := range file.Providers {
next[key] = provider
}
}

c.Module.ProviderConfigs = next
return func() {
// Reset the original config within the returned function.
c.Module.ProviderConfigs = previous
}, diags
}
Loading