@@ -282,7 +282,7 @@ func (runner *TestFileRunner) Test(file *moduletest.File) {
282282 }
283283
284284 // walk and execute the graph
285- diags = runner .walkGraph (graph )
285+ diags = runner .walkGraph (graph , file )
286286
287287 // If the graph walk was terminated, we don't want to add the diagnostics.
288288 // The error the user receives will just be:
@@ -298,8 +298,9 @@ func (runner *TestFileRunner) Test(file *moduletest.File) {
298298}
299299
300300// walkGraph goes through the graph and execute each run it finds.
301- func (runner * TestFileRunner ) walkGraph (g * terraform.Graph ) tfdiags.Diagnostics {
301+ func (runner * TestFileRunner ) walkGraph (g * terraform.Graph , file * moduletest. File ) tfdiags.Diagnostics {
302302 sem := runner .Suite .semaphore
303+ collectRunStatus , updateFileStatus := runner .trackRunStatuses (file )
303304
304305 // Walk the graph.
305306 walkFn := func (v dag.Vertex ) (diags tfdiags.Diagnostics ) {
@@ -376,9 +377,13 @@ func (runner *TestFileRunner) walkGraph(g *terraform.Graph) tfdiags.Diagnostics
376377 }
377378
378379 startTime := time .Now ().UTC ()
379- runner .run (run , file , startTime )
380+ deferFileStatus := runner .run (run , file , startTime )
380381 runner .Suite .View .Run (run , file , moduletest .Complete , 0 )
381- file .UpdateStatus (run .Status )
382+ // If the run block is done, but it was due to an expected failure, we
383+ // don't want to update the file status immediately. We'll collect the
384+ // status of this run block and update the file status at the end of the
385+ // file execution.
386+ collectRunStatus (run , deferFileStatus )
382387 case graph.GraphNodeExecutable :
383388 diags = v .Execute (runner .EvalContext )
384389 return diags
@@ -389,10 +394,12 @@ func (runner *TestFileRunner) walkGraph(g *terraform.Graph) tfdiags.Diagnostics
389394 return
390395 }
391396
392- return g .AcyclicGraph .Walk (walkFn )
397+ diags := g .AcyclicGraph .Walk (walkFn )
398+ updateFileStatus ()
399+ return diags
393400}
394401
395- func (runner * TestFileRunner ) run (run * moduletest.Run , file * moduletest.File , startTime time.Time ) {
402+ func (runner * TestFileRunner ) run (run * moduletest.Run , file * moduletest.File , startTime time.Time ) ( deferFileStatus bool ) {
396403 log .Printf ("[TRACE] TestFileRunner: executing run block %s/%s" , file .Name , run .Name )
397404 defer func () {
398405 // If we got far enough to actually execute the run then we'll give
@@ -401,6 +408,7 @@ func (runner *TestFileRunner) run(run *moduletest.Run, file *moduletest.File, st
401408 Start : startTime ,
402409 Duration : time .Since (startTime ),
403410 }
411+
404412 }()
405413
406414 key := run .GetStateKey ()
@@ -487,9 +495,9 @@ func (runner *TestFileRunner) run(run *moduletest.Run, file *moduletest.File, st
487495
488496 planScope , plan , planDiags := runner .plan (tfCtx , config , state , run , file , setVariables , references , start )
489497 if run .Config .Command == configs .PlanTestCommand {
490- // Then we want to assess our conditions and diagnostics differently.
491498 planDiags = run .ValidateExpectedFailures (planDiags )
492499 run .Diagnostics = run .Diagnostics .Append (planDiags )
500+ // Then we want to assess our conditions and diagnostics differently.
493501 if planDiags .HasErrors () {
494502 run .Status = moduletest .Error
495503 return
@@ -536,12 +544,21 @@ func (runner *TestFileRunner) run(run *moduletest.Run, file *moduletest.File, st
536544 return
537545 }
538546
539- // Otherwise any error during the planning prevents our apply from
547+ // Otherwise any error (expected or unexpected) during the planning prevents our apply from
540548 // continuing which is an error.
541549 planDiags = run .ExplainExpectedFailures (planDiags )
542550 run .Diagnostics = run .Diagnostics .Append (planDiags )
543551 if planDiags .HasErrors () {
544552 run .Status = moduletest .Error
553+ // If the plan failed, but all the failures were expected, then we don't
554+ // want to mark the overall file as a failure, so that subsequent runs can
555+ // still be executed.
556+ // We will collect the status of this run instead of updating the file status.
557+ // At the end of the file execution, we will update the file status based on the
558+ // statuses of all the runs.
559+ if ! run .ValidateExpectedFailures (planDiags ).HasErrors () {
560+ deferFileStatus = true
561+ }
545562 return
546563 }
547564
@@ -1255,3 +1272,27 @@ func (runner *TestFileRunner) AddVariablesToConfig(run *moduletest.Run, variable
12551272 }
12561273
12571274}
1275+
1276+ // trackRunStatuses is a helper function that returns two functions. The first
1277+ // function is used to collect the statuses of the runs, and the second function
1278+ // is used to update the overall file status based on the statuses of the runs.
1279+ func (runner * TestFileRunner ) trackRunStatuses (file * moduletest.File ) (func (* moduletest.Run , bool ), func ()) {
1280+ statuses := make ([]moduletest.Status , len (file .Runs ))
1281+ collector := func (run * moduletest.Run , deferred bool ) {
1282+ if deferred {
1283+ statuses [run .Index ] = run .Status
1284+ } else {
1285+ file .UpdateStatus (run .Status )
1286+ }
1287+ }
1288+
1289+ updater := func () {
1290+ for _ , status := range statuses {
1291+ file .UpdateStatus (status )
1292+ }
1293+ }
1294+
1295+ // We'll return two functions, one to collect the statuses of the runs, and
1296+ // one to update the overall file status.
1297+ return collector , updater
1298+ }
0 commit comments