10 "github.com/davecgh/go-spew/spew"
11 "github.com/hashicorp/hcl/hcl/ast"
14 func TestDecode_interface(t *testing.T) {
23 map[string]interface{}{
25 "bar": "${file(\"bing/bong.txt\")}",
31 map[string]interface{}{
33 "bar": "${file(\"bing/bong.txt\")}",
40 map[string]interface{}{
41 "resource": []map[string]interface{}{
42 map[string]interface{}{
43 "foo": []map[string]interface{}{
44 map[string]interface{}{},
53 map[string]interface{}{
54 "regularvar": "Should work",
56 "map.key2": "Other value",
62 map[string]interface{}{
66 "qax": `slash\:colon`,
67 "nested": `${HH\\:mm\\:ss}`,
68 "nestedquotes": `${"\"stringwrappedinquotes\""}`,
74 map[string]interface{}{
85 "multiline_literal.hcl",
90 "multiline_literal_with_hil.hcl",
92 map[string]interface{}{"multiline_literal_with_hil": "${hello\n world}"},
95 "multiline_no_marker.hcl",
102 map[string]interface{}{"foo": "bar\nbaz\n"},
105 "multiline_indented.hcl",
107 map[string]interface{}{"foo": " bar\n baz\n"},
110 "multiline_no_hanging_indent.hcl",
112 map[string]interface{}{"foo": " baz\n bar\n foo\n"},
115 "multiline_no_eof.hcl",
117 map[string]interface{}{"foo": "bar\nbaz\n", "key": "value"},
122 map[string]interface{}{"foo": "bar\nbaz"},
127 map[string]interface{}{
128 "module": []map[string]interface{}{
129 map[string]interface{}{
130 "app": []map[string]interface{}{
131 map[string]interface{}{"foo": ""},
140 map[string]interface{}{
152 map[string]interface{}{
162 "terraform_heroku.hcl",
164 map[string]interface{}{
165 "name": "terraform-test-app",
166 "config_vars": []map[string]interface{}{
167 map[string]interface{}{
174 "structure_multi.hcl",
176 map[string]interface{}{
177 "foo": []map[string]interface{}{
178 map[string]interface{}{
179 "baz": []map[string]interface{}{
180 map[string]interface{}{"key": 7},
183 map[string]interface{}{
184 "bar": []map[string]interface{}{
185 map[string]interface{}{"key": 12},
192 "structure_multi.json",
194 map[string]interface{}{
195 "foo": []map[string]interface{}{
196 map[string]interface{}{
197 "baz": []map[string]interface{}{
198 map[string]interface{}{"key": 7},
201 map[string]interface{}{
202 "bar": []map[string]interface{}{
203 map[string]interface{}{"key": 12},
212 map[string]interface{}{
213 "foo": []interface{}{
214 []interface{}{"foo"},
215 []interface{}{"bar"},
222 map[string]interface{}{
223 "foo": []interface{}{
224 map[string]interface{}{"somekey1": "someval1"},
225 map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
232 map[string]interface{}{
233 "resource": []interface{}{
234 map[string]interface{}{
235 "foo": []interface{}{
236 map[string]interface{}{
237 "bar": []map[string]interface{}{
238 map[string]interface{}{}}}}}}},
241 "structure_list.hcl",
243 map[string]interface{}{
244 "foo": []map[string]interface{}{
245 map[string]interface{}{
248 map[string]interface{}{
255 "structure_list.json",
257 map[string]interface{}{
258 "foo": []map[string]interface{}{
259 map[string]interface{}{
262 map[string]interface{}{
269 "structure_list_deep.json",
271 map[string]interface{}{
272 "bar": []map[string]interface{}{
273 map[string]interface{}{
274 "foo": []map[string]interface{}{
275 map[string]interface{}{
276 "name": "terraform_example",
277 "ingress": []map[string]interface{}{
278 map[string]interface{}{
281 map[string]interface{}{
293 "structure_list_empty.json",
295 map[string]interface{}{
296 "foo": []interface{}{},
301 "nested_block_comment.hcl",
303 map[string]interface{}{
309 "unterminated_block_comment.hcl",
315 "unterminated_brace.hcl",
321 "nested_provider_bad.hcl",
329 map[string]interface{}{
330 "resource": []map[string]interface{}{
331 map[string]interface{}{
332 "aws_instance": []map[string]interface{}{
333 map[string]interface{}{
334 "db": []map[string]interface{}{
335 map[string]interface{}{
337 "provisioner": []map[string]interface{}{
338 map[string]interface{}{
339 "file": []map[string]interface{}{
340 map[string]interface{}{
342 "destination": "bar",
356 // Terraform GH-8295 sanity test that basic decoding into
357 // interface{} works.
359 "terraform_variable_invalid.json",
361 map[string]interface{}{
362 "variable": []map[string]interface{}{
363 map[string]interface{}{
364 "whatever": "abc123",
373 map[string]interface{}{
374 "default": `${replace("europe-west", "-", " ")}`,
385 "escape_backslash.hcl",
387 map[string]interface{}{
388 "output": []map[string]interface{}{
389 map[string]interface{}{
390 "one": `${replace(var.sub_domain, ".", "\\.")}`,
391 "two": `${replace(var.sub_domain, ".", "\\\\.")}`,
392 "many": `${replace(var.sub_domain, ".", "\\\\\\\\.")}`,
405 "object_with_bool.hcl",
407 map[string]interface{}{
408 "path": []map[string]interface{}{
409 map[string]interface{}{
411 "permissions": []map[string]interface{}{
412 map[string]interface{}{
413 "bool": []interface{}{false},
422 for _, tc := range cases {
423 t.Run(tc.File, func(t *testing.T) {
424 d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.File))
426 t.Fatalf("err: %s", err)
430 err = Decode(&out, string(d))
431 if (err != nil) != tc.Err {
432 t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
435 if !reflect.DeepEqual(out, tc.Out) {
436 t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
440 err = Unmarshal(d, &v)
441 if (err != nil) != tc.Err {
442 t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
445 if !reflect.DeepEqual(v, tc.Out) {
446 t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
452 func TestDecode_interfaceInline(t *testing.T) {
458 {"t t e{{}}", true, nil},
459 {"t=0t d {}", true, map[string]interface{}{"t": 0}},
460 {"v=0E0v d{}", true, map[string]interface{}{"v": float64(0)}},
463 for _, tc := range cases {
464 t.Logf("Testing: %q", tc.Value)
467 err := Decode(&out, tc.Value)
468 if (err != nil) != tc.Err {
469 t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
472 if !reflect.DeepEqual(out, tc.Out) {
473 t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
477 err = Unmarshal([]byte(tc.Value), &v)
478 if (err != nil) != tc.Err {
479 t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
482 if !reflect.DeepEqual(v, tc.Out) {
483 t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
488 func TestDecode_equal(t *testing.T) {
508 "structure_flat.json",
511 "terraform_heroku.hcl",
512 "terraform_heroku.json",
516 for _, tc := range cases {
517 p1 := filepath.Join(fixtureDir, tc.One)
518 p2 := filepath.Join(fixtureDir, tc.Two)
520 d1, err := ioutil.ReadFile(p1)
522 t.Fatalf("err: %s", err)
525 d2, err := ioutil.ReadFile(p2)
527 t.Fatalf("err: %s", err)
530 var i1, i2 interface{}
531 err = Decode(&i1, string(d1))
533 t.Fatalf("err: %s", err)
536 err = Decode(&i2, string(d2))
538 t.Fatalf("err: %s", err)
541 if !reflect.DeepEqual(i1, i2) {
543 "%s != %s\n\n%#v\n\n%#v",
550 func TestDecode_flatMap(t *testing.T) {
551 var val map[string]map[string]string
553 err := Decode(&val, testReadFile(t, "structure_flatmap.hcl"))
555 t.Fatalf("err: %s", err)
558 expected := map[string]map[string]string{
559 "foo": map[string]string{
565 if !reflect.DeepEqual(val, expected) {
566 t.Fatalf("Actual: %#v\n\nExpected: %#v", val, expected)
570 func TestDecode_structure(t *testing.T) {
571 type Embedded interface{}
581 err := Decode(&actual, testReadFile(t, "flat.hcl"))
583 t.Fatalf("err: %s", err)
591 if !reflect.DeepEqual(actual, expected) {
592 t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
596 func TestDecode_structurePtr(t *testing.T) {
604 err := Decode(&actual, testReadFile(t, "flat.hcl"))
606 t.Fatalf("err: %s", err)
614 if !reflect.DeepEqual(actual, expected) {
615 t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
619 func TestDecode_structureArray(t *testing.T) {
620 // This test is extracted from a failure in Consul (consul.io),
621 // hence the interesting structure naming.
623 type KeyPolicyType string
625 type KeyPolicy struct {
626 Prefix string `hcl:",key"`
631 Keys []KeyPolicy `hcl:"key,expand"`
649 Prefix: "foo/bar/baz",
657 "decode_policy.json",
660 for _, f := range files {
663 err := Decode(&actual, testReadFile(t, f))
665 t.Fatalf("Input: %s\n\nerr: %s", f, err)
668 if !reflect.DeepEqual(actual, expected) {
669 t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
674 func TestDecode_sliceExpand(t *testing.T) {
675 type testInner struct {
676 Name string `hcl:",key"`
680 type testStruct struct {
681 Services []testInner `hcl:"service,expand"`
684 expected := testStruct{
685 Services: []testInner{
687 Name: "my-service-0",
691 Name: "my-service-1",
701 for _, f := range files {
702 t.Logf("Testing: %s", f)
704 var actual testStruct
705 err := Decode(&actual, testReadFile(t, f))
707 t.Fatalf("Input: %s\n\nerr: %s", f, err)
710 if !reflect.DeepEqual(actual, expected) {
711 t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
716 func TestDecode_structureMap(t *testing.T) {
717 // This test is extracted from a failure in Terraform (terraform.io),
718 // hence the interesting structure naming.
720 type hclVariable struct {
723 Fields []string `hcl:",decodedFields"`
726 type rawConfig struct {
727 Variable map[string]hclVariable
730 expected := rawConfig{
731 Variable: map[string]hclVariable{
735 Fields: []string{"Default", "Description"},
739 Default: []map[string]interface{}{
740 map[string]interface{}{
744 Fields: []string{"Default"},
750 "decode_tf_variable.hcl",
751 "decode_tf_variable.json",
754 for _, f := range files {
755 t.Logf("Testing: %s", f)
758 err := Decode(&actual, testReadFile(t, f))
760 t.Fatalf("Input: %s\n\nerr: %s", f, err)
763 if !reflect.DeepEqual(actual, expected) {
764 t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
769 func TestDecode_structureMapInvalid(t *testing.T) {
772 type hclVariable struct {
775 Fields []string `hcl:",decodedFields"`
778 type rawConfig struct {
779 Variable map[string]*hclVariable
783 err := Decode(&actual, testReadFile(t, "terraform_variable_invalid.json"))
785 t.Fatal("expected error")
789 func TestDecode_interfaceNonPointer(t *testing.T) {
790 var value interface{}
791 err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
793 t.Fatal("should error")
797 func TestDecode_intString(t *testing.T) {
802 err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
804 t.Fatalf("err: %s", err)
807 if value.Count != 3 {
808 t.Fatalf("bad: %#v", value.Count)
812 func TestDecode_float32(t *testing.T) {
818 err := Decode(&value, testReadFile(t, "float.hcl"))
820 t.Fatalf("err: %s", err)
823 if got, want := value.A, float32(1.02); got != want {
824 t.Fatalf("wrong result %#v; want %#v", got, want)
826 if got, want := value.B, float32(2); got != want {
827 t.Fatalf("wrong result %#v; want %#v", got, want)
831 func TestDecode_float64(t *testing.T) {
837 err := Decode(&value, testReadFile(t, "float.hcl"))
839 t.Fatalf("err: %s", err)
842 if got, want := value.A, float64(1.02); got != want {
843 t.Fatalf("wrong result %#v; want %#v", got, want)
845 if got, want := value.B, float64(2); got != want {
846 t.Fatalf("wrong result %#v; want %#v", got, want)
850 func TestDecode_intStringAliased(t *testing.T) {
855 err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
857 t.Fatalf("err: %s", err)
860 if value.Count != time.Duration(3) {
861 t.Fatalf("bad: %#v", value.Count)
865 func TestDecode_Node(t *testing.T) {
881 err := Decode(&value, content)
885 t.Errorf("unable to decode content, %v", err)
889 // verify ast.Node can be decoded later
890 var v map[string]interface{}
891 err = DecodeObject(&v, value.Content)
893 t.Errorf("unable to decode content, %v", err)
897 if v["hello"] != "world" {
898 t.Errorf("expected mapping to be returned")
902 func TestDecode_NestedNode(t *testing.T) {
917 err := Decode(&value, content)
921 t.Errorf("unable to decode content, %v", err)
925 // verify ast.Node can be decoded later
926 var v map[string]interface{}
927 err = DecodeObject(&v, value.Nested.Content)
929 t.Errorf("unable to decode content, %v", err)
933 if v["hello"] != "world" {
934 t.Errorf("expected mapping to be returned")
938 // https://github.com/hashicorp/hcl/issues/60
939 func TestDecode_topLevelKeys(t *testing.T) {
940 type Template struct {
944 templates := struct {
945 Templates []*Template `hcl:"template"`
948 err := Decode(&templates, `
961 if templates.Templates[0].Source != "blah" {
962 t.Errorf("bad source: %s", templates.Templates[0].Source)
965 if templates.Templates[1].Source != "blahblah" {
966 t.Errorf("bad source: %s", templates.Templates[1].Source)
970 func TestDecode_flattenedJSON(t *testing.T) {
971 // make sure we can also correctly extract a Name key too
973 Name string `hcl:",key"`
975 Default map[string]string
986 { // Nested object, no sibling keys
1001 Default: map[string]string{"key1": "a", "key2": "b"},
1006 { // Nested object with a sibling key (this worked previously)
1010 "description": "Described",
1022 Description: "Described",
1023 Default: map[string]string{"key1": "a", "key2": "b"},
1028 { // Multiple nested objects, one with a sibling key
1039 "description": "Described",
1053 Default: map[string]string{"key1": "a", "key2": "b"},
1057 Description: "Described",
1058 Default: map[string]string{"key1": "a", "key2": "b"},
1064 { // Nested object to maps
1069 "description": "Described",
1078 Out: &[]map[string]interface{}{},
1079 Expected: &[]map[string]interface{}{
1081 "variable": []map[string]interface{}{
1083 "var_name": []map[string]interface{}{
1085 "description": "Described",
1086 "default": []map[string]interface{}{
1100 { // Nested object to maps without a sibling key should decode the same as above
1113 Out: &[]map[string]interface{}{},
1114 Expected: &[]map[string]interface{}{
1116 "variable": []map[string]interface{}{
1118 "var_name": []map[string]interface{}{
1120 "default": []map[string]interface{}{
1134 { // Nested objects, one with a sibling key, and one without
1145 "description": "Described",
1154 Out: &[]map[string]interface{}{},
1155 Expected: &[]map[string]interface{}{
1157 "variable": []map[string]interface{}{
1159 "var_1": []map[string]interface{}{
1161 "default": []map[string]interface{}{
1173 "variable": []map[string]interface{}{
1175 "var_2": []map[string]interface{}{
1177 "description": "Described",
1178 "default": []map[string]interface{}{
1193 for i, tc := range cases {
1194 err := Decode(tc.Out, tc.JSON)
1196 t.Fatalf("[%d] err: %s", i, err)
1199 if !reflect.DeepEqual(tc.Out, tc.Expected) {
1200 t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))