github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/round_trip_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "bytes" 8 "testing" 9 10 "github.com/sergi/go-diff/diffmatchpatch" 11 "github.com/zclconf/go-cty/cty" 12 "github.com/zclconf/go-cty/cty/function" 13 "github.com/zclconf/go-cty/cty/function/stdlib" 14 15 "github.com/hashicorp/hcl/v2" 16 "github.com/hashicorp/hcl/v2/hclsyntax" 17 ) 18 19 func TestRoundTripVerbatim(t *testing.T) { 20 tests := []string{ 21 ``, 22 `foo = 1 23 `, 24 ` 25 foobar = 1 26 baz = 1 27 `, 28 ` 29 # this file is awesome 30 31 # tossed salads and scrambled eggs 32 foobar = 1 33 baz = 1 34 35 block { 36 a = "a" 37 b = "b" 38 c = "c" 39 d = "d" 40 41 subblock { 42 } 43 44 subblock { 45 e = "e" 46 } 47 } 48 49 # and they all lived happily ever after 50 `, 51 } 52 53 for _, test := range tests { 54 t.Run(test, func(t *testing.T) { 55 src := []byte(test) 56 file, diags := parse(src, "", hcl.Pos{Line: 1, Column: 1}) 57 if len(diags) != 0 { 58 for _, diag := range diags { 59 t.Logf(" - %s", diag.Error()) 60 } 61 t.Fatalf("unexpected diagnostics") 62 } 63 64 wr := &bytes.Buffer{} 65 n, err := file.WriteTo(wr) 66 if n != int64(len(test)) { 67 t.Errorf("wrong number of bytes %d; want %d", n, len(test)) 68 } 69 if err != nil { 70 t.Fatalf("error from WriteTo") 71 } 72 73 result := wr.Bytes() 74 75 if !bytes.Equal(result, src) { 76 dmp := diffmatchpatch.New() 77 diffs := dmp.DiffMain(string(src), string(result), false) 78 // t.Errorf("wrong result\nresult:\n%s\ninput:\n%s", result, src) 79 t.Errorf("wrong result\ndiff: (red indicates missing lines, and green indicates unexpected lines)\n%s", dmp.DiffPrettyText(diffs)) 80 } 81 }) 82 } 83 } 84 85 func TestRoundTripFormat(t *testing.T) { 86 // The goal of this test is to verify that the formatter doesn't change 87 // the semantics of any expressions when it adds and removes whitespace. 88 // String templates are the primary area of concern here, but we also 89 // test some other things for completeness sake. 90 // 91 // The tests here must define zero or more attributes, which will be 92 // extract with JustAttributes and evaluated both before and after 93 // formatting. 94 95 tests := []string{ 96 "", 97 "\n\n\n", 98 "a=1\n", 99 "a=\"hello\"\n", 100 "a=\"${hello} world\"\n", 101 "a=upper(\"hello\")\n", 102 "a=upper(hello)\n", 103 "a=[1,2,3,4,five]\n", 104 "a={greeting=hello}\n", 105 "a={\ngreeting=hello\n}\n", 106 "a={\ngreeting=hello}\n", 107 "a={greeting=hello\n}\n", 108 "a={greeting=hello,number=five,sarcastic=\"${upper(hello)}\"\n}\n", 109 "a={\ngreeting=hello\nnumber=five\nsarcastic=\"${upper(hello)}\"\n}\n", 110 "a=<<EOT\nhello\nEOT\n\n", 111 "a=[<<EOT\nhello\nEOT\n]\n", 112 "a=[\n<<EOT\nhello\nEOT\n]\n", 113 "a=[\n]\n", 114 "a=1\nb=2\nc=3\n", 115 "a=\"${\n5\n}\"\n", 116 } 117 118 ctx := &hcl.EvalContext{ 119 Variables: map[string]cty.Value{ 120 "hello": cty.StringVal("hello"), 121 "five": cty.NumberIntVal(5), 122 }, 123 Functions: map[string]function.Function{ 124 "upper": stdlib.UpperFunc, 125 }, 126 } 127 128 for _, test := range tests { 129 t.Run(test, func(t *testing.T) { 130 attrsAsObj := func(src []byte, phase string) cty.Value { 131 t.Logf("source %s:\n%s", phase, src) 132 f, diags := hclsyntax.ParseConfig(src, "", hcl.Pos{Line: 1, Column: 1}) 133 if len(diags) != 0 { 134 for _, diag := range diags { 135 t.Logf(" - %s", diag.Error()) 136 } 137 t.Fatalf("unexpected diagnostics in parse %s", phase) 138 } 139 140 attrs, diags := f.Body.JustAttributes() 141 if len(diags) != 0 { 142 for _, diag := range diags { 143 t.Logf(" - %s", diag.Error()) 144 } 145 t.Fatalf("unexpected diagnostics in JustAttributes %s", phase) 146 } 147 148 vals := map[string]cty.Value{} 149 for k, attr := range attrs { 150 val, diags := attr.Expr.Value(ctx) 151 if len(diags) != 0 { 152 for _, diag := range diags { 153 t.Logf(" - %s", diag.Error()) 154 } 155 t.Fatalf("unexpected diagnostics evaluating %s", phase) 156 } 157 vals[k] = val 158 } 159 return cty.ObjectVal(vals) 160 } 161 162 src := []byte(test) 163 before := attrsAsObj(src, "before") 164 165 formatted := Format(src) 166 after := attrsAsObj(formatted, "after") 167 168 if !after.RawEquals(before) { 169 t.Errorf("mismatching after format\nbefore: %#v\nafter: %#v", before, after) 170 } 171 }) 172 } 173 } 174 175 // TestRoundTripSafeConcurrent concurrently generates a new file. When run with 176 // the race detector it will fail if a data race is detected. 177 func TestRoundTripSafeConcurrent(t *testing.T) { 178 for i := 0; i < 2; i++ { 179 go func() { 180 f := NewEmptyFile() 181 b := f.Body() 182 b.SetAttributeValue("foo", cty.StringVal("bar")) 183 }() 184 } 185 }