hcl/hclwrite/ast_block_test.go
Martin Atkins 7d8f0ff870 hclwrite: Make block labels a node in their own right
All of the other subdivisions of a block were already nodes, but we'd
represented the labels as an undifferentiated set of nodes belonging
directly to the block's child node list.

Now that we support replacing the labels in the public API, that's a good
excuse to refactor this slightly to make the labels their own node. As
well as being consistent with everything else in Block, this also makes
it easier to implement the Block.SetLabels operation because we can
just  change the children of the labels node, rather than having to
carefully identify and extract the individual child nodes of the block
that happen to represent labels.

Internally this models the labels in a similar sort of way as the content
of a body, although we've kept the public API directly on the Block type
here because that's a more straightforward model for the use-cases we
currently know and matches better with the API of hcl.Block. This is just
an internal change for consistency.

I also added a few tests for having comments interspersed with labels
while I was here, because that helped to better exercise the new
parseBlockLabels function.
2020-08-21 11:30:32 -07:00

480 lines
9.5 KiB
Go

package hclwrite
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
func TestBlockType(t *testing.T) {
tests := []struct {
src string
want string
}{
{
`
service {
attr0 = "val0"
}
`,
"service",
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s", test.want), func(t *testing.T) {
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
if len(diags) != 0 {
for _, diag := range diags {
t.Logf("- %s", diag.Error())
}
t.Fatalf("unexpected diagnostics")
}
block := f.Body().Blocks()[0]
got := string(block.Type())
if got != test.want {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, test.want)
}
})
}
}
func TestBlockLabels(t *testing.T) {
tests := []struct {
src string
want []string
}{
{
`
nolabel {
}
`,
[]string{},
},
{
`
quoted "label1" {
}
`,
[]string{"label1"},
},
{
`
quoted "label1" "label2" {
}
`,
[]string{"label1", "label2"},
},
{
`
quoted "label1" /* foo */ "label2" {
}
`,
[]string{"label1", "label2"},
},
{
`
unquoted label1 {
}
`,
[]string{"label1"},
},
{
`
unquoted label1 /* foo */ label2 {
}
`,
[]string{"label1", "label2"},
},
{
`
mixed label1 "label2" {
}
`,
[]string{"label1", "label2"},
},
{
`
escape "\u0041" {
}
`,
[]string{"\u0041"},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s", strings.Join(test.want, " ")), func(t *testing.T) {
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
if len(diags) != 0 {
for _, diag := range diags {
t.Logf("- %s", diag.Error())
}
t.Fatalf("unexpected diagnostics")
}
block := f.Body().Blocks()[0]
got := block.Labels()
if !reflect.DeepEqual(got, test.want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
}
})
}
}
func TestBlockSetType(t *testing.T) {
tests := []struct {
src string
oldTypeName string
newTypeName string
labels []string
want Tokens
}{
{
"foo {}",
"foo",
"bar",
nil,
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`bar`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s %s %s in %s", test.oldTypeName, test.newTypeName, test.labels, test.src), func(t *testing.T) {
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
if len(diags) != 0 {
for _, diag := range diags {
t.Logf("- %s", diag.Error())
}
t.Fatalf("unexpected diagnostics")
}
b := f.Body().FirstMatchingBlock(test.oldTypeName, test.labels)
b.SetType(test.newTypeName)
got := f.BuildTokens(nil)
format(got)
if !reflect.DeepEqual(got, test.want) {
diff := cmp.Diff(test.want, got)
t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff)
}
})
}
}
func TestBlockSetLabels(t *testing.T) {
tests := []struct {
src string
typeName string
oldLabels []string
newLabels []string
want Tokens
}{
{
`foo "hoge" {}`,
"foo",
[]string{"hoge"},
[]string{"fuga"}, // update first label
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`fuga`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
{
`foo "hoge" "fuga" {}`,
"foo",
[]string{"hoge", "fuga"},
[]string{"hoge", "piyo"}, // update second label
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`hoge`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`piyo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
{
`foo {}`,
"foo",
[]string{},
[]string{"fuga"}, // insert a new label to empty list
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`fuga`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
{
`foo "hoge" {}`,
"foo",
[]string{"hoge"},
[]string{}, // remove all labels
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
{
`foo "hoge" /* fuga */ "piyo" {}`,
"foo",
[]string{"hoge", "piyo"},
[]string{"fuga"}, // force quoted form even if the old one is unquoted.
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`fuga`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
{
`foo "hoge" /* foo */ "" {}`,
"foo",
[]string{"hoge"},
[]string{"fuga"}, // force quoted form even if the old one is unquoted.
Tokens{
{
Type: hclsyntax.TokenIdent,
Bytes: []byte(`foo`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOQuote,
Bytes: []byte(`"`),
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenQuotedLit,
Bytes: []byte(`fuga`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenCQuote,
Bytes: []byte(`"`),
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
SpacesBefore: 1,
},
{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
SpacesBefore: 0,
},
{
Type: hclsyntax.TokenEOF,
Bytes: []byte{},
SpacesBefore: 0,
},
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s %s %s in %s", test.typeName, test.oldLabels, test.newLabels, test.src), func(t *testing.T) {
f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
if len(diags) != 0 {
for _, diag := range diags {
t.Logf("- %s", diag.Error())
}
t.Fatalf("unexpected diagnostics")
}
b := f.Body().FirstMatchingBlock(test.typeName, test.oldLabels)
b.SetLabels(test.newLabels)
got := f.BuildTokens(nil)
format(got)
if !reflect.DeepEqual(got, test.want) {
diff := cmp.Diff(test.want, got)
t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff)
}
})
}
}