2017-09-11 23:40:37 +00:00
package hclsyntax
2017-05-25 15:14:43 +00:00
import (
"testing"
2019-09-09 23:08:19 +00:00
"github.com/hashicorp/hcl/v2"
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-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
2017-09-11 23:40:37 +00:00
ctx * hcl . EvalContext
2017-06-01 02:27:16 +00:00
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 ,
} ,
2018-03-03 15:56:54 +00:00
{
` 2*5+1 ` ,
nil ,
cty . NumberIntVal ( 11 ) ,
0 ,
} ,
{
` 9%8 ` ,
nil ,
cty . NumberIntVal ( 1 ) ,
0 ,
} ,
2017-06-12 14:09:24 +00:00
{
` (2+unk) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-12 14:09:24 +00:00
Variables : map [ string ] cty . Value {
"unk" : cty . UnknownVal ( cty . Number ) ,
} ,
} ,
cty . UnknownVal ( cty . Number ) ,
0 ,
} ,
{
` (2+unk) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-12 14:09:24 +00:00
Variables : map [ string ] cty . Value {
"unk" : cty . DynamicVal ,
} ,
} ,
cty . UnknownVal ( cty . Number ) ,
0 ,
} ,
{
` (unk+unk) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-12 14:09:24 +00:00
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 ,
} ,
2018-03-03 19:24:31 +00:00
{
"\"hello `backtick` world\"" ,
nil ,
cty . StringVal ( "hello `backtick` world" ) ,
0 ,
} ,
2017-06-01 15:01:12 +00:00
{
` "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 ,
} ,
2019-05-03 21:00:54 +00:00
{
` "$" ` ,
nil ,
cty . StringVal ( "$" ) ,
0 ,
} ,
{
` "%" ` ,
nil ,
cty . StringVal ( "%" ) ,
0 ,
} ,
2017-06-02 14:55:03 +00:00
{
` upper("foo") ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-02 14:55:03 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . StringVal ( "FOO" ) ,
0 ,
} ,
{
`
upper (
"foo"
)
` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-02 14:55:03 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . StringVal ( "FOO" ) ,
0 ,
} ,
2017-06-15 15:18:00 +00:00
{
` upper(["foo"]...) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-15 15:18:00 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . StringVal ( "FOO" ) ,
0 ,
} ,
{
` upper("foo", []...) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-15 15:18:00 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . StringVal ( "FOO" ) ,
0 ,
} ,
{
` upper("foo", "bar") ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-15 15:18:00 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . DynamicVal ,
1 , // too many function arguments
} ,
{
` upper(["foo", "bar"]...) ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-15 15:18:00 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . DynamicVal ,
1 , // too many function arguments
} ,
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 ,
} ,
2018-02-26 16:38:35 +00:00
{
` { true: "yes"} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"true" : cty . StringVal ( "yes" ) ,
} ) ,
0 ,
} ,
{
` { false: "yes"} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"false" : cty . StringVal ( "yes" ) ,
} ) ,
0 ,
} ,
{
` { null: "yes"} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"null" : cty . StringVal ( "yes" ) ,
} ) ,
0 ,
} ,
{
` { 15: "yes"} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"15" : cty . StringVal ( "yes" ) ,
} ) ,
0 ,
} ,
2018-12-11 23:33:14 +00:00
{
` { []: "yes"} ` ,
nil ,
cty . DynamicVal ,
1 , // Incorrect key type; Can't use this value as a key: string required
} ,
hcl/hclsyntax: Better handling of naked attribute keys with dots
To make things read better in the normal case, we treat naked identifiers
in the place of object keys as literal strings containing the identifier
text rather than as references. However, this had a couple sub-optimal
implications:
- If a user would try to create a key containing a period, the evaluator
would see that it wasn't a valid keyword and try to resolve it as a
normal scope traversal, causing a confusing error that didn't align
with the user's intent.
- In the rarer case where the attempted key contains a period followed by
a digit, the parser would trip over what seems to be an unexpected
identifier following the colon and produce, again, a confusing error
that doesn't align with what the user intended.
To address the first of these problems, it is now invalid to use a naked
traversal with more than one step as an object key, which allows us to
produce a targeted error message that directs the user to either put the
expression in parentheses to force interpretation as a scope traversal
or in quotes to force interpretation as a literal.
The second problem can't be addressed exactly due to it being a parser
problem, but we improve the situation slightly here by adding an extra
hint to the parse error message in this case so that a user making this
mistake might understand better how the error relates to what they were
trying to express.
2018-12-12 00:48:31 +00:00
{
` { "centos_7.2_ap-south-1" = "ami-abc123"} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"centos_7.2_ap-south-1" : cty . StringVal ( "ami-abc123" ) ,
} ) ,
0 ,
} ,
{
// This is syntactically valid (it's similar to foo["bar"])
// but is rejected during evaluation to force the user to be explicit
// about which of the following interpretations they mean:
// -{(foo.bar) = "baz"}
// -{"foo.bar" = "baz"}
// naked traversals as keys are allowed when analyzing an expression
// statically so an application can define object-syntax-based
// language constructs with looser requirements, but we reject
// this during normal expression evaluation.
` { foo.bar = "ami-abc123"} ` ,
nil ,
cty . DynamicVal ,
1 , // Ambiguous attribute key; If this expression is intended to be a reference, wrap it in parentheses. If it's instead intended as a literal name containing periods, wrap it in quotes to create a string literal.
} ,
{
// This is a weird variant of the above where a period is followed
// by a digit, causing the parser to interpret it as an index
// operator using the legacy HIL/Terraform index syntax.
// This one _does_ fail parsing, causing it to be subject to
// parser recovery behavior.
` { centos_7.2_ap-south-1 = "ami-abc123"} ` ,
nil ,
cty . EmptyObjectVal , // (due to parser recovery behavior)
1 , // Missing key/value separator; Expected an equals sign ("=") to mark the beginning of the attribute value. If you intended to given an attribute name containing periods or spaces, write the name in quotes to create a string literal.
} ,
2019-09-11 22:31:32 +00:00
{
` { var.greeting = "world"} ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . ObjectVal ( map [ string ] cty . Value {
"greeting" : cty . StringVal ( "hello" ) ,
} ) ,
} ,
} ,
cty . DynamicVal ,
1 , // Ambiguous attribute key
} ,
{
` { (var.greeting) = "world"} ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . ObjectVal ( map [ string ] cty . Value {
"greeting" : cty . StringVal ( "hello" ) ,
} ) ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "world" ) ,
} ) ,
0 ,
} ,
{
` { "$ { var.greeting}" = "world"} ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . ObjectVal ( map [ string ] cty . Value {
"greeting" : cty . StringVal ( "hello" ) ,
} ) ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "world" ) ,
} ) ,
0 ,
} ,
2017-06-04 23:14:02 +00:00
{
` { "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
2018-12-12 01:16:06 +00:00
{
"{\n for k, v in {hello: \"world\"}:\nk => v\n}" ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "world" ) ,
} ) ,
0 ,
} ,
2019-02-26 23:41:59 +00:00
{
// This one is different than the previous because the extra level of
// object constructor causes the inner for expression to begin parsing
// in newline-sensitive mode, which it must then properly disable in
// order to peek the "for" keyword.
"{\n a = {\n for k, v in {hello: \"world\"}:\nk => v\n }\n}" ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "world" ) ,
} ) ,
} ) ,
0 ,
} ,
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 ,
} ,
2017-06-16 14:28:29 +00:00
{
` { for k, v in { hello: "world"}: upper(k) => upper(v) if k == "hello"} ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-16 14:28:29 +00:00
Functions : map [ string ] function . Function {
"upper" : stdlib . UpperFunc ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"HELLO" : cty . StringVal ( "WORLD" ) ,
} ) ,
0 ,
} ,
2017-06-14 15:56:28 +00:00
{
` { 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} ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-14 15:56:28 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . StringVal ( "foo" ) ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "foo" ) ,
} ) ,
0 ,
} ,
2017-06-18 15:14:36 +00:00
{
` [for k, v in { hello: "world"}: "$ { k}=$ { v}"] ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "hello=world" ) ,
} ) ,
0 ,
} ,
2017-06-18 15:28:20 +00:00
{
` [for k, v in { hello: "world"}: k => v] ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"hello" : cty . StringVal ( "world" ) ,
} ) ,
1 , // can't have a key expr when producing a tuple
} ,
{
` { for v in { hello: "world"}: v} ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "world" ) ,
} ) ,
1 , // must have a key expr when producing a map
} ,
2017-06-18 15:45:45 +00:00
{
` { for i, v in ["a", "b", "c", "b", "d"]: v => i...} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 0 ) ,
} ) ,
"b" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 1 ) ,
cty . NumberIntVal ( 3 ) ,
} ) ,
"c" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 2 ) ,
} ) ,
"d" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 4 ) ,
} ) ,
} ) ,
0 ,
} ,
2017-06-18 16:13:48 +00:00
{
` { for i, v in ["a", "b", "c", "b", "d"]: v => i... if i <= 2} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 0 ) ,
} ) ,
"b" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 1 ) ,
} ) ,
"c" : cty . TupleVal ( [ ] cty . Value {
cty . NumberIntVal ( 2 ) ,
} ) ,
} ) ,
0 ,
} ,
2017-06-18 15:49:52 +00:00
{
` { for i, v in ["a", "b", "c", "b", "d"]: v => i} ` ,
nil ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NumberIntVal ( 0 ) ,
"b" : cty . NumberIntVal ( 1 ) ,
"c" : cty . NumberIntVal ( 2 ) ,
"d" : cty . NumberIntVal ( 4 ) ,
} ) ,
1 , // duplicate key "b"
} ,
2017-06-18 15:28:20 +00:00
{
` [for v in { hello: "world"}: v...] ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "world" ) ,
} ) ,
1 , // can't use grouping when producing a tuple
} ,
{
` [for v in "hello": v] ` ,
nil ,
cty . DynamicVal ,
1 , // can't iterate over a string
} ,
{
` [for v in null: v] ` ,
nil ,
cty . DynamicVal ,
1 , // can't iterate over a null value
} ,
{
` [for v in unk: v] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 15:28:20 +00:00
Variables : map [ string ] cty . Value {
"unk" : cty . UnknownVal ( cty . List ( cty . String ) ) ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
{
` [for v in unk: v] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 15:28:20 +00:00
Variables : map [ string ] cty . Value {
"unk" : cty . DynamicVal ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
{
` [for v in unk: v] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 15:28:20 +00:00
Variables : map [ string ] cty . Value {
"unk" : cty . UnknownVal ( cty . String ) ,
} ,
} ,
cty . DynamicVal ,
1 , // can't iterate over a string (even if it's unknown)
} ,
2017-06-18 16:13:48 +00:00
{
2017-06-18 16:36:06 +00:00
` [for v in ["a", "b"]: v if unkbool] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 16:13:48 +00:00
Variables : map [ string ] cty . Value {
2017-06-18 16:36:06 +00:00
"unkbool" : cty . UnknownVal ( cty . Bool ) ,
2017-06-18 16:13:48 +00:00
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
{
2017-06-18 16:36:06 +00:00
` [for v in ["a", "b"]: v if nullbool] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 16:13:48 +00:00
Variables : map [ string ] cty . Value {
2017-06-18 16:36:06 +00:00
"nullbool" : cty . NullVal ( cty . Bool ) ,
2017-06-18 16:13:48 +00:00
} ,
} ,
cty . DynamicVal ,
2017-06-18 16:36:06 +00:00
1 , // value of if clause must not be null
2017-06-18 16:13:48 +00:00
} ,
{
2017-06-18 16:36:06 +00:00
` [for v in ["a", "b"]: v if dyn] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 16:13:48 +00:00
Variables : map [ string ] cty . Value {
2017-06-18 16:36:06 +00:00
"dyn" : cty . DynamicVal ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
{
` [for v in ["a", "b"]: v if unknum] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 16:36:06 +00:00
Variables : map [ string ] cty . Value {
"unknum" : cty . UnknownVal ( cty . List ( cty . Number ) ) ,
} ,
} ,
cty . DynamicVal ,
1 , // if expression must be bool
} ,
{
` [for i, v in ["a", "b"]: v if i + i] ` ,
nil ,
cty . DynamicVal ,
1 , // if expression must be bool
} ,
{
` [for v in ["a", "b"]: unkstr] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-18 16:36:06 +00:00
Variables : map [ string ] cty . Value {
"unkstr" : cty . UnknownVal ( cty . String ) ,
2017-06-18 16:13:48 +00:00
} ,
} ,
cty . TupleVal ( [ ] cty . Value {
cty . UnknownVal ( cty . String ) ,
cty . UnknownVal ( cty . String ) ,
} ) ,
0 ,
} ,
2017-06-14 15:56:28 +00:00
2017-06-16 15:33:35 +00:00
{
` [ { name: "Steve"}, { name: "Ermintrude"}].*.name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "Steve" ) ,
cty . StringVal ( "Ermintrude" ) ,
} ) ,
0 ,
} ,
{
` { name: "Steve"}.*.name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "Steve" ) ,
} ) ,
0 ,
} ,
2018-12-13 01:41:28 +00:00
{
` { name: "Steve"}[*].name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "Steve" ) ,
} ) ,
0 ,
} ,
2018-05-23 23:40:24 +00:00
{
` set.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"set" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . StringVal ( "Steve" ) ,
} ) ,
} ) ,
} ,
} ,
2018-12-06 00:59:33 +00:00
cty . ListVal ( [ ] cty . Value {
2018-05-23 23:40:24 +00:00
cty . StringVal ( "Steve" ) ,
} ) ,
0 ,
} ,
2018-12-06 00:59:33 +00:00
{
` unkstr.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"unkstr" : cty . UnknownVal ( cty . String ) ,
} ,
} ,
cty . UnknownVal ( cty . Tuple ( [ ] cty . Type { cty . DynamicPseudoType } ) ) ,
1 , // a string has no attribute "name"
} ,
2018-12-08 00:37:05 +00:00
{
` dyn.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"dyn" : cty . DynamicVal ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
2018-12-06 00:59:33 +00:00
{
` unkobj.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"unkobj" : cty . UnknownVal ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
} ,
} ,
cty . TupleVal ( [ ] cty . Value {
cty . UnknownVal ( cty . String ) ,
} ) ,
0 ,
} ,
{
` unklistobj.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"unklistobj" : cty . UnknownVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ) ,
} ,
} ,
cty . UnknownVal ( cty . List ( cty . String ) ) ,
0 ,
} ,
{
` unktupleobj.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"unktupleobj" : cty . UnknownVal (
cty . Tuple ( [ ] cty . Type {
cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ,
cty . Object ( map [ string ] cty . Type {
"name" : cty . Bool ,
} ) ,
} ) ,
) ,
} ,
} ,
cty . UnknownVal ( cty . Tuple ( [ ] cty . Type { cty . String , cty . Bool } ) ) ,
0 ,
} ,
2019-01-03 16:44:27 +00:00
{
` nullobj.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nullobj" : cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
} ,
} ,
cty . TupleVal ( [ ] cty . Value { } ) ,
0 ,
} ,
{
` nulllist.*.name ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nulllist" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ) ,
} ,
} ,
cty . DynamicVal ,
1 , // splat cannot be applied to null sequence
} ,
2017-06-16 15:33:35 +00:00
{
` ["hello", "goodbye"].* ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "hello" ) ,
cty . StringVal ( "goodbye" ) ,
} ) ,
0 ,
} ,
{
` "hello".* ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "hello" ) ,
} ) ,
0 ,
} ,
2017-06-24 16:39:16 +00:00
{
` [["hello"], ["world", "unused"]].*.0 ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "hello" ) ,
cty . StringVal ( "world" ) ,
} ) ,
0 ,
} ,
{
` [[ { name:"foo"}], [ { name:"bar"}, { name:"baz"}]].*.0.name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "foo" ) ,
cty . StringVal ( "bar" ) ,
} ) ,
0 ,
} ,
2018-03-03 17:02:29 +00:00
{
` [[[ { name:"foo"}]], [[ { name:"bar"}], [ { name:"baz"}]]].*.0.0.name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . DynamicVal ,
cty . DynamicVal ,
} ) ,
1 , // can't chain legacy index syntax together, like .0.0 (because 0.0 parses as a single number)
} ,
2017-06-16 15:33:35 +00:00
{
// For an "attribute-only" splat, an index operator applies to
// the splat result as a whole, rather than being incorporated
// into the splat traversal itself.
` [ { name: "Steve"}, { name: "Ermintrude"}].*.name[0] ` ,
nil ,
cty . StringVal ( "Steve" ) ,
0 ,
} ,
2018-12-13 01:41:28 +00:00
{
// For a "full" splat, an index operator is consumed as part
// of the splat's traversal.
` [ { names: ["Steve"]}, { names: ["Ermintrude"]}][*].names[0] ` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Steve" ) , cty . StringVal ( "Ermintrude" ) } ) ,
0 ,
} ,
{
// Another "full" splat, this time with the index first.
` [[ { name: "Steve"}], [ { name: "Ermintrude"}]][*][0].name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Steve" ) , cty . StringVal ( "Ermintrude" ) } ) ,
0 ,
} ,
{
// Full splats can nest, which produces nested tuples.
` [[ { name: "Steve"}], [ { name: "Ermintrude"}]][*][*].name ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Steve" ) } ) ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Ermintrude" ) } ) ,
} ) ,
0 ,
} ,
2017-06-16 15:39:28 +00:00
{
` [["hello"], ["goodbye"]].*.* ` ,
nil ,
cty . TupleVal ( [ ] cty . Value {
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "hello" ) } ) ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "goodbye" ) } ) ,
} ) ,
1 ,
} ,
2017-06-16 15:33:35 +00:00
2017-06-05 14:41:02 +00:00
{
` ["hello"][0] ` ,
nil ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
2018-03-03 17:02:29 +00:00
{
` ["hello"].0 ` ,
nil ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
{
` [["hello"]].0.0 ` ,
nil ,
cty . DynamicVal ,
1 , // can't chain legacy index syntax together (because 0.0 parses as 0)
} ,
{
` [ { greeting = "hello"}].0.greeting ` ,
nil ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
2017-06-05 14:41:02 +00:00
{
` [][0] ` ,
nil ,
cty . DynamicVal ,
1 , // invalid index
} ,
{
` ["hello"][negate(0)] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 14:41:02 +00:00
Functions : map [ string ] function . Function {
"negate" : stdlib . NegateFunc ,
} ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
{
` [][negate(0)] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 14:41:02 +00:00
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 ,
} ,
2018-12-12 23:51:40 +00:00
{
` ["boop"].foo[index] ` , // index is a variable to force IndexExpr instead of traversal
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"index" : cty . NumberIntVal ( 0 ) ,
} ,
} ,
cty . DynamicVal ,
1 , // expression ["boop"] does not have attributes
} ,
2017-06-05 15:02:54 +00:00
{
` foo ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . StringVal ( "hello" ) ,
} ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
{
` bar ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext { } ,
2017-06-05 15:02:54 +00:00
cty . DynamicVal ,
1 , // variables not allowed here
} ,
{
` foo.bar ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . StringVal ( "hello" ) ,
} ,
} ,
cty . DynamicVal ,
1 , // foo does not have attributes
} ,
{
` foo.baz ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . ObjectVal ( map [ string ] cty . Value {
"baz" : cty . StringVal ( "hello" ) ,
} ) ,
} ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
{
` foo["baz"] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
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
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . ObjectVal ( map [ string ] cty . Value {
"true" : cty . StringVal ( "hello" ) ,
} ) ,
} ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
{
` foo[0].baz ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"foo" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"baz" : cty . StringVal ( "hello" ) ,
} ) ,
} ) ,
} ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
2018-12-13 19:59:00 +00:00
{
`
<< EOT
Foo
Bar
Baz
EOT
` ,
nil ,
cty . StringVal ( "Foo\nBar\nBaz\n" ) ,
0 ,
} ,
{
`
<< EOT
Foo
$ { bar }
Baz
EOT
` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"bar" : cty . StringVal ( "Bar" ) ,
} ,
} ,
cty . StringVal ( "Foo\nBar\nBaz\n" ) ,
0 ,
} ,
{
`
<< EOT
Foo
% { for x in bars } $ { x } % { endfor }
Baz
EOT
` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"bars" : cty . ListVal ( [ ] cty . Value {
cty . StringVal ( "Bar" ) ,
cty . StringVal ( "Bar" ) ,
cty . StringVal ( "Bar" ) ,
} ) ,
} ,
} ,
cty . StringVal ( "Foo\nBarBarBar\nBaz\n" ) ,
0 ,
} ,
{
` [
<< EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( " Foo\n Bar\n Baz\n" ) } ) ,
0 ,
} ,
2018-12-14 01:22:41 +00:00
{
` [
<< - EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Foo\nBar\nBaz\n" ) } ) ,
0 ,
} ,
{
` [
<< - EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Foo\n Bar\n Baz\n" ) } ) ,
0 ,
} ,
{
` [
<< - EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( " Foo\nBar\n Baz\n" ) } ) ,
0 ,
} ,
{
` [
<< - EOT
Foo
$ { bar }
Baz
EOT
]
` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"bar" : cty . StringVal ( " Bar" ) , // Spaces in the interpolation result don't affect the outcome
} ,
} ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( " Foo\n Bar\n Baz\n" ) } ) ,
0 ,
} ,
2019-08-05 23:24:22 +00:00
{
` [
<< EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( " Foo\n\n Bar\n\n Baz\n" ) } ) ,
0 ,
} ,
{
` [
<< - EOT
Foo
Bar
Baz
EOT
]
` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "Foo\n\nBar\n\nBaz\n" ) } ) ,
0 ,
} ,
2018-12-13 19:59:00 +00:00
2017-06-05 15:02:54 +00:00
{
` unk["baz"] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
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"] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
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"] ` ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-06-05 15:02:54 +00:00
Variables : map [ string ] cty . Value {
"dyn" : cty . DynamicVal ,
} ,
} ,
cty . DynamicVal , // don't know what it is yet
0 ,
} ,
2018-11-29 19:43:45 +00:00
{
` nullstr == "foo" ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nullstr" : cty . NullVal ( cty . String ) ,
} ,
} ,
cty . False ,
0 ,
} ,
{
` nullstr == nullstr ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nullstr" : cty . NullVal ( cty . String ) ,
} ,
} ,
cty . True ,
0 ,
} ,
{
` nullstr == null ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nullstr" : cty . NullVal ( cty . String ) ,
} ,
} ,
cty . True ,
0 ,
} ,
{
` nullstr == nullnum ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nullstr" : cty . NullVal ( cty . String ) ,
"nullnum" : cty . NullVal ( cty . Number ) ,
} ,
} ,
cty . True ,
0 ,
} ,
{
` "" == nulldyn ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"nulldyn" : cty . NullVal ( cty . DynamicPseudoType ) ,
} ,
} ,
cty . False ,
0 ,
} ,
2019-07-02 18:56:34 +00:00
{
` true ? var : null ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . ObjectVal ( map [ string ] cty . Value { "a" : cty . StringVal ( "A" ) } ) ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value { "a" : cty . StringVal ( "A" ) } ) ,
0 ,
} ,
{
` true ? var : null ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . UnknownVal ( cty . DynamicPseudoType ) ,
} ,
} ,
cty . UnknownVal ( cty . DynamicPseudoType ) ,
0 ,
} ,
{
` true ? ["a", "b"] : null ` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "a" ) , cty . StringVal ( "b" ) } ) ,
0 ,
} ,
{
` true ? null: ["a", "b"] ` ,
nil ,
cty . NullVal ( cty . Tuple ( [ ] cty . Type { cty . String , cty . String } ) ) ,
0 ,
} ,
{
` false ? ["a", "b"] : null ` ,
nil ,
cty . NullVal ( cty . Tuple ( [ ] cty . Type { cty . String , cty . String } ) ) ,
0 ,
} ,
{
` false ? null: ["a", "b"] ` ,
nil ,
cty . TupleVal ( [ ] cty . Value { cty . StringVal ( "a" ) , cty . StringVal ( "b" ) } ) ,
0 ,
} ,
{
` false ? null: null ` ,
nil ,
cty . NullVal ( cty . DynamicPseudoType ) ,
0 ,
} ,
2019-07-19 00:26:11 +00:00
{
` false ? var: { a = "b"} ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . DynamicVal ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "b" ) ,
} ) ,
0 ,
} ,
{
` true ? ["a", "b"]: var ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . UnknownVal ( cty . DynamicPseudoType ) ,
} ,
} ,
cty . TupleVal ( [ ] cty . Value {
cty . StringVal ( "a" ) ,
cty . StringVal ( "b" ) ,
} ) ,
0 ,
} ,
{
` false ? ["a", "b"]: var ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . DynamicVal ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
{
` false ? ["a", "b"]: var ` ,
& hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . UnknownVal ( cty . DynamicPseudoType ) ,
} ,
} ,
cty . DynamicVal ,
0 ,
} ,
2017-06-01 02:27:16 +00:00
}
for _ , test := range tests {
t . Run ( test . input , func ( t * testing . T ) {
2017-09-11 23:40:37 +00:00
expr , parseDiags := ParseExpression ( [ ] byte ( test . input ) , "" , hcl . Pos { Line : 1 , Column : 1 , Byte : 0 } )
2017-06-01 02:27:16 +00:00
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
2017-09-11 23:40:37 +00:00
ctx * hcl . EvalContext
2017-05-25 15:14:43 +00:00
want cty . Value
diagCount int
} {
"valid call with no conversions" : {
& FunctionCallExpr {
Name : "length" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . StringVal ( "hello" ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . NumberIntVal ( 5 ) ,
0 ,
} ,
"valid call with arg conversion" : {
& FunctionCallExpr {
Name : "length" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . BoolVal ( true ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
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 ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
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 ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . UnknownVal ( cty . Number ) ,
0 ,
} ,
"valid call with dynamic arg" : {
& FunctionCallExpr {
Name : "length" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . DynamicVal ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . UnknownVal ( cty . Number ) ,
0 ,
} ,
"invalid arg type" : {
& FunctionCallExpr {
Name : "length" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . ListVal ( [ ] cty . Value { cty . StringVal ( "hello" ) } ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . DynamicVal ,
1 ,
} ,
"function with dynamic return type" : {
& FunctionCallExpr {
Name : "jsondecode" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . StringVal ( ` "hello" ` ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . StringVal ( "hello" ) ,
0 ,
} ,
"function with dynamic return type unknown arg" : {
& FunctionCallExpr {
Name : "jsondecode" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . UnknownVal ( cty . String ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . DynamicVal , // type depends on arg value
0 ,
} ,
"error in function" : {
& FunctionCallExpr {
Name : "jsondecode" ,
Args : [ ] Expression {
& LiteralValueExpr {
Val : cty . StringVal ( "invalid-json" ) ,
} ,
} ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
Functions : funcs ,
} ,
cty . DynamicVal ,
1 , // JSON parse error
} ,
"unknown function" : {
& FunctionCallExpr {
Name : "lenth" ,
Args : [ ] Expression { } ,
} ,
2017-09-11 23:40:37 +00:00
& hcl . EvalContext {
2017-05-25 15:14:43 +00:00
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 )
}
} )
}
}
2018-01-13 06:58:55 +00:00
func TestExpressionAsTraversal ( t * testing . T ) {
2019-06-17 22:50:56 +00:00
expr , _ := ParseExpression ( [ ] byte ( "a.b[0][\"c\"]" ) , "" , hcl . Pos { } )
2018-01-13 06:58:55 +00:00
traversal , diags := hcl . AbsTraversalForExpr ( expr )
if len ( diags ) != 0 {
2019-06-17 22:50:56 +00:00
t . Fatalf ( "unexpected diagnostics:\n%s" , diags . Error ( ) )
2018-01-13 06:58:55 +00:00
}
2019-06-17 22:50:56 +00:00
if len ( traversal ) != 4 {
2018-01-13 06:58:55 +00:00
t . Fatalf ( "wrong traversal %#v; want length 3" , traversal )
}
if traversal . RootName ( ) != "a" {
2019-06-17 22:50:56 +00:00
t . Errorf ( "wrong root name %q; want %q" , traversal . RootName ( ) , "a" )
}
if step , ok := traversal [ 1 ] . ( hcl . TraverseAttr ) ; ok {
if got , want := step . Name , "b" ; got != want {
t . Errorf ( "wrong name %q for step 1; want %q" , got , want )
}
} else {
t . Errorf ( "wrong type %T for step 1; want %T" , traversal [ 1 ] , step )
}
if step , ok := traversal [ 2 ] . ( hcl . TraverseIndex ) ; ok {
if got , want := step . Key , cty . Zero ; ! want . RawEquals ( got ) {
t . Errorf ( "wrong name %#v for step 2; want %#v" , got , want )
}
} else {
t . Errorf ( "wrong type %T for step 2; want %T" , traversal [ 2 ] , step )
}
if step , ok := traversal [ 3 ] . ( hcl . TraverseIndex ) ; ok {
if got , want := step . Key , cty . StringVal ( "c" ) ; ! want . RawEquals ( got ) {
t . Errorf ( "wrong name %#v for step 3; want %#v" , got , want )
}
} else {
t . Errorf ( "wrong type %T for step 3; want %T" , traversal [ 3 ] , step )
2018-01-13 06:58:55 +00:00
}
}
2018-01-13 07:30:41 +00:00
func TestStaticExpressionList ( t * testing . T ) {
expr , _ := ParseExpression ( [ ] byte ( "[0, a, true]" ) , "" , hcl . Pos { } )
exprs , diags := hcl . ExprList ( expr )
if len ( diags ) != 0 {
2019-06-17 22:50:56 +00:00
t . Fatalf ( "unexpected diagnostics:\n%s" , diags . Error ( ) )
2018-01-13 07:30:41 +00:00
}
if len ( exprs ) != 3 {
t . Fatalf ( "wrong result %#v; want length 3" , exprs )
}
first , ok := exprs [ 0 ] . ( * LiteralValueExpr )
if ! ok {
2018-01-24 05:54:38 +00:00
t . Fatalf ( "first expr has wrong type %T; want *hclsyntax.LiteralValueExpr" , exprs [ 0 ] )
2018-01-13 07:30:41 +00:00
}
if ! first . Val . RawEquals ( cty . Zero ) {
t . Fatalf ( "wrong first value %#v; want cty.Zero" , first . Val )
}
}