core: EvalContextMock code-based mocking of evaluation

EvaluateBlock and EvaluateExpr are often called multiple times in a single
operation, and our usual approach of mocking with static values is a poor
fit for those cases.

To accommodate more complex tests, we allow the test to optionally provide
a callback function to use instead of the static return values.

Since a pretty common need is to just evaluate the given block or
expression in a simple way, we also now have a helper method
installSimpleEval that installs reasonable implementations of these
two methods that can (assuming no other customization) just evaluate
constant expressions, which is sufficient for many tests.
This commit is contained in:
Martin Atkins 2018-05-10 17:30:26 -07:00
parent abf0284ca5
commit e6049d79b7

View File

@ -3,15 +3,16 @@ package terraform
import ( import (
"sync" "sync"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/lang"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/terraform/config/configschema" "github.com/hashicorp/hcl2/hcldec"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/lang"
"github.com/hashicorp/terraform/tfdiags"
) )
// MockEvalContext is a mock version of EvalContext that can be used // MockEvalContext is a mock version of EvalContext that can be used
@ -80,6 +81,12 @@ type MockEvalContext struct {
EvaluateBlockSchema *configschema.Block EvaluateBlockSchema *configschema.Block
EvaluateBlockSelf addrs.Referenceable EvaluateBlockSelf addrs.Referenceable
EvaluateBlockKey addrs.InstanceKey EvaluateBlockKey addrs.InstanceKey
EvaluateBlockResultFunc func(
body hcl.Body,
schema *configschema.Block,
self addrs.Referenceable,
key addrs.InstanceKey,
) (cty.Value, hcl.Body, tfdiags.Diagnostics) // overrides the other values below, if set
EvaluateBlockResult cty.Value EvaluateBlockResult cty.Value
EvaluateBlockExpandedBody hcl.Body EvaluateBlockExpandedBody hcl.Body
EvaluateBlockDiags tfdiags.Diagnostics EvaluateBlockDiags tfdiags.Diagnostics
@ -88,6 +95,11 @@ type MockEvalContext struct {
EvaluateExprExpr hcl.Expression EvaluateExprExpr hcl.Expression
EvaluateExprWantType cty.Type EvaluateExprWantType cty.Type
EvaluateExprSelf addrs.Referenceable EvaluateExprSelf addrs.Referenceable
EvaluateExprResultFunc func(
expr hcl.Expression,
wantType cty.Type,
self addrs.Referenceable,
) (cty.Value, tfdiags.Diagnostics) // overrides the other values below, if set
EvaluateExprResult cty.Value EvaluateExprResult cty.Value
EvaluateExprDiags tfdiags.Diagnostics EvaluateExprDiags tfdiags.Diagnostics
@ -222,6 +234,9 @@ func (c *MockEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Bloc
c.EvaluateBlockSchema = schema c.EvaluateBlockSchema = schema
c.EvaluateBlockSelf = self c.EvaluateBlockSelf = self
c.EvaluateBlockKey = key c.EvaluateBlockKey = key
if c.EvaluateBlockResultFunc != nil {
return c.EvaluateBlockResultFunc(body, schema, self, key)
}
return c.EvaluateBlockResult, c.EvaluateBlockExpandedBody, c.EvaluateBlockDiags return c.EvaluateBlockResult, c.EvaluateBlockExpandedBody, c.EvaluateBlockDiags
} }
@ -230,9 +245,65 @@ func (c *MockEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, s
c.EvaluateExprExpr = expr c.EvaluateExprExpr = expr
c.EvaluateExprWantType = wantType c.EvaluateExprWantType = wantType
c.EvaluateExprSelf = self c.EvaluateExprSelf = self
if c.EvaluateExprResultFunc != nil {
return c.EvaluateExprResultFunc(expr, wantType, self)
}
return c.EvaluateExprResult, c.EvaluateExprDiags return c.EvaluateExprResult, c.EvaluateExprDiags
} }
// installSimpleEval is a helper to install a simple mock implementation of
// both EvaluateBlock and EvaluateExpr into the receiver.
//
// These default implementations will either evaluate the given input against
// the scope in field EvaluationScopeScope or, if it is nil, with no eval
// context at all so that only constant values may be used.
//
// This function overwrites any existing functions installed in fields
// EvaluateBlockResultFunc and EvaluateExprResultFunc.
func (c *MockEvalContext) installSimpleEval() {
c.EvaluateBlockResultFunc = func(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
if scope := c.EvaluationScopeScope; scope != nil {
// Fully-functional codepath.
var diags tfdiags.Diagnostics
body, diags = scope.ExpandBlock(body, schema)
if diags.HasErrors() {
return cty.DynamicVal, body, diags
}
val, evalDiags := c.EvaluationScopeScope.EvalBlock(body, schema)
diags = diags.Append(evalDiags)
if evalDiags.HasErrors() {
return cty.DynamicVal, body, diags
}
return val, body, diags
}
// Fallback codepath supporting constant values only.
val, hclDiags := hcldec.Decode(body, schema.DecoderSpec(), nil)
return val, body, tfdiags.Diagnostics(nil).Append(hclDiags)
}
c.EvaluateExprResultFunc = func(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
if scope := c.EvaluationScopeScope; scope != nil {
// Fully-functional codepath.
return scope.EvalExpr(expr, wantType)
}
// Fallback codepath supporting constant values only.
var diags tfdiags.Diagnostics
val, hclDiags := expr.Value(nil)
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
return cty.DynamicVal, diags
}
var err error
val, err = convert.Convert(val, wantType)
if err != nil {
diags = diags.Append(err)
return cty.DynamicVal, diags
}
return val, diags
}
}
func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope { func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope {
c.EvaluationScopeCalled = true c.EvaluationScopeCalled = true
c.EvaluationScopeSelf = self c.EvaluationScopeSelf = self