132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|