Skip to content

Commit 36227ed

Browse files
committed
add cancel context to EvalContext
1 parent b0d9edd commit 36227ed

File tree

4 files changed

+41
-44
lines changed

4 files changed

+41
-44
lines changed

internal/backend/local/test.go

+14-41
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package local
55

66
import (
77
"context"
8-
"errors"
98
"fmt"
109
"log"
1110
"path/filepath"
@@ -131,7 +130,10 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) {
131130
}
132131

133132
file := suite.Files[name]
134-
evalCtx := graph.NewEvalContext()
133+
// The eval context inherits the cancelled context from the runner.
134+
// This allows the eval context to stop the graph walk if the runner
135+
// requests a hard stop.
136+
evalCtx := graph.NewEvalContext(runner.CancelledCtx)
135137

136138
for _, run := range file.Runs {
137139
// Pre-initialise the prior outputs, so we can easily tell between
@@ -254,8 +256,6 @@ type TestFileRunner struct {
254256
EvalContext *graph.EvalContext
255257
}
256258

257-
var graphTerminatedError = errors.New("graph walk terminated")
258-
259259
func (runner *TestFileRunner) Test(file *moduletest.File) {
260260
log.Printf("[TRACE] TestFileRunner: executing test file %s", file.Name)
261261

@@ -288,7 +288,8 @@ func (runner *TestFileRunner) Test(file *moduletest.File) {
288288
// The error the user receives will just be:
289289
// Failure! 0 passed, 1 failed.
290290
// exit status 1
291-
if diags.HasErrors() && diags.Err().Error() == graphTerminatedError.Error() {
291+
if runner.EvalContext.Cancelled() {
292+
file.UpdateStatus(moduletest.Error)
292293
log.Printf("[TRACE] TestFileRunner: graph walk terminated for %s", file.Name)
293294
return
294295
}
@@ -300,16 +301,15 @@ func (runner *TestFileRunner) Test(file *moduletest.File) {
300301
func (runner *TestFileRunner) walkGraph(g *terraform.Graph) tfdiags.Diagnostics {
301302
sem := runner.Suite.semaphore
302303

303-
// We'll use this context to cancel the walk if the test is stopped.
304-
// There is currently no mechanism by the graph for stopping the entire
305-
// graph walk, so we'll just cancel the context and let the walk function
306-
// return early for each node.
307-
ctx, cancel := context.WithCancelCause(context.Background())
308-
309304
// Walk the graph.
310305
walkFn := func(v dag.Vertex) (diags tfdiags.Diagnostics) {
311-
if ctx.Err() != nil {
312-
// If the context was cancelled, the node should just return immediately.
306+
if runner.EvalContext.Cancelled() {
307+
// If the graph walk has been cancelled, the node should just return immediately.
308+
// For now, this means a hard stop has been requested, in this case we don't
309+
// even stop to mark future test runs as having been skipped. They'll
310+
// just show up as pending in the printed summary. We will quickly
311+
// just mark the overall file status has having errored to indicate
312+
// it was interrupted.
313313
return
314314
}
315315

@@ -371,17 +371,6 @@ func (runner *TestFileRunner) walkGraph(g *terraform.Graph) tfdiags.Diagnostics
371371
file := runNode.File()
372372
run := runNode.Run()
373373

374-
if runner.Suite.Cancelled {
375-
// This means a hard stop has been requested, in this case we don't
376-
// even stop to mark future tests as having been skipped. They'll
377-
// just show up as pending in the printed summary. We will quickly
378-
// just mark the overall file status has having errored to indicate
379-
// it was interrupted.
380-
file.UpdateStatus(moduletest.Error)
381-
cancel(graphTerminatedError)
382-
return
383-
}
384-
385374
if runner.Suite.Stopped {
386375
// Then the test was requested to be stopped, so we just mark each
387376
// following test as skipped, print the status, and move on.
@@ -445,23 +434,7 @@ func (runner *TestFileRunner) walkGraph(g *terraform.Graph) tfdiags.Diagnostics
445434
return
446435
}
447436

448-
// We want to either wait for the walk to complete or for the context to be
449-
// cancelled. If the context is cancelled, we'll return immediately.
450-
doneCh := make(chan tfdiags.Diagnostics)
451-
go func() {
452-
defer close(doneCh)
453-
doneCh <- g.AcyclicGraph.Walk(walkFn)
454-
}()
455-
456-
select {
457-
case <-ctx.Done():
458-
// If the context was cancelled, we'll just return the cause of the
459-
// cancellation. For now, this can only be the graphTerminatedError.
460-
return tfdiags.Diagnostics{}.Append(context.Cause(ctx))
461-
case diags := <-doneCh:
462-
return diags
463-
}
464-
437+
return g.AcyclicGraph.Walk(walkFn)
465438
}
466439

467440
func (runner *TestFileRunner) run(run *moduletest.Run, file *moduletest.File, state *states.State) (*states.State, bool) {

internal/moduletest/graph/eval_context.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package graph
55

66
import (
7+
"context"
78
"fmt"
89
"log"
910
"sort"
@@ -58,11 +59,19 @@ type EvalContext struct {
5859
// the test has finished.
5960
FileStates map[string]*TestFileState
6061
stateLock sync.Mutex
62+
63+
// cancelContext is a context that can be used to terminate the evaluation of the
64+
// test suite.
65+
// cancelFunc is the conrresponding cancel function that should be called to
66+
// cancel the context.
67+
cancelContext context.Context
68+
cancelFunc context.CancelFunc
6169
}
6270

6371
// NewEvalContext constructs a new graph evaluation context for use in
6472
// evaluating the runs within a test suite.
65-
func NewEvalContext() *EvalContext {
73+
func NewEvalContext(cancelCtx context.Context) *EvalContext {
74+
cancelCtx, cancel := context.WithCancel(cancelCtx)
6675
return &EvalContext{
6776
runOutputs: make(map[addrs.Run]cty.Value),
6877
outputsLock: sync.Mutex{},
@@ -71,9 +80,23 @@ func NewEvalContext() *EvalContext {
7180
FileStates: make(map[string]*TestFileState),
7281
stateLock: sync.Mutex{},
7382
VariableCaches: hcltest.NewVariableCaches(),
83+
cancelContext: cancelCtx,
84+
cancelFunc: cancel,
7485
}
7586
}
7687

88+
// Cancel cancels the context, which signals to the test suite that it should
89+
// stop evaluating the test suite.
90+
func (ec *EvalContext) Cancel() {
91+
ec.cancelFunc()
92+
}
93+
94+
// Cancelled returns true if the context has been stopped. The default cause
95+
// of the error is context.Canceled.
96+
func (ec *EvalContext) Cancelled() bool {
97+
return ec.cancelContext.Err() != nil
98+
}
99+
77100
// EvaluateRun processes the assertions inside the provided configs.TestRun against
78101
// the run results, returning a status, an object value representing the output
79102
// values from the module under test, and diagnostics describing any problems.

internal/moduletest/graph/eval_context_test.go

+1-1
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()
738+
testCtx := NewEvalContext(context.Background())
739739
testCtx.runOutputs = priorOutputs
740740
gotStatus, gotOutputs, diags := testCtx.EvaluateRun(run, planScope, test.testOnlyVars)
741741

internal/moduletest/graph/transform_config_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package graph
55

66
import (
77
"bytes"
8+
"context"
89
"fmt"
910
"strings"
1011
"testing"
@@ -218,7 +219,7 @@ func TestTransformForTest(t *testing.T) {
218219
availableProviders[provider] = true
219220
}
220221

221-
ctx := NewEvalContext()
222+
ctx := NewEvalContext(context.Background())
222223
ctx.configProviders = map[string]map[string]bool{
223224
run.GetModuleConfigID(): availableProviders,
224225
}

0 commit comments

Comments
 (0)