repl: Make tests compile and execute without panics
There are still some errors left, because our expression evaluator now does more validation than before and so we'll need to (in a subsequent commit) actually use a fixture configuration for these tests so that the validations will allow the expressions to be validated.
This commit is contained in:
parent
85aa8769db
commit
5ff35c1a9a
|
@ -4,44 +4,44 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/providers"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSession_basicState(t *testing.T) {
|
func TestSession_basicState(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func (s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
Type: "test_instance",
|
||||||
Primary: &terraform.InstanceState{
|
Name: "foo",
|
||||||
ID: "bar",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Attributes: map[string]string{
|
&states.ResourceInstanceObjectSrc{
|
||||||
"id": "bar",
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{
|
||||||
},
|
Type: "aws",
|
||||||
},
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
},
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root", "module"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
Type: "test_instance",
|
||||||
Primary: &terraform.InstanceState{
|
Name: "foo",
|
||||||
ID: "bar",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("module", addrs.NoKey)),
|
||||||
Attributes: map[string]string{
|
&states.ResourceInstanceObjectSrc{
|
||||||
"id": "bar",
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{
|
||||||
},
|
Type: "aws",
|
||||||
},
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
},
|
)
|
||||||
},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("basic", func(t *testing.T) {
|
t.Run("basic", func(t *testing.T) {
|
||||||
testSession(t, testSessionTest{
|
testSession(t, testSessionTest{
|
||||||
|
@ -113,8 +113,7 @@ func TestSession_stateless(t *testing.T) {
|
||||||
Inputs: []testSessionInput{
|
Inputs: []testSessionInput{
|
||||||
{
|
{
|
||||||
Input: "exit",
|
Input: "exit",
|
||||||
Error: true,
|
Exit: true,
|
||||||
ErrorContains: ErrSessionExit.Error(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -159,7 +158,7 @@ func TestSession_stateless(t *testing.T) {
|
||||||
{
|
{
|
||||||
Input: "test_instance.bar.id",
|
Input: "test_instance.bar.id",
|
||||||
Error: true,
|
Error: true,
|
||||||
ErrorContains: "'test_instance.bar' not found",
|
ErrorContains: `resource "test_instance" "bar" has not been declared`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -167,34 +166,49 @@ func TestSession_stateless(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSession(t *testing.T, test testSessionTest) {
|
func testSession(t *testing.T, test testSessionTest) {
|
||||||
|
p := &terraform.MockProvider{}
|
||||||
|
p.GetSchemaReturn = &terraform.ProviderSchema{}
|
||||||
|
|
||||||
// Build the TF context
|
// Build the TF context
|
||||||
ctx, err := terraform.NewContext(&terraform.ContextOpts{
|
ctx, diags := terraform.NewContext(&terraform.ContextOpts{
|
||||||
State: test.State,
|
State: test.State,
|
||||||
Module: module.NewEmptyTree(),
|
ProviderResolver: providers.ResolverFixed(map[string]providers.Factory{
|
||||||
|
"aws": providers.FactoryFixed(p),
|
||||||
|
}),
|
||||||
|
Config: configs.NewEmptyConfig(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if diags.HasErrors() {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("failed to create context: %s", diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
scope, diags := ctx.Eval(addrs.RootModuleInstance)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatalf("failed to create scope: %s", diags.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the session
|
// Build the session
|
||||||
s := &Session{
|
s := &Session{
|
||||||
Interpolater: ctx.Interpolater(),
|
Scope: scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the inputs. We purposely don't use subtests here because
|
// Test the inputs. We purposely don't use subtests here because
|
||||||
// the inputs don't recognize subtests, but a sequence of stateful
|
// the inputs don't represent subtests, but a sequence of stateful
|
||||||
// operations.
|
// operations.
|
||||||
for _, input := range test.Inputs {
|
for _, input := range test.Inputs {
|
||||||
result, err := s.Handle(input.Input)
|
result, exit, diags := s.Handle(input.Input)
|
||||||
if (err != nil) != input.Error {
|
if exit != input.Exit {
|
||||||
t.Fatalf("%q: err: %s", input.Input, err)
|
t.Fatalf("incorrect 'exit' result %t; want %t", exit, input.Exit)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if (diags.HasErrors()) != input.Error {
|
||||||
|
t.Fatalf("%q: unexpected errors: %s", input.Input, diags.Err())
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
if input.ErrorContains != "" {
|
if input.ErrorContains != "" {
|
||||||
if !strings.Contains(err.Error(), input.ErrorContains) {
|
if !strings.Contains(diags.Err().Error(), input.ErrorContains) {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"%q: err should contain: %q\n\n%s",
|
"%q: diagnostics should contain: %q\n\n%s",
|
||||||
input.Input, input.ErrorContains, err)
|
input.Input, input.ErrorContains, diags.Err(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +230,7 @@ func testSession(t *testing.T, test testSessionTest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type testSessionTest struct {
|
type testSessionTest struct {
|
||||||
State *terraform.State // State to use
|
State *states.State // State to use
|
||||||
Module string // Module name in test-fixtures to load
|
Module string // Module name in test-fixtures to load
|
||||||
|
|
||||||
// Inputs are the list of test inputs that are run in order.
|
// Inputs are the list of test inputs that are run in order.
|
||||||
|
@ -230,5 +244,6 @@ type testSessionInput struct {
|
||||||
Output string // Exact output string to check
|
Output string // Exact output string to check
|
||||||
OutputContains string
|
OutputContains string
|
||||||
Error bool // Error is true if error is expected
|
Error bool // Error is true if error is expected
|
||||||
|
Exit bool // Exit is true if exiting is expected
|
||||||
ErrorContains string
|
ErrorContains string
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,9 +329,17 @@ func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.ModuleCallOutpu
|
||||||
// name is declared at all.
|
// name is declared at all.
|
||||||
moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
|
moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
|
||||||
if moduleConfig == nil {
|
if moduleConfig == nil {
|
||||||
// should never happen, since we can't be evaluating in a module
|
// this doesn't happen in normal circumstances due to our validation
|
||||||
// that wasn't mentioned in configuration.
|
// pass, but it can turn up in some unusual situations, like in the
|
||||||
panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
|
// "terraform console" repl where arbitrary expressions can be
|
||||||
|
// evaluated.
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: `Reference to undeclared module`,
|
||||||
|
Detail: fmt.Sprintf(`The configuration contains no %s.`, moduleAddr),
|
||||||
|
Subject: rng.ToHCL().Ptr(),
|
||||||
|
})
|
||||||
|
return cty.DynamicVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
config := moduleConfig.Module.Outputs[addr.Name]
|
config := moduleConfig.Module.Outputs[addr.Name]
|
||||||
|
@ -348,7 +356,7 @@ func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.ModuleCallOutpu
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: `Reference to undeclared output value`,
|
Summary: `Reference to undeclared output value`,
|
||||||
Detail: fmt.Sprintf(`An output value with the name %q has not been declared in %s.%s`, addr.Name, moduleAddr, suggestion),
|
Detail: fmt.Sprintf(`An output value with the name %q has not been declared in %s.%s`, addr.Name, moduleDisplayAddr(moduleAddr), suggestion),
|
||||||
Subject: rng.ToHCL().Ptr(),
|
Subject: rng.ToHCL().Ptr(),
|
||||||
})
|
})
|
||||||
return cty.DynamicVal, diags
|
return cty.DynamicVal, diags
|
||||||
|
@ -448,7 +456,7 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: `Reference to undeclared resource`,
|
Summary: `Reference to undeclared resource`,
|
||||||
Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Resource.Type, addr.Resource.Name, moduleAddr),
|
Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Resource.Type, addr.Resource.Name, moduleDisplayAddr(moduleAddr)),
|
||||||
Subject: rng.ToHCL().Ptr(),
|
Subject: rng.ToHCL().Ptr(),
|
||||||
})
|
})
|
||||||
return cty.DynamicVal, diags
|
return cty.DynamicVal, diags
|
||||||
|
@ -872,3 +880,17 @@ func nameSuggestion(given string, suggestions []string) string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// moduleDisplayAddr returns a string describing the given module instance
|
||||||
|
// address that is appropriate for returning to users in situations where the
|
||||||
|
// root module is possible. Specifically, it returns "the root module" if the
|
||||||
|
// root module instance is given, or a string representation of the module
|
||||||
|
// address otherwise.
|
||||||
|
func moduleDisplayAddr(addr addrs.ModuleInstance) string {
|
||||||
|
switch {
|
||||||
|
case addr.IsRoot():
|
||||||
|
return "the root module"
|
||||||
|
default:
|
||||||
|
return addr.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue