blob: cd713dd35a20abe0f609649c01b8932653130024 [file] [log] [blame]
package integrationtest
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/customdecode"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
)
// TestHCLDecDecodeToExpr tests both hcldec's support for types with custom
// expression decoding rules and the two expression capsule types implemented
// in ext/customdecode. This mechanism requires cooperation between those
// two components and cty in order to work, so it's helpful to exercise it in
// an integration test.
func TestHCLDecDecodeToExpr(t *testing.T) {
// Here we're going to capture the structure of two simple expressions
// without immediately evaluating them.
const input = `
a = foo
b = foo
c = "hello"
`
// We'll capture "a" directly as an expression, losing its evaluation
// context but retaining its structure. We'll capture "b" as a
// customdecode.ExpressionClosure, which gives us both the expression
// itself and the evaluation context it was originally evaluated in.
// We also have "c" here just to make sure we can still decode into a
// "normal" type via standard expression evaluation.
f, diags := hclsyntax.ParseConfig([]byte(input), "", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatalf("unexpected problems: %s", diags.Error())
}
spec := hcldec.ObjectSpec{
"a": &hcldec.AttrSpec{
Name: "a",
Type: customdecode.ExpressionType,
Required: true,
},
"b": &hcldec.AttrSpec{
Name: "b",
Type: customdecode.ExpressionClosureType,
Required: true,
},
"c": &hcldec.AttrSpec{
Name: "c",
Type: cty.String,
Required: true,
},
}
ctx := &hcl.EvalContext{
Variables: map[string]cty.Value{
"foo": cty.StringVal("foo value"),
},
}
objVal, diags := hcldec.Decode(f.Body, spec, ctx)
if diags.HasErrors() {
t.Fatalf("unexpected problems: %s", diags.Error())
}
aVal := objVal.GetAttr("a")
bVal := objVal.GetAttr("b")
cVal := objVal.GetAttr("c")
if got, want := aVal.Type(), customdecode.ExpressionType; !got.Equals(want) {
t.Fatalf("wrong type for 'a'\ngot: %#v\nwant: %#v", got, want)
}
if got, want := bVal.Type(), customdecode.ExpressionClosureType; !got.Equals(want) {
t.Fatalf("wrong type for 'b'\ngot: %#v\nwant: %#v", got, want)
}
if got, want := cVal.Type(), cty.String; !got.Equals(want) {
t.Fatalf("wrong type for 'c'\ngot: %#v\nwant: %#v", got, want)
}
gotAExpr := customdecode.ExpressionFromVal(aVal)
wantAExpr := &hclsyntax.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: "foo",
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 5, Byte: 5},
End: hcl.Pos{Line: 2, Column: 8, Byte: 8},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 5, Byte: 5},
End: hcl.Pos{Line: 2, Column: 8, Byte: 8},
},
}
if diff := cmp.Diff(wantAExpr, gotAExpr, cmpopts.IgnoreUnexported(hcl.TraverseRoot{})); diff != "" {
t.Errorf("wrong expression for a\n%s", diff)
}
bClosure := customdecode.ExpressionClosureFromVal(bVal)
gotBVal, diags := bClosure.Value()
wantBVal := cty.StringVal("foo value")
if diags.HasErrors() {
t.Fatalf("unexpected problems: %s", diags.Error())
}
if got, want := gotBVal, wantBVal; !want.RawEquals(got) {
t.Errorf("wrong 'b' result\ngot: %#v\nwant: %#v", got, want)
}
if got, want := cVal, cty.StringVal("hello"); !want.RawEquals(got) {
t.Errorf("wrong 'c'\ngot: %#v\nwant: %#v", got, want)
}
// One additional "trick" we can do with the expression closure is to
// evaluate the expression in a _derived_ EvalContext, rather than the
// captured one. This could be useful for introducing additional local
// variables/functions in a particular context, for example.
deriveCtx := bClosure.EvalContext.NewChild()
deriveCtx.Variables = map[string]cty.Value{
"foo": cty.StringVal("overridden foo value"),
}
gotBVal2, diags := bClosure.Expression.Value(deriveCtx)
wantBVal2 := cty.StringVal("overridden foo value")
if diags.HasErrors() {
t.Fatalf("unexpected problems: %s", diags.Error())
}
if got, want := gotBVal2, wantBVal2; !want.RawEquals(got) {
t.Errorf("wrong 'b' result with derived EvalContext\ngot: %#v\nwant: %#v", got, want)
}
}