Skip to content

Commit b2026d7

Browse files
author
Liam Cervante
committed
terraform test: refactor the context initialisation into specific node in the graph
1 parent 8135fef commit b2026d7

File tree

8 files changed

+90
-83
lines changed

8 files changed

+90
-83
lines changed

internal/backend/local/test.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ import (
77
"context"
88
"fmt"
99
"log"
10+
"maps"
1011
"path/filepath"
1112
"slices"
1213

13-
"github.com/zclconf/go-cty/cty"
14-
15-
"maps"
16-
1714
"github.com/hashicorp/terraform/internal/backend/backendrun"
1815
"github.com/hashicorp/terraform/internal/command/junit"
1916
"github.com/hashicorp/terraform/internal/command/views"
@@ -113,28 +110,22 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) {
113110
}
114111

115112
file := suite.Files[name]
116-
evalCtx := graph.NewEvalContext(&graph.EvalContextOpts{
113+
evalCtx := graph.NewEvalContext(graph.EvalContextOpts{
117114
CancelCtx: runner.CancelledCtx,
118115
StopCtx: runner.StoppedCtx,
119116
Verbose: runner.Verbose,
120117
Render: runner.View,
121118
})
122119

123-
for _, run := range file.Runs {
124-
// Pre-initialise the prior outputs, so we can easily tell between
125-
// a run block that doesn't exist and a run block that hasn't been
126-
// executed yet.
127-
// (moduletest.EvalContext treats cty.NilVal as "not visited yet")
128-
evalCtx.SetOutput(run, cty.NilVal)
129-
}
120+
// TODO(liamcervante): Do the variables in the EvalContextTransformer
121+
// as well as the run blocks.
130122

131123
currentGlobalVariables := runner.GlobalVariables
132124
if filepath.Dir(file.Name) == runner.TestingDirectory {
133125
// If the file is in the test directory, we'll use the union of the
134126
// global variables and the global test variables.
135127
currentGlobalVariables = testDirectoryGlobalVariables
136128
}
137-
138129
evalCtx.VariableCaches = hcltest.NewVariableCaches(func(vc *hcltest.VariableCaches) {
139130
maps.Copy(vc.GlobalVariables, currentGlobalVariables)
140131
vc.FileVariables = file.Config.Variables

internal/moduletest/graph/eval_context.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ import (
77
"context"
88
"fmt"
99
"log"
10+
"maps"
1011
"sort"
1112
"sync"
1213

1314
"github.com/hashicorp/hcl/v2"
1415
"github.com/zclconf/go-cty/cty"
1516
"github.com/zclconf/go-cty/cty/convert"
1617

17-
"maps"
18-
1918
"github.com/hashicorp/terraform/internal/addrs"
2019
"github.com/hashicorp/terraform/internal/command/views"
2120
"github.com/hashicorp/terraform/internal/configs"
@@ -24,10 +23,18 @@ import (
2423
"github.com/hashicorp/terraform/internal/lang/langrefs"
2524
"github.com/hashicorp/terraform/internal/moduletest"
2625
hcltest "github.com/hashicorp/terraform/internal/moduletest/hcl"
26+
"github.com/hashicorp/terraform/internal/states"
2727
"github.com/hashicorp/terraform/internal/terraform"
2828
"github.com/hashicorp/terraform/internal/tfdiags"
2929
)
3030

31+
// TestFileState is a helper struct that just maps a run block to the state that
32+
// was produced by the execution of that run block.
33+
type TestFileState struct {
34+
Run *moduletest.Run
35+
State *states.State
36+
}
37+
3138
// EvalContext is a container for context relating to the evaluation of a
3239
// particular .tftest.hcl file.
3340
// This context is used to track the various values that are available to the
@@ -87,7 +94,7 @@ type EvalContextOpts struct {
8794
// evaluating the runs within a test suite.
8895
// The context is initialized with the provided cancel and stop contexts, and
8996
// these contexts can be used from external commands to signal the termination of the test suite.
90-
func NewEvalContext(opts *EvalContextOpts) *EvalContext {
97+
func NewEvalContext(opts EvalContextOpts) *EvalContext {
9198
cancelCtx, cancel := context.WithCancel(opts.CancelCtx)
9299
stopCtx, stop := context.WithCancel(opts.StopCtx)
93100
return &EvalContext{

internal/moduletest/graph/eval_context_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ func TestEvalContext_Evaluate(t *testing.T) {
735735
priorOutputs[addrs.Run{Name: name}] = val
736736
}
737737

738-
testCtx := NewEvalContext(&EvalContextOpts{
738+
testCtx := NewEvalContext(EvalContextOpts{
739739
CancelCtx: context.Background(),
740740
StopCtx: context.Background(),
741741
})

internal/moduletest/graph/test_graph_builder.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"github.com/hashicorp/terraform/internal/tfdiags"
1414
)
1515

16+
type GraphNodeExecutable interface {
17+
Execute(ctx *EvalContext) tfdiags.Diagnostics
18+
}
19+
1620
// TestGraphBuilder is a GraphBuilder implementation that builds a graph for
1721
// a terraform test file. The file may contain multiple runs, and each run may have
1822
// dependencies on other runs.
@@ -46,11 +50,11 @@ func (b *TestGraphBuilder) Steps() []terraform.GraphTransformer {
4650
}
4751
steps := []terraform.GraphTransformer{
4852
&TestRunTransformer{opts},
49-
&TestConfigTransformer{File: b.File},
5053
&TestStateCleanupTransformer{opts},
5154
terraform.DynamicTransformer(validateRunConfigs),
5255
&TestProvidersTransformer{},
5356
&CloseTestGraphTransformer{},
57+
&EvalContextTransformer{File: b.File},
5458
&terraform.TransitiveReductionTransformer{},
5559
}
5660

internal/moduletest/graph/transform_config.go

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,81 +7,22 @@ import (
77
"fmt"
88

99
"github.com/hashicorp/hcl/v2"
10+
1011
"github.com/hashicorp/terraform/internal/configs"
11-
"github.com/hashicorp/terraform/internal/dag"
1212
"github.com/hashicorp/terraform/internal/moduletest"
1313
hcltest "github.com/hashicorp/terraform/internal/moduletest/hcl"
14-
"github.com/hashicorp/terraform/internal/states"
15-
"github.com/hashicorp/terraform/internal/terraform"
16-
"github.com/hashicorp/terraform/internal/tfdiags"
1714
)
1815

19-
type GraphNodeExecutable interface {
20-
Execute(ctx *EvalContext) tfdiags.Diagnostics
21-
}
22-
23-
// TestFileState is a helper struct that just maps a run block to the state that
24-
// was produced by the execution of that run block.
25-
type TestFileState struct {
26-
Run *moduletest.Run
27-
State *states.State
28-
}
29-
30-
// TestConfigTransformer is a GraphTransformer that adds all the test runs,
31-
// and the variables defined in each run block, to the graph.
32-
type TestConfigTransformer struct {
33-
File *moduletest.File
34-
}
35-
36-
func (t *TestConfigTransformer) Transform(g *terraform.Graph) error {
37-
// This map tracks the state of each run in the file. If multiple runs
38-
// have the same state key, they will share the same state.
39-
statesMap := make(map[string]*TestFileState)
40-
41-
// a root config node that will add the file states to the context
42-
rootConfigNode := t.addRootConfigNode(g, statesMap)
43-
44-
for _, v := range g.Vertices() {
45-
node, ok := v.(*NodeTestRun)
46-
if !ok {
47-
continue
48-
}
49-
key := node.run.GetStateKey()
50-
if _, exists := statesMap[key]; !exists {
51-
state := &TestFileState{
52-
Run: nil,
53-
State: states.NewState(),
54-
}
55-
statesMap[key] = state
56-
}
57-
58-
// Connect all the test runs to the config node, so that the config node
59-
// is executed before any of the test runs.
60-
g.Connect(dag.BasicEdge(node, rootConfigNode))
61-
}
62-
63-
return nil
64-
}
65-
66-
func (t *TestConfigTransformer) addRootConfigNode(g *terraform.Graph, statesMap map[string]*TestFileState) *dynamicNode {
67-
rootConfigNode := &dynamicNode{
68-
eval: func(ctx *EvalContext) tfdiags.Diagnostics {
69-
var diags tfdiags.Diagnostics
70-
ctx.FileStates = statesMap
71-
return diags
72-
},
73-
}
74-
g.Add(rootConfigNode)
75-
return rootConfigNode
76-
}
77-
7816
// TransformConfigForRun transforms the run's module configuration to include
7917
// the providers and variables from its block and the test file.
8018
//
8119
// In practice, this actually just means performing some surgery on the
8220
// available providers. We want to copy the relevant providers from the test
8321
// file into the configuration. We also want to process the providers so they
8422
// use variables from the file instead of variables from within the test file.
23+
//
24+
// TODO(liamcervante): Push provider initialisation into the test graph
25+
// so we don't have to transform the config in this way anymore.
8526
func TransformConfigForRun(ctx *EvalContext, run *moduletest.Run, file *moduletest.File) hcl.Diagnostics {
8627
var diags hcl.Diagnostics
8728

internal/moduletest/graph/transform_config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/google/go-cmp/cmp/cmpopts"
1515
"github.com/hashicorp/hcl/v2"
1616
"github.com/hashicorp/hcl/v2/hclparse"
17+
1718
"github.com/hashicorp/terraform/internal/configs"
1819
"github.com/hashicorp/terraform/internal/moduletest"
1920
)
@@ -219,7 +220,7 @@ func TestTransformForTest(t *testing.T) {
219220
availableProviders[provider] = true
220221
}
221222

222-
ctx := NewEvalContext(&EvalContextOpts{
223+
ctx := NewEvalContext(EvalContextOpts{
223224
CancelCtx: context.Background(),
224225
StopCtx: context.Background(),
225226
})
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package graph
5+
6+
import (
7+
"github.com/zclconf/go-cty/cty"
8+
9+
"github.com/hashicorp/terraform/internal/dag"
10+
"github.com/hashicorp/terraform/internal/moduletest"
11+
"github.com/hashicorp/terraform/internal/states"
12+
"github.com/hashicorp/terraform/internal/terraform"
13+
"github.com/hashicorp/terraform/internal/tfdiags"
14+
)
15+
16+
var _ terraform.GraphTransformer = (*EvalContextTransformer)(nil)
17+
18+
// EvalContextTransformer should be the first node to execute in the graph, and
19+
// it initialises the run blocks and state files in the evaluation context.
20+
// TODO(liamcervante): Also initialise the variables in here when needed.
21+
type EvalContextTransformer struct {
22+
File *moduletest.File
23+
}
24+
25+
func (e *EvalContextTransformer) Transform(graph *terraform.Graph) error {
26+
node := &dynamicNode{
27+
eval: func(ctx *EvalContext) tfdiags.Diagnostics {
28+
for _, run := range e.File.Runs {
29+
30+
// Within the run outputs a nil but present entry means the
31+
// run block exists but hasn't executed yet.
32+
// TODO(liamcervante): Once providers are embedded in the graph
33+
// we don't need to track run blocks in this way anymore.
34+
35+
ctx.SetOutput(run, cty.NilVal)
36+
37+
// We also want to set an empty state file for every state key
38+
// we're going to be executing within the graph.
39+
40+
key := run.GetStateKey()
41+
if state := ctx.GetFileState(key); state == nil {
42+
ctx.SetFileState(key, &TestFileState{
43+
Run: nil,
44+
State: states.NewState(),
45+
})
46+
}
47+
48+
}
49+
50+
return nil
51+
},
52+
}
53+
54+
graph.Add(node)
55+
for _, v := range graph.Vertices() {
56+
if v == node {
57+
continue
58+
}
59+
graph.Connect(dag.BasicEdge(v, node))
60+
}
61+
62+
return nil
63+
}

internal/moduletest/graph/transform_providers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func (t *TestProvidersTransformer) transformSingleConfig(config *configs.Config)
5959
providers[provider.Addr().StringCompact()] = true
6060
}
6161

62-
// Third, we look at the resources and data sources.
62+
// Third, we look at the resources and data sources.g
6363
for _, resource := range config.Module.ManagedResources {
6464
if resource.ProviderConfigRef != nil {
6565
providers[resource.ProviderConfigRef.String()] = true

0 commit comments

Comments
 (0)