Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

terraform test: Collect variables from default var file within testing directory #34341

Merged
merged 9 commits into from
Dec 4, 2023
63 changes: 49 additions & 14 deletions internal/backend/local/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"fmt"
"log"
"path"
"path/filepath"
"sort"
"time"

Expand All @@ -23,7 +24,7 @@
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/moduletest"
configtest "github.com/hashicorp/terraform/internal/moduletest/config"
hcltest "github.com/hashicorp/terraform/internal/moduletest/hcl"

Check failure on line 27 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Race Tests

"github.com/hashicorp/terraform/internal/moduletest/hcl" imported as hcltest and not used

Check failure on line 27 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Unit Tests

"github.com/hashicorp/terraform/internal/moduletest/hcl" imported as hcltest and not used
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
Expand All @@ -38,7 +39,13 @@
type TestSuiteRunner struct {
Config *configs.Config

TestingDirectory string

// Global variables comes from the main configuration directory,
// and the Global Test Variables are loaded from the test directory.
GlobalVariables map[string]backend.UnparsedVariableValue
GlobalTestVariables map[string]backend.UnparsedVariableValue

Opts *terraform.ContextOpts

View views.Test
Expand Down Expand Up @@ -1023,26 +1030,54 @@
diags = diags.Append(valueDiags)
}

// Second, we'll check the run level variables.
// We need to also process all global variables for tests.
// If we run "terrafrom test" from the testing directory itself,
// the filepath.Dir(file.Name) is equal to "." so we need to extend the logic
if filepath.Dir(file.Name) == runner.Suite.TestingDirectory {
for name, value := range runner.Suite.GlobalTestVariables {
if !relevantVariables[name] {
// Then this run block doesn't need this value.
continue
}

// This is a bit more complicated, as the run level variables can reference
// previously defined variables.
// By default, we parse global variables as HCL inputs.
parsingMode := configs.VariableParseHCL

// Preload the available expressions, we're going to validate them when we
// build the context.
var exprs []hcl.Expression
for _, expr := range run.Config.Variables {
exprs = append(exprs, expr)
cfg, exists := config.Module.Variables[name]
liamcervante marked this conversation as resolved.
Show resolved Hide resolved
if exists {
// Unless we have some configuration that can actually tell us
// what parsing mode to use.

Check failure on line 1049 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Race Tests

undefined: file

Check failure on line 1049 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Unit Tests

undefined: file
parsingMode = cfg.ParsingMode
}

var valueDiags tfdiags.Diagnostics
values[name], valueDiags = value.ParseVariableValue(parsingMode)
diags = diags.Append(valueDiags)
}
}

// Preformat the variables we've processed already - these will be made
// available to the eval context.
variables := make(map[string]cty.Value)
for name, value := range values {
variables[name] = value.Value
// Second, we'll check the file level variables.
liamcervante marked this conversation as resolved.
Show resolved Hide resolved
for name, expr := range file.Config.Variables {
if !relevantVariables[name] {
continue
}

value, valueDiags := expr.Value(nil)
diags = diags.Append(valueDiags)

values[name] = &terraform.InputValue{
Value: value,
SourceType: terraform.ValueFromConfig,
SourceRange: tfdiags.SourceRangeFromHCL(expr.Range()),
}
}

Check failure on line 1073 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Race Tests

undefined: file

Check failure on line 1073 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Unit Tests

undefined: file

ctx, ctxDiags := hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorStates)
// Third, we'll check the run level variables.

// This is a bit more complicated, as the run level variables can reference
// previously defined variables.

ctx, ctxDiags := runner.ctx(run, file, values)
diags = diags.Append(ctxDiags)

var failedContext bool
Expand All @@ -1055,7 +1090,7 @@

for name, expr := range run.Config.Variables {
if !relevantVariables[name] {
// We'll add a warning for this. Since we're right in the run block

Check failure on line 1093 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Race Tests

runner.ctx undefined (type *TestFileRunner has no field or method ctx)

Check failure on line 1093 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Race Tests

undefined: file

Check failure on line 1093 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Unit Tests

runner.ctx undefined (type *TestFileRunner has no field or method ctx)

Check failure on line 1093 in internal/backend/local/test.go

View workflow job for this annotation

GitHub Actions / Unit Tests

undefined: file
// users shouldn't be defining variables that are not relevant.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Expand Down
47 changes: 47 additions & 0 deletions internal/command/meta_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/hashicorp/hcl/v2"
Expand All @@ -23,6 +24,52 @@ import (
// for root module input variables.
const VarEnvPrefix = "TF_VAR_"

// collectVariableValuesForTests inspects the various places that test
// values can come from and constructs a map ready to be passed to the
// backend as part of a backend.Operation.
//
// This method returns diagnostics relating to the collection of the values,
// but the values themselves may produce additional diagnostics when finally
// parsed.
func (m *Meta) collectVariableValuesForTests(testsFilePath string) (map[string]backend.UnparsedVariableValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := map[string]backend.UnparsedVariableValue{}

// We collect the variables from the ./tests directory
// there is no other need to process environmental variables
// as this is done via collectVariableValues function
if(testsFilePath == ""){
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
"Missing test directory",
"The test directory was unspecified when it should always be set. This is a bug in Terraform - please report it."))
return ret,diags
}

// Firstly we collect variables from .tfvars file
testVarsFilename := filepath.Join(testsFilePath, DefaultVarsFilename)
fmt.Println(testVarsFilename)
if _, err := os.Stat(testVarsFilename); err == nil {
moreDiags := m.addVarsFromFile(testVarsFilename, terraform.ValueFromAutoFile, ret)
diags = diags.Append(moreDiags)

}

// Then we collect variables from .tfvars.json file
const defaultVarsFilenameJSON = DefaultVarsFilename + ".json"
testVarsFilenameJSON := filepath.Join(testsFilePath, defaultVarsFilenameJSON)

if _, err := os.Stat(testVarsFilenameJSON); err == nil {
moreDiags := m.addVarsFromFile(testVarsFilenameJSON, terraform.ValueFromAutoFile, ret)
diags = diags.Append(moreDiags)
}

// Also, no need to additionally process variables from command line,
// as this is also done via collectVariableValues

return ret, diags
}

// collectVariableValues inspects the various places that root module input variable
// values can come from and constructs a map ready to be passed to the
// backend as part of a backend.Operation.
Expand Down
10 changes: 10 additions & 0 deletions internal/command/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func (c *TestCommand) Run(rawArgs []string) int {
}
c.variableArgs = rawFlags{items: &items}

// Collect variables for "terraform test"
testVariables, variableDiags := c.collectVariableValuesForTests(args.TestDirectory)
diags = diags.Append(variableDiags)

variables, variableDiags := c.collectVariableValues()
diags = diags.Append(variableDiags)
if variableDiags.HasErrors() {
Expand Down Expand Up @@ -196,7 +200,13 @@ func (c *TestCommand) Run(rawArgs []string) int {
} else {
runner = &local.TestSuiteRunner{
Config: config,
// The GlobalVariables are loaded from the
// main configuration directory
// The GlobalTestVariables are loaded from the
// test directory
GlobalVariables: variables,
GlobalTestVariables: testVariables,
TestingDirectory: args.TestDirectory,
Opts: opts,
View: view,
Stopped: false,
Expand Down
4 changes: 4 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ func TestTest(t *testing.T) {
expectedOut: "4 passed, 0 failed.",
code: 0,
},
"tfvars_in_test_dir": {
expected: "2 passed, 0 failed.",
code: 0,
},
"functions_available": {
expectedOut: "1 passed, 0 failed.",
code: 0,
Expand Down
17 changes: 17 additions & 0 deletions internal/command/testdata/test/tfvars_in_test_dir/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
variable "foo" {
description = "This is test variable"
default = "def_value"
}

variable "fooJSON" {
description = "This is test variable"
default = "def_value"
}

output "out_foo" {
value = var.foo
}

output "out_fooJSON" {
value = var.fooJSON
}
13 changes: 13 additions & 0 deletions internal/command/testdata/test/tfvars_in_test_dir/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
run "primary" {
assert {
condition = var.foo == var.test_foo
error_message = "Expected: ${var.test_foo}, Actual: ${var.foo}"
}
}

run "secondary" {
assert {
condition = var.fooJSON == var.test_foo_json
error_message = "Expected: ${var.test_foo_json}, Actual: ${var.fooJSON}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
foo = "foo_tfvars_value"
test_foo = "foo_tfvars_value"
test_foo_json = "foo_json_tfvars_value"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"fooJSON": "foo_json_tfvars_value"
}