|
7 | 7 | "context" |
8 | 8 | "fmt" |
9 | 9 | "log" |
10 | | - "maps" |
11 | 10 | "sort" |
12 | 11 | "sync" |
13 | 12 |
|
@@ -44,14 +43,8 @@ type TestFileState struct { |
44 | 43 | type EvalContext struct { |
45 | 44 | VariableCache *hcltest.VariableCache |
46 | 45 |
|
47 | | - // runOutputs is a mapping from run addresses to cty object values |
48 | | - // representing the collected output values from the module under test. |
49 | | - // |
50 | | - // This is used to allow run blocks to refer back to the output values of |
51 | | - // previous run blocks. It is passed into the Evaluate functions that |
52 | | - // validate the test assertions, and used when calculating values for |
53 | | - // variables within run blocks. |
54 | | - runOutputs map[addrs.Run]cty.Value |
| 46 | + // runBlocks caches all the known run blocks that this EvalContext manages. |
| 47 | + runBlocks map[addrs.Run]*moduletest.Run |
55 | 48 | outputsLock sync.Mutex |
56 | 49 |
|
57 | 50 | // configProviders is a cache of config keys mapped to all the providers |
@@ -99,7 +92,7 @@ func NewEvalContext(opts EvalContextOpts) *EvalContext { |
99 | 92 | cancelCtx, cancel := context.WithCancel(opts.CancelCtx) |
100 | 93 | stopCtx, stop := context.WithCancel(opts.StopCtx) |
101 | 94 | return &EvalContext{ |
102 | | - runOutputs: make(map[addrs.Run]cty.Value), |
| 95 | + runBlocks: make(map[addrs.Run]*moduletest.Run), |
103 | 96 | outputsLock: sync.Mutex{}, |
104 | 97 | configProviders: make(map[string]map[string]bool), |
105 | 98 | providersLock: sync.Mutex{}, |
@@ -313,17 +306,28 @@ func (ec *EvalContext) EvaluateRun(run *moduletest.Run, resultScope *lang.Scope, |
313 | 306 | return status, cty.ObjectVal(outputVals), diags |
314 | 307 | } |
315 | 308 |
|
316 | | -func (ec *EvalContext) SetOutput(run *moduletest.Run, output cty.Value) { |
| 309 | +func (ec *EvalContext) AddRunBlock(run *moduletest.Run) { |
317 | 310 | ec.outputsLock.Lock() |
318 | 311 | defer ec.outputsLock.Unlock() |
319 | | - ec.runOutputs[run.Addr()] = output |
| 312 | + ec.runBlocks[run.Addr()] = run |
320 | 313 | } |
321 | 314 |
|
322 | 315 | func (ec *EvalContext) GetOutputs() map[addrs.Run]cty.Value { |
323 | 316 | ec.outputsLock.Lock() |
324 | 317 | defer ec.outputsLock.Unlock() |
325 | | - outputCopy := make(map[addrs.Run]cty.Value, len(ec.runOutputs)) |
326 | | - maps.Copy(outputCopy, ec.runOutputs) // don't use clone here, so we can return a non-nil map |
| 318 | + outputCopy := make(map[addrs.Run]cty.Value, len(ec.runBlocks)) |
| 319 | + for addr, run := range ec.runBlocks { |
| 320 | + // Normally, we should check the run.Status before reading the outputs |
| 321 | + // to make sure they are actually valid. But, for now we are tracking |
| 322 | + // a difference between run blocks not yet executed and run blocks that |
| 323 | + // do not exist by setting cty.NilVal for run blocks that haven't |
| 324 | + // executed yet so we do actually just want to include all run blocks |
| 325 | + // here. |
| 326 | + // TODO(liamcervante): Validate run status before adding to this map |
| 327 | + // once providers and variables are in the graph and we don't need to |
| 328 | + // rely on this hack. |
| 329 | + outputCopy[addr] = run.Outputs |
| 330 | + } |
327 | 331 | return outputCopy |
328 | 332 | } |
329 | 333 |
|
@@ -378,6 +382,60 @@ func (ec *EvalContext) GetFileState(key string) *TestFileState { |
378 | 382 | return ec.FileStates[key] |
379 | 383 | } |
380 | 384 |
|
| 385 | +// ReferencesCompleted returns true if all the listed references were actually |
| 386 | +// executed successfully. This allows nodes in the graph to decide if they |
| 387 | +// should execute or not based on the |
| 388 | +// |
| 389 | +// TODO(liamcervante): Expand this with providers and variables once we've added |
| 390 | +// them to the graph. |
| 391 | +func (ec *EvalContext) ReferencesCompleted(refs []*addrs.Reference) bool { |
| 392 | + for _, ref := range refs { |
| 393 | + switch ref := ref.Subject.(type) { |
| 394 | + case addrs.Run: |
| 395 | + ec.outputsLock.Lock() |
| 396 | + if run, ok := ec.runBlocks[ref]; ok { |
| 397 | + if run.Status != moduletest.Pass && run.Status != moduletest.Fail { |
| 398 | + ec.outputsLock.Unlock() |
| 399 | + |
| 400 | + // see also prior runs completed |
| 401 | + |
| 402 | + return false |
| 403 | + } |
| 404 | + } |
| 405 | + ec.outputsLock.Unlock() |
| 406 | + } |
| 407 | + } |
| 408 | + return true |
| 409 | +} |
| 410 | + |
| 411 | +// PriorRunsCompleted checks a list of run blocks against our internal log of |
| 412 | +// completed run blocks and makes sure that any that do exist successfully |
| 413 | +// executed to completion. |
| 414 | +// |
| 415 | +// Note that run blocks that are not in the list indicate a bad reference, |
| 416 | +// which we ignore here. This is actually the problem of the caller to identify |
| 417 | +// and error. |
| 418 | +func (ec *EvalContext) PriorRunsCompleted(runs map[addrs.Run]*moduletest.Run) bool { |
| 419 | + ec.outputsLock.Lock() |
| 420 | + defer ec.outputsLock.Unlock() |
| 421 | + |
| 422 | + for addr := range runs { |
| 423 | + if run, ok := ec.runBlocks[addr]; ok { |
| 424 | + if run.Status != moduletest.Pass && run.Status != moduletest.Fail { |
| 425 | + |
| 426 | + // pass and fail indicate the run block still executed the plan |
| 427 | + // or apply operate and wrote outputs. fail means the |
| 428 | + // post-execution checks failed, but we still had data to check. |
| 429 | + // this is in contrast to pending, skip, or error which indicate |
| 430 | + // that we never even wrote data for this run block. |
| 431 | + |
| 432 | + return false |
| 433 | + } |
| 434 | + } |
| 435 | + } |
| 436 | + return true |
| 437 | +} |
| 438 | + |
381 | 439 | // evaluationData augments an underlying lang.Data -- presumably resulting |
382 | 440 | // from a terraform.Context.PlanAndEval or terraform.Context.ApplyAndEval call -- |
383 | 441 | // with results from prior runs that should therefore be available when |
|
0 commit comments