hcl/hclsyntax/structure_at_pos_test.go
Martin Atkins 6c4344623b Unfold the "hcl" directory up into the root
The main HCL package is more visible this way, and so it's easier than
having to pick it out from dozens of other package directories.
2019-09-09 16:08:19 -07:00

336 lines
6.5 KiB
Go

package hclsyntax
import (
"reflect"
"testing"
"github.com/hashicorp/hcl/v2"
)
func TestBlocksAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantTypes []string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
nil,
},
"spaces": {
` `,
hcl.Pos{Byte: 1},
nil,
},
"single in header": {
`foo {}`,
hcl.Pos{Byte: 1},
[]string{"foo"},
},
"single in body": {
`foo { }`,
hcl.Pos{Byte: 7},
[]string{"foo"},
},
"single in body with unselected nested": {
`
foo {
bar {
}
}
`,
hcl.Pos{Byte: 10},
[]string{"foo"},
},
"single in body with unselected sibling": {
`
foo { }
bar { }
`,
hcl.Pos{Byte: 10},
[]string{"foo"},
},
"selected nested two levels": {
`
foo {
bar {
}
}
`,
hcl.Pos{Byte: 20},
[]string{"foo", "bar"},
},
"selected nested three levels": {
`
foo {
bar {
baz {
}
}
}
`,
hcl.Pos{Byte: 31},
[]string{"foo", "bar", "baz"},
},
"selected nested three levels with unselected sibling after": {
`
foo {
bar {
baz {
}
}
not_wanted {}
}
`,
hcl.Pos{Byte: 31},
[]string{"foo", "bar", "baz"},
},
"selected nested three levels with unselected sibling before": {
`
foo {
not_wanted {}
bar {
baz {
}
}
}
`,
hcl.Pos{Byte: 49},
[]string{"foo", "bar", "baz"},
},
"unterminated": {
`foo { `,
hcl.Pos{Byte: 7},
[]string{"foo"},
},
"unterminated nested": {
`
foo {
bar {
}
`,
hcl.Pos{Byte: 16},
[]string{"foo", "bar"},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
blocks := f.BlocksAtPos(test.Pos)
outermost := f.OutermostBlockAtPos(test.Pos)
innermost := f.InnermostBlockAtPos(test.Pos)
gotTypes := make([]string, len(blocks))
for i, block := range blocks {
gotTypes[i] = block.Type
}
if len(test.WantTypes) == 0 {
if len(gotTypes) != 0 {
t.Errorf("wrong block types\ngot: %#v\nwant: (none)", gotTypes)
}
if outermost != nil {
t.Errorf("wrong outermost type\ngot: %#v\nwant: (none)", outermost.Type)
}
if innermost != nil {
t.Errorf("wrong innermost type\ngot: %#v\nwant: (none)", innermost.Type)
}
return
}
if !reflect.DeepEqual(gotTypes, test.WantTypes) {
if len(gotTypes) != 0 {
t.Errorf("wrong block types\ngot: %#v\nwant: %#v", gotTypes, test.WantTypes)
}
}
if got, want := outermost.Type, test.WantTypes[0]; got != want {
t.Errorf("wrong outermost type\ngot: %#v\nwant: %#v", got, want)
}
if got, want := innermost.Type, test.WantTypes[len(test.WantTypes)-1]; got != want {
t.Errorf("wrong innermost type\ngot: %#v\nwant: %#v", got, want)
}
})
}
}
func TestAttributeAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantName string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
"",
},
"top-level": {
`foo = 1`,
hcl.Pos{Byte: 0},
"foo",
},
"top-level with ignored sibling after": {
`
foo = 1
bar = 2
`,
hcl.Pos{Byte: 6},
"foo",
},
"top-level ignored sibling before": {
`
foo = 1
bar = 2
`,
hcl.Pos{Byte: 17},
"bar",
},
"nested": {
`
foo {
bar = 2
}
`,
hcl.Pos{Byte: 17},
"bar",
},
"nested in unterminated block": {
`
foo {
bar = 2
`,
hcl.Pos{Byte: 17},
"bar",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
got := f.AttributeAtPos(test.Pos)
if test.WantName == "" {
if got != nil {
t.Errorf("wrong attribute name\ngot: %#v\nwant: (none)", got.Name)
}
return
}
if got == nil {
t.Fatalf("wrong attribute name\ngot: (none)\nwant: %#v", test.WantName)
}
if got.Name != test.WantName {
t.Errorf("wrong attribute name\ngot: %#v\nwant: %#v", got.Name, test.WantName)
}
})
}
}
func TestOutermostExprAtPos(t *testing.T) {
tests := map[string]struct {
Src string
Pos hcl.Pos
WantSrc string
}{
"empty": {
``,
hcl.Pos{Byte: 0},
``,
},
"simple bool": {
`a = true`,
hcl.Pos{Byte: 6},
`true`,
},
"simple reference": {
`a = blah`,
hcl.Pos{Byte: 6},
`blah`,
},
"attribute reference": {
`a = blah.foo`,
hcl.Pos{Byte: 6},
`blah.foo`,
},
"parens": {
`a = (1 + 1)`,
hcl.Pos{Byte: 6},
`1 + 1`, // The parser trims the parens off, so they aren't considered as part of the expression :(
},
"tuple cons": {
`a = [1, 2, 3]`,
hcl.Pos{Byte: 5},
`[1, 2, 3]`,
},
"function call": {
`a = foom("a")`,
hcl.Pos{Byte: 10},
`foom("a")`,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
inputSrc := []byte(test.Src)
f, diags := ParseConfig(inputSrc, "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
// We intentionally ignore diagnostics here because we should be
// able to work with the incomplete configuration that results
// when the parser does its recovery behavior. However, we do
// log them in case it's helpful to someone debugging a failing
// test.
t.Logf(diag.Error())
}
gotExpr := f.OutermostExprAtPos(test.Pos)
var gotSrc string
if gotExpr != nil {
rng := gotExpr.Range()
gotSrc = string(rng.SliceBytes(inputSrc))
}
if test.WantSrc == "" {
if gotExpr != nil {
t.Errorf("wrong expression source\ngot: %s\nwant: (none)", gotSrc)
}
return
}
if gotExpr == nil {
t.Fatalf("wrong expression source\ngot: (none)\nwant: %s", test.WantSrc)
}
if gotSrc != test.WantSrc {
t.Errorf("wrong expression source\ngot: %#v\nwant: %#v", gotSrc, test.WantSrc)
}
})
}
}