| package hcldec |
| |
| import ( |
| "fmt" |
| "reflect" |
| "testing" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/hcl/v2/hclsyntax" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| func TestDecode(t *testing.T) { |
| tests := []struct { |
| config string |
| spec Spec |
| ctx *hcl.EvalContext |
| want cty.Value |
| diagCount int |
| }{ |
| { |
| ``, |
| &ObjectSpec{}, |
| nil, |
| cty.EmptyObjectVal, |
| 0, |
| }, |
| { |
| "a = 1\n", |
| &ObjectSpec{}, |
| nil, |
| cty.EmptyObjectVal, |
| 1, // attribute named "a" is not expected here |
| }, |
| { |
| "a = 1\n", |
| &ObjectSpec{ |
| "a": &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "a": cty.NumberIntVal(1), |
| }), |
| 0, |
| }, |
| { |
| "a = 1\n", |
| &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| nil, |
| cty.NumberIntVal(1), |
| 0, |
| }, |
| { |
| "a = 1\n", |
| &DefaultSpec{ |
| Primary: &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| Default: &LiteralSpec{ |
| Value: cty.NumberIntVal(10), |
| }, |
| }, |
| nil, |
| cty.NumberIntVal(1), |
| 0, |
| }, |
| { |
| "", |
| &DefaultSpec{ |
| Primary: &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| Default: &LiteralSpec{ |
| Value: cty.NumberIntVal(10), |
| }, |
| }, |
| nil, |
| cty.NumberIntVal(10), |
| 0, |
| }, |
| { |
| "a = 1\n", |
| ObjectSpec{ |
| "foo": &DefaultSpec{ |
| Primary: &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| Default: &LiteralSpec{ |
| Value: cty.NumberIntVal(10), |
| }, |
| }, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{"foo": cty.NumberIntVal(1)}), |
| 0, |
| }, |
| { |
| "a = \"1\"\n", |
| &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| nil, |
| cty.NumberIntVal(1), |
| 0, |
| }, |
| { |
| "a = true\n", |
| &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| }, |
| nil, |
| cty.UnknownVal(cty.Number), |
| 1, // incorrect type - number required. |
| }, |
| { |
| ``, |
| &AttrSpec{ |
| Name: "a", |
| Type: cty.Number, |
| Required: true, |
| }, |
| nil, |
| cty.NullVal(cty.Number), |
| 1, // attribute "a" is required |
| }, |
| |
| { |
| ` |
| b { |
| } |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.EmptyObjectVal, |
| 0, |
| }, |
| { |
| ` |
| b "baz" { |
| } |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: &BlockLabelSpec{ |
| Index: 0, |
| Name: "name", |
| }, |
| }, |
| nil, |
| cty.StringVal("baz"), |
| 0, |
| }, |
| { |
| ` |
| b "baz" {} |
| b "foo" {} |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: &BlockLabelSpec{ |
| Index: 0, |
| Name: "name", |
| }, |
| }, |
| nil, |
| cty.StringVal("baz"), |
| 1, // duplicate "b" block |
| }, |
| { |
| ` |
| b { |
| } |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: &BlockLabelSpec{ |
| Index: 0, |
| Name: "name", |
| }, |
| }, |
| nil, |
| cty.NullVal(cty.String), |
| 1, // missing name label |
| }, |
| { |
| ``, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.NullVal(cty.EmptyObject), |
| 0, |
| }, |
| { |
| "a {}\n", |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.NullVal(cty.EmptyObject), |
| 1, // blocks of type "a" are not supported |
| }, |
| { |
| ``, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| Required: true, |
| }, |
| nil, |
| cty.NullVal(cty.EmptyObject), |
| 1, // a block of type "b" is required |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| Required: true, |
| }, |
| nil, |
| cty.EmptyObjectVal, |
| 1, // only one "b" block is allowed |
| }, |
| { |
| ` |
| b { |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.MapValEmpty(cty.String), |
| 0, |
| }, |
| { |
| ` |
| b { |
| hello = "world" |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "hello": cty.StringVal("world"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b { |
| hello = true |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "hello": cty.StringVal("true"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b { |
| hello = true |
| goodbye = 5 |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "hello": cty.StringVal("true"), |
| "goodbye": cty.StringVal("5"), |
| }), |
| 0, |
| }, |
| { |
| ``, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.NullVal(cty.Map(cty.String)), |
| 0, |
| }, |
| { |
| ``, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| Required: true, |
| }, |
| nil, |
| cty.NullVal(cty.Map(cty.String)), |
| 1, // missing b block |
| }, |
| { |
| ` |
| b { |
| } |
| b { |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| }, |
| nil, |
| cty.MapValEmpty(cty.String), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b { |
| } |
| b { |
| } |
| `, |
| &BlockAttrsSpec{ |
| TypeName: "b", |
| ElementType: cty.String, |
| Required: true, |
| }, |
| nil, |
| cty.MapValEmpty(cty.String), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 0, |
| }, |
| { |
| ``, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ListValEmpty(cty.EmptyObject), |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| b "bar" {} |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), |
| 0, |
| }, |
| { |
| ` |
| b {} |
| b {} |
| b {} |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| MaxItems: 2, |
| }, |
| nil, |
| cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 1, // too many b blocks |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| MinItems: 10, |
| }, |
| nil, |
| cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 1, // insufficient b blocks |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = 1 |
| } |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.DynamicVal, |
| 1, // Unconsistent argument types in b blocks |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = "not a bool" |
| } |
| `, |
| &BlockListSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.ListVal([]cty.Value{ |
| cty.StringVal("true"), // type unification generalizes all the values to strings |
| cty.StringVal("not a bool"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockSetSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| MaxItems: 2, |
| }, |
| nil, |
| cty.SetVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "baz" {} |
| `, |
| &BlockSetSpec{ |
| TypeName: "b", |
| Nested: TupleSpec{ |
| &BlockLabelSpec{ |
| Name: "name", |
| Index: 1, |
| }, |
| &BlockLabelSpec{ |
| Name: "type", |
| Index: 0, |
| }, |
| }, |
| }, |
| nil, |
| cty.SetVal([]cty.Value{ |
| cty.TupleVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("foo")}), |
| cty.TupleVal([]cty.Value{cty.StringVal("baz"), cty.StringVal("bar")}), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = 1 |
| } |
| `, |
| &BlockSetSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.DynamicVal, |
| 1, // Unconsistent argument types in b blocks |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = "not a bool" |
| } |
| `, |
| &BlockSetSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.SetVal([]cty.Value{ |
| cty.StringVal("true"), // type unification generalizes all the values to strings |
| cty.StringVal("not a bool"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| b "bar" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "baz" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "foo": cty.MapVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| "bar": cty.MapVal(map[string]cty.Value{ |
| "baz": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "bar" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "foo": cty.MapVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| "bar": cty.MapVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "foo" "baz" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "foo": cty.MapVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| "baz": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapValEmpty(cty.EmptyObject), |
| 1, // too many labels |
| }, |
| { |
| ` |
| b "bar" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapValEmpty(cty.EmptyObject), |
| 1, // not enough labels |
| }, |
| { |
| ` |
| b "foo" {} |
| b "foo" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "foo" "bar" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{"foo": cty.MapVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "baz" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"type"}, |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.MapVal(map[string]cty.Value{ |
| "foo": cty.StringVal("bar"), |
| "bar": cty.StringVal("baz"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| `, |
| &BlockMapSpec{ |
| TypeName: "b", |
| LabelNames: []string{"type"}, |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.MapValEmpty(cty.String), |
| 1, // missing name |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 0, |
| }, |
| { |
| ``, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.EmptyTupleVal, |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| b "bar" {} |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), |
| 0, |
| }, |
| { |
| ` |
| b {} |
| b {} |
| b {} |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| MaxItems: 2, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 1, // too many b blocks |
| }, |
| { |
| ` |
| b {} |
| b {} |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: ObjectSpec{}, |
| MinItems: 10, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), |
| 1, // insufficient b blocks |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = 1 |
| } |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{ |
| cty.True, |
| cty.NumberIntVal(1), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b { |
| a = true |
| } |
| b { |
| a = "not a bool" |
| } |
| `, |
| &BlockTupleSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.TupleVal([]cty.Value{ |
| cty.True, |
| cty.StringVal("not a bool"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| b "bar" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "baz" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "foo": cty.ObjectVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| "bar": cty.ObjectVal(map[string]cty.Value{ |
| "baz": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "bar" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "foo": cty.ObjectVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| "bar": cty.ObjectVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "foo" "baz" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "foo": cty.ObjectVal(map[string]cty.Value{ |
| "bar": cty.EmptyObjectVal, |
| "baz": cty.EmptyObjectVal, |
| }), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.EmptyObjectVal, |
| 1, // too many labels |
| }, |
| { |
| ` |
| b "bar" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.EmptyObjectVal, |
| 1, // not enough labels |
| }, |
| { |
| ` |
| b "foo" {} |
| b "foo" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "foo" "bar" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"key1", "key2"}, |
| Nested: ObjectSpec{}, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{"foo": cty.ObjectVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}), |
| 1, // duplicate b block |
| }, |
| { |
| ` |
| b "foo" "bar" {} |
| b "bar" "baz" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"type"}, |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "foo": cty.StringVal("bar"), |
| "bar": cty.StringVal("baz"), |
| }), |
| 0, |
| }, |
| { |
| ` |
| b "foo" {} |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"type"}, |
| Nested: &BlockLabelSpec{ |
| Name: "name", |
| Index: 0, |
| }, |
| }, |
| nil, |
| cty.EmptyObjectVal, |
| 1, // missing name |
| }, |
| { |
| ` |
| b "foo" { |
| arg = true |
| } |
| b "bar" { |
| arg = 1 |
| } |
| `, |
| &BlockObjectSpec{ |
| TypeName: "b", |
| LabelNames: []string{"type"}, |
| Nested: &AttrSpec{ |
| Name: "arg", |
| Type: cty.DynamicPseudoType, |
| }, |
| }, |
| nil, |
| cty.ObjectVal(map[string]cty.Value{ |
| "foo": cty.True, |
| "bar": cty.NumberIntVal(1), |
| }), |
| 0, |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%02d-%s", i, test.config), func(t *testing.T) { |
| file, parseDiags := hclsyntax.ParseConfig([]byte(test.config), "", hcl.Pos{Line: 1, Column: 1, Byte: 0}) |
| body := file.Body |
| got, valDiags := Decode(body, test.spec, test.ctx) |
| |
| var diags hcl.Diagnostics |
| diags = append(diags, parseDiags...) |
| diags = append(diags, valDiags...) |
| |
| if len(diags) != test.diagCount { |
| t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) |
| for _, diag := range diags { |
| t.Logf(" - %s", diag.Error()) |
| } |
| } |
| |
| if !got.RawEquals(test.want) { |
| t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want) |
| } |
| }) |
| } |
| } |
| |
| func TestSourceRange(t *testing.T) { |
| tests := []struct { |
| config string |
| spec Spec |
| want hcl.Range |
| }{ |
| { |
| "a = 1\n", |
| &AttrSpec{ |
| Name: "a", |
| }, |
| hcl.Range{ |
| Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, |
| End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, |
| }, |
| }, |
| { |
| ` |
| b { |
| a = 1 |
| } |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: &AttrSpec{ |
| Name: "a", |
| }, |
| }, |
| hcl.Range{ |
| Start: hcl.Pos{Line: 3, Column: 7, Byte: 11}, |
| End: hcl.Pos{Line: 3, Column: 8, Byte: 12}, |
| }, |
| }, |
| { |
| ` |
| b { |
| c { |
| a = 1 |
| } |
| } |
| `, |
| &BlockSpec{ |
| TypeName: "b", |
| Nested: &BlockSpec{ |
| TypeName: "c", |
| Nested: &AttrSpec{ |
| Name: "a", |
| }, |
| }, |
| }, |
| hcl.Range{ |
| Start: hcl.Pos{Line: 4, Column: 9, Byte: 19}, |
| End: hcl.Pos{Line: 4, Column: 10, Byte: 20}, |
| }, |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%02d-%s", i, test.config), func(t *testing.T) { |
| file, diags := hclsyntax.ParseConfig([]byte(test.config), "", hcl.Pos{Line: 1, Column: 1, Byte: 0}) |
| if len(diags) != 0 { |
| t.Errorf("wrong number of diagnostics %d; want %d", len(diags), 0) |
| for _, diag := range diags { |
| t.Logf(" - %s", diag.Error()) |
| } |
| } |
| body := file.Body |
| |
| got := SourceRange(body, test.spec) |
| |
| if !reflect.DeepEqual(got, test.want) { |
| t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want) |
| } |
| }) |
| } |
| |
| } |