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