github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/tfdiags/diagnostics_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package tfdiags 5 6 import ( 7 "errors" 8 "fmt" 9 "reflect" 10 "strings" 11 "testing" 12 13 "github.com/hashicorp/go-multierror" 14 15 "github.com/davecgh/go-spew/spew" 16 "github.com/hashicorp/hcl/v2" 17 ) 18 19 func TestBuild(t *testing.T) { 20 type diagFlat struct { 21 Severity Severity 22 Summary string 23 Detail string 24 Subject *SourceRange 25 Context *SourceRange 26 } 27 28 tests := map[string]struct { 29 Cons func(Diagnostics) Diagnostics 30 Want []diagFlat 31 }{ 32 "nil": { 33 func(diags Diagnostics) Diagnostics { 34 return diags 35 }, 36 nil, 37 }, 38 "fmt.Errorf": { 39 func(diags Diagnostics) Diagnostics { 40 diags = diags.Append(fmt.Errorf("oh no bad")) 41 return diags 42 }, 43 []diagFlat{ 44 { 45 Severity: Error, 46 Summary: "oh no bad", 47 }, 48 }, 49 }, 50 "errors.New": { 51 func(diags Diagnostics) Diagnostics { 52 diags = diags.Append(errors.New("oh no bad")) 53 return diags 54 }, 55 []diagFlat{ 56 { 57 Severity: Error, 58 Summary: "oh no bad", 59 }, 60 }, 61 }, 62 "hcl.Diagnostic": { 63 func(diags Diagnostics) Diagnostics { 64 diags = diags.Append(&hcl.Diagnostic{ 65 Severity: hcl.DiagError, 66 Summary: "Something bad happened", 67 Detail: "It was really, really bad.", 68 Subject: &hcl.Range{ 69 Filename: "foo.tf", 70 Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 71 End: hcl.Pos{Line: 2, Column: 3, Byte: 25}, 72 }, 73 Context: &hcl.Range{ 74 Filename: "foo.tf", 75 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 76 End: hcl.Pos{Line: 3, Column: 1, Byte: 30}, 77 }, 78 }) 79 return diags 80 }, 81 []diagFlat{ 82 { 83 Severity: Error, 84 Summary: "Something bad happened", 85 Detail: "It was really, really bad.", 86 Subject: &SourceRange{ 87 Filename: "foo.tf", 88 Start: SourcePos{Line: 1, Column: 10, Byte: 9}, 89 End: SourcePos{Line: 2, Column: 3, Byte: 25}, 90 }, 91 Context: &SourceRange{ 92 Filename: "foo.tf", 93 Start: SourcePos{Line: 1, Column: 1, Byte: 0}, 94 End: SourcePos{Line: 3, Column: 1, Byte: 30}, 95 }, 96 }, 97 }, 98 }, 99 "hcl.Diagnostics": { 100 func(diags Diagnostics) Diagnostics { 101 diags = diags.Append(hcl.Diagnostics{ 102 { 103 Severity: hcl.DiagError, 104 Summary: "Something bad happened", 105 Detail: "It was really, really bad.", 106 }, 107 { 108 Severity: hcl.DiagWarning, 109 Summary: "Also, somebody sneezed", 110 Detail: "How rude!", 111 }, 112 }) 113 return diags 114 }, 115 []diagFlat{ 116 { 117 Severity: Error, 118 Summary: "Something bad happened", 119 Detail: "It was really, really bad.", 120 }, 121 { 122 Severity: Warning, 123 Summary: "Also, somebody sneezed", 124 Detail: "How rude!", 125 }, 126 }, 127 }, 128 "multierror.Error": { 129 func(diags Diagnostics) Diagnostics { 130 err := multierror.Append(nil, errors.New("bad thing A")) 131 err = multierror.Append(err, errors.New("bad thing B")) 132 diags = diags.Append(err) 133 return diags 134 }, 135 []diagFlat{ 136 { 137 Severity: Error, 138 Summary: "bad thing A", 139 }, 140 { 141 Severity: Error, 142 Summary: "bad thing B", 143 }, 144 }, 145 }, 146 "concat Diagnostics": { 147 func(diags Diagnostics) Diagnostics { 148 var moreDiags Diagnostics 149 moreDiags = moreDiags.Append(errors.New("bad thing A")) 150 moreDiags = moreDiags.Append(errors.New("bad thing B")) 151 return diags.Append(moreDiags) 152 }, 153 []diagFlat{ 154 { 155 Severity: Error, 156 Summary: "bad thing A", 157 }, 158 { 159 Severity: Error, 160 Summary: "bad thing B", 161 }, 162 }, 163 }, 164 "single Diagnostic": { 165 func(diags Diagnostics) Diagnostics { 166 return diags.Append(SimpleWarning("Don't forget your toothbrush!")) 167 }, 168 []diagFlat{ 169 { 170 Severity: Warning, 171 Summary: "Don't forget your toothbrush!", 172 }, 173 }, 174 }, 175 "multiple appends": { 176 func(diags Diagnostics) Diagnostics { 177 diags = diags.Append(SimpleWarning("Don't forget your toothbrush!")) 178 diags = diags.Append(fmt.Errorf("exploded")) 179 return diags 180 }, 181 []diagFlat{ 182 { 183 Severity: Warning, 184 Summary: "Don't forget your toothbrush!", 185 }, 186 { 187 Severity: Error, 188 Summary: "exploded", 189 }, 190 }, 191 }, 192 } 193 194 for name, test := range tests { 195 t.Run(name, func(t *testing.T) { 196 gotDiags := test.Cons(nil) 197 var got []diagFlat 198 for _, item := range gotDiags { 199 desc := item.Description() 200 source := item.Source() 201 got = append(got, diagFlat{ 202 Severity: item.Severity(), 203 Summary: desc.Summary, 204 Detail: desc.Detail, 205 Subject: source.Subject, 206 Context: source.Context, 207 }) 208 } 209 210 if !reflect.DeepEqual(got, test.Want) { 211 t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want)) 212 } 213 }) 214 } 215 } 216 217 func TestDiagnosticsErr(t *testing.T) { 218 t.Run("empty", func(t *testing.T) { 219 var diags Diagnostics 220 err := diags.Err() 221 if err != nil { 222 t.Errorf("got non-nil error %#v; want nil", err) 223 } 224 }) 225 t.Run("warning only", func(t *testing.T) { 226 var diags Diagnostics 227 diags = diags.Append(SimpleWarning("bad")) 228 err := diags.Err() 229 if err != nil { 230 t.Errorf("got non-nil error %#v; want nil", err) 231 } 232 }) 233 t.Run("one error", func(t *testing.T) { 234 var diags Diagnostics 235 diags = diags.Append(errors.New("didn't work")) 236 err := diags.Err() 237 if err == nil { 238 t.Fatalf("got nil error %#v; want non-nil", err) 239 } 240 if got, want := err.Error(), "didn't work"; got != want { 241 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 242 } 243 }) 244 t.Run("two errors", func(t *testing.T) { 245 var diags Diagnostics 246 diags = diags.Append(errors.New("didn't work")) 247 diags = diags.Append(errors.New("didn't work either")) 248 err := diags.Err() 249 if err == nil { 250 t.Fatalf("got nil error %#v; want non-nil", err) 251 } 252 want := strings.TrimSpace(` 253 2 problems: 254 255 - didn't work 256 - didn't work either 257 `) 258 if got := err.Error(); got != want { 259 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 260 } 261 }) 262 t.Run("error and warning", func(t *testing.T) { 263 var diags Diagnostics 264 diags = diags.Append(errors.New("didn't work")) 265 diags = diags.Append(SimpleWarning("didn't work either")) 266 err := diags.Err() 267 if err == nil { 268 t.Fatalf("got nil error %#v; want non-nil", err) 269 } 270 // Since this "as error" mode is just a fallback for 271 // non-diagnostics-aware situations like tests, we don't actually 272 // distinguish warnings and errors here since the point is to just 273 // get the messages rendered. User-facing code should be printing 274 // each diagnostic separately, so won't enter this codepath, 275 want := strings.TrimSpace(` 276 2 problems: 277 278 - didn't work 279 - didn't work either 280 `) 281 if got := err.Error(); got != want { 282 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 283 } 284 }) 285 } 286 287 func TestDiagnosticsErrWithWarnings(t *testing.T) { 288 t.Run("empty", func(t *testing.T) { 289 var diags Diagnostics 290 err := diags.ErrWithWarnings() 291 if err != nil { 292 t.Errorf("got non-nil error %#v; want nil", err) 293 } 294 }) 295 t.Run("warning only", func(t *testing.T) { 296 var diags Diagnostics 297 diags = diags.Append(SimpleWarning("bad")) 298 err := diags.ErrWithWarnings() 299 if err == nil { 300 t.Errorf("got nil error; want NonFatalError") 301 return 302 } 303 if _, ok := err.(NonFatalError); !ok { 304 t.Errorf("got %T; want NonFatalError", err) 305 } 306 }) 307 t.Run("one error", func(t *testing.T) { 308 var diags Diagnostics 309 diags = diags.Append(errors.New("didn't work")) 310 err := diags.ErrWithWarnings() 311 if err == nil { 312 t.Fatalf("got nil error %#v; want non-nil", err) 313 } 314 if got, want := err.Error(), "didn't work"; got != want { 315 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 316 } 317 }) 318 t.Run("two errors", func(t *testing.T) { 319 var diags Diagnostics 320 diags = diags.Append(errors.New("didn't work")) 321 diags = diags.Append(errors.New("didn't work either")) 322 err := diags.ErrWithWarnings() 323 if err == nil { 324 t.Fatalf("got nil error %#v; want non-nil", err) 325 } 326 want := strings.TrimSpace(` 327 2 problems: 328 329 - didn't work 330 - didn't work either 331 `) 332 if got := err.Error(); got != want { 333 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 334 } 335 }) 336 t.Run("error and warning", func(t *testing.T) { 337 var diags Diagnostics 338 diags = diags.Append(errors.New("didn't work")) 339 diags = diags.Append(SimpleWarning("didn't work either")) 340 err := diags.ErrWithWarnings() 341 if err == nil { 342 t.Fatalf("got nil error %#v; want non-nil", err) 343 } 344 // Since this "as error" mode is just a fallback for 345 // non-diagnostics-aware situations like tests, we don't actually 346 // distinguish warnings and errors here since the point is to just 347 // get the messages rendered. User-facing code should be printing 348 // each diagnostic separately, so won't enter this codepath, 349 want := strings.TrimSpace(` 350 2 problems: 351 352 - didn't work 353 - didn't work either 354 `) 355 if got := err.Error(); got != want { 356 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 357 } 358 }) 359 } 360 361 func TestDiagnosticsNonFatalErr(t *testing.T) { 362 t.Run("empty", func(t *testing.T) { 363 var diags Diagnostics 364 err := diags.NonFatalErr() 365 if err != nil { 366 t.Errorf("got non-nil error %#v; want nil", err) 367 } 368 }) 369 t.Run("warning only", func(t *testing.T) { 370 var diags Diagnostics 371 diags = diags.Append(SimpleWarning("bad")) 372 err := diags.NonFatalErr() 373 if err == nil { 374 t.Errorf("got nil error; want NonFatalError") 375 return 376 } 377 if _, ok := err.(NonFatalError); !ok { 378 t.Errorf("got %T; want NonFatalError", err) 379 } 380 }) 381 t.Run("one error", func(t *testing.T) { 382 var diags Diagnostics 383 diags = diags.Append(errors.New("didn't work")) 384 err := diags.NonFatalErr() 385 if err == nil { 386 t.Fatalf("got nil error %#v; want non-nil", err) 387 } 388 if got, want := err.Error(), "didn't work"; got != want { 389 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 390 } 391 if _, ok := err.(NonFatalError); !ok { 392 t.Errorf("got %T; want NonFatalError", err) 393 } 394 }) 395 t.Run("two errors", func(t *testing.T) { 396 var diags Diagnostics 397 diags = diags.Append(errors.New("didn't work")) 398 diags = diags.Append(errors.New("didn't work either")) 399 err := diags.NonFatalErr() 400 if err == nil { 401 t.Fatalf("got nil error %#v; want non-nil", err) 402 } 403 want := strings.TrimSpace(` 404 2 problems: 405 406 - didn't work 407 - didn't work either 408 `) 409 if got := err.Error(); got != want { 410 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 411 } 412 if _, ok := err.(NonFatalError); !ok { 413 t.Errorf("got %T; want NonFatalError", err) 414 } 415 }) 416 t.Run("error and warning", func(t *testing.T) { 417 var diags Diagnostics 418 diags = diags.Append(errors.New("didn't work")) 419 diags = diags.Append(SimpleWarning("didn't work either")) 420 err := diags.NonFatalErr() 421 if err == nil { 422 t.Fatalf("got nil error %#v; want non-nil", err) 423 } 424 // Since this "as error" mode is just a fallback for 425 // non-diagnostics-aware situations like tests, we don't actually 426 // distinguish warnings and errors here since the point is to just 427 // get the messages rendered. User-facing code should be printing 428 // each diagnostic separately, so won't enter this codepath, 429 want := strings.TrimSpace(` 430 2 problems: 431 432 - didn't work 433 - didn't work either 434 `) 435 if got := err.Error(); got != want { 436 t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) 437 } 438 if _, ok := err.(NonFatalError); !ok { 439 t.Errorf("got %T; want NonFatalError", err) 440 } 441 }) 442 }