| 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) |
| } |
| } |