mirror of
https://github.com/hashicorp/terraform.git
synced 2026-05-28 04:03:27 -04:00
stacks: introduce shared functions for common tests (#35718)
This commit is contained in:
parent
9ea9905b43
commit
0ae6bc34c4
4 changed files with 392 additions and 395 deletions
|
|
@ -6,11 +6,11 @@ package stackruntime
|
|||
import (
|
||||
"context"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
|
|
@ -35,21 +35,12 @@ func TestApplyDestroy(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
type cycle struct {
|
||||
mode plans.Mode
|
||||
inputs map[string]cty.Value
|
||||
wantPlannedChanges []stackplan.PlannedChange
|
||||
wantPlannedDiags []expectedDiagnostic
|
||||
wantAppliedChanges []stackstate.AppliedChange
|
||||
wantAppliedDiags []expectedDiagnostic
|
||||
}
|
||||
|
||||
tcs := map[string]struct {
|
||||
path string
|
||||
description string
|
||||
state *stackstate.State
|
||||
store *stacks_testing_provider.ResourceStore
|
||||
cycles []cycle
|
||||
cycles []TestCycle
|
||||
}{
|
||||
"missing-resource": {
|
||||
path: path.Join("with-single-input", "valid"),
|
||||
|
|
@ -70,10 +61,10 @@ func TestApplyDestroy(t *testing.T) {
|
|||
Status: states.ObjectReady,
|
||||
})).
|
||||
Build(),
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
inputs: map[string]cty.Value{
|
||||
planMode: plans.DestroyMode,
|
||||
planInputs: map[string]cty.Value{
|
||||
"input": cty.StringVal("hello"),
|
||||
},
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
|
|
@ -119,10 +110,10 @@ func TestApplyDestroy(t *testing.T) {
|
|||
Status: states.ObjectReady,
|
||||
})).
|
||||
Build(),
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
inputs: map[string]cty.Value{
|
||||
planMode: plans.DestroyMode,
|
||||
planInputs: map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
"resource": cty.StringVal("bar"),
|
||||
},
|
||||
|
|
@ -183,10 +174,10 @@ func TestApplyDestroy(t *testing.T) {
|
|||
Status: states.ObjectReady,
|
||||
})).
|
||||
Build(),
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.NormalMode,
|
||||
inputs: map[string]cty.Value{
|
||||
planMode: plans.NormalMode,
|
||||
planInputs: map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
"resource": cty.StringVal("bar"),
|
||||
},
|
||||
|
|
@ -239,8 +230,8 @@ func TestApplyDestroy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
inputs: map[string]cty.Value{
|
||||
planMode: plans.DestroyMode,
|
||||
planInputs: map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
"resource": cty.StringVal("bar"),
|
||||
},
|
||||
|
|
@ -273,9 +264,9 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"dependent-resources": {
|
||||
path: "dependent-component",
|
||||
description: "test the order of operations during create and destroy",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.self"),
|
||||
|
|
@ -329,7 +320,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.self"),
|
||||
|
|
@ -391,9 +382,9 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"fail_apply": cty.True,
|
||||
})).
|
||||
Build(),
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.self"),
|
||||
|
|
@ -422,18 +413,22 @@ func TestApplyDestroy(t *testing.T) {
|
|||
Schema: stacks_testing_provider.FailedResourceSchema,
|
||||
},
|
||||
},
|
||||
wantAppliedDiags: []expectedDiagnostic{
|
||||
expectDiagnostic(tfdiags.Error, "failedResource error", "failed during apply"),
|
||||
},
|
||||
wantAppliedDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
return diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "failedResource error",
|
||||
Detail: "failed during apply",
|
||||
})
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
"destroy-after-failed-apply": {
|
||||
path: path.Join("with-single-input", "failed-child"),
|
||||
description: "tests destroying when state is only partially applied",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.child"),
|
||||
|
|
@ -475,12 +470,16 @@ func TestApplyDestroy(t *testing.T) {
|
|||
Schema: stacks_testing_provider.TestingResourceSchema,
|
||||
},
|
||||
},
|
||||
wantAppliedDiags: []expectedDiagnostic{
|
||||
expectDiagnostic(tfdiags.Error, "failedResource error", "failed during apply"),
|
||||
},
|
||||
wantAppliedDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
return diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "failedResource error",
|
||||
Detail: "failed during apply",
|
||||
})
|
||||
}),
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.child"),
|
||||
|
|
@ -515,9 +514,9 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"destroy-after-deferred-apply": {
|
||||
path: "deferred-dependent",
|
||||
description: "tests what happens when a destroy plan is applied after components have been deferred",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.deferred"),
|
||||
|
|
@ -555,7 +554,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.deferred"),
|
||||
|
|
@ -626,9 +625,9 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"deferred": cty.True,
|
||||
})).
|
||||
Build(),
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantPlannedChanges: []stackplan.PlannedChange{
|
||||
&stackplan.PlannedChangeApplyable{
|
||||
Applyable: true,
|
||||
|
|
@ -762,13 +761,13 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"destroy-with-input-dependency": {
|
||||
path: path.Join("with-single-input-and-output", "input-dependency"),
|
||||
description: "tests destroy operations with input dependencies",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
// Just create everything normally, and don't validate it.
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.child"),
|
||||
|
|
@ -805,13 +804,13 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"destroy-with-provider-dependency": {
|
||||
path: path.Join("with-single-input-and-output", "provider-dependency"),
|
||||
description: "tests destroy operations with provider dependencies",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
// Just create everything normally, and don't validate it.
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.child"),
|
||||
|
|
@ -848,13 +847,13 @@ func TestApplyDestroy(t *testing.T) {
|
|||
"destroy-with-for-each-dependency": {
|
||||
path: path.Join("with-single-input-and-output", "for-each-dependency"),
|
||||
description: "tests destroy operations with for-each dependencies",
|
||||
cycles: []cycle{
|
||||
cycles: []TestCycle{
|
||||
{
|
||||
// Just create everything normally, and don't validate it.
|
||||
mode: plans.NormalMode,
|
||||
planMode: plans.NormalMode,
|
||||
},
|
||||
{
|
||||
mode: plans.DestroyMode,
|
||||
planMode: plans.DestroyMode,
|
||||
wantAppliedChanges: []stackstate.AppliedChange{
|
||||
&stackstate.AppliedChangeComponentInstance{
|
||||
ComponentAddr: mustAbsComponent("component.child"),
|
||||
|
|
@ -891,9 +890,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, tc.path)
|
||||
|
||||
lock := depsfile.NewLocks()
|
||||
lock.SetProvider(
|
||||
|
|
@ -908,123 +905,28 @@ func TestApplyDestroy(t *testing.T) {
|
|||
store = stacks_testing_provider.NewResourceStore()
|
||||
}
|
||||
|
||||
testContext := TestContext{
|
||||
timestamp: &fakePlanTimestamp,
|
||||
config: loadMainBundleConfigForTest(t, tc.path),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProviderWithData(t, store), nil
|
||||
},
|
||||
},
|
||||
dependencyLocks: *lock,
|
||||
}
|
||||
|
||||
state := tc.state
|
||||
for ix, cycle := range tc.cycles {
|
||||
|
||||
planReq := PlanRequest{
|
||||
PlanMode: cycle.mode,
|
||||
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProviderWithData(t, store), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
ForcePlanTimestamp: &fakePlanTimestamp,
|
||||
InputValues: func() map[stackaddrs.InputVariable]ExternalInputValue {
|
||||
inputs := make(map[stackaddrs.InputVariable]ExternalInputValue, len(cycle.inputs))
|
||||
for k, v := range cycle.inputs {
|
||||
inputs[stackaddrs.InputVariable{Name: k}] = ExternalInputValue{Value: v}
|
||||
}
|
||||
return inputs
|
||||
}(),
|
||||
|
||||
PrevState: state,
|
||||
}
|
||||
|
||||
planChangesCh := make(chan stackplan.PlannedChange)
|
||||
planDiagsCh := make(chan tfdiags.Diagnostic)
|
||||
planResp := PlanResponse{
|
||||
PlannedChanges: planChangesCh,
|
||||
Diagnostics: planDiagsCh,
|
||||
}
|
||||
|
||||
go Plan(ctx, &planReq, &planResp)
|
||||
planChanges, planDiags := collectPlanOutput(planChangesCh, planDiagsCh)
|
||||
expectDiagnosticsForTest(t, planDiags, cycle.wantPlannedDiags...)
|
||||
|
||||
if cycle.wantPlannedChanges != nil {
|
||||
// nil indicates skip this check, empty slice indicates no changes expected.
|
||||
sort.SliceStable(planChanges, func(i, j int) bool {
|
||||
return plannedChangeSortKey(planChanges[i]) < plannedChangeSortKey(planChanges[j])
|
||||
t.Run(strconv.FormatInt(int64(ix), 10), func(t *testing.T) {
|
||||
var plan *stackplan.Plan
|
||||
t.Run("plan", func(t *testing.T) {
|
||||
plan = testContext.Plan(t, ctx, state, cycle)
|
||||
})
|
||||
if diff := cmp.Diff(cycle.wantPlannedChanges, planChanges, changesCmpOpts); diff != "" {
|
||||
t.Fatalf("wrong planned changes %d\n%s", ix, diff)
|
||||
}
|
||||
}
|
||||
|
||||
planLoader := stackplan.NewLoader()
|
||||
for _, change := range planChanges {
|
||||
proto, err := change.PlannedChangeProto()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, rawMsg := range proto.Raw {
|
||||
err = planLoader.AddRaw(rawMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
plan, err := planLoader.Plan()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
applyReq := ApplyRequest{
|
||||
Config: cfg,
|
||||
Plan: plan,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProviderWithData(t, store), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
}
|
||||
|
||||
applyChangesCh := make(chan stackstate.AppliedChange)
|
||||
applyDiagsCh := make(chan tfdiags.Diagnostic)
|
||||
applyResp := ApplyResponse{
|
||||
AppliedChanges: applyChangesCh,
|
||||
Diagnostics: applyDiagsCh,
|
||||
}
|
||||
|
||||
go Apply(ctx, &applyReq, &applyResp)
|
||||
applyChanges, applyDiags := collectApplyOutput(applyChangesCh, applyDiagsCh)
|
||||
expectDiagnosticsForTest(t, applyDiags, cycle.wantAppliedDiags...)
|
||||
|
||||
if cycle.wantAppliedChanges != nil {
|
||||
// nil indicates skip this check, empty slice indicates no changes expected.
|
||||
sort.SliceStable(applyChanges, func(i, j int) bool {
|
||||
return appliedChangeSortKey(applyChanges[i]) < appliedChangeSortKey(applyChanges[j])
|
||||
t.Run("apply", func(t *testing.T) {
|
||||
state = testContext.Apply(t, ctx, plan, cycle)
|
||||
})
|
||||
if diff := cmp.Diff(cycle.wantAppliedChanges, applyChanges, changesCmpOpts); diff != "" {
|
||||
t.Fatalf("wrong applied changes %d\n%s", ix, diff)
|
||||
}
|
||||
}
|
||||
|
||||
stateLoader := stackstate.NewLoader()
|
||||
for _, change := range applyChanges {
|
||||
proto, err := change.AppliedChangeProto()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, rawMsg := range proto.Raw {
|
||||
if rawMsg.Value == nil {
|
||||
// This is a removal notice, so we don't need to add it to the
|
||||
// state.
|
||||
continue
|
||||
}
|
||||
err = stateLoader.AddRaw(rawMsg.Key, rawMsg.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
state = stateLoader.State()
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@
|
|||
package stackruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-slug/sourceaddrs"
|
||||
|
|
@ -17,7 +20,9 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/depsfile"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
||||
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
|
||||
"github.com/hashicorp/terraform/internal/stacks/stackplan"
|
||||
|
|
@ -28,6 +33,200 @@ import (
|
|||
// This file has helper functions used by other tests. It doesn't contain any
|
||||
// test cases of its own.
|
||||
|
||||
// TestContext contains all the information shared across multiple operations
|
||||
// in a single test.
|
||||
type TestContext struct {
|
||||
// timestamp is the timestamp that should be applied for this test.
|
||||
timestamp *time.Time
|
||||
|
||||
// config is the config to use for this test.
|
||||
config *stackconfig.Config
|
||||
|
||||
// providers are the providers that should be available within this test.
|
||||
providers map[addrs.Provider]providers.Factory
|
||||
|
||||
// dependencyLocks is the locks file that should be used for this test.
|
||||
dependencyLocks depsfile.Locks
|
||||
}
|
||||
|
||||
// TestCycle defines a single plan / apply cycle that should be performed within
|
||||
// a test.
|
||||
type TestCycle struct {
|
||||
|
||||
// Validate options
|
||||
|
||||
wantValidateDiags tfdiags.Diagnostics
|
||||
|
||||
// Plan options
|
||||
|
||||
planMode plans.Mode
|
||||
planInputs map[string]cty.Value
|
||||
wantPlannedChanges []stackplan.PlannedChange
|
||||
wantPlannedDiags tfdiags.Diagnostics
|
||||
|
||||
// Apply options
|
||||
|
||||
applyInputs map[string]cty.Value
|
||||
wantAppliedChanges []stackstate.AppliedChange
|
||||
wantAppliedDiags tfdiags.Diagnostics
|
||||
}
|
||||
|
||||
func (tc TestContext) Validate(t *testing.T, ctx context.Context, cycle TestCycle) {
|
||||
t.Helper()
|
||||
|
||||
gotDiags := Validate(ctx, &ValidateRequest{
|
||||
Config: tc.config,
|
||||
ProviderFactories: tc.providers,
|
||||
DependencyLocks: tc.dependencyLocks,
|
||||
ExperimentsAllowed: true,
|
||||
})
|
||||
validateDiags(t, cycle.wantValidateDiags, gotDiags)
|
||||
}
|
||||
|
||||
func (tc TestContext) Plan(t *testing.T, ctx context.Context, state *stackstate.State, cycle TestCycle) *stackplan.Plan {
|
||||
t.Helper()
|
||||
|
||||
request := PlanRequest{
|
||||
PlanMode: cycle.planMode,
|
||||
Config: tc.config,
|
||||
PrevState: state,
|
||||
InputValues: func() map[stackaddrs.InputVariable]ExternalInputValue {
|
||||
inputs := make(map[stackaddrs.InputVariable]ExternalInputValue, len(cycle.planInputs))
|
||||
for k, v := range cycle.planInputs {
|
||||
inputs[stackaddrs.InputVariable{Name: k}] = ExternalInputValue{Value: v}
|
||||
}
|
||||
return inputs
|
||||
}(),
|
||||
ProviderFactories: tc.providers,
|
||||
DependencyLocks: tc.dependencyLocks,
|
||||
ForcePlanTimestamp: tc.timestamp,
|
||||
ExperimentsAllowed: true,
|
||||
}
|
||||
|
||||
changesCh := make(chan stackplan.PlannedChange)
|
||||
diagsCh := make(chan tfdiags.Diagnostic)
|
||||
response := PlanResponse{
|
||||
PlannedChanges: changesCh,
|
||||
Diagnostics: diagsCh,
|
||||
}
|
||||
|
||||
go Plan(ctx, &request, &response)
|
||||
changes, diags := collectPlanOutput(changesCh, diagsCh)
|
||||
validateDiags(t, cycle.wantPlannedDiags, diags)
|
||||
|
||||
if cycle.wantPlannedChanges != nil {
|
||||
// if this is nil (as opposed to empty) then we don't validate the
|
||||
// returned changes.
|
||||
|
||||
sort.SliceStable(changes, func(i, j int) bool {
|
||||
return plannedChangeSortKey(changes[i]) < plannedChangeSortKey(changes[j])
|
||||
})
|
||||
if diff := cmp.Diff(cycle.wantPlannedChanges, changes, changesCmpOpts); len(diff) > 0 {
|
||||
t.Errorf("wrong planned changes\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
planLoader := stackplan.NewLoader()
|
||||
for _, change := range changes {
|
||||
proto, err := change.PlannedChangeProto()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, rawMsg := range proto.Raw {
|
||||
err = planLoader.AddRaw(rawMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
plan, err := planLoader.Plan()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return plan
|
||||
}
|
||||
|
||||
func (tc TestContext) Apply(t *testing.T, ctx context.Context, plan *stackplan.Plan, cycle TestCycle) *stackstate.State {
|
||||
t.Helper()
|
||||
|
||||
request := ApplyRequest{
|
||||
Config: tc.config,
|
||||
Plan: plan,
|
||||
InputValues: func() map[stackaddrs.InputVariable]ExternalInputValue {
|
||||
inputs := make(map[stackaddrs.InputVariable]ExternalInputValue, len(cycle.applyInputs))
|
||||
for k, v := range cycle.applyInputs {
|
||||
inputs[stackaddrs.InputVariable{Name: k}] = ExternalInputValue{Value: v}
|
||||
}
|
||||
return inputs
|
||||
}(),
|
||||
ProviderFactories: tc.providers,
|
||||
ExperimentsAllowed: true,
|
||||
DependencyLocks: tc.dependencyLocks,
|
||||
}
|
||||
|
||||
changesCh := make(chan stackstate.AppliedChange)
|
||||
diagsCh := make(chan tfdiags.Diagnostic)
|
||||
response := ApplyResponse{
|
||||
AppliedChanges: changesCh,
|
||||
Diagnostics: diagsCh,
|
||||
}
|
||||
|
||||
go Apply(ctx, &request, &response)
|
||||
changes, diags := collectApplyOutput(changesCh, diagsCh)
|
||||
validateDiags(t, cycle.wantAppliedDiags, diags)
|
||||
|
||||
if cycle.wantAppliedChanges != nil {
|
||||
// nil indicates skip this check, empty slice indicates no changes expected.
|
||||
sort.SliceStable(changes, func(i, j int) bool {
|
||||
return appliedChangeSortKey(changes[i]) < appliedChangeSortKey(changes[j])
|
||||
})
|
||||
if diff := cmp.Diff(cycle.wantAppliedChanges, changes, changesCmpOpts); diff != "" {
|
||||
t.Errorf("wrong applied changes\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
stateLoader := stackstate.NewLoader()
|
||||
for _, change := range changes {
|
||||
proto, err := change.AppliedChangeProto()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, rawMsg := range proto.Raw {
|
||||
if rawMsg.Value == nil {
|
||||
// This is a removal notice, so we don't need to add it to the
|
||||
// state.
|
||||
continue
|
||||
}
|
||||
err = stateLoader.AddRaw(rawMsg.Key, rawMsg.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateLoader.State()
|
||||
}
|
||||
|
||||
func initDiags(cb func(diags tfdiags.Diagnostics) tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
return cb(diags)
|
||||
}
|
||||
|
||||
func validateDiags(t *testing.T, wantDiags, gotDiags tfdiags.Diagnostics) {
|
||||
t.Helper()
|
||||
|
||||
sort.SliceStable(gotDiags, diagnosticSortFunc(gotDiags))
|
||||
sort.SliceStable(wantDiags, diagnosticSortFunc(wantDiags))
|
||||
|
||||
gotDiags = gotDiags.ForRPC()
|
||||
wantDiags = wantDiags.ForRPC()
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); len(diff) > 0 {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// loadConfigForTest is a test helper that tries to open bundleRoot as a
|
||||
// source bundle, and then if successful tries to load the given source address
|
||||
// from it as a stack configuration. If any part of the operation fails then
|
||||
|
|
|
|||
|
|
@ -58,17 +58,8 @@ func TestPlan_valid(t *testing.T) {
|
|||
// We've added this test before the implementation was ready.
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, name)
|
||||
|
||||
fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changesCh := make(chan stackplan.PlannedChange, 8)
|
||||
diagsCh := make(chan tfdiags.Diagnostic, 2)
|
||||
lock := depsfile.NewLocks()
|
||||
lock.SetProvider(
|
||||
addrs.NewDefaultProvider("testing"),
|
||||
|
|
@ -82,9 +73,15 @@ func TestPlan_valid(t *testing.T) {
|
|||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
req := PlanRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
|
||||
fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testContext := TestContext{
|
||||
config: loadMainBundleConfigForTest(t, name),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
// We support both hashicorp/testing and
|
||||
// terraform.io/builtin/testing as providers. This lets us
|
||||
// test the provider aliasing feature. Both providers
|
||||
|
|
@ -101,40 +98,16 @@ func TestPlan_valid(t *testing.T) {
|
|||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
InputValues: func() map[stackaddrs.InputVariable]ExternalInputValue {
|
||||
inputs := map[stackaddrs.InputVariable]ExternalInputValue{}
|
||||
for k, v := range tc.planInputVars {
|
||||
inputs[stackaddrs.InputVariable{Name: k}] = ExternalInputValue{
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
return inputs
|
||||
}(),
|
||||
ForcePlanTimestamp: &fakePlanTimestamp,
|
||||
}
|
||||
resp := PlanResponse{
|
||||
PlannedChanges: changesCh,
|
||||
Diagnostics: diagsCh,
|
||||
dependencyLocks: *lock,
|
||||
timestamp: &fakePlanTimestamp,
|
||||
}
|
||||
|
||||
go Plan(ctx, &req, &resp)
|
||||
_, diags := collectPlanOutput(changesCh, diagsCh)
|
||||
|
||||
// We don't care about the planned changes here, just the
|
||||
// diagnostics.
|
||||
|
||||
// The following will fail the test if there are any error
|
||||
// diagnostics.
|
||||
reportDiagnosticsForTest(t, diags)
|
||||
|
||||
// We also want to fail if there are just warnings, since the
|
||||
// configurations here are supposed to be totally problem-free.
|
||||
if len(diags) != 0 {
|
||||
// reportDiagnosticsForTest already showed the diagnostics in
|
||||
// the log
|
||||
t.FailNow()
|
||||
cycle := TestCycle{
|
||||
planInputs: tc.planInputVars,
|
||||
wantPlannedChanges: nil, // don't care about the planned changes in this test.
|
||||
wantPlannedDiags: nil, // should return no diagnostics.
|
||||
}
|
||||
testContext.Plan(t, ctx, nil, cycle)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -158,17 +131,8 @@ func TestPlan_invalid(t *testing.T) {
|
|||
// We've added this test before the implementation was ready.
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, name)
|
||||
|
||||
fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changesCh := make(chan stackplan.PlannedChange, 8)
|
||||
diagsCh := make(chan tfdiags.Diagnostic, 2)
|
||||
lock := depsfile.NewLocks()
|
||||
lock.SetProvider(
|
||||
addrs.NewDefaultProvider("testing"),
|
||||
|
|
@ -176,9 +140,15 @@ func TestPlan_invalid(t *testing.T) {
|
|||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
req := PlanRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
|
||||
fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testContext := TestContext{
|
||||
config: loadMainBundleConfigForTest(t, name),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
// We support both hashicorp/testing and
|
||||
// terraform.io/builtin/testing as providers. This lets us
|
||||
// test the provider aliasing feature. Both providers
|
||||
|
|
@ -190,31 +160,16 @@ func TestPlan_invalid(t *testing.T) {
|
|||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
InputValues: func() map[stackaddrs.InputVariable]ExternalInputValue {
|
||||
inputs := map[stackaddrs.InputVariable]ExternalInputValue{}
|
||||
for k, v := range tc.planInputVars {
|
||||
inputs[stackaddrs.InputVariable{Name: k}] = ExternalInputValue{
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
return inputs
|
||||
}(),
|
||||
ForcePlanTimestamp: &fakePlanTimestamp,
|
||||
}
|
||||
resp := PlanResponse{
|
||||
PlannedChanges: changesCh,
|
||||
Diagnostics: diagsCh,
|
||||
dependencyLocks: *lock,
|
||||
timestamp: &fakePlanTimestamp,
|
||||
}
|
||||
|
||||
go Plan(ctx, &req, &resp)
|
||||
_, gotDiags := collectPlanOutput(changesCh, diagsCh)
|
||||
wantDiags := tc.diags()
|
||||
sort.SliceStable(gotDiags, diagnosticSortFunc(gotDiags))
|
||||
|
||||
if diff := cmp.Diff(wantDiags.ForRPC(), gotDiags.ForRPC()); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
cycle := TestCycle{
|
||||
planInputs: tc.planInputVars,
|
||||
wantPlannedChanges: nil, // don't care about the planned changes in this test.
|
||||
wantPlannedDiags: tc.diags(),
|
||||
}
|
||||
testContext.Plan(t, ctx, nil, cycle)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,8 @@ package stackruntime
|
|||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
|
|
@ -338,9 +335,8 @@ func TestValidate_valid(t *testing.T) {
|
|||
// We've added this test before the implementation was ready.
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, name)
|
||||
|
||||
lock := depsfile.NewLocks()
|
||||
lock.SetProvider(
|
||||
addrs.NewDefaultProvider("testing"),
|
||||
|
|
@ -355,9 +351,9 @@ func TestValidate_valid(t *testing.T) {
|
|||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
|
||||
diags := Validate(ctx, &ValidateRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
testContext := TestContext{
|
||||
config: loadMainBundleConfigForTest(t, name),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
// We support both hashicorp/testing and
|
||||
// terraform.io/builtin/testing as providers. This lets us
|
||||
// test the provider aliasing feature. Both providers
|
||||
|
|
@ -374,20 +370,11 @@ func TestValidate_valid(t *testing.T) {
|
|||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
})
|
||||
|
||||
// The following will fail the test if there are any error
|
||||
// diagnostics.
|
||||
reportDiagnosticsForTest(t, diags)
|
||||
|
||||
// We also want to fail if there are just warnings, since the
|
||||
// configurations here are supposed to be totally problem-free.
|
||||
if len(diags) != 0 {
|
||||
// reportDiagnosticsForTest already showed the diagnostics in
|
||||
// the log
|
||||
t.FailNow()
|
||||
dependencyLocks: *lock,
|
||||
}
|
||||
|
||||
cycle := TestCycle{} // empty, as we expect no diagnostics
|
||||
testContext.Validate(t, ctx, cycle)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -399,9 +386,7 @@ func TestValidate_invalid(t *testing.T) {
|
|||
// We've added this test before the implementation was ready.
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, name)
|
||||
|
||||
lock := depsfile.NewLocks()
|
||||
lock.SetProvider(
|
||||
|
|
@ -410,10 +395,16 @@ func TestValidate_invalid(t *testing.T) {
|
|||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
lock.SetProvider(
|
||||
addrs.NewDefaultProvider("other"),
|
||||
providerreqs.MustParseVersion("0.0.0"),
|
||||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
|
||||
gotDiags := Validate(ctx, &ValidateRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
testContext := TestContext{
|
||||
config: loadMainBundleConfigForTest(t, name),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
// We support both hashicorp/testing and
|
||||
// terraform.io/builtin/testing as providers. This lets us
|
||||
// test the provider aliasing feature. Both providers
|
||||
|
|
@ -424,128 +415,81 @@ func TestValidate_invalid(t *testing.T) {
|
|||
addrs.NewBuiltInProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
// We also support an "other" provider out of the box to
|
||||
// test the provider aliasing feature.
|
||||
addrs.NewDefaultProvider("other"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
}).ForRPC()
|
||||
|
||||
// Let's make the returned diagnostics stable so that we can
|
||||
// compare them easily.
|
||||
sort.SliceStable(gotDiags, diagnosticSortFunc(gotDiags))
|
||||
|
||||
wantDiags := tc.diags().ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
dependencyLocks: *lock,
|
||||
}
|
||||
testContext.Validate(t, ctx, TestCycle{
|
||||
wantValidateDiags: tc.diags(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_embeddedStackSelfRef(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// One possible failure mode for this test is to deadlock itself if
|
||||
// our deadlock detection is incorrect, so we'll try to make it bail
|
||||
// if it runs too long.
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
ctx, span := tracer.Start(ctx, "TestValidate_embeddedStackSelfRef")
|
||||
defer span.End()
|
||||
|
||||
cfg := loadMainBundleConfigForTest(t, "validate-embedded-stack-selfref")
|
||||
|
||||
gotDiags := Validate(ctx, &ValidateRequest{
|
||||
Config: cfg,
|
||||
})
|
||||
|
||||
// We'll normalize the diagnostics to be of consistent underlying type
|
||||
// using ForRPC, so that we can easily diff them; we don't actually care
|
||||
// about which underlying implementation is in use.
|
||||
gotDiags = gotDiags.ForRPC()
|
||||
var wantDiags tfdiags.Diagnostics
|
||||
wantDiags = wantDiags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Self-dependent items in configuration",
|
||||
`The following items in your configuration form a circular dependency chain through their references:
|
||||
func TestValidate(t *testing.T) {
|
||||
tcs := map[string]struct {
|
||||
path string
|
||||
providers map[addrs.Provider]providers.Factory
|
||||
locks *depsfile.Locks
|
||||
wantDiags tfdiags.Diagnostics
|
||||
}{
|
||||
"embedded-stack-selfref": {
|
||||
path: "validate-embedded-stack-selfref",
|
||||
wantDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
return diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Self-dependent items in configuration",
|
||||
`The following items in your configuration form a circular dependency chain through their references:
|
||||
- stack.a collected outputs
|
||||
- stack.a.output.a value
|
||||
- stack.a inputs
|
||||
|
||||
Terraform uses references to decide a suitable order for performing operations, so configuration items may not refer to their own results either directly or indirectly.`,
|
||||
))
|
||||
wantDiags = wantDiags.ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_missing_provider_from_lockfile(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cfg := loadMainBundleConfigForTest(t, filepath.Join("with-single-input", "input-from-component"))
|
||||
lock := depsfile.NewLocks()
|
||||
|
||||
diags := Validate(ctx, &ValidateRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: map[addrs.Provider]providers.Factory{
|
||||
// We support both hashicorp/testing and
|
||||
// terraform.io/builtin/testing as providers. This lets us
|
||||
// test the provider aliasing feature. Both providers
|
||||
// support the same set of resources and data sources.
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
addrs.NewBuiltInProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
))
|
||||
}),
|
||||
},
|
||||
DependencyLocks: *lock,
|
||||
})
|
||||
|
||||
if len(diags) != 1 {
|
||||
t.Fatalf("expected exactly one diagnostic, got %d", len(diags))
|
||||
}
|
||||
|
||||
diag := diags[0]
|
||||
if diag.Severity() != tfdiags.Error {
|
||||
t.Fatalf("expected error diagnostic, got %s", diag.Severity())
|
||||
}
|
||||
|
||||
if diag.Description().Summary != "Provider missing from lockfile" {
|
||||
t.Fatalf("expected diagnostic summary 'Provider missing from lockfile', got %q", diag.Description().Summary)
|
||||
}
|
||||
|
||||
if diag.Description().Detail != "Provider \"registry.terraform.io/hashicorp/testing\" is not in the lockfile. This provider must be in the lockfile to be used in the configuration. Please run `tfstacks providers lock` to update the lockfile and run this operation again with an updated configuration." {
|
||||
t.Fatalf("expected diagnostic detail to be a specific message, got %q", diag.Description().Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_impliedProviderTypes(t *testing.T) {
|
||||
|
||||
tcs := []struct {
|
||||
directory string
|
||||
providers map[addrs.Provider]providers.Factory
|
||||
wantDiags func() tfdiags.Diagnostics
|
||||
}{
|
||||
{
|
||||
directory: "with-hashicorp-provider",
|
||||
"missing-provider-from-lockfile": {
|
||||
path: filepath.Join("with-single-input", "input-from-component"),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
locks: depsfile.NewLocks(), // deliberately empty
|
||||
wantDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
return diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Provider missing from lockfile",
|
||||
Detail: "Provider \"registry.terraform.io/hashicorp/testing\" is not in the lockfile. This provider must be in the lockfile to be used in the configuration. Please run `tfstacks providers lock` to update the lockfile and run this operation again with an updated configuration.",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "git::https://example.com/test.git//with-single-input/input-from-component/input-from-component.tfstack.hcl",
|
||||
Start: hcl.Pos{Line: 8, Column: 1, Byte: 98},
|
||||
End: hcl.Pos{Line: 8, Column: 29, Byte: 126},
|
||||
},
|
||||
})
|
||||
}),
|
||||
},
|
||||
"implied-provider-type-with-hashicorp-provider": {
|
||||
path: filepath.Join("legacy-module", "with-hashicorp-provider"),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
directory: "with-non-hashicorp-provider",
|
||||
"implied-provider-type-with-non-hashicorp-provider": {
|
||||
path: filepath.Join("legacy-module", "with-non-hashicorp-provider"),
|
||||
providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewProvider(addrs.DefaultProviderRegistryHost, "other", "testing"): func() (providers.Interface, error) {
|
||||
return stacks_testing_provider.NewProvider(t), nil
|
||||
},
|
||||
},
|
||||
wantDiags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
wantDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
return diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider configuration",
|
||||
Detail: "The provider configuration slot \"testing\" requires a configuration for provider \"registry.terraform.io/hashicorp/testing\", not for provider \"registry.terraform.io/other/testing\"." +
|
||||
|
|
@ -556,40 +500,37 @@ func TestValidate_impliedProviderTypes(t *testing.T) {
|
|||
End: hcl.Pos{Line: 21, Column: 39, Byte: 471},
|
||||
},
|
||||
})
|
||||
return diags
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.directory, func(t *testing.T) {
|
||||
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lock := depsfile.NewLocks()
|
||||
for addr := range tc.providers {
|
||||
lock.SetProvider(
|
||||
addr,
|
||||
providerreqs.MustParseVersion("0.0.0"),
|
||||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
ctx, span := tracer.Start(ctx, name)
|
||||
defer span.End()
|
||||
|
||||
locks := tc.locks
|
||||
if locks == nil {
|
||||
locks = depsfile.NewLocks()
|
||||
for addr := range tc.providers {
|
||||
locks.SetProvider(
|
||||
addr,
|
||||
providerreqs.MustParseVersion("0.0.0"),
|
||||
providerreqs.MustParseVersionConstraints("=0.0.0"),
|
||||
providerreqs.PreferredHashes([]providerreqs.Hash{}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := loadMainBundleConfigForTest(t, filepath.Join("legacy-module", tc.directory))
|
||||
gotDiags := Validate(ctx, &ValidateRequest{
|
||||
Config: cfg,
|
||||
ProviderFactories: tc.providers,
|
||||
DependencyLocks: *lock,
|
||||
}).ForRPC()
|
||||
|
||||
wantDiags := tfdiags.Diagnostics{}.ForRPC()
|
||||
if tc.wantDiags != nil {
|
||||
wantDiags = tc.wantDiags().ForRPC()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
testContext := TestContext{
|
||||
config: loadMainBundleConfigForTest(t, tc.path),
|
||||
providers: tc.providers,
|
||||
dependencyLocks: *locks,
|
||||
}
|
||||
testContext.Validate(t, ctx, TestCycle{
|
||||
wantValidateDiags: tc.wantDiags,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue