1283 lines
23 KiB
Go
1283 lines
23 KiB
Go
package hcl
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
)
|
|
|
|
func TestDecode_interface(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Err bool
|
|
Out interface{}
|
|
}{
|
|
{
|
|
"basic.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar",
|
|
"bar": "${file(\"bing/bong.txt\")}",
|
|
},
|
|
},
|
|
{
|
|
"basic_squish.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar",
|
|
"bar": "${file(\"bing/bong.txt\")}",
|
|
"foo-bar": "baz",
|
|
},
|
|
},
|
|
{
|
|
"empty.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"tfvars.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"regularvar": "Should work",
|
|
"map.key1": "Value",
|
|
"map.key2": "Other value",
|
|
},
|
|
},
|
|
{
|
|
"escape.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar\"baz\\n",
|
|
"qux": "back\\slash",
|
|
"bar": "new\nline",
|
|
"qax": `slash\:colon`,
|
|
"nested": `${HH\\:mm\\:ss}`,
|
|
"nestedquotes": `${"\"stringwrappedinquotes\""}`,
|
|
},
|
|
},
|
|
{
|
|
"float.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1.02,
|
|
"b": 2,
|
|
},
|
|
},
|
|
{
|
|
"multiline_bad.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"multiline_literal.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"multiline_literal_with_hil.hcl",
|
|
false,
|
|
map[string]interface{}{"multiline_literal_with_hil": "${hello\n world}"},
|
|
},
|
|
{
|
|
"multiline_no_marker.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"multiline.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": "bar\nbaz\n"},
|
|
},
|
|
{
|
|
"multiline_indented.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": " bar\n baz\n"},
|
|
},
|
|
{
|
|
"multiline_no_hanging_indent.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": " baz\n bar\n foo\n"},
|
|
},
|
|
{
|
|
"multiline_no_eof.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": "bar\nbaz\n", "key": "value"},
|
|
},
|
|
{
|
|
"multiline.json",
|
|
false,
|
|
map[string]interface{}{"foo": "bar\nbaz"},
|
|
},
|
|
{
|
|
"null_strings.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"module": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"app": []map[string]interface{}{
|
|
map[string]interface{}{"foo": ""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"scientific.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1e-10,
|
|
"b": 1e+10,
|
|
"c": 1e10,
|
|
"d": 1.2e-10,
|
|
"e": 1.2e+10,
|
|
"f": 1.2e10,
|
|
},
|
|
},
|
|
{
|
|
"scientific.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1e-10,
|
|
"b": 1e+10,
|
|
"c": 1e10,
|
|
"d": 1.2e-10,
|
|
"e": 1.2e+10,
|
|
"f": 1.2e10,
|
|
},
|
|
},
|
|
{
|
|
"terraform_heroku.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"name": "terraform-test-app",
|
|
"config_vars": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"FOO": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_multi.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"baz": []map[string]interface{}{
|
|
map[string]interface{}{"key": 7},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{"key": 12},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_multi.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"baz": []map[string]interface{}{
|
|
map[string]interface{}{"key": 7},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{"key": 12},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"list_of_lists.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []interface{}{
|
|
[]interface{}{"foo"},
|
|
[]interface{}{"bar"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"list_of_maps.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []interface{}{
|
|
map[string]interface{}{"somekey1": "someval1"},
|
|
map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"assign_deep.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []interface{}{
|
|
map[string]interface{}{
|
|
"foo": []interface{}{
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{}}}}}}},
|
|
},
|
|
{
|
|
"structure_list.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"key": 7,
|
|
},
|
|
map[string]interface{}{
|
|
"key": 12,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_list.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"key": 7,
|
|
},
|
|
map[string]interface{}{
|
|
"key": 12,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_list_deep.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"name": "terraform_example",
|
|
"ingress": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"from_port": 22,
|
|
},
|
|
map[string]interface{}{
|
|
"from_port": 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"structure_list_empty.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []interface{}{},
|
|
},
|
|
},
|
|
|
|
{
|
|
"nested_block_comment.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"bar": "value",
|
|
},
|
|
},
|
|
|
|
{
|
|
"unterminated_block_comment.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"unterminated_brace.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"nested_provider_bad.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"object_list.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"aws_instance": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"db": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"vpc": "foo",
|
|
"provisioner": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"file": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"source": "foo",
|
|
"destination": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// Terraform GH-8295 sanity test that basic decoding into
|
|
// interface{} works.
|
|
{
|
|
"terraform_variable_invalid.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"variable": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"whatever": "abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"interpolate.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"default": `${replace("europe-west", "-", " ")}`,
|
|
},
|
|
},
|
|
|
|
{
|
|
"block_assign.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"escape_backslash.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"output": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"one": `${replace(var.sub_domain, ".", "\\.")}`,
|
|
"two": `${replace(var.sub_domain, ".", "\\\\.")}`,
|
|
"many": `${replace(var.sub_domain, ".", "\\\\\\\\.")}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"git_crypt.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"object_with_bool.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"path": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"policy": "write",
|
|
"permissions": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"bool": []interface{}{false},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.File, func(t *testing.T) {
|
|
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.File))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
var out interface{}
|
|
err = Decode(&out, string(d))
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(out, tc.Out) {
|
|
t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
|
|
}
|
|
|
|
var v interface{}
|
|
err = Unmarshal(d, &v)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(v, tc.Out) {
|
|
t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecode_interfaceInline(t *testing.T) {
|
|
cases := []struct {
|
|
Value string
|
|
Err bool
|
|
Out interface{}
|
|
}{
|
|
{"t t e{{}}", true, nil},
|
|
{"t=0t d {}", true, map[string]interface{}{"t": 0}},
|
|
{"v=0E0v d{}", true, map[string]interface{}{"v": float64(0)}},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Logf("Testing: %q", tc.Value)
|
|
|
|
var out interface{}
|
|
err := Decode(&out, tc.Value)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(out, tc.Out) {
|
|
t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
|
|
}
|
|
|
|
var v interface{}
|
|
err = Unmarshal([]byte(tc.Value), &v)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(v, tc.Out) {
|
|
t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_equal(t *testing.T) {
|
|
cases := []struct {
|
|
One, Two string
|
|
}{
|
|
{
|
|
"basic.hcl",
|
|
"basic.json",
|
|
},
|
|
{
|
|
"float.hcl",
|
|
"float.json",
|
|
},
|
|
/*
|
|
{
|
|
"structure.hcl",
|
|
"structure.json",
|
|
},
|
|
*/
|
|
{
|
|
"structure.hcl",
|
|
"structure_flat.json",
|
|
},
|
|
{
|
|
"terraform_heroku.hcl",
|
|
"terraform_heroku.json",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
p1 := filepath.Join(fixtureDir, tc.One)
|
|
p2 := filepath.Join(fixtureDir, tc.Two)
|
|
|
|
d1, err := ioutil.ReadFile(p1)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
d2, err := ioutil.ReadFile(p2)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
var i1, i2 interface{}
|
|
err = Decode(&i1, string(d1))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
err = Decode(&i2, string(d2))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(i1, i2) {
|
|
t.Fatalf(
|
|
"%s != %s\n\n%#v\n\n%#v",
|
|
tc.One, tc.Two,
|
|
i1, i2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_flatMap(t *testing.T) {
|
|
var val map[string]map[string]string
|
|
|
|
err := Decode(&val, testReadFile(t, "structure_flatmap.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := map[string]map[string]string{
|
|
"foo": map[string]string{
|
|
"foo": "bar",
|
|
"key": "7",
|
|
},
|
|
}
|
|
|
|
if !reflect.DeepEqual(val, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", val, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structure(t *testing.T) {
|
|
type Embedded interface{}
|
|
|
|
type V struct {
|
|
Embedded `hcl:"-"`
|
|
Key int
|
|
Foo string
|
|
}
|
|
|
|
var actual V
|
|
|
|
err := Decode(&actual, testReadFile(t, "flat.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := V{
|
|
Key: 7,
|
|
Foo: "bar",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structurePtr(t *testing.T) {
|
|
type V struct {
|
|
Key int
|
|
Foo string
|
|
}
|
|
|
|
var actual *V
|
|
|
|
err := Decode(&actual, testReadFile(t, "flat.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := &V{
|
|
Key: 7,
|
|
Foo: "bar",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureArray(t *testing.T) {
|
|
// This test is extracted from a failure in Consul (consul.io),
|
|
// hence the interesting structure naming.
|
|
|
|
type KeyPolicyType string
|
|
|
|
type KeyPolicy struct {
|
|
Prefix string `hcl:",key"`
|
|
Policy KeyPolicyType
|
|
}
|
|
|
|
type Policy struct {
|
|
Keys []KeyPolicy `hcl:"key,expand"`
|
|
}
|
|
|
|
expected := Policy{
|
|
Keys: []KeyPolicy{
|
|
KeyPolicy{
|
|
Prefix: "",
|
|
Policy: "read",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/",
|
|
Policy: "write",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/bar/",
|
|
Policy: "read",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/bar/baz",
|
|
Policy: "deny",
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"decode_policy.hcl",
|
|
"decode_policy.json",
|
|
}
|
|
|
|
for _, f := range files {
|
|
var actual Policy
|
|
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_sliceExpand(t *testing.T) {
|
|
type testInner struct {
|
|
Name string `hcl:",key"`
|
|
Key string
|
|
}
|
|
|
|
type testStruct struct {
|
|
Services []testInner `hcl:"service,expand"`
|
|
}
|
|
|
|
expected := testStruct{
|
|
Services: []testInner{
|
|
testInner{
|
|
Name: "my-service-0",
|
|
Key: "value",
|
|
},
|
|
testInner{
|
|
Name: "my-service-1",
|
|
Key: "value",
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"slice_expand.hcl",
|
|
}
|
|
|
|
for _, f := range files {
|
|
t.Logf("Testing: %s", f)
|
|
|
|
var actual testStruct
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureMap(t *testing.T) {
|
|
// This test is extracted from a failure in Terraform (terraform.io),
|
|
// hence the interesting structure naming.
|
|
|
|
type hclVariable struct {
|
|
Default interface{}
|
|
Description string
|
|
Fields []string `hcl:",decodedFields"`
|
|
}
|
|
|
|
type rawConfig struct {
|
|
Variable map[string]hclVariable
|
|
}
|
|
|
|
expected := rawConfig{
|
|
Variable: map[string]hclVariable{
|
|
"foo": hclVariable{
|
|
Default: "bar",
|
|
Description: "bar",
|
|
Fields: []string{"Default", "Description"},
|
|
},
|
|
|
|
"amis": hclVariable{
|
|
Default: []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"east": "foo",
|
|
},
|
|
},
|
|
Fields: []string{"Default"},
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"decode_tf_variable.hcl",
|
|
"decode_tf_variable.json",
|
|
}
|
|
|
|
for _, f := range files {
|
|
t.Logf("Testing: %s", f)
|
|
|
|
var actual rawConfig
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureMapInvalid(t *testing.T) {
|
|
// Terraform GH-8295
|
|
|
|
type hclVariable struct {
|
|
Default interface{}
|
|
Description string
|
|
Fields []string `hcl:",decodedFields"`
|
|
}
|
|
|
|
type rawConfig struct {
|
|
Variable map[string]*hclVariable
|
|
}
|
|
|
|
var actual rawConfig
|
|
err := Decode(&actual, testReadFile(t, "terraform_variable_invalid.json"))
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureMapExtraKeys(t *testing.T) {
|
|
type hclVariable struct {
|
|
A int
|
|
B int
|
|
Found []string `hcl:",decodedFields"`
|
|
Extra []string `hcl:",unusedKeys"`
|
|
}
|
|
|
|
q := hclVariable{
|
|
A: 1,
|
|
B: 2,
|
|
Found: []string{"A", "B"},
|
|
Extra: []string{"extra1", "extra2"},
|
|
}
|
|
|
|
var p hclVariable
|
|
ast, _ := Parse(testReadFile(t, "structure_map_extra_keys.hcl"))
|
|
DecodeObject(&p, ast)
|
|
if !(p.A == q.A && p.B == q.B &&
|
|
reflect.DeepEqual(p.Found, q.Found) &&
|
|
reflect.DeepEqual(p.Extra, q.Extra)) {
|
|
t.Fatal("not equal")
|
|
}
|
|
|
|
var j hclVariable
|
|
ast, _ = Parse(testReadFile(t, "structure_map_extra_keys.json"))
|
|
DecodeObject(&j, ast)
|
|
if !(j.A == q.A && j.B == q.B &&
|
|
reflect.DeepEqual(j.Found, q.Found) &&
|
|
reflect.DeepEqual(j.Extra, q.Extra)) {
|
|
t.Fatal("not equal")
|
|
}
|
|
}
|
|
|
|
func TestDecode_interfaceNonPointer(t *testing.T) {
|
|
var value interface{}
|
|
err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
|
|
if err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_boolString(t *testing.T) {
|
|
var value struct {
|
|
Boolean bool
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_bool_string.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Boolean != true {
|
|
t.Fatalf("bad: %#v", value.Boolean)
|
|
}
|
|
}
|
|
|
|
func TestDecode_boolInt(t *testing.T) {
|
|
var value struct {
|
|
Boolean bool
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_bool_int.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Boolean != true {
|
|
t.Fatalf("bad: %#v", value.Boolean)
|
|
}
|
|
}
|
|
|
|
func TestDecode_bool(t *testing.T) {
|
|
var value struct {
|
|
Boolean bool
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_bool.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Boolean != true {
|
|
t.Fatalf("bad: %#v", value.Boolean)
|
|
}
|
|
}
|
|
|
|
func TestDecode_intString(t *testing.T) {
|
|
var value struct {
|
|
Count int
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Count != 3 {
|
|
t.Fatalf("bad: %#v", value.Count)
|
|
}
|
|
}
|
|
|
|
func TestDecode_float32(t *testing.T) {
|
|
var value struct {
|
|
A float32 `hcl:"a"`
|
|
B float32 `hcl:"b"`
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "float.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if got, want := value.A, float32(1.02); got != want {
|
|
t.Fatalf("wrong result %#v; want %#v", got, want)
|
|
}
|
|
if got, want := value.B, float32(2); got != want {
|
|
t.Fatalf("wrong result %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDecode_float64(t *testing.T) {
|
|
var value struct {
|
|
A float64 `hcl:"a"`
|
|
B float64 `hcl:"b"`
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "float.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if got, want := value.A, float64(1.02); got != want {
|
|
t.Fatalf("wrong result %#v; want %#v", got, want)
|
|
}
|
|
if got, want := value.B, float64(2); got != want {
|
|
t.Fatalf("wrong result %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDecode_intStringAliased(t *testing.T) {
|
|
var value struct {
|
|
Count time.Duration
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Count != time.Duration(3) {
|
|
t.Fatalf("bad: %#v", value.Count)
|
|
}
|
|
}
|
|
|
|
func TestDecode_Node(t *testing.T) {
|
|
// given
|
|
var value struct {
|
|
Content ast.Node
|
|
Nested struct {
|
|
Content ast.Node
|
|
}
|
|
}
|
|
|
|
content := `
|
|
content {
|
|
hello = "world"
|
|
}
|
|
`
|
|
|
|
// when
|
|
err := Decode(&value, content)
|
|
|
|
// then
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
// verify ast.Node can be decoded later
|
|
var v map[string]interface{}
|
|
err = DecodeObject(&v, value.Content)
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
if v["hello"] != "world" {
|
|
t.Errorf("expected mapping to be returned")
|
|
}
|
|
}
|
|
|
|
func TestDecode_NestedNode(t *testing.T) {
|
|
// given
|
|
var value struct {
|
|
Nested struct {
|
|
Content ast.Node
|
|
}
|
|
}
|
|
|
|
content := `
|
|
nested "content" {
|
|
hello = "world"
|
|
}
|
|
`
|
|
|
|
// when
|
|
err := Decode(&value, content)
|
|
|
|
// then
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
// verify ast.Node can be decoded later
|
|
var v map[string]interface{}
|
|
err = DecodeObject(&v, value.Nested.Content)
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
if v["hello"] != "world" {
|
|
t.Errorf("expected mapping to be returned")
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/hcl/issues/60
|
|
func TestDecode_topLevelKeys(t *testing.T) {
|
|
type Template struct {
|
|
Source string
|
|
}
|
|
|
|
templates := struct {
|
|
Templates []*Template `hcl:"template"`
|
|
}{}
|
|
|
|
err := Decode(&templates, `
|
|
template {
|
|
source = "blah"
|
|
}
|
|
|
|
template {
|
|
source = "blahblah"
|
|
}`)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if templates.Templates[0].Source != "blah" {
|
|
t.Errorf("bad source: %s", templates.Templates[0].Source)
|
|
}
|
|
|
|
if templates.Templates[1].Source != "blahblah" {
|
|
t.Errorf("bad source: %s", templates.Templates[1].Source)
|
|
}
|
|
}
|
|
|
|
func TestDecode_flattenedJSON(t *testing.T) {
|
|
// make sure we can also correctly extract a Name key too
|
|
type V struct {
|
|
Name string `hcl:",key"`
|
|
Description string
|
|
Default map[string]string
|
|
}
|
|
type Vars struct {
|
|
Variable []*V
|
|
}
|
|
|
|
cases := []struct {
|
|
JSON string
|
|
Out interface{}
|
|
Expected interface{}
|
|
}{
|
|
{ // Nested object, no sibling keys
|
|
JSON: `
|
|
{
|
|
"var_name": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]*V{},
|
|
Expected: &[]*V{
|
|
&V{
|
|
Name: "var_name",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object with a sibling key (this worked previously)
|
|
JSON: `
|
|
{
|
|
"var_name": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]*V{},
|
|
Expected: &[]*V{
|
|
&V{
|
|
Name: "var_name",
|
|
Description: "Described",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Multiple nested objects, one with a sibling key
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_1": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
},
|
|
"var_2": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &Vars{},
|
|
Expected: &Vars{
|
|
Variable: []*V{
|
|
&V{
|
|
Name: "var_1",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
&V{
|
|
Name: "var_2",
|
|
Description: "Described",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object to maps
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_name": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_name": []map[string]interface{}{
|
|
{
|
|
"description": "Described",
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object to maps without a sibling key should decode the same as above
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_name": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_name": []map[string]interface{}{
|
|
{
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested objects, one with a sibling key, and one without
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_1": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
},
|
|
"var_2": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_1": []map[string]interface{}{
|
|
{
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_2": []map[string]interface{}{
|
|
{
|
|
"description": "Described",
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
err := Decode(tc.Out, tc.JSON)
|
|
if err != nil {
|
|
t.Fatalf("[%d] err: %s", i, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(tc.Out, tc.Expected) {
|
|
t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
|
|
}
|
|
}
|
|
}
|