hcl/json/parser_test.go

668 lines
14 KiB
Go

package json
import (
"math/big"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/hcl/v2"
)
func init() {
deep.MaxDepth = 999
}
func TestParse(t *testing.T) {
tests := []struct {
Input string
Want node
DiagCount int
}{
// Simple, single-token constructs
{
`true`,
&booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 5, Byte: 4},
},
},
0,
},
{
`false`,
&booleanVal{
Value: false,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
},
},
0,
},
{
`null`,
&nullVal{
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 5, Byte: 4},
},
},
0,
},
{
`undefined`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 10, Byte: 9},
}},
1,
},
{
`flase`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
}},
1,
},
{
`"hello"`,
&stringVal{
Value: "hello",
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 8, Byte: 7},
},
},
0,
},
{
`"hello\nworld"`,
&stringVal{
Value: "hello\nworld",
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
},
},
0,
},
{
`"hello \"world\""`,
&stringVal{
Value: `hello "world"`,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 18, Byte: 17},
},
},
0,
},
{
`"hello \\"`,
&stringVal{
Value: "hello \\",
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 11, Byte: 10},
},
},
0,
},
{
`"hello`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 7, Byte: 6},
}},
1,
},
{
`"he\llo"`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 9, Byte: 8},
}},
1,
},
{
`1`,
&numberVal{
Value: mustBigFloat("1"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`1.2`,
&numberVal{
Value: mustBigFloat("1.2"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
},
},
0,
},
{
`-1`,
&numberVal{
Value: mustBigFloat("-1"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
},
},
0,
},
{
`1.2e5`,
&numberVal{
Value: mustBigFloat("120000"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
},
},
0,
},
{
`1.2e+5`,
&numberVal{
Value: mustBigFloat("120000"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 7, Byte: 6},
},
},
0,
},
{
`1.2e-5`,
&numberVal{
Value: mustBigFloat("1.2e-5"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 7, Byte: 6},
},
},
0,
},
{
`.1`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
}},
1,
},
{
`+2`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
}},
1,
},
{
`1 2`,
&numberVal{
Value: mustBigFloat("1"),
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
1,
},
// Objects
{
`{"hello": true}`,
&objectVal{
Attrs: []*objectAttr{
{
Name: "hello",
Value: &booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 11, Byte: 10},
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 9, Byte: 8},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
CloseRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 15, Byte: 14},
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
},
},
0,
},
{
`{"hello": true, "bye": false}`,
&objectVal{
Attrs: []*objectAttr{
{
Name: "hello",
Value: &booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 11, Byte: 10},
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 9, Byte: 8},
},
},
{
Name: "bye",
Value: &booleanVal{
Value: false,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 24, Byte: 23},
End: hcl.Pos{Line: 1, Column: 29, Byte: 28},
},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 17, Byte: 16},
End: hcl.Pos{Line: 1, Column: 22, Byte: 21},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 30, Byte: 29},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
CloseRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 29, Byte: 28},
End: hcl.Pos{Line: 1, Column: 30, Byte: 29},
},
},
0,
},
{
`{}`,
&objectVal{
Attrs: []*objectAttr{},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
CloseRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
},
},
0,
},
{
`{"hello":true`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"hello":true]`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"hello":true,}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{true:false}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"hello": true, "hello": true}`,
&objectVal{
Attrs: []*objectAttr{
{
Name: "hello",
Value: &booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 11, Byte: 10},
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 9, Byte: 8},
},
},
{
Name: "hello",
Value: &booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 26, Byte: 25},
End: hcl.Pos{Line: 1, Column: 30, Byte: 29},
},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 17, Byte: 16},
End: hcl.Pos{Line: 1, Column: 24, Byte: 23},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 31, Byte: 30},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
CloseRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 30, Byte: 29},
End: hcl.Pos{Line: 1, Column: 31, Byte: 30},
},
},
0,
},
{
`{"hello": true, "hello": true, "hello", true}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1, // comma used where colon is expected
},
{
`{"hello", "world"}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`[]`,
&arrayVal{
Values: []node{},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`[true]`,
&arrayVal{
Values: []node{
&booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 7, Byte: 6},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`[true, false]`,
&arrayVal{
Values: []node{
&booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
},
},
&booleanVal{
Value: false,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7},
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 14, Byte: 13},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`[[]]`,
&arrayVal{
Values: []node{
&arrayVal{
Values: []node{},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 2, Byte: 1},
End: hcl.Pos{Line: 1, Column: 3, Byte: 2},
},
},
},
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 5, Byte: 4},
},
OpenRange: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`[`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
2,
},
{
`[true`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`]`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`[true,]`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`[[],]`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`["hello":true]`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`[true}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"wrong"=true}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"wrong" = true}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
{
`{"wrong" true}`,
invalidVal{hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
}},
1,
},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got, diag := parseFileContent([]byte(test.Input), "", hcl.Pos{Byte: 0, Line: 1, Column: 1})
if len(diag) != test.DiagCount {
t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount)
for _, d := range diag {
t.Logf(" - %s", d.Error())
}
}
if diff := deep.Equal(got, test.Want); diff != nil {
for _, problem := range diff {
t.Error(problem)
}
}
})
}
}
func TestParseWithPos(t *testing.T) {
tests := []struct {
Input string
StartPos hcl.Pos
Want node
DiagCount int
}{
// Simple, single-token constructs
{
`true`,
hcl.Pos{Byte: 0, Line: 3, Column: 10},
&booleanVal{
Value: true,
SrcRange: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 10, Byte: 0},
End: hcl.Pos{Line: 3, Column: 14, Byte: 4},
},
},
0,
},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got, diag := parseFileContent([]byte(test.Input), "", test.StartPos)
if len(diag) != test.DiagCount {
t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount)
for _, d := range diag {
t.Logf(" - %s", d.Error())
}
}
if diff := deep.Equal(got, test.Want); diff != nil {
for _, problem := range diff {
t.Error(problem)
}
}
})
}
}
func mustBigFloat(s string) *big.Float {
f, _, err := (&big.Float{}).Parse(s, 10)
if err != nil {
panic(err)
}
return f
}