2017-05-25 15:14:43 +00:00
|
|
|
package zclsyntax
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
2017-05-28 00:35:44 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
|
|
"github.com/zclconf/go-cty/cty/function/stdlib"
|
2017-05-28 00:33:09 +00:00
|
|
|
"github.com/zclconf/go-zcl/zcl"
|
2017-05-25 15:14:43 +00:00
|
|
|
)
|
|
|
|
|
2017-06-01 02:27:16 +00:00
|
|
|
func TestExpressionParseAndValue(t *testing.T) {
|
|
|
|
// This is a combo test that exercises both the parser and the Value
|
|
|
|
// method, with the focus on the latter but indirectly testing the former.
|
|
|
|
tests := []struct {
|
|
|
|
input string
|
|
|
|
ctx *zcl.EvalContext
|
|
|
|
want cty.Value
|
|
|
|
diagCount int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
`1`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(1)`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-12 01:55:27 +00:00
|
|
|
{
|
|
|
|
`(2+3)`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(5),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-12 14:09:24 +00:00
|
|
|
{
|
|
|
|
`(2+unk)`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"unk": cty.UnknownVal(cty.Number),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(2+unk)`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"unk": cty.DynamicVal,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(unk+unk)`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"unk": cty.DynamicVal,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-12 01:55:27 +00:00
|
|
|
{
|
|
|
|
`(2+true)`,
|
|
|
|
nil,
|
2017-06-12 14:09:24 +00:00
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
1, // unsuitable type for right operand
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(false+true)`,
|
|
|
|
nil,
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
2, // unsuitable type for each operand
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(5 == 5)`,
|
|
|
|
nil,
|
|
|
|
cty.True,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(5 == 4)`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(1 == true)`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`("true" == true)`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(true == "true")`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(true != "true")`,
|
|
|
|
nil,
|
|
|
|
cty.True,
|
|
|
|
0,
|
2017-06-12 01:55:27 +00:00
|
|
|
},
|
2017-06-12 14:13:17 +00:00
|
|
|
{
|
|
|
|
`(- 2)`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(-2),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`(! true)`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
2017-06-02 14:26:45 +00:00
|
|
|
{
|
|
|
|
`(
|
|
|
|
1
|
|
|
|
)`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-01 02:27:16 +00:00
|
|
|
{
|
|
|
|
`(1`,
|
|
|
|
nil,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
1, // Unbalanced parentheses
|
|
|
|
},
|
2017-06-01 13:56:45 +00:00
|
|
|
{
|
|
|
|
`true`,
|
|
|
|
nil,
|
|
|
|
cty.True,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`false`,
|
|
|
|
nil,
|
|
|
|
cty.False,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`null`,
|
|
|
|
nil,
|
|
|
|
cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`true true`,
|
|
|
|
nil,
|
|
|
|
cty.True,
|
|
|
|
1, // extra characters after expression
|
|
|
|
},
|
2017-06-01 15:01:12 +00:00
|
|
|
{
|
|
|
|
`"hello"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"hello\nworld"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello\nworld"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"unclosed`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("unclosed"),
|
|
|
|
1, // Unterminated template string
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"hello ${"world"}"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello world"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"hello ${12.5}"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello 12.5"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"silly ${"${"nesting"}"}"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("silly nesting"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"silly ${"${true}"}"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("silly true"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"hello $${escaped}"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello ${escaped}"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`"hello $$nonescape"`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello $$nonescape"),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-02 14:55:03 +00:00
|
|
|
{
|
|
|
|
`upper("foo")`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: map[string]function.Function{
|
|
|
|
"upper": stdlib.UpperFunc,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("FOO"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`
|
|
|
|
upper(
|
|
|
|
"foo"
|
|
|
|
)
|
|
|
|
`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: map[string]function.Function{
|
|
|
|
"upper": stdlib.UpperFunc,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("FOO"),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-04 21:22:51 +00:00
|
|
|
{
|
|
|
|
`[]`,
|
|
|
|
nil,
|
|
|
|
cty.EmptyTupleVal,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[1]`,
|
|
|
|
nil,
|
|
|
|
cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[1,]`,
|
|
|
|
nil,
|
|
|
|
cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[1,true]`,
|
|
|
|
nil,
|
|
|
|
cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[
|
|
|
|
1,
|
|
|
|
true
|
|
|
|
]`,
|
|
|
|
nil,
|
|
|
|
cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-04 23:14:02 +00:00
|
|
|
{
|
|
|
|
`{}`,
|
|
|
|
nil,
|
|
|
|
cty.EmptyObjectVal,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{"hello": "world"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{"hello" = "world"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{hello = "world"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{hello: "world"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{"hello" = "world", "goodbye" = "cruel world"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
"goodbye": cty.StringVal("cruel world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{
|
|
|
|
"hello" = "world"
|
|
|
|
}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{
|
|
|
|
"hello" = "world"
|
|
|
|
"goodbye" = "cruel world"
|
|
|
|
}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
"goodbye": cty.StringVal("cruel world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{
|
|
|
|
"hello" = "world",
|
|
|
|
"goodbye" = "cruel world"
|
|
|
|
}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
"goodbye": cty.StringVal("cruel world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{
|
|
|
|
"hello" = "world",
|
|
|
|
"goodbye" = "cruel world",
|
|
|
|
}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
"goodbye": cty.StringVal("cruel world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-05 14:41:02 +00:00
|
|
|
|
2017-06-14 15:56:28 +00:00
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: k => v if k == "hello"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in ["world"]: k => v if k == 0}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"0": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for v in ["world"]: v => v}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"world": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: k => v if k == "foo"}`,
|
|
|
|
nil,
|
|
|
|
cty.EmptyObjectVal,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: 5 => v}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"5": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: [] => v}`,
|
|
|
|
nil,
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // key expression has the wrong type
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: k => k if k == "hello"}`,
|
|
|
|
nil,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`{for k, v in {hello: "world"}: k => foo}`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.StringVal("foo"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("foo"),
|
|
|
|
}),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
|
2017-06-05 14:41:02 +00:00
|
|
|
{
|
|
|
|
`["hello"][0]`,
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[][0]`,
|
|
|
|
nil,
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // invalid index
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`["hello"][negate(0)]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: map[string]function.Function{
|
|
|
|
"negate": stdlib.NegateFunc,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`[][negate(0)]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: map[string]function.Function{
|
|
|
|
"negate": stdlib.NegateFunc,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // invalid index
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`["hello"]["0"]`, // key gets converted to number
|
|
|
|
nil,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
2017-06-05 15:02:54 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
`foo`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`bar`,
|
|
|
|
&zcl.EvalContext{},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // variables not allowed here
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`foo.bar`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // foo does not have attributes
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`foo.baz`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"baz": cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`foo["baz"]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"baz": cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`foo[true]`, // key is converted to string
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"true": cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`foo[0].baz`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"foo": cty.ListVal([]cty.Value{
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"baz": cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`unk["baz"]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"unk": cty.UnknownVal(cty.String),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // value does not have indices (because we know it's a string)
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`unk["boop"]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"unk": cty.UnknownVal(cty.Map(cty.String)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.String), // we know it's a map of string
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`dyn["boop"]`,
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Variables: map[string]cty.Value{
|
|
|
|
"dyn": cty.DynamicVal,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cty.DynamicVal, // don't know what it is yet
|
|
|
|
0,
|
|
|
|
},
|
2017-06-01 02:27:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.input, func(t *testing.T) {
|
|
|
|
expr, parseDiags := ParseExpression([]byte(test.input), "", zcl.Pos{Line: 1, Column: 1, Byte: 0})
|
|
|
|
|
|
|
|
got, valDiags := expr.Value(test.ctx)
|
|
|
|
|
|
|
|
diagCount := len(parseDiags) + len(valDiags)
|
|
|
|
|
|
|
|
if diagCount != test.diagCount {
|
|
|
|
t.Errorf("wrong number of diagnostics %d; want %d", diagCount, test.diagCount)
|
|
|
|
for _, diag := range parseDiags {
|
|
|
|
t.Logf(" - %s", diag.Error())
|
|
|
|
}
|
|
|
|
for _, diag := range valDiags {
|
|
|
|
t.Logf(" - %s", diag.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !got.RawEquals(test.want) {
|
|
|
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-05-25 15:14:43 +00:00
|
|
|
func TestFunctionCallExprValue(t *testing.T) {
|
|
|
|
funcs := map[string]function.Function{
|
|
|
|
"length": stdlib.StrlenFunc,
|
|
|
|
"jsondecode": stdlib.JSONDecodeFunc,
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := map[string]struct {
|
|
|
|
expr *FunctionCallExpr
|
|
|
|
ctx *zcl.EvalContext
|
|
|
|
want cty.Value
|
|
|
|
diagCount int
|
|
|
|
}{
|
|
|
|
"valid call with no conversions": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.NumberIntVal(5),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"valid call with arg conversion": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.BoolVal(true),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.NumberIntVal(4), // length of string "true"
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"valid call with unknown arg": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.UnknownVal(cty.String),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"valid call with unknown arg needing conversion": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.UnknownVal(cty.Bool),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"valid call with dynamic arg": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.DynamicVal,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.UnknownVal(cty.Number),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"invalid arg type": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "length",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.ListVal([]cty.Value{cty.StringVal("hello")}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1,
|
|
|
|
},
|
|
|
|
"function with dynamic return type": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "jsondecode",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.StringVal(`"hello"`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"function with dynamic return type unknown arg": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "jsondecode",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.UnknownVal(cty.String),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.DynamicVal, // type depends on arg value
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"error in function": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "jsondecode",
|
|
|
|
Args: []Expression{
|
|
|
|
&LiteralValueExpr{
|
|
|
|
Val: cty.StringVal("invalid-json"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1, // JSON parse error
|
|
|
|
},
|
|
|
|
"unknown function": {
|
|
|
|
&FunctionCallExpr{
|
|
|
|
Name: "lenth",
|
|
|
|
Args: []Expression{},
|
|
|
|
},
|
|
|
|
&zcl.EvalContext{
|
|
|
|
Functions: funcs,
|
|
|
|
},
|
|
|
|
cty.DynamicVal,
|
|
|
|
1,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, test := range tests {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
got, diags := test.expr.Value(test.ctx)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|