mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
testing framework: refactor interrupt logic for immediate exits (#33532)
* testing framework: refactor interrupt logic * fix formatting
This commit is contained in:
parent
6882dd9530
commit
6c7db16566
@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
@ -173,6 +174,14 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// We have two levels of interrupt here. A 'stop' and a 'cancel'. A 'stop'
|
||||
// is a soft request to stop. We'll finish the current test, do the tidy up,
|
||||
// but then skip all remaining tests and run blocks. A 'cancel' is a hard
|
||||
// request to stop now. We'll cancel the current operation immediately
|
||||
// even if it's a delete operation, and we won't clean up any infrastructure
|
||||
// if we're halfway through a test. We'll print details explaining what was
|
||||
// stopped so the user can do their best to recover from it.
|
||||
|
||||
runningCtx, done := context.WithCancel(context.Background())
|
||||
stopCtx, stop := context.WithCancel(runningCtx)
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
@ -199,7 +208,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
|
||||
go func() {
|
||||
defer logging.PanicHandler()
|
||||
defer done() // We completed successfully.
|
||||
defer done()
|
||||
defer stop()
|
||||
defer cancel()
|
||||
|
||||
@ -224,10 +233,12 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
runner.Cancelled = true
|
||||
cancel()
|
||||
|
||||
// TODO(liamcervante): Should we add a timer here? That would mean
|
||||
// after 5 seconds we just give up and don't even print out the
|
||||
// lists of resources left behind?
|
||||
<-runningCtx.Done() // Nothing left to do now but wait.
|
||||
// We'll wait 5 seconds for this operation to finish now, regardless
|
||||
// of whether it finishes successfully or not.
|
||||
select {
|
||||
case <-runningCtx.Done():
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
case <-runningCtx.Done():
|
||||
// The application finished nicely after the request was stopped.
|
||||
@ -331,13 +342,13 @@ func (runner *TestRunner) ExecuteTestFile(file *moduletest.File, globals map[str
|
||||
if run.Config.ConfigUnderTest != nil {
|
||||
// Then we want to execute a different module under a kind of
|
||||
// sandbox.
|
||||
state := runner.ExecuteTestRun(run, file, states.NewState(), run.Config.ConfigUnderTest, globals)
|
||||
state := runner.ExecuteTestRun(mgr, run, file, states.NewState(), run.Config.ConfigUnderTest, globals)
|
||||
mgr.States = append(mgr.States, &TestModuleState{
|
||||
State: state,
|
||||
Run: run,
|
||||
})
|
||||
} else {
|
||||
mgr.State = runner.ExecuteTestRun(run, file, mgr.State, runner.Config, globals)
|
||||
mgr.State = runner.ExecuteTestRun(mgr, run, file, mgr.State, runner.Config, globals)
|
||||
}
|
||||
file.Status = file.Status.Merge(run.Status)
|
||||
}
|
||||
@ -348,7 +359,7 @@ func (runner *TestRunner) ExecuteTestFile(file *moduletest.File, globals map[str
|
||||
}
|
||||
}
|
||||
|
||||
func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.File, state *states.State, config *configs.Config, globals map[string]backend.UnparsedVariableValue) *states.State {
|
||||
func (runner *TestRunner) ExecuteTestRun(mgr *TestStateManager, run *moduletest.Run, file *moduletest.File, state *states.State, config *configs.Config, globals map[string]backend.UnparsedVariableValue) *states.State {
|
||||
if runner.Cancelled {
|
||||
// Don't do anything, just give up and return immediately.
|
||||
// The surrounding functions should stop this even being called, but in
|
||||
@ -376,7 +387,7 @@ func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.F
|
||||
return state
|
||||
}
|
||||
|
||||
ctx, plan, state, diags := runner.execute(run, file, config, state, &terraform.PlanOpts{
|
||||
ctx, plan, state, diags := runner.execute(mgr, run, file, config, state, &terraform.PlanOpts{
|
||||
Mode: func() plans.Mode {
|
||||
switch run.Config.Options.Mode {
|
||||
case configs.RefreshOnlyTestMode:
|
||||
@ -458,17 +469,12 @@ func (runner *TestRunner) ExecuteTestRun(run *moduletest.Run, file *moduletest.F
|
||||
//
|
||||
// The command argument decides whether it executes only a plan or also applies
|
||||
// the plan it creates during the planning.
|
||||
func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, config *configs.Config, state *states.State, opts *terraform.PlanOpts, command configs.TestCommand, globals map[string]backend.UnparsedVariableValue) (*terraform.Context, *plans.Plan, *states.State, tfdiags.Diagnostics) {
|
||||
func (runner *TestRunner) execute(mgr *TestStateManager, run *moduletest.Run, file *moduletest.File, config *configs.Config, state *states.State, opts *terraform.PlanOpts, command configs.TestCommand, globals map[string]backend.UnparsedVariableValue) (*terraform.Context, *plans.Plan, *states.State, tfdiags.Diagnostics) {
|
||||
if opts.Mode == plans.DestroyMode && state.Empty() {
|
||||
// Nothing to do!
|
||||
return nil, nil, state, nil
|
||||
}
|
||||
|
||||
identifier := file.Name
|
||||
if run != nil {
|
||||
identifier = fmt.Sprintf("%s/%s", identifier, run.Name)
|
||||
}
|
||||
|
||||
// First, transform the config for the given test run and test file.
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
@ -517,7 +523,7 @@ func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, co
|
||||
defer done()
|
||||
plan, planDiags = tfCtx.Plan(config, state, opts)
|
||||
}()
|
||||
waitDiags, cancelled := runner.wait(tfCtx, runningCtx, opts, identifier)
|
||||
waitDiags, cancelled := runner.wait(tfCtx, runningCtx, mgr, run, file, nil)
|
||||
planDiags = planDiags.Append(waitDiags)
|
||||
|
||||
diags = diags.Append(planDiags)
|
||||
@ -556,6 +562,25 @@ func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, co
|
||||
|
||||
runningCtx, done = context.WithCancel(context.Background())
|
||||
|
||||
// If things get cancelled while we are executing the apply operation below
|
||||
// we want to print out all the objects that we were creating so the user
|
||||
// can verify we managed to tidy everything up possibly.
|
||||
//
|
||||
// Unfortunately, this creates a race condition as the apply operation can
|
||||
// edit the plan (by removing changes once they are applied) while at the
|
||||
// same time our cancellation process will try to read the plan.
|
||||
//
|
||||
// We take a quick copy of the changes we care about here, which will then
|
||||
// be used in place of the plan when we print out the objects to be created
|
||||
// as part of the cancellation process.
|
||||
var created []*plans.ResourceInstanceChangeSrc
|
||||
for _, change := range plan.Changes.Resources {
|
||||
if change.Action != plans.Create {
|
||||
continue
|
||||
}
|
||||
created = append(created, change)
|
||||
}
|
||||
|
||||
var updated *states.State
|
||||
var applyDiags tfdiags.Diagnostics
|
||||
|
||||
@ -564,77 +589,58 @@ func (runner *TestRunner) execute(run *moduletest.Run, file *moduletest.File, co
|
||||
defer done()
|
||||
updated, applyDiags = tfCtx.Apply(plan, config)
|
||||
}()
|
||||
waitDiags, _ = runner.wait(tfCtx, runningCtx, opts, identifier)
|
||||
waitDiags, _ = runner.wait(tfCtx, runningCtx, mgr, run, file, created)
|
||||
applyDiags = applyDiags.Append(waitDiags)
|
||||
|
||||
diags = diags.Append(applyDiags)
|
||||
return tfCtx, plan, updated, diags
|
||||
}
|
||||
|
||||
func (runner *TestRunner) wait(ctx *terraform.Context, runningCtx context.Context, opts *terraform.PlanOpts, identifier string) (diags tfdiags.Diagnostics, cancelled bool) {
|
||||
select {
|
||||
case <-runner.StoppedCtx.Done():
|
||||
func (runner *TestRunner) wait(ctx *terraform.Context, runningCtx context.Context, mgr *TestStateManager, run *moduletest.Run, file *moduletest.File, created []*plans.ResourceInstanceChangeSrc) (diags tfdiags.Diagnostics, cancelled bool) {
|
||||
|
||||
if opts.Mode != plans.DestroyMode {
|
||||
// It takes more impetus from the user to cancel the cleanup
|
||||
// operations, so we only do this during the actual tests.
|
||||
cancelled = true
|
||||
go ctx.Stop()
|
||||
// This function handles what happens when the user presses the second
|
||||
// interrupt. This is a "hard cancel", we are going to stop doing whatever
|
||||
// it is we're doing. This means even if we're halfway through creating or
|
||||
// destroying infrastructure we just give up.
|
||||
handleCancelled := func() {
|
||||
|
||||
states := make(map[*moduletest.Run]*states.State)
|
||||
states[nil] = mgr.State
|
||||
for _, module := range mgr.States {
|
||||
states[module.Run] = module.State
|
||||
}
|
||||
runner.View.FatalInterruptSummary(run, file, states, created)
|
||||
|
||||
select {
|
||||
case <-runner.CancelledCtx.Done():
|
||||
|
||||
// If the user still really wants to cancel, then we'll oblige
|
||||
// even during the destroy mode at this point.
|
||||
if opts.Mode == plans.DestroyMode {
|
||||
cancelled = true
|
||||
go ctx.Stop()
|
||||
}
|
||||
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Terraform Test Interrupted",
|
||||
fmt.Sprintf("Terraform test was interrupted while executing %s. This means resources that were created during the test may have been left active, please monitor the rest of the output closely as any dangling resources will be listed.", identifier)))
|
||||
|
||||
// It is actually quite disastrous if we exist early at this
|
||||
// point as it means we'll have created resources that we
|
||||
// haven't tracked at all. So for now, we won't ever actually
|
||||
// forcibly terminate the test. When cancelled, we make the
|
||||
// clean up faster by not performing it but we should still
|
||||
// always manage it give an accurate list of resources left
|
||||
// alive.
|
||||
// TODO(liamcervante): Consider adding a timer here, so that we
|
||||
// exit early even if that means some resources are just lost
|
||||
// forever.
|
||||
<-runningCtx.Done() // Just wait for things to finish now.
|
||||
|
||||
case <-runningCtx.Done():
|
||||
// The operation exited nicely when asked!
|
||||
}
|
||||
case <-runner.CancelledCtx.Done():
|
||||
// This shouldn't really happen, as we'd expect to see the StoppedCtx
|
||||
// being triggered first. But, just in case.
|
||||
cancelled = true
|
||||
go ctx.Stop()
|
||||
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Terraform Test Interrupted",
|
||||
fmt.Sprintf("Terraform test was interrupted while executing %s. This means resources that were created during the test may have been left active, please monitor the rest of the output closely as any dangling resources will be listed.", identifier)))
|
||||
// Just wait for things to finish now, the overall test execution will
|
||||
// exit early if this takes too long.
|
||||
<-runningCtx.Done()
|
||||
}
|
||||
|
||||
// It is actually quite disastrous if we exist early at this
|
||||
// point as it means we'll have created resources that we
|
||||
// haven't tracked at all. So for now, we won't ever actually
|
||||
// forcibly terminate the test. When cancelled, we make the
|
||||
// clean up faster by not performing it but we should still
|
||||
// always manage it give an accurate list of resources left
|
||||
// alive.
|
||||
// TODO(liamcervante): Consider adding a timer here, so that we
|
||||
// exit early even if that means some resources are just lost
|
||||
// forever.
|
||||
<-runningCtx.Done() // Just wait for things to finish now.
|
||||
// This function handles what happens when the user presses the first
|
||||
// interrupt. This is essentially a "soft cancel", we're not going to do
|
||||
// anything but just wait for things to finish safely. But, we do listen
|
||||
// for the crucial second interrupt which will prompt a hard stop / cancel.
|
||||
handleStopped := func() {
|
||||
select {
|
||||
case <-runner.CancelledCtx.Done():
|
||||
// We've been asked again. This time we stop whatever we're doing
|
||||
// and abandon all attempts to do anything reasonable.
|
||||
handleCancelled()
|
||||
case <-runningCtx.Done():
|
||||
// Do nothing, we finished safely and skipping the remaining tests
|
||||
// will be handled elsewhere.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
select {
|
||||
case <-runner.StoppedCtx.Done():
|
||||
handleStopped()
|
||||
case <-runner.CancelledCtx.Done():
|
||||
handleCancelled()
|
||||
case <-runningCtx.Done():
|
||||
// The operation exited normally.
|
||||
}
|
||||
@ -675,27 +681,21 @@ type TestModuleState struct {
|
||||
|
||||
func (manager *TestStateManager) cleanupStates(file *moduletest.File, globals map[string]backend.UnparsedVariableValue) {
|
||||
if manager.runner.Cancelled {
|
||||
|
||||
// We are still going to print out the resources that we have left
|
||||
// even though the user asked for an immediate exit.
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Test cleanup skipped due to immediate exit", "Terraform could not clean up the state left behind due to immediate interrupt."))
|
||||
manager.runner.View.DestroySummary(diags, nil, file, manager.State)
|
||||
|
||||
for _, module := range manager.States {
|
||||
manager.runner.View.DestroySummary(diags, module.Run, file, module.State)
|
||||
}
|
||||
|
||||
// Don't try and clean anything up if the execution has been cancelled.
|
||||
return
|
||||
}
|
||||
|
||||
// First, we'll clean up the main state.
|
||||
_, _, state, diags := manager.runner.execute(nil, file, manager.runner.Config, manager.State, &terraform.PlanOpts{
|
||||
_, _, state, diags := manager.runner.execute(manager, nil, file, manager.runner.Config, manager.State, &terraform.PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
}, configs.ApplyTestCommand, globals)
|
||||
manager.runner.View.DestroySummary(diags, nil, file, state)
|
||||
|
||||
if manager.runner.Cancelled {
|
||||
// In case things were cancelled during the last execution.
|
||||
return
|
||||
}
|
||||
|
||||
// Then we'll clean up the additional states for custom modules in reverse
|
||||
// order.
|
||||
for ix := len(manager.States); ix > 0; ix-- {
|
||||
@ -704,11 +704,10 @@ func (manager *TestStateManager) cleanupStates(file *moduletest.File, globals ma
|
||||
if manager.runner.Cancelled {
|
||||
// In case the cancellation came while a previous state was being
|
||||
// destroyed.
|
||||
manager.runner.View.DestroySummary(diags, module.Run, file, module.State)
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
_, _, state, diags := manager.runner.execute(module.Run, file, module.Run.Config.ConfigUnderTest, module.State, &terraform.PlanOpts{
|
||||
_, _, state, diags := manager.runner.execute(manager, module.Run, file, module.Run.Config.ConfigUnderTest, module.State, &terraform.PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
}, configs.ApplyTestCommand, globals)
|
||||
manager.runner.View.DestroySummary(diags, module.Run, file, state)
|
||||
|
@ -211,6 +211,19 @@ func TestTest_DoubleInterrupt(t *testing.T) {
|
||||
t.Errorf("output didn't produce the right output:\n\n%s", output)
|
||||
}
|
||||
|
||||
cleanupMessage := `Terraform was interrupted while executing main.tftest, and may not have performed the expected cleanup operations.
|
||||
|
||||
Terraform has already created the following resources from the module under test:
|
||||
- test_resource.primary
|
||||
- test_resource.secondary
|
||||
- test_resource.tertiary`
|
||||
|
||||
// It's really important that the above message is printed, so we're testing
|
||||
// for it specifically and making sure it contains all the resources.
|
||||
if !strings.Contains(output, cleanupMessage) {
|
||||
t.Errorf("output didn't produce the right output:\n\n%s", output)
|
||||
}
|
||||
|
||||
// This time the test command shouldn't have cleaned up the resource because
|
||||
// of the hard interrupt.
|
||||
if provider.ResourceCount() != 3 {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
@ -217,6 +218,11 @@ func (provider *TestProvider) ApplyResourceChange(request providers.ApplyResourc
|
||||
for ix := 0; ix < int(count); ix++ {
|
||||
provider.Interrupt <- struct{}{}
|
||||
}
|
||||
|
||||
// Wait for a second to make sure the interrupts are processed by
|
||||
// Terraform before the provider finishes. This is an attempt to ensure
|
||||
// the output of any tests that rely on this behaviour is deterministic.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
provider.Store.Put(provider.GetResourceKey(id.AsString()), resource)
|
||||
|
@ -30,11 +30,12 @@ const (
|
||||
MessageRefreshComplete MessageType = "refresh_complete"
|
||||
|
||||
// Test messages
|
||||
MessageTestAbstract MessageType = "test_abstract"
|
||||
MessageTestFile MessageType = "test_file"
|
||||
MessageTestRun MessageType = "test_run"
|
||||
MessageTestPlan MessageType = "test_plan"
|
||||
MessageTestState MessageType = "test_state"
|
||||
MessageTestSummary MessageType = "test_summary"
|
||||
MessageTestCleanup MessageType = "test_cleanup"
|
||||
MessageTestAbstract MessageType = "test_abstract"
|
||||
MessageTestFile MessageType = "test_file"
|
||||
MessageTestRun MessageType = "test_run"
|
||||
MessageTestPlan MessageType = "test_plan"
|
||||
MessageTestState MessageType = "test_state"
|
||||
MessageTestSummary MessageType = "test_summary"
|
||||
MessageTestCleanup MessageType = "test_cleanup"
|
||||
MessageTestInterrupt MessageType = "test_interrupt"
|
||||
)
|
||||
|
@ -38,6 +38,12 @@ type TestFailedResource struct {
|
||||
DeposedKey string `json:"deposed_key,omitempty"`
|
||||
}
|
||||
|
||||
type TestFatalInterrupt struct {
|
||||
State []TestFailedResource `json:"state,omitempty"`
|
||||
States map[string][]TestFailedResource `json:"states,omitempty"`
|
||||
Planned []string `json:"planned,omitempty"`
|
||||
}
|
||||
|
||||
func ToTestStatus(status moduletest.Status) TestStatus {
|
||||
return TestStatus(strings.ToLower(status.String()))
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/command/views/json"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/moduletest"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
@ -53,6 +54,16 @@ type Test interface {
|
||||
// FatalInterrupt prints out a message stating that a hard interrupt has
|
||||
// been received and testing will stop and cleanup will be skipped.
|
||||
FatalInterrupt()
|
||||
|
||||
// FatalInterruptSummary prints out the resources that were held in state
|
||||
// and were being created at the time the FatalInterrupt was received.
|
||||
//
|
||||
// This will typically be called in place of DestroySummary, as there is no
|
||||
// guarantee that this function will be called during a FatalInterrupt. In
|
||||
// addition, this function prints additional details about the current
|
||||
// operation alongside the current state as the state will be missing newly
|
||||
// created resources that also need to be handled manually.
|
||||
FatalInterruptSummary(run *moduletest.Run, file *moduletest.File, states map[*moduletest.Run]*states.State, created []*plans.ResourceInstanceChangeSrc)
|
||||
}
|
||||
|
||||
func NewTest(vt arguments.ViewType, view *View) Test {
|
||||
@ -221,11 +232,65 @@ func (t *TestHuman) Diagnostics(_ *moduletest.Run, _ *moduletest.File, diags tfd
|
||||
}
|
||||
|
||||
func (t *TestHuman) Interrupted() {
|
||||
t.view.streams.Print(interrupted)
|
||||
t.view.streams.Eprint(interrupted)
|
||||
}
|
||||
|
||||
func (t *TestHuman) FatalInterrupt() {
|
||||
t.view.streams.Print(fatalInterrupt)
|
||||
t.view.streams.Eprint(fatalInterrupt)
|
||||
}
|
||||
|
||||
func (t *TestHuman) FatalInterruptSummary(run *moduletest.Run, file *moduletest.File, existingStates map[*moduletest.Run]*states.State, created []*plans.ResourceInstanceChangeSrc) {
|
||||
t.view.streams.Eprintf("\nTerraform was interrupted while executing %s, and may not have performed the expected cleanup operations.\n", file.Name)
|
||||
|
||||
for run, state := range existingStates {
|
||||
if state.Empty() {
|
||||
// Then it's fine, don't worry about it.
|
||||
continue
|
||||
}
|
||||
|
||||
if run == nil {
|
||||
// Then this is just the main state for the whole file.
|
||||
t.view.streams.Eprintln("\nTerraform has already created the following resources from the module under test:")
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
if resource.DeposedKey != states.NotDeposed {
|
||||
t.view.streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
|
||||
continue
|
||||
}
|
||||
t.view.streams.Eprintf(" - %s\n", resource.Instance)
|
||||
}
|
||||
} else {
|
||||
t.view.streams.Eprintf("\nTerraform has already created the following resources for %s from %s:\n", run.Name, run.Config.Module.Source)
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
if resource.DeposedKey != states.NotDeposed {
|
||||
t.view.streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
|
||||
continue
|
||||
}
|
||||
t.view.streams.Eprintf(" - %s\n", resource.Instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(created) == 0 {
|
||||
// No planned changes, so we won't print anything.
|
||||
return
|
||||
}
|
||||
|
||||
var resources []string
|
||||
for _, change := range created {
|
||||
resources = append(resources, change.Addr.String())
|
||||
}
|
||||
|
||||
if len(resources) > 0 {
|
||||
module := "the module under test"
|
||||
if run.Config.ConfigUnderTest != nil {
|
||||
module = run.Config.Module.Source.String()
|
||||
}
|
||||
|
||||
t.view.streams.Eprintf("\nTerraform was in the process of creating the following resources for %s from %s, and they may not have been destroyed:\n", run.Name, module)
|
||||
for _, resource := range resources {
|
||||
t.view.streams.Eprintf(" - %s\n", resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TestJSON struct {
|
||||
@ -422,6 +487,50 @@ func (t *TestJSON) FatalInterrupt() {
|
||||
t.view.Log(fatalInterrupt)
|
||||
}
|
||||
|
||||
func (t *TestJSON) FatalInterruptSummary(run *moduletest.Run, file *moduletest.File, existingStates map[*moduletest.Run]*states.State, created []*plans.ResourceInstanceChangeSrc) {
|
||||
|
||||
message := json.TestFatalInterrupt{
|
||||
States: make(map[string][]json.TestFailedResource),
|
||||
}
|
||||
|
||||
for run, state := range existingStates {
|
||||
if state.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
var resources []json.TestFailedResource
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
resources = append(resources, json.TestFailedResource{
|
||||
Instance: resource.Instance.String(),
|
||||
DeposedKey: resource.DeposedKey.String(),
|
||||
})
|
||||
}
|
||||
|
||||
if run == nil {
|
||||
message.State = resources
|
||||
} else {
|
||||
message.States[run.Name] = resources
|
||||
}
|
||||
}
|
||||
|
||||
if len(created) > 0 {
|
||||
for _, change := range created {
|
||||
message.Planned = append(message.Planned, change.Addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
if len(message.States) == 0 && len(message.State) == 0 && len(message.Planned) == 0 {
|
||||
// Then we don't have any information to share with the user.
|
||||
return
|
||||
}
|
||||
|
||||
t.view.log.Error(
|
||||
"Terraform was interrupted during test execution, and may not have performed the expected cleanup operations.",
|
||||
"type", json.MessageTestInterrupt,
|
||||
json.MessageTestInterrupt, message,
|
||||
"@testfile", file.Name)
|
||||
}
|
||||
|
||||
func colorizeTestStatus(status moduletest.Status, color *colorstring.Colorize) string {
|
||||
switch status {
|
||||
case moduletest.Error, moduletest.Fail:
|
||||
|
@ -939,6 +939,285 @@ Terraform left the following resources in state after executing main.tftest, the
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestHuman_FatalInterruptSummary(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
states map[*moduletest.Run]*states.State
|
||||
run *moduletest.Run
|
||||
created []*plans.ResourceInstanceChangeSrc
|
||||
want string
|
||||
}{
|
||||
"no_state_only_plan": {
|
||||
states: make(map[*moduletest.Run]*states.State),
|
||||
run: &moduletest.Run{
|
||||
Config: &configs.TestRun{},
|
||||
Name: "run_block",
|
||||
},
|
||||
created: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: `
|
||||
Terraform was interrupted while executing main.tftest, and may not have performed the expected cleanup operations.
|
||||
|
||||
Terraform was in the process of creating the following resources for run_block from the module under test, and they may not have been destroyed:
|
||||
- test_instance.one
|
||||
- test_instance.two
|
||||
`,
|
||||
},
|
||||
"file_state_no_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
nil: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
created: nil,
|
||||
want: `
|
||||
Terraform was interrupted while executing main.tftest, and may not have performed the expected cleanup operations.
|
||||
|
||||
Terraform has already created the following resources from the module under test:
|
||||
- test_instance.one
|
||||
- test_instance.two
|
||||
`,
|
||||
},
|
||||
"run_states_no_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
&moduletest.Run{
|
||||
Name: "setup_block",
|
||||
Config: &configs.TestRun{
|
||||
Module: &configs.TestRunModuleCall{
|
||||
Source: addrs.ModuleSourceLocal("../setup"),
|
||||
},
|
||||
},
|
||||
}: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
created: nil,
|
||||
want: `
|
||||
Terraform was interrupted while executing main.tftest, and may not have performed the expected cleanup operations.
|
||||
|
||||
Terraform has already created the following resources for setup_block from ../setup:
|
||||
- test_instance.one
|
||||
- test_instance.two
|
||||
`,
|
||||
},
|
||||
"all_states_with_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
&moduletest.Run{
|
||||
Name: "setup_block",
|
||||
Config: &configs.TestRun{
|
||||
Module: &configs.TestRunModuleCall{
|
||||
Source: addrs.ModuleSourceLocal("../setup"),
|
||||
},
|
||||
},
|
||||
}: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "setup_one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "setup_two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
nil: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
created: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "new_one",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "new_two",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
},
|
||||
run: &moduletest.Run{
|
||||
Config: &configs.TestRun{},
|
||||
Name: "run_block",
|
||||
},
|
||||
want: `
|
||||
Terraform was interrupted while executing main.tftest, and may not have performed the expected cleanup operations.
|
||||
|
||||
Terraform has already created the following resources for setup_block from ../setup:
|
||||
- test_instance.setup_one
|
||||
- test_instance.setup_two
|
||||
|
||||
Terraform has already created the following resources from the module under test:
|
||||
- test_instance.one
|
||||
- test_instance.two
|
||||
|
||||
Terraform was in the process of creating the following resources for run_block from the module under test, and they may not have been destroyed:
|
||||
- test_instance.new_one
|
||||
- test_instance.new_two
|
||||
`,
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewTest(arguments.ViewHuman, NewView(streams))
|
||||
|
||||
file := &moduletest.File{Name: "main.tftest"}
|
||||
|
||||
view.FatalInterruptSummary(tc.run, file, tc.states, tc.created)
|
||||
actual, expected := done(t).Stderr(), tc.want
|
||||
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
|
||||
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestJSON_Abstract(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
suite *moduletest.Suite
|
||||
@ -2462,6 +2741,312 @@ func TestTestJSON_Run(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestJSON_FatalInterruptSummary(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
states map[*moduletest.Run]*states.State
|
||||
changes []*plans.ResourceInstanceChangeSrc
|
||||
want []map[string]interface{}
|
||||
}{
|
||||
"no_state_only_plan": {
|
||||
states: make(map[*moduletest.Run]*states.State),
|
||||
changes: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "Terraform was interrupted during test execution, and may not have performed the expected cleanup operations.",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"test_interrupt": map[string]interface{}{
|
||||
"planned": []interface{}{
|
||||
"test_instance.one",
|
||||
"test_instance.two",
|
||||
},
|
||||
},
|
||||
"type": "test_interrupt",
|
||||
},
|
||||
},
|
||||
},
|
||||
"file_state_no_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
nil: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
changes: nil,
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "Terraform was interrupted during test execution, and may not have performed the expected cleanup operations.",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"test_interrupt": map[string]interface{}{
|
||||
"state": []interface{}{
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.one",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.two",
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "test_interrupt",
|
||||
},
|
||||
},
|
||||
},
|
||||
"run_states_no_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
&moduletest.Run{Name: "setup_block"}: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
changes: nil,
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "Terraform was interrupted during test execution, and may not have performed the expected cleanup operations.",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"test_interrupt": map[string]interface{}{
|
||||
"states": map[string]interface{}{
|
||||
"setup_block": []interface{}{
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.one",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.two",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "test_interrupt",
|
||||
},
|
||||
},
|
||||
},
|
||||
"all_states_with_plan": {
|
||||
states: map[*moduletest.Run]*states.State{
|
||||
&moduletest.Run{Name: "setup_block"}: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "setup_one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "setup_two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
nil: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "one",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
&states.ResourceInstanceObjectSrc{},
|
||||
addrs.AbsProviderConfig{})
|
||||
}),
|
||||
},
|
||||
changes: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "new_one",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
Addr: addrs.AbsResourceInstance{
|
||||
Module: addrs.RootModuleInstance,
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "new_two",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "Terraform was interrupted during test execution, and may not have performed the expected cleanup operations.",
|
||||
"@module": "terraform.ui",
|
||||
"@testfile": "main.tftest",
|
||||
"test_interrupt": map[string]interface{}{
|
||||
"state": []interface{}{
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.one",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.two",
|
||||
},
|
||||
},
|
||||
"states": map[string]interface{}{
|
||||
"setup_block": []interface{}{
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.setup_one",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"instance": "test_instance.setup_two",
|
||||
},
|
||||
},
|
||||
},
|
||||
"planned": []interface{}{
|
||||
"test_instance.new_one",
|
||||
"test_instance.new_two",
|
||||
},
|
||||
},
|
||||
"type": "test_interrupt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewTest(arguments.ViewJSON, NewView(streams))
|
||||
|
||||
file := &moduletest.File{Name: "main.tftest"}
|
||||
run := &moduletest.Run{Name: "run_block"}
|
||||
|
||||
view.FatalInterruptSummary(run, file, tc.states, tc.changes)
|
||||
testJSONViewOutputEquals(t, done(t).All(), tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue {
|
||||
d, err := plans.NewDynamicValue(value, typ)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user