fde586e193
Currently only deals with the literal HCL structure. Later we will also support HIL parsing and evaluation within strings, to achieve parity with existing uses of HCL/HIL together. For now, this has parity with uses of HCL alone, with the exception that float and int values are not distinguished, because cty does not make this distinction.
500 lines
9.7 KiB
Go
500 lines
9.7 KiB
Go
package hclhil
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/apparentlymart/go-cty/cty"
|
|
"github.com/apparentlymart/go-zcl/zcl"
|
|
"github.com/davecgh/go-spew/spew"
|
|
hclast "github.com/hashicorp/hcl/hcl/ast"
|
|
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
|
)
|
|
|
|
func TestBodyPartialContent(t *testing.T) {
|
|
tests := []struct {
|
|
Source string
|
|
Schema *zcl.BodySchema
|
|
Want *zcl.BodyContent
|
|
DiagCount int
|
|
}{
|
|
{
|
|
``,
|
|
&zcl.BodySchema{},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
`foo = 1`,
|
|
&zcl.BodySchema{},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
`foo = 1`,
|
|
&zcl.BodySchema{
|
|
Attributes: []zcl.AttributeSchema{
|
|
{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{
|
|
"foo": {
|
|
Name: "foo",
|
|
Expr: &expression{
|
|
src: &hclast.LiteralType{
|
|
Token: hcltoken.Token{
|
|
Type: hcltoken.NUMBER,
|
|
Text: `1`,
|
|
Pos: hcltoken.Pos{
|
|
Offset: 6,
|
|
Line: 1,
|
|
Column: 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
Range: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
NameRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
``,
|
|
&zcl.BodySchema{
|
|
Attributes: []zcl.AttributeSchema{
|
|
{
|
|
Name: "foo",
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
},
|
|
1, // missing required attribute
|
|
},
|
|
{
|
|
`foo {}`,
|
|
&zcl.BodySchema{
|
|
Blocks: []zcl.BlockHeaderSchema{
|
|
{
|
|
Type: "foo",
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
Blocks: zcl.Blocks{
|
|
{
|
|
Type: "foo",
|
|
Body: &body{
|
|
oli: &hclast.ObjectList{},
|
|
},
|
|
DefRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
|
|
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
|
|
},
|
|
TypeRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
`foo "unwanted" {}`,
|
|
&zcl.BodySchema{
|
|
Blocks: []zcl.BlockHeaderSchema{
|
|
{
|
|
Type: "foo",
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
},
|
|
1, // no labels are expected
|
|
},
|
|
{
|
|
`foo {}`,
|
|
&zcl.BodySchema{
|
|
Blocks: []zcl.BlockHeaderSchema{
|
|
{
|
|
Type: "foo",
|
|
LabelNames: []string{"name"},
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
},
|
|
1, // missing name
|
|
},
|
|
{
|
|
`foo "wanted" {}`,
|
|
&zcl.BodySchema{
|
|
Blocks: []zcl.BlockHeaderSchema{
|
|
{
|
|
Type: "foo",
|
|
LabelNames: []string{"name"},
|
|
},
|
|
},
|
|
},
|
|
&zcl.BodyContent{
|
|
Attributes: zcl.Attributes{},
|
|
Blocks: zcl.Blocks{
|
|
{
|
|
Type: "foo",
|
|
Labels: []string{"wanted"},
|
|
Body: &body{
|
|
oli: &hclast.ObjectList{},
|
|
},
|
|
DefRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 13, Line: 1, Column: 14},
|
|
End: zcl.Pos{Byte: 14, Line: 1, Column: 15},
|
|
},
|
|
TypeRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
LabelRanges: []zcl.Range{
|
|
{
|
|
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
|
|
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
|
file, diags := Parse([]byte(test.Source), "test.hcl")
|
|
if len(diags) != 0 {
|
|
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
|
}
|
|
|
|
got, _, diags := file.Body.PartialContent(test.Schema)
|
|
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 !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestBodyJustAttributes(t *testing.T) {
|
|
tests := []struct {
|
|
Source string
|
|
Want zcl.Attributes
|
|
DiagCount int
|
|
}{
|
|
{
|
|
``,
|
|
zcl.Attributes{},
|
|
0,
|
|
},
|
|
{
|
|
`foo = "a"`,
|
|
zcl.Attributes{
|
|
"foo": &zcl.Attribute{
|
|
Name: "foo",
|
|
Expr: &expression{
|
|
src: &hclast.LiteralType{
|
|
Token: hcltoken.Token{
|
|
Type: hcltoken.STRING,
|
|
Pos: hcltoken.Pos{
|
|
Offset: 6,
|
|
Line: 1,
|
|
Column: 7,
|
|
},
|
|
Text: `"a"`,
|
|
},
|
|
},
|
|
},
|
|
Range: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
NameRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
`foo = {}`,
|
|
zcl.Attributes{
|
|
"foo": &zcl.Attribute{
|
|
Name: "foo",
|
|
Expr: &expression{
|
|
src: &hclast.ObjectType{
|
|
List: &hclast.ObjectList{},
|
|
Lbrace: hcltoken.Pos{
|
|
Offset: 6,
|
|
Line: 1,
|
|
Column: 7,
|
|
},
|
|
Rbrace: hcltoken.Pos{
|
|
Offset: 7,
|
|
Line: 1,
|
|
Column: 8,
|
|
},
|
|
},
|
|
},
|
|
Range: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
NameRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
`foo {}`,
|
|
zcl.Attributes{
|
|
"foo": &zcl.Attribute{
|
|
Name: "foo",
|
|
Expr: &expression{
|
|
src: &hclast.ObjectType{
|
|
List: &hclast.ObjectList{},
|
|
Lbrace: hcltoken.Pos{
|
|
Offset: 4,
|
|
Line: 1,
|
|
Column: 5,
|
|
},
|
|
Rbrace: hcltoken.Pos{
|
|
Offset: 5,
|
|
Line: 1,
|
|
Column: 6,
|
|
},
|
|
},
|
|
},
|
|
Range: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
NameRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
|
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
|
},
|
|
},
|
|
},
|
|
1, // warning about using block syntax
|
|
},
|
|
{
|
|
`foo "bar" {}`,
|
|
zcl.Attributes{},
|
|
1, // blocks are not allowed here
|
|
},
|
|
{
|
|
`
|
|
foo = 1
|
|
foo = 2
|
|
`,
|
|
zcl.Attributes{
|
|
"foo": &zcl.Attribute{
|
|
Name: "foo",
|
|
Expr: &expression{
|
|
src: &hclast.LiteralType{
|
|
Token: hcltoken.Token{
|
|
Type: hcltoken.NUMBER,
|
|
Pos: hcltoken.Pos{
|
|
Offset: 14,
|
|
Line: 2,
|
|
Column: 14,
|
|
},
|
|
Text: `1`,
|
|
},
|
|
},
|
|
},
|
|
Range: zcl.Range{
|
|
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
|
|
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
|
|
},
|
|
NameRange: zcl.Range{
|
|
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
|
|
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
|
|
},
|
|
},
|
|
},
|
|
1, // duplicate definition of foo
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
|
file, diags := Parse([]byte(test.Source), "test.hcl")
|
|
if len(diags) != 0 {
|
|
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
|
}
|
|
|
|
got, diags := file.Body.JustAttributes()
|
|
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 !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpressionValue(t *testing.T) {
|
|
tests := []struct {
|
|
Source string // HCL source assigning a value to attribute "v"
|
|
Want cty.Value
|
|
DiagCount int
|
|
}{
|
|
{
|
|
`v = 1`,
|
|
cty.NumberIntVal(1),
|
|
0,
|
|
},
|
|
{
|
|
`v = 1.5`,
|
|
cty.NumberFloatVal(1.5),
|
|
0,
|
|
},
|
|
{
|
|
`v = "hello"`,
|
|
cty.StringVal("hello"),
|
|
0,
|
|
},
|
|
{
|
|
`v = <<EOT
|
|
heredoc
|
|
EOT
|
|
`,
|
|
cty.StringVal("heredoc\n"),
|
|
0,
|
|
},
|
|
{
|
|
`v = true`,
|
|
cty.True,
|
|
0,
|
|
},
|
|
{
|
|
`v = false`,
|
|
cty.False,
|
|
0,
|
|
},
|
|
{
|
|
`v = []`,
|
|
cty.EmptyTupleVal,
|
|
0,
|
|
},
|
|
{
|
|
`v = ["hello", 5, true, 3.4]`,
|
|
cty.TupleVal([]cty.Value{
|
|
cty.StringVal("hello"),
|
|
cty.NumberIntVal(5),
|
|
cty.True,
|
|
cty.NumberFloatVal(3.4),
|
|
}),
|
|
0,
|
|
},
|
|
{
|
|
`v = {}`,
|
|
cty.EmptyObjectVal,
|
|
0,
|
|
},
|
|
{
|
|
`v = {
|
|
string = "hello"
|
|
int = 5
|
|
bool = true
|
|
float = 3.4
|
|
list = []
|
|
object = {}
|
|
}`,
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"string": cty.StringVal("hello"),
|
|
"int": cty.NumberIntVal(5),
|
|
"bool": cty.True,
|
|
"float": cty.NumberFloatVal(3.4),
|
|
"list": cty.EmptyTupleVal,
|
|
"object": cty.EmptyObjectVal,
|
|
}),
|
|
0,
|
|
},
|
|
{
|
|
`v {}`,
|
|
cty.EmptyObjectVal,
|
|
0, // warns about using block syntax during content extraction, but we ignore that here
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
|
file, diags := Parse([]byte(test.Source), "test.hcl")
|
|
if len(diags) != 0 {
|
|
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
|
}
|
|
|
|
content, diags := file.Body.Content(&zcl.BodySchema{
|
|
Attributes: []zcl.AttributeSchema{
|
|
{
|
|
Name: "v",
|
|
Required: true,
|
|
},
|
|
},
|
|
})
|
|
|
|
expr := content.Attributes["v"].Expr
|
|
|
|
got, diags := expr.Value(nil)
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|