github.com/hugorut/terraform@v1.1.3/src/command/format/diagnostic_test.go (about) 1 package format 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hcltest" 10 "github.com/mitchellh/colorstring" 11 "github.com/zclconf/go-cty/cty" 12 13 viewsjson "github.com/hugorut/terraform/src/command/views/json" 14 "github.com/hugorut/terraform/src/lang/marks" 15 16 "github.com/hugorut/terraform/src/tfdiags" 17 ) 18 19 func TestDiagnostic(t *testing.T) { 20 21 tests := map[string]struct { 22 Diag interface{} 23 Want string 24 }{ 25 "sourceless error": { 26 tfdiags.Sourceless( 27 tfdiags.Error, 28 "A sourceless error", 29 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 30 ), 31 `[red]╷[reset] 32 [red]│[reset] [bold][red]Error: [reset][bold]A sourceless error[reset] 33 [red]│[reset] 34 [red]│[reset] It has no source references but it 35 [red]│[reset] does have a pretty long detail that 36 [red]│[reset] should wrap over multiple lines. 37 [red]╵[reset] 38 `, 39 }, 40 "sourceless warning": { 41 tfdiags.Sourceless( 42 tfdiags.Warning, 43 "A sourceless warning", 44 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 45 ), 46 `[yellow]╷[reset] 47 [yellow]│[reset] [bold][yellow]Warning: [reset][bold]A sourceless warning[reset] 48 [yellow]│[reset] 49 [yellow]│[reset] It has no source references but it 50 [yellow]│[reset] does have a pretty long detail that 51 [yellow]│[reset] should wrap over multiple lines. 52 [yellow]╵[reset] 53 `, 54 }, 55 "error with source code subject": { 56 &hcl.Diagnostic{ 57 Severity: hcl.DiagError, 58 Summary: "Bad bad bad", 59 Detail: "Whatever shall we do?", 60 Subject: &hcl.Range{ 61 Filename: "test.tf", 62 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 63 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 64 }, 65 }, 66 `[red]╷[reset] 67 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 68 [red]│[reset] 69 [red]│[reset] on test.tf line 1: 70 [red]│[reset] 1: test [underline]source[reset] code 71 [red]│[reset] 72 [red]│[reset] Whatever shall we do? 73 [red]╵[reset] 74 `, 75 }, 76 "error with source code subject and known expression": { 77 &hcl.Diagnostic{ 78 Severity: hcl.DiagError, 79 Summary: "Bad bad bad", 80 Detail: "Whatever shall we do?", 81 Subject: &hcl.Range{ 82 Filename: "test.tf", 83 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 84 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 85 }, 86 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 87 hcl.TraverseRoot{Name: "boop"}, 88 hcl.TraverseAttr{Name: "beep"}, 89 }), 90 EvalContext: &hcl.EvalContext{ 91 Variables: map[string]cty.Value{ 92 "boop": cty.ObjectVal(map[string]cty.Value{ 93 "beep": cty.StringVal("blah"), 94 }), 95 }, 96 }, 97 }, 98 `[red]╷[reset] 99 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 100 [red]│[reset] 101 [red]│[reset] on test.tf line 1: 102 [red]│[reset] 1: test [underline]source[reset] code 103 [red]│[reset] [dark_gray]├────────────────[reset] 104 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] is "blah" 105 [red]│[reset] 106 [red]│[reset] Whatever shall we do? 107 [red]╵[reset] 108 `, 109 }, 110 "error with source code subject and expression referring to sensitive value": { 111 &hcl.Diagnostic{ 112 Severity: hcl.DiagError, 113 Summary: "Bad bad bad", 114 Detail: "Whatever shall we do?", 115 Subject: &hcl.Range{ 116 Filename: "test.tf", 117 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 118 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 119 }, 120 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 121 hcl.TraverseRoot{Name: "boop"}, 122 hcl.TraverseAttr{Name: "beep"}, 123 }), 124 EvalContext: &hcl.EvalContext{ 125 Variables: map[string]cty.Value{ 126 "boop": cty.ObjectVal(map[string]cty.Value{ 127 "beep": cty.StringVal("blah").Mark(marks.Sensitive), 128 }), 129 }, 130 }, 131 }, 132 `[red]╷[reset] 133 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 134 [red]│[reset] 135 [red]│[reset] on test.tf line 1: 136 [red]│[reset] 1: test [underline]source[reset] code 137 [red]│[reset] [dark_gray]├────────────────[reset] 138 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] has a sensitive value 139 [red]│[reset] 140 [red]│[reset] Whatever shall we do? 141 [red]╵[reset] 142 `, 143 }, 144 "error with source code subject and unknown string expression": { 145 &hcl.Diagnostic{ 146 Severity: hcl.DiagError, 147 Summary: "Bad bad bad", 148 Detail: "Whatever shall we do?", 149 Subject: &hcl.Range{ 150 Filename: "test.tf", 151 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 152 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 153 }, 154 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 155 hcl.TraverseRoot{Name: "boop"}, 156 hcl.TraverseAttr{Name: "beep"}, 157 }), 158 EvalContext: &hcl.EvalContext{ 159 Variables: map[string]cty.Value{ 160 "boop": cty.ObjectVal(map[string]cty.Value{ 161 "beep": cty.UnknownVal(cty.String), 162 }), 163 }, 164 }, 165 }, 166 `[red]╷[reset] 167 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 168 [red]│[reset] 169 [red]│[reset] on test.tf line 1: 170 [red]│[reset] 1: test [underline]source[reset] code 171 [red]│[reset] [dark_gray]├────────────────[reset] 172 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] is a string, known only after apply 173 [red]│[reset] 174 [red]│[reset] Whatever shall we do? 175 [red]╵[reset] 176 `, 177 }, 178 "error with source code subject and unknown expression of unknown type": { 179 &hcl.Diagnostic{ 180 Severity: hcl.DiagError, 181 Summary: "Bad bad bad", 182 Detail: "Whatever shall we do?", 183 Subject: &hcl.Range{ 184 Filename: "test.tf", 185 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 186 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 187 }, 188 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 189 hcl.TraverseRoot{Name: "boop"}, 190 hcl.TraverseAttr{Name: "beep"}, 191 }), 192 EvalContext: &hcl.EvalContext{ 193 Variables: map[string]cty.Value{ 194 "boop": cty.ObjectVal(map[string]cty.Value{ 195 "beep": cty.UnknownVal(cty.DynamicPseudoType), 196 }), 197 }, 198 }, 199 }, 200 `[red]╷[reset] 201 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 202 [red]│[reset] 203 [red]│[reset] on test.tf line 1: 204 [red]│[reset] 1: test [underline]source[reset] code 205 [red]│[reset] [dark_gray]├────────────────[reset] 206 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] will be known only after apply 207 [red]│[reset] 208 [red]│[reset] Whatever shall we do? 209 [red]╵[reset] 210 `, 211 }, 212 } 213 214 sources := map[string][]byte{ 215 "test.tf": []byte(`test source code`), 216 } 217 218 // This empty Colorize just passes through all of the formatting codes 219 // untouched, because it doesn't define any formatting keywords. 220 colorize := &colorstring.Colorize{} 221 222 for name, test := range tests { 223 t.Run(name, func(t *testing.T) { 224 var diags tfdiags.Diagnostics 225 diags = diags.Append(test.Diag) // to normalize it into a tfdiag.Diagnostic 226 diag := diags[0] 227 got := strings.TrimSpace(Diagnostic(diag, sources, colorize, 40)) 228 want := strings.TrimSpace(test.Want) 229 if got != want { 230 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 231 } 232 }) 233 } 234 } 235 236 func TestDiagnosticPlain(t *testing.T) { 237 238 tests := map[string]struct { 239 Diag interface{} 240 Want string 241 }{ 242 "sourceless error": { 243 tfdiags.Sourceless( 244 tfdiags.Error, 245 "A sourceless error", 246 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 247 ), 248 ` 249 Error: A sourceless error 250 251 It has no source references but it does 252 have a pretty long detail that should 253 wrap over multiple lines. 254 `, 255 }, 256 "sourceless warning": { 257 tfdiags.Sourceless( 258 tfdiags.Warning, 259 "A sourceless warning", 260 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 261 ), 262 ` 263 Warning: A sourceless warning 264 265 It has no source references but it does 266 have a pretty long detail that should 267 wrap over multiple lines. 268 `, 269 }, 270 "error with source code subject": { 271 &hcl.Diagnostic{ 272 Severity: hcl.DiagError, 273 Summary: "Bad bad bad", 274 Detail: "Whatever shall we do?", 275 Subject: &hcl.Range{ 276 Filename: "test.tf", 277 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 278 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 279 }, 280 }, 281 ` 282 Error: Bad bad bad 283 284 on test.tf line 1: 285 1: test source code 286 287 Whatever shall we do? 288 `, 289 }, 290 "error with source code subject and known expression": { 291 &hcl.Diagnostic{ 292 Severity: hcl.DiagError, 293 Summary: "Bad bad bad", 294 Detail: "Whatever shall we do?", 295 Subject: &hcl.Range{ 296 Filename: "test.tf", 297 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 298 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 299 }, 300 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 301 hcl.TraverseRoot{Name: "boop"}, 302 hcl.TraverseAttr{Name: "beep"}, 303 }), 304 EvalContext: &hcl.EvalContext{ 305 Variables: map[string]cty.Value{ 306 "boop": cty.ObjectVal(map[string]cty.Value{ 307 "beep": cty.StringVal("blah"), 308 }), 309 }, 310 }, 311 }, 312 ` 313 Error: Bad bad bad 314 315 on test.tf line 1: 316 1: test source code 317 ├──────────────── 318 │ boop.beep is "blah" 319 320 Whatever shall we do? 321 `, 322 }, 323 "error with source code subject and expression referring to sensitive value": { 324 &hcl.Diagnostic{ 325 Severity: hcl.DiagError, 326 Summary: "Bad bad bad", 327 Detail: "Whatever shall we do?", 328 Subject: &hcl.Range{ 329 Filename: "test.tf", 330 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 331 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 332 }, 333 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 334 hcl.TraverseRoot{Name: "boop"}, 335 hcl.TraverseAttr{Name: "beep"}, 336 }), 337 EvalContext: &hcl.EvalContext{ 338 Variables: map[string]cty.Value{ 339 "boop": cty.ObjectVal(map[string]cty.Value{ 340 "beep": cty.StringVal("blah").Mark(marks.Sensitive), 341 }), 342 }, 343 }, 344 }, 345 ` 346 Error: Bad bad bad 347 348 on test.tf line 1: 349 1: test source code 350 ├──────────────── 351 │ boop.beep has a sensitive value 352 353 Whatever shall we do? 354 `, 355 }, 356 "error with source code subject and unknown string expression": { 357 &hcl.Diagnostic{ 358 Severity: hcl.DiagError, 359 Summary: "Bad bad bad", 360 Detail: "Whatever shall we do?", 361 Subject: &hcl.Range{ 362 Filename: "test.tf", 363 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 364 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 365 }, 366 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 367 hcl.TraverseRoot{Name: "boop"}, 368 hcl.TraverseAttr{Name: "beep"}, 369 }), 370 EvalContext: &hcl.EvalContext{ 371 Variables: map[string]cty.Value{ 372 "boop": cty.ObjectVal(map[string]cty.Value{ 373 "beep": cty.UnknownVal(cty.String), 374 }), 375 }, 376 }, 377 }, 378 ` 379 Error: Bad bad bad 380 381 on test.tf line 1: 382 1: test source code 383 ├──────────────── 384 │ boop.beep is a string, known only after apply 385 386 Whatever shall we do? 387 `, 388 }, 389 "error with source code subject and unknown expression of unknown type": { 390 &hcl.Diagnostic{ 391 Severity: hcl.DiagError, 392 Summary: "Bad bad bad", 393 Detail: "Whatever shall we do?", 394 Subject: &hcl.Range{ 395 Filename: "test.tf", 396 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 397 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 398 }, 399 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 400 hcl.TraverseRoot{Name: "boop"}, 401 hcl.TraverseAttr{Name: "beep"}, 402 }), 403 EvalContext: &hcl.EvalContext{ 404 Variables: map[string]cty.Value{ 405 "boop": cty.ObjectVal(map[string]cty.Value{ 406 "beep": cty.UnknownVal(cty.DynamicPseudoType), 407 }), 408 }, 409 }, 410 }, 411 ` 412 Error: Bad bad bad 413 414 on test.tf line 1: 415 1: test source code 416 ├──────────────── 417 │ boop.beep will be known only after apply 418 419 Whatever shall we do? 420 `, 421 }, 422 } 423 424 sources := map[string][]byte{ 425 "test.tf": []byte(`test source code`), 426 } 427 428 for name, test := range tests { 429 t.Run(name, func(t *testing.T) { 430 var diags tfdiags.Diagnostics 431 diags = diags.Append(test.Diag) // to normalize it into a tfdiag.Diagnostic 432 diag := diags[0] 433 got := strings.TrimSpace(DiagnosticPlain(diag, sources, 40)) 434 want := strings.TrimSpace(test.Want) 435 if got != want { 436 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 437 } 438 }) 439 } 440 } 441 442 func TestDiagnosticWarningsCompact(t *testing.T) { 443 var diags tfdiags.Diagnostics 444 diags = diags.Append(tfdiags.SimpleWarning("foo")) 445 diags = diags.Append(tfdiags.SimpleWarning("foo")) 446 diags = diags.Append(tfdiags.SimpleWarning("bar")) 447 diags = diags.Append(&hcl.Diagnostic{ 448 Severity: hcl.DiagWarning, 449 Summary: "source foo", 450 Detail: "...", 451 Subject: &hcl.Range{ 452 Filename: "source.tf", 453 Start: hcl.Pos{Line: 2, Column: 1, Byte: 5}, 454 End: hcl.Pos{Line: 2, Column: 1, Byte: 5}, 455 }, 456 }) 457 diags = diags.Append(&hcl.Diagnostic{ 458 Severity: hcl.DiagWarning, 459 Summary: "source foo", 460 Detail: "...", 461 Subject: &hcl.Range{ 462 Filename: "source.tf", 463 Start: hcl.Pos{Line: 3, Column: 1, Byte: 7}, 464 End: hcl.Pos{Line: 3, Column: 1, Byte: 7}, 465 }, 466 }) 467 diags = diags.Append(&hcl.Diagnostic{ 468 Severity: hcl.DiagWarning, 469 Summary: "source bar", 470 Detail: "...", 471 Subject: &hcl.Range{ 472 Filename: "source2.tf", 473 Start: hcl.Pos{Line: 1, Column: 1, Byte: 1}, 474 End: hcl.Pos{Line: 1, Column: 1, Byte: 1}, 475 }, 476 }) 477 478 // ConsolidateWarnings groups together the ones 479 // that have source location information and that 480 // have the same summary text. 481 diags = diags.ConsolidateWarnings(1) 482 483 // A zero-value Colorize just passes all the formatting 484 // codes back to us, so we can test them literally. 485 got := DiagnosticWarningsCompact(diags, &colorstring.Colorize{}) 486 want := `[bold][yellow]Warnings:[reset] 487 488 - foo 489 - foo 490 - bar 491 - source foo 492 on source.tf line 2 (and 1 more) 493 - source bar 494 on source2.tf line 1 495 ` 496 if got != want { 497 t.Errorf( 498 "wrong result\ngot:\n%s\n\nwant:\n%s\n\ndiff:\n%s", 499 got, want, cmp.Diff(want, got), 500 ) 501 } 502 } 503 504 // Test case via https://github.com/hugorut/terraform/issues/21359 505 func TestDiagnostic_nonOverlappingHighlightContext(t *testing.T) { 506 var diags tfdiags.Diagnostics 507 508 diags = diags.Append(&hcl.Diagnostic{ 509 Severity: hcl.DiagError, 510 Summary: "Some error", 511 Detail: "...", 512 Subject: &hcl.Range{ 513 Filename: "source.tf", 514 Start: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 515 End: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 516 }, 517 Context: &hcl.Range{ 518 Filename: "source.tf", 519 Start: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 520 End: hcl.Pos{Line: 4, Column: 2, Byte: 60}, 521 }, 522 }) 523 sources := map[string][]byte{ 524 "source.tf": []byte(`x = somefunc("testing", { 525 alpha = "foo" 526 beta = "bar" 527 }) 528 `), 529 } 530 color := &colorstring.Colorize{ 531 Colors: colorstring.DefaultColors, 532 Reset: true, 533 Disable: true, 534 } 535 expected := `╷ 536 │ Error: Some error 537 │ 538 │ on source.tf line 1: 539 │ 1: x = somefunc("testing", { 540 │ 2: alpha = "foo" 541 │ 3: beta = "bar" 542 │ 4: }) 543 │ 544 │ ... 545 ╵ 546 ` 547 output := Diagnostic(diags[0], sources, color, 80) 548 549 if output != expected { 550 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 551 } 552 } 553 554 func TestDiagnostic_emptyOverlapHighlightContext(t *testing.T) { 555 var diags tfdiags.Diagnostics 556 557 diags = diags.Append(&hcl.Diagnostic{ 558 Severity: hcl.DiagError, 559 Summary: "Some error", 560 Detail: "...", 561 Subject: &hcl.Range{ 562 Filename: "source.tf", 563 Start: hcl.Pos{Line: 3, Column: 10, Byte: 38}, 564 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 565 }, 566 Context: &hcl.Range{ 567 Filename: "source.tf", 568 Start: hcl.Pos{Line: 2, Column: 13, Byte: 27}, 569 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 570 }, 571 }) 572 sources := map[string][]byte{ 573 "source.tf": []byte(`variable "x" { 574 default = { 575 "foo" 576 } 577 `), 578 } 579 color := &colorstring.Colorize{ 580 Colors: colorstring.DefaultColors, 581 Reset: true, 582 Disable: true, 583 } 584 expected := `╷ 585 │ Error: Some error 586 │ 587 │ on source.tf line 3, in variable "x": 588 │ 2: default = { 589 │ 3: "foo" 590 │ 4: } 591 │ 592 │ ... 593 ╵ 594 ` 595 output := Diagnostic(diags[0], sources, color, 80) 596 597 if output != expected { 598 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 599 } 600 } 601 602 func TestDiagnosticPlain_emptyOverlapHighlightContext(t *testing.T) { 603 var diags tfdiags.Diagnostics 604 605 diags = diags.Append(&hcl.Diagnostic{ 606 Severity: hcl.DiagError, 607 Summary: "Some error", 608 Detail: "...", 609 Subject: &hcl.Range{ 610 Filename: "source.tf", 611 Start: hcl.Pos{Line: 3, Column: 10, Byte: 38}, 612 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 613 }, 614 Context: &hcl.Range{ 615 Filename: "source.tf", 616 Start: hcl.Pos{Line: 2, Column: 13, Byte: 27}, 617 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 618 }, 619 }) 620 sources := map[string][]byte{ 621 "source.tf": []byte(`variable "x" { 622 default = { 623 "foo" 624 } 625 `), 626 } 627 628 expected := ` 629 Error: Some error 630 631 on source.tf line 3, in variable "x": 632 2: default = { 633 3: "foo" 634 4: } 635 636 ... 637 ` 638 output := DiagnosticPlain(diags[0], sources, 80) 639 640 if output != expected { 641 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 642 } 643 } 644 645 func TestDiagnostic_wrapDetailIncludingCommand(t *testing.T) { 646 var diags tfdiags.Diagnostics 647 648 diags = diags.Append(&hcl.Diagnostic{ 649 Severity: hcl.DiagError, 650 Summary: "Everything went wrong", 651 Detail: "This is a very long sentence about whatever went wrong which is supposed to wrap onto multiple lines. Thank-you very much for listening.\n\nTo fix this, run this very long command:\n terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces\n\nHere is a coda which is also long enough to wrap and so it should eventually make it onto multiple lines. THE END", 652 }) 653 color := &colorstring.Colorize{ 654 Colors: colorstring.DefaultColors, 655 Reset: true, 656 Disable: true, 657 } 658 expected := `╷ 659 │ Error: Everything went wrong 660 │ 661 │ This is a very long sentence about whatever went wrong which is supposed 662 │ to wrap onto multiple lines. Thank-you very much for listening. 663 │ 664 │ To fix this, run this very long command: 665 │ terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces 666 │ 667 │ Here is a coda which is also long enough to wrap and so it should 668 │ eventually make it onto multiple lines. THE END 669 ╵ 670 ` 671 output := Diagnostic(diags[0], nil, color, 76) 672 673 if output != expected { 674 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 675 } 676 } 677 678 func TestDiagnosticPlain_wrapDetailIncludingCommand(t *testing.T) { 679 var diags tfdiags.Diagnostics 680 681 diags = diags.Append(&hcl.Diagnostic{ 682 Severity: hcl.DiagError, 683 Summary: "Everything went wrong", 684 Detail: "This is a very long sentence about whatever went wrong which is supposed to wrap onto multiple lines. Thank-you very much for listening.\n\nTo fix this, run this very long command:\n terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces\n\nHere is a coda which is also long enough to wrap and so it should eventually make it onto multiple lines. THE END", 685 }) 686 687 expected := ` 688 Error: Everything went wrong 689 690 This is a very long sentence about whatever went wrong which is supposed to 691 wrap onto multiple lines. Thank-you very much for listening. 692 693 To fix this, run this very long command: 694 terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces 695 696 Here is a coda which is also long enough to wrap and so it should 697 eventually make it onto multiple lines. THE END 698 ` 699 output := DiagnosticPlain(diags[0], nil, 76) 700 701 if output != expected { 702 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 703 } 704 } 705 706 // Test cases covering invalid JSON diagnostics which should still render 707 // correctly. These JSON diagnostic values cannot be generated from the 708 // json.NewDiagnostic code path, but we may read and display JSON diagnostics 709 // in future from other sources. 710 func TestDiagnosticFromJSON_invalid(t *testing.T) { 711 tests := map[string]struct { 712 Diag *viewsjson.Diagnostic 713 Want string 714 }{ 715 "zero-value end range and highlight end byte": { 716 &viewsjson.Diagnostic{ 717 Severity: viewsjson.DiagnosticSeverityError, 718 Summary: "Bad end", 719 Detail: "It all went wrong.", 720 Range: &viewsjson.DiagnosticRange{ 721 Filename: "ohno.tf", 722 Start: viewsjson.Pos{Line: 1, Column: 23, Byte: 22}, 723 End: viewsjson.Pos{Line: 0, Column: 0, Byte: 0}, 724 }, 725 Snippet: &viewsjson.DiagnosticSnippet{ 726 Code: `resource "foo_bar "baz" {`, 727 StartLine: 1, 728 HighlightStartOffset: 22, 729 HighlightEndOffset: 0, 730 }, 731 }, 732 `[red]╷[reset] 733 [red]│[reset] [bold][red]Error: [reset][bold]Bad end[reset] 734 [red]│[reset] 735 [red]│[reset] on ohno.tf line 1: 736 [red]│[reset] 1: resource "foo_bar "baz[underline]"[reset] { 737 [red]│[reset] 738 [red]│[reset] It all went wrong. 739 [red]╵[reset] 740 `, 741 }, 742 } 743 744 // This empty Colorize just passes through all of the formatting codes 745 // untouched, because it doesn't define any formatting keywords. 746 colorize := &colorstring.Colorize{} 747 748 for name, test := range tests { 749 t.Run(name, func(t *testing.T) { 750 got := strings.TrimSpace(DiagnosticFromJSON(test.Diag, colorize, 40)) 751 want := strings.TrimSpace(test.Want) 752 if got != want { 753 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 754 } 755 }) 756 } 757 }