github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/parser_config_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package configs 5 6 import ( 7 "bufio" 8 "bytes" 9 "io/ioutil" 10 "path/filepath" 11 "strings" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 16 "github.com/hashicorp/hcl/v2" 17 ) 18 19 // TestParseLoadConfigFileSuccess is a simple test that just verifies that 20 // a number of test configuration files (in testdata/valid-files) can 21 // be parsed without raising any diagnostics. 22 // 23 // This test does not verify that reading these files produces the correct 24 // file element contents. More detailed assertions may be made on some subset 25 // of these configuration files in other tests. 26 func TestParserLoadConfigFileSuccess(t *testing.T) { 27 files, err := ioutil.ReadDir("testdata/valid-files") 28 if err != nil { 29 t.Fatal(err) 30 } 31 32 for _, info := range files { 33 name := info.Name() 34 t.Run(name, func(t *testing.T) { 35 src, err := ioutil.ReadFile(filepath.Join("testdata/valid-files", name)) 36 if err != nil { 37 t.Fatal(err) 38 } 39 40 parser := testParser(map[string]string{ 41 name: string(src), 42 }) 43 44 _, diags := parser.LoadConfigFile(name) 45 if len(diags) != 0 { 46 t.Errorf("unexpected diagnostics") 47 for _, diag := range diags { 48 t.Logf("- %s", diag) 49 } 50 } 51 }) 52 } 53 } 54 55 // TestParseLoadConfigFileFailure is a simple test that just verifies that 56 // a number of test configuration files (in testdata/invalid-files) 57 // produce errors as expected. 58 // 59 // This test does not verify specific error messages, so more detailed 60 // assertions should be made on some subset of these configuration files in 61 // other tests. 62 func TestParserLoadConfigFileFailure(t *testing.T) { 63 files, err := ioutil.ReadDir("testdata/invalid-files") 64 if err != nil { 65 t.Fatal(err) 66 } 67 68 for _, info := range files { 69 name := info.Name() 70 t.Run(name, func(t *testing.T) { 71 src, err := ioutil.ReadFile(filepath.Join("testdata/invalid-files", name)) 72 if err != nil { 73 t.Fatal(err) 74 } 75 76 parser := testParser(map[string]string{ 77 name: string(src), 78 }) 79 80 _, diags := parser.LoadConfigFile(name) 81 if !diags.HasErrors() { 82 t.Errorf("LoadConfigFile succeeded; want errors") 83 } 84 for _, diag := range diags { 85 t.Logf("- %s", diag) 86 } 87 }) 88 } 89 } 90 91 // This test uses a subset of the same fixture files as 92 // TestParserLoadConfigFileFailure, but additionally verifies that each 93 // file produces the expected diagnostic summary. 94 func TestParserLoadConfigFileFailureMessages(t *testing.T) { 95 tests := []struct { 96 Filename string 97 WantSeverity hcl.DiagnosticSeverity 98 WantDiag string 99 }{ 100 { 101 "invalid-files/data-resource-lifecycle.tf", 102 hcl.DiagError, 103 "Invalid data resource lifecycle argument", 104 }, 105 { 106 "invalid-files/variable-type-unknown.tf", 107 hcl.DiagError, 108 "Invalid type specification", 109 }, 110 { 111 "invalid-files/unexpected-attr.tf", 112 hcl.DiagError, 113 "Unsupported argument", 114 }, 115 { 116 "invalid-files/unexpected-block.tf", 117 hcl.DiagError, 118 "Unsupported block type", 119 }, 120 { 121 "invalid-files/resource-count-and-for_each.tf", 122 hcl.DiagError, 123 `Invalid combination of "count" and "for_each"`, 124 }, 125 { 126 "invalid-files/data-count-and-for_each.tf", 127 hcl.DiagError, 128 `Invalid combination of "count" and "for_each"`, 129 }, 130 { 131 "invalid-files/resource-lifecycle-badbool.tf", 132 hcl.DiagError, 133 "Unsuitable value type", 134 }, 135 } 136 137 for _, test := range tests { 138 t.Run(test.Filename, func(t *testing.T) { 139 src, err := ioutil.ReadFile(filepath.Join("testdata", test.Filename)) 140 if err != nil { 141 t.Fatal(err) 142 } 143 144 parser := testParser(map[string]string{ 145 test.Filename: string(src), 146 }) 147 148 _, diags := parser.LoadConfigFile(test.Filename) 149 if len(diags) != 1 { 150 t.Errorf("Wrong number of diagnostics %d; want 1", len(diags)) 151 for _, diag := range diags { 152 t.Logf("- %s", diag) 153 } 154 return 155 } 156 if diags[0].Severity != test.WantSeverity { 157 t.Errorf("Wrong diagnostic severity %#v; want %#v", diags[0].Severity, test.WantSeverity) 158 } 159 if diags[0].Summary != test.WantDiag { 160 t.Errorf("Wrong diagnostic summary\ngot: %s\nwant: %s", diags[0].Summary, test.WantDiag) 161 } 162 }) 163 } 164 } 165 166 // TestParseLoadConfigFileWarning is a test that verifies files from 167 // testdata/warning-files produce particular warnings. 168 // 169 // This test does not verify that reading these files produces the correct 170 // file element contents in spite of those warnings. More detailed assertions 171 // may be made on some subset of these configuration files in other tests. 172 func TestParserLoadConfigFileWarning(t *testing.T) { 173 files, err := ioutil.ReadDir("testdata/warning-files") 174 if err != nil { 175 t.Fatal(err) 176 } 177 178 for _, info := range files { 179 name := info.Name() 180 t.Run(name, func(t *testing.T) { 181 src, err := ioutil.ReadFile(filepath.Join("testdata/warning-files", name)) 182 if err != nil { 183 t.Fatal(err) 184 } 185 186 // First we'll scan the file to see what warnings are expected. 187 // That's declared inside the files themselves by using the 188 // string "WARNING: " somewhere on each line that is expected 189 // to produce a warning, followed by the expected warning summary 190 // text. A single-line comment (with #) is the main way to do that. 191 const marker = "WARNING: " 192 sc := bufio.NewScanner(bytes.NewReader(src)) 193 wantWarnings := make(map[int]string) 194 lineNum := 1 195 for sc.Scan() { 196 lineText := sc.Text() 197 if idx := strings.Index(lineText, marker); idx != -1 { 198 summaryText := lineText[idx+len(marker):] 199 wantWarnings[lineNum] = summaryText 200 } 201 lineNum++ 202 } 203 204 parser := testParser(map[string]string{ 205 name: string(src), 206 }) 207 208 _, diags := parser.LoadConfigFile(name) 209 if diags.HasErrors() { 210 t.Errorf("unexpected error diagnostics") 211 for _, diag := range diags { 212 t.Logf("- %s", diag) 213 } 214 } 215 216 gotWarnings := make(map[int]string) 217 for _, diag := range diags { 218 if diag.Severity != hcl.DiagWarning || diag.Subject == nil { 219 continue 220 } 221 gotWarnings[diag.Subject.Start.Line] = diag.Summary 222 } 223 224 if diff := cmp.Diff(wantWarnings, gotWarnings); diff != "" { 225 t.Errorf("wrong warnings\n%s", diff) 226 } 227 }) 228 } 229 } 230 231 // TestParseLoadConfigFileError is a test that verifies files from 232 // testdata/warning-files produce particular errors. 233 // 234 // This test does not verify that reading these files produces the correct 235 // file element contents in spite of those errors. More detailed assertions 236 // may be made on some subset of these configuration files in other tests. 237 func TestParserLoadConfigFileError(t *testing.T) { 238 files, err := ioutil.ReadDir("testdata/error-files") 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 for _, info := range files { 244 name := info.Name() 245 t.Run(name, func(t *testing.T) { 246 src, err := ioutil.ReadFile(filepath.Join("testdata/error-files", name)) 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 // First we'll scan the file to see what warnings are expected. 252 // That's declared inside the files themselves by using the 253 // string "ERROR: " somewhere on each line that is expected 254 // to produce a warning, followed by the expected warning summary 255 // text. A single-line comment (with #) is the main way to do that. 256 const marker = "ERROR: " 257 sc := bufio.NewScanner(bytes.NewReader(src)) 258 wantErrors := make(map[int]string) 259 lineNum := 1 260 for sc.Scan() { 261 lineText := sc.Text() 262 if idx := strings.Index(lineText, marker); idx != -1 { 263 summaryText := lineText[idx+len(marker):] 264 wantErrors[lineNum] = summaryText 265 } 266 lineNum++ 267 } 268 269 parser := testParser(map[string]string{ 270 name: string(src), 271 }) 272 273 _, diags := parser.LoadConfigFile(name) 274 275 gotErrors := make(map[int]string) 276 for _, diag := range diags { 277 if diag.Severity != hcl.DiagError || diag.Subject == nil { 278 continue 279 } 280 gotErrors[diag.Subject.Start.Line] = diag.Summary 281 } 282 283 if diff := cmp.Diff(wantErrors, gotErrors); diff != "" { 284 t.Errorf("wrong errors\n%s", diff) 285 } 286 }) 287 } 288 }