github.com/pulumi/terraform@v1.4.0/pkg/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/hclsyntax" 10 "github.com/hashicorp/hcl/v2/hcltest" 11 "github.com/mitchellh/colorstring" 12 "github.com/zclconf/go-cty/cty" 13 "github.com/zclconf/go-cty/cty/function" 14 15 viewsjson "github.com/pulumi/terraform/pkg/command/views/json" 16 "github.com/pulumi/terraform/pkg/lang/marks" 17 18 "github.com/pulumi/terraform/pkg/tfdiags" 19 ) 20 21 func TestDiagnostic(t *testing.T) { 22 23 tests := map[string]struct { 24 Diag interface{} 25 Want string 26 }{ 27 "sourceless error": { 28 tfdiags.Sourceless( 29 tfdiags.Error, 30 "A sourceless error", 31 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 32 ), 33 `[red]╷[reset] 34 [red]│[reset] [bold][red]Error: [reset][bold]A sourceless error[reset] 35 [red]│[reset] 36 [red]│[reset] It has no source references but it 37 [red]│[reset] does have a pretty long detail that 38 [red]│[reset] should wrap over multiple lines. 39 [red]╵[reset] 40 `, 41 }, 42 "sourceless warning": { 43 tfdiags.Sourceless( 44 tfdiags.Warning, 45 "A sourceless warning", 46 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 47 ), 48 `[yellow]╷[reset] 49 [yellow]│[reset] [bold][yellow]Warning: [reset][bold]A sourceless warning[reset] 50 [yellow]│[reset] 51 [yellow]│[reset] It has no source references but it 52 [yellow]│[reset] does have a pretty long detail that 53 [yellow]│[reset] should wrap over multiple lines. 54 [yellow]╵[reset] 55 `, 56 }, 57 "error with source code subject": { 58 &hcl.Diagnostic{ 59 Severity: hcl.DiagError, 60 Summary: "Bad bad bad", 61 Detail: "Whatever shall we do?", 62 Subject: &hcl.Range{ 63 Filename: "test.tf", 64 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 65 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 66 }, 67 }, 68 `[red]╷[reset] 69 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 70 [red]│[reset] 71 [red]│[reset] on test.tf line 1: 72 [red]│[reset] 1: test [underline]source[reset] code 73 [red]│[reset] 74 [red]│[reset] Whatever shall we do? 75 [red]╵[reset] 76 `, 77 }, 78 "error with source code subject and known expression": { 79 &hcl.Diagnostic{ 80 Severity: hcl.DiagError, 81 Summary: "Bad bad bad", 82 Detail: "Whatever shall we do?", 83 Subject: &hcl.Range{ 84 Filename: "test.tf", 85 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 86 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 87 }, 88 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 89 hcl.TraverseRoot{Name: "boop"}, 90 hcl.TraverseAttr{Name: "beep"}, 91 }), 92 EvalContext: &hcl.EvalContext{ 93 Variables: map[string]cty.Value{ 94 "boop": cty.ObjectVal(map[string]cty.Value{ 95 "beep": cty.StringVal("blah"), 96 }), 97 }, 98 }, 99 }, 100 `[red]╷[reset] 101 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 102 [red]│[reset] 103 [red]│[reset] on test.tf line 1: 104 [red]│[reset] 1: test [underline]source[reset] code 105 [red]│[reset] [dark_gray]├────────────────[reset] 106 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] is "blah" 107 [red]│[reset] 108 [red]│[reset] Whatever shall we do? 109 [red]╵[reset] 110 `, 111 }, 112 "error with source code subject and expression referring to sensitive value": { 113 &hcl.Diagnostic{ 114 Severity: hcl.DiagError, 115 Summary: "Bad bad bad", 116 Detail: "Whatever shall we do?", 117 Subject: &hcl.Range{ 118 Filename: "test.tf", 119 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 120 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 121 }, 122 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 123 hcl.TraverseRoot{Name: "boop"}, 124 hcl.TraverseAttr{Name: "beep"}, 125 }), 126 EvalContext: &hcl.EvalContext{ 127 Variables: map[string]cty.Value{ 128 "boop": cty.ObjectVal(map[string]cty.Value{ 129 "beep": cty.StringVal("blah").Mark(marks.Sensitive), 130 }), 131 }, 132 }, 133 Extra: diagnosticCausedBySensitive(true), 134 }, 135 `[red]╷[reset] 136 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 137 [red]│[reset] 138 [red]│[reset] on test.tf line 1: 139 [red]│[reset] 1: test [underline]source[reset] code 140 [red]│[reset] [dark_gray]├────────────────[reset] 141 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] has a sensitive value 142 [red]│[reset] 143 [red]│[reset] Whatever shall we do? 144 [red]╵[reset] 145 `, 146 }, 147 "error with source code subject and unknown string expression": { 148 &hcl.Diagnostic{ 149 Severity: hcl.DiagError, 150 Summary: "Bad bad bad", 151 Detail: "Whatever shall we do?", 152 Subject: &hcl.Range{ 153 Filename: "test.tf", 154 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 155 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 156 }, 157 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 158 hcl.TraverseRoot{Name: "boop"}, 159 hcl.TraverseAttr{Name: "beep"}, 160 }), 161 EvalContext: &hcl.EvalContext{ 162 Variables: map[string]cty.Value{ 163 "boop": cty.ObjectVal(map[string]cty.Value{ 164 "beep": cty.UnknownVal(cty.String), 165 }), 166 }, 167 }, 168 Extra: diagnosticCausedByUnknown(true), 169 }, 170 `[red]╷[reset] 171 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 172 [red]│[reset] 173 [red]│[reset] on test.tf line 1: 174 [red]│[reset] 1: test [underline]source[reset] code 175 [red]│[reset] [dark_gray]├────────────────[reset] 176 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] is a string, known only after apply 177 [red]│[reset] 178 [red]│[reset] Whatever shall we do? 179 [red]╵[reset] 180 `, 181 }, 182 "error with source code subject and unknown expression of unknown type": { 183 &hcl.Diagnostic{ 184 Severity: hcl.DiagError, 185 Summary: "Bad bad bad", 186 Detail: "Whatever shall we do?", 187 Subject: &hcl.Range{ 188 Filename: "test.tf", 189 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 190 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 191 }, 192 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 193 hcl.TraverseRoot{Name: "boop"}, 194 hcl.TraverseAttr{Name: "beep"}, 195 }), 196 EvalContext: &hcl.EvalContext{ 197 Variables: map[string]cty.Value{ 198 "boop": cty.ObjectVal(map[string]cty.Value{ 199 "beep": cty.UnknownVal(cty.DynamicPseudoType), 200 }), 201 }, 202 }, 203 Extra: diagnosticCausedByUnknown(true), 204 }, 205 `[red]╷[reset] 206 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 207 [red]│[reset] 208 [red]│[reset] on test.tf line 1: 209 [red]│[reset] 1: test [underline]source[reset] code 210 [red]│[reset] [dark_gray]├────────────────[reset] 211 [red]│[reset] [dark_gray]│[reset] [bold]boop.beep[reset] will be known only after apply 212 [red]│[reset] 213 [red]│[reset] Whatever shall we do? 214 [red]╵[reset] 215 `, 216 }, 217 "error with source code subject and function call annotation": { 218 &hcl.Diagnostic{ 219 Severity: hcl.DiagError, 220 Summary: "Bad bad bad", 221 Detail: "Whatever shall we do?", 222 Subject: &hcl.Range{ 223 Filename: "test.tf", 224 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 225 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 226 }, 227 Expression: hcltest.MockExprLiteral(cty.True), 228 EvalContext: &hcl.EvalContext{ 229 Functions: map[string]function.Function{ 230 "beep": function.New(&function.Spec{ 231 Params: []function.Parameter{ 232 { 233 Name: "pos_param_0", 234 Type: cty.String, 235 }, 236 { 237 Name: "pos_param_1", 238 Type: cty.Number, 239 }, 240 }, 241 VarParam: &function.Parameter{ 242 Name: "var_param", 243 Type: cty.Bool, 244 }, 245 }), 246 }, 247 }, 248 // This is simulating what the HCL function call expression 249 // type would generate on evaluation, by implementing the 250 // same interface it uses. 251 Extra: fakeDiagFunctionCallExtra("beep"), 252 }, 253 `[red]╷[reset] 254 [red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset] 255 [red]│[reset] 256 [red]│[reset] on test.tf line 1: 257 [red]│[reset] 1: test [underline]source[reset] code 258 [red]│[reset] [dark_gray]├────────────────[reset] 259 [red]│[reset] [dark_gray]│[reset] while calling [bold]beep[reset](pos_param_0, pos_param_1, var_param...) 260 [red]│[reset] 261 [red]│[reset] Whatever shall we do? 262 [red]╵[reset] 263 `, 264 }, 265 } 266 267 sources := map[string][]byte{ 268 "test.tf": []byte(`test source code`), 269 } 270 271 // This empty Colorize just passes through all of the formatting codes 272 // untouched, because it doesn't define any formatting keywords. 273 colorize := &colorstring.Colorize{} 274 275 for name, test := range tests { 276 t.Run(name, func(t *testing.T) { 277 var diags tfdiags.Diagnostics 278 diags = diags.Append(test.Diag) // to normalize it into a tfdiag.Diagnostic 279 diag := diags[0] 280 got := strings.TrimSpace(Diagnostic(diag, sources, colorize, 40)) 281 want := strings.TrimSpace(test.Want) 282 if got != want { 283 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 284 } 285 }) 286 } 287 } 288 289 func TestDiagnosticPlain(t *testing.T) { 290 291 tests := map[string]struct { 292 Diag interface{} 293 Want string 294 }{ 295 "sourceless error": { 296 tfdiags.Sourceless( 297 tfdiags.Error, 298 "A sourceless error", 299 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 300 ), 301 ` 302 Error: A sourceless error 303 304 It has no source references but it does 305 have a pretty long detail that should 306 wrap over multiple lines. 307 `, 308 }, 309 "sourceless warning": { 310 tfdiags.Sourceless( 311 tfdiags.Warning, 312 "A sourceless warning", 313 "It has no source references but it does have a pretty long detail that should wrap over multiple lines.", 314 ), 315 ` 316 Warning: A sourceless warning 317 318 It has no source references but it does 319 have a pretty long detail that should 320 wrap over multiple lines. 321 `, 322 }, 323 "error with source code subject": { 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 }, 334 ` 335 Error: Bad bad bad 336 337 on test.tf line 1: 338 1: test source code 339 340 Whatever shall we do? 341 `, 342 }, 343 "error with source code subject and known expression": { 344 &hcl.Diagnostic{ 345 Severity: hcl.DiagError, 346 Summary: "Bad bad bad", 347 Detail: "Whatever shall we do?", 348 Subject: &hcl.Range{ 349 Filename: "test.tf", 350 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 351 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 352 }, 353 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 354 hcl.TraverseRoot{Name: "boop"}, 355 hcl.TraverseAttr{Name: "beep"}, 356 }), 357 EvalContext: &hcl.EvalContext{ 358 Variables: map[string]cty.Value{ 359 "boop": cty.ObjectVal(map[string]cty.Value{ 360 "beep": cty.StringVal("blah"), 361 }), 362 }, 363 }, 364 }, 365 ` 366 Error: Bad bad bad 367 368 on test.tf line 1: 369 1: test source code 370 ├──────────────── 371 │ boop.beep is "blah" 372 373 Whatever shall we do? 374 `, 375 }, 376 "error with source code subject and expression referring to sensitive value": { 377 &hcl.Diagnostic{ 378 Severity: hcl.DiagError, 379 Summary: "Bad bad bad", 380 Detail: "Whatever shall we do?", 381 Subject: &hcl.Range{ 382 Filename: "test.tf", 383 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 384 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 385 }, 386 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 387 hcl.TraverseRoot{Name: "boop"}, 388 hcl.TraverseAttr{Name: "beep"}, 389 }), 390 EvalContext: &hcl.EvalContext{ 391 Variables: map[string]cty.Value{ 392 "boop": cty.ObjectVal(map[string]cty.Value{ 393 "beep": cty.StringVal("blah").Mark(marks.Sensitive), 394 }), 395 }, 396 }, 397 Extra: diagnosticCausedBySensitive(true), 398 }, 399 ` 400 Error: Bad bad bad 401 402 on test.tf line 1: 403 1: test source code 404 ├──────────────── 405 │ boop.beep has a sensitive value 406 407 Whatever shall we do? 408 `, 409 }, 410 "error with source code subject and expression referring to sensitive value when not related to sensitivity": { 411 &hcl.Diagnostic{ 412 Severity: hcl.DiagError, 413 Summary: "Bad bad bad", 414 Detail: "Whatever shall we do?", 415 Subject: &hcl.Range{ 416 Filename: "test.tf", 417 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 418 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 419 }, 420 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 421 hcl.TraverseRoot{Name: "boop"}, 422 hcl.TraverseAttr{Name: "beep"}, 423 }), 424 EvalContext: &hcl.EvalContext{ 425 Variables: map[string]cty.Value{ 426 "boop": cty.ObjectVal(map[string]cty.Value{ 427 "beep": cty.StringVal("blah").Mark(marks.Sensitive), 428 }), 429 }, 430 }, 431 }, 432 ` 433 Error: Bad bad bad 434 435 on test.tf line 1: 436 1: test source code 437 438 Whatever shall we do? 439 `, 440 }, 441 "error with source code subject and unknown string expression": { 442 &hcl.Diagnostic{ 443 Severity: hcl.DiagError, 444 Summary: "Bad bad bad", 445 Detail: "Whatever shall we do?", 446 Subject: &hcl.Range{ 447 Filename: "test.tf", 448 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 449 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 450 }, 451 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 452 hcl.TraverseRoot{Name: "boop"}, 453 hcl.TraverseAttr{Name: "beep"}, 454 }), 455 EvalContext: &hcl.EvalContext{ 456 Variables: map[string]cty.Value{ 457 "boop": cty.ObjectVal(map[string]cty.Value{ 458 "beep": cty.UnknownVal(cty.String), 459 }), 460 }, 461 }, 462 Extra: diagnosticCausedByUnknown(true), 463 }, 464 ` 465 Error: Bad bad bad 466 467 on test.tf line 1: 468 1: test source code 469 ├──────────────── 470 │ boop.beep is a string, known only after apply 471 472 Whatever shall we do? 473 `, 474 }, 475 "error with source code subject and unknown string expression when problem isn't unknown-related": { 476 &hcl.Diagnostic{ 477 Severity: hcl.DiagError, 478 Summary: "Bad bad bad", 479 Detail: "Whatever shall we do?", 480 Subject: &hcl.Range{ 481 Filename: "test.tf", 482 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 483 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 484 }, 485 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 486 hcl.TraverseRoot{Name: "boop"}, 487 hcl.TraverseAttr{Name: "beep"}, 488 }), 489 EvalContext: &hcl.EvalContext{ 490 Variables: map[string]cty.Value{ 491 "boop": cty.ObjectVal(map[string]cty.Value{ 492 "beep": cty.UnknownVal(cty.String), 493 }), 494 }, 495 }, 496 }, 497 ` 498 Error: Bad bad bad 499 500 on test.tf line 1: 501 1: test source code 502 ├──────────────── 503 │ boop.beep is a string 504 505 Whatever shall we do? 506 `, 507 }, 508 "error with source code subject and unknown expression of unknown type": { 509 &hcl.Diagnostic{ 510 Severity: hcl.DiagError, 511 Summary: "Bad bad bad", 512 Detail: "Whatever shall we do?", 513 Subject: &hcl.Range{ 514 Filename: "test.tf", 515 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 516 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 517 }, 518 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 519 hcl.TraverseRoot{Name: "boop"}, 520 hcl.TraverseAttr{Name: "beep"}, 521 }), 522 EvalContext: &hcl.EvalContext{ 523 Variables: map[string]cty.Value{ 524 "boop": cty.ObjectVal(map[string]cty.Value{ 525 "beep": cty.UnknownVal(cty.DynamicPseudoType), 526 }), 527 }, 528 }, 529 Extra: diagnosticCausedByUnknown(true), 530 }, 531 ` 532 Error: Bad bad bad 533 534 on test.tf line 1: 535 1: test source code 536 ├──────────────── 537 │ boop.beep will be known only after apply 538 539 Whatever shall we do? 540 `, 541 }, 542 "error with source code subject and unknown expression of unknown type when problem isn't unknown-related": { 543 &hcl.Diagnostic{ 544 Severity: hcl.DiagError, 545 Summary: "Bad bad bad", 546 Detail: "Whatever shall we do?", 547 Subject: &hcl.Range{ 548 Filename: "test.tf", 549 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 550 End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 551 }, 552 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 553 hcl.TraverseRoot{Name: "boop"}, 554 hcl.TraverseAttr{Name: "beep"}, 555 }), 556 EvalContext: &hcl.EvalContext{ 557 Variables: map[string]cty.Value{ 558 "boop": cty.ObjectVal(map[string]cty.Value{ 559 "beep": cty.UnknownVal(cty.DynamicPseudoType), 560 }), 561 }, 562 }, 563 }, 564 ` 565 Error: Bad bad bad 566 567 on test.tf line 1: 568 1: test source code 569 570 Whatever shall we do? 571 `, 572 }, 573 } 574 575 sources := map[string][]byte{ 576 "test.tf": []byte(`test source code`), 577 } 578 579 for name, test := range tests { 580 t.Run(name, func(t *testing.T) { 581 var diags tfdiags.Diagnostics 582 diags = diags.Append(test.Diag) // to normalize it into a tfdiag.Diagnostic 583 diag := diags[0] 584 got := strings.TrimSpace(DiagnosticPlain(diag, sources, 40)) 585 want := strings.TrimSpace(test.Want) 586 if got != want { 587 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 588 } 589 }) 590 } 591 } 592 593 func TestDiagnosticWarningsCompact(t *testing.T) { 594 var diags tfdiags.Diagnostics 595 diags = diags.Append(tfdiags.SimpleWarning("foo")) 596 diags = diags.Append(tfdiags.SimpleWarning("foo")) 597 diags = diags.Append(tfdiags.SimpleWarning("bar")) 598 diags = diags.Append(&hcl.Diagnostic{ 599 Severity: hcl.DiagWarning, 600 Summary: "source foo", 601 Detail: "...", 602 Subject: &hcl.Range{ 603 Filename: "source.tf", 604 Start: hcl.Pos{Line: 2, Column: 1, Byte: 5}, 605 End: hcl.Pos{Line: 2, Column: 1, Byte: 5}, 606 }, 607 }) 608 diags = diags.Append(&hcl.Diagnostic{ 609 Severity: hcl.DiagWarning, 610 Summary: "source foo", 611 Detail: "...", 612 Subject: &hcl.Range{ 613 Filename: "source.tf", 614 Start: hcl.Pos{Line: 3, Column: 1, Byte: 7}, 615 End: hcl.Pos{Line: 3, Column: 1, Byte: 7}, 616 }, 617 }) 618 diags = diags.Append(&hcl.Diagnostic{ 619 Severity: hcl.DiagWarning, 620 Summary: "source bar", 621 Detail: "...", 622 Subject: &hcl.Range{ 623 Filename: "source2.tf", 624 Start: hcl.Pos{Line: 1, Column: 1, Byte: 1}, 625 End: hcl.Pos{Line: 1, Column: 1, Byte: 1}, 626 }, 627 }) 628 629 // ConsolidateWarnings groups together the ones 630 // that have source location information and that 631 // have the same summary text. 632 diags = diags.ConsolidateWarnings(1) 633 634 // A zero-value Colorize just passes all the formatting 635 // codes back to us, so we can test them literally. 636 got := DiagnosticWarningsCompact(diags, &colorstring.Colorize{}) 637 want := `[bold][yellow]Warnings:[reset] 638 639 - foo 640 - foo 641 - bar 642 - source foo 643 on source.tf line 2 (and 1 more) 644 - source bar 645 on source2.tf line 1 646 ` 647 if got != want { 648 t.Errorf( 649 "wrong result\ngot:\n%s\n\nwant:\n%s\n\ndiff:\n%s", 650 got, want, cmp.Diff(want, got), 651 ) 652 } 653 } 654 655 // Test case via https://github.com/pulumi/terraform/issues/21359 656 func TestDiagnostic_nonOverlappingHighlightContext(t *testing.T) { 657 var diags tfdiags.Diagnostics 658 659 diags = diags.Append(&hcl.Diagnostic{ 660 Severity: hcl.DiagError, 661 Summary: "Some error", 662 Detail: "...", 663 Subject: &hcl.Range{ 664 Filename: "source.tf", 665 Start: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 666 End: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 667 }, 668 Context: &hcl.Range{ 669 Filename: "source.tf", 670 Start: hcl.Pos{Line: 1, Column: 5, Byte: 5}, 671 End: hcl.Pos{Line: 4, Column: 2, Byte: 60}, 672 }, 673 }) 674 sources := map[string][]byte{ 675 "source.tf": []byte(`x = somefunc("testing", { 676 alpha = "foo" 677 beta = "bar" 678 }) 679 `), 680 } 681 color := &colorstring.Colorize{ 682 Colors: colorstring.DefaultColors, 683 Reset: true, 684 Disable: true, 685 } 686 expected := `╷ 687 │ Error: Some error 688 │ 689 │ on source.tf line 1: 690 │ 1: x = somefunc("testing", { 691 │ 2: alpha = "foo" 692 │ 3: beta = "bar" 693 │ 4: }) 694 │ 695 │ ... 696 ╵ 697 ` 698 output := Diagnostic(diags[0], sources, color, 80) 699 700 if output != expected { 701 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 702 } 703 } 704 705 func TestDiagnostic_emptyOverlapHighlightContext(t *testing.T) { 706 var diags tfdiags.Diagnostics 707 708 diags = diags.Append(&hcl.Diagnostic{ 709 Severity: hcl.DiagError, 710 Summary: "Some error", 711 Detail: "...", 712 Subject: &hcl.Range{ 713 Filename: "source.tf", 714 Start: hcl.Pos{Line: 3, Column: 10, Byte: 38}, 715 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 716 }, 717 Context: &hcl.Range{ 718 Filename: "source.tf", 719 Start: hcl.Pos{Line: 2, Column: 13, Byte: 27}, 720 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 721 }, 722 }) 723 sources := map[string][]byte{ 724 "source.tf": []byte(`variable "x" { 725 default = { 726 "foo" 727 } 728 `), 729 } 730 color := &colorstring.Colorize{ 731 Colors: colorstring.DefaultColors, 732 Reset: true, 733 Disable: true, 734 } 735 expected := `╷ 736 │ Error: Some error 737 │ 738 │ on source.tf line 3, in variable "x": 739 │ 2: default = { 740 │ 3: "foo" 741 │ 4: } 742 │ 743 │ ... 744 ╵ 745 ` 746 output := Diagnostic(diags[0], sources, color, 80) 747 748 if output != expected { 749 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 750 } 751 } 752 753 func TestDiagnosticPlain_emptyOverlapHighlightContext(t *testing.T) { 754 var diags tfdiags.Diagnostics 755 756 diags = diags.Append(&hcl.Diagnostic{ 757 Severity: hcl.DiagError, 758 Summary: "Some error", 759 Detail: "...", 760 Subject: &hcl.Range{ 761 Filename: "source.tf", 762 Start: hcl.Pos{Line: 3, Column: 10, Byte: 38}, 763 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 764 }, 765 Context: &hcl.Range{ 766 Filename: "source.tf", 767 Start: hcl.Pos{Line: 2, Column: 13, Byte: 27}, 768 End: hcl.Pos{Line: 4, Column: 1, Byte: 39}, 769 }, 770 }) 771 sources := map[string][]byte{ 772 "source.tf": []byte(`variable "x" { 773 default = { 774 "foo" 775 } 776 `), 777 } 778 779 expected := ` 780 Error: Some error 781 782 on source.tf line 3, in variable "x": 783 2: default = { 784 3: "foo" 785 4: } 786 787 ... 788 ` 789 output := DiagnosticPlain(diags[0], sources, 80) 790 791 if output != expected { 792 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 793 } 794 } 795 796 func TestDiagnostic_wrapDetailIncludingCommand(t *testing.T) { 797 var diags tfdiags.Diagnostics 798 799 diags = diags.Append(&hcl.Diagnostic{ 800 Severity: hcl.DiagError, 801 Summary: "Everything went wrong", 802 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", 803 }) 804 color := &colorstring.Colorize{ 805 Colors: colorstring.DefaultColors, 806 Reset: true, 807 Disable: true, 808 } 809 expected := `╷ 810 │ Error: Everything went wrong 811 │ 812 │ This is a very long sentence about whatever went wrong which is supposed 813 │ to wrap onto multiple lines. Thank-you very much for listening. 814 │ 815 │ To fix this, run this very long command: 816 │ terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces 817 │ 818 │ Here is a coda which is also long enough to wrap and so it should 819 │ eventually make it onto multiple lines. THE END 820 ╵ 821 ` 822 output := Diagnostic(diags[0], nil, color, 76) 823 824 if output != expected { 825 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 826 } 827 } 828 829 func TestDiagnosticPlain_wrapDetailIncludingCommand(t *testing.T) { 830 var diags tfdiags.Diagnostics 831 832 diags = diags.Append(&hcl.Diagnostic{ 833 Severity: hcl.DiagError, 834 Summary: "Everything went wrong", 835 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", 836 }) 837 838 expected := ` 839 Error: Everything went wrong 840 841 This is a very long sentence about whatever went wrong which is supposed to 842 wrap onto multiple lines. Thank-you very much for listening. 843 844 To fix this, run this very long command: 845 terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces 846 847 Here is a coda which is also long enough to wrap and so it should 848 eventually make it onto multiple lines. THE END 849 ` 850 output := DiagnosticPlain(diags[0], nil, 76) 851 852 if output != expected { 853 t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected) 854 } 855 } 856 857 // Test cases covering invalid JSON diagnostics which should still render 858 // correctly. These JSON diagnostic values cannot be generated from the 859 // json.NewDiagnostic code path, but we may read and display JSON diagnostics 860 // in future from other sources. 861 func TestDiagnosticFromJSON_invalid(t *testing.T) { 862 tests := map[string]struct { 863 Diag *viewsjson.Diagnostic 864 Want string 865 }{ 866 "zero-value end range and highlight end byte": { 867 &viewsjson.Diagnostic{ 868 Severity: viewsjson.DiagnosticSeverityError, 869 Summary: "Bad end", 870 Detail: "It all went wrong.", 871 Range: &viewsjson.DiagnosticRange{ 872 Filename: "ohno.tf", 873 Start: viewsjson.Pos{Line: 1, Column: 23, Byte: 22}, 874 End: viewsjson.Pos{Line: 0, Column: 0, Byte: 0}, 875 }, 876 Snippet: &viewsjson.DiagnosticSnippet{ 877 Code: `resource "foo_bar "baz" {`, 878 StartLine: 1, 879 HighlightStartOffset: 22, 880 HighlightEndOffset: 0, 881 }, 882 }, 883 `[red]╷[reset] 884 [red]│[reset] [bold][red]Error: [reset][bold]Bad end[reset] 885 [red]│[reset] 886 [red]│[reset] on ohno.tf line 1: 887 [red]│[reset] 1: resource "foo_bar "baz[underline]"[reset] { 888 [red]│[reset] 889 [red]│[reset] It all went wrong. 890 [red]╵[reset] 891 `, 892 }, 893 } 894 895 // This empty Colorize just passes through all of the formatting codes 896 // untouched, because it doesn't define any formatting keywords. 897 colorize := &colorstring.Colorize{} 898 899 for name, test := range tests { 900 t.Run(name, func(t *testing.T) { 901 got := strings.TrimSpace(DiagnosticFromJSON(test.Diag, colorize, 40)) 902 want := strings.TrimSpace(test.Want) 903 if got != want { 904 t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want) 905 } 906 }) 907 } 908 } 909 910 // fakeDiagFunctionCallExtra is a fake implementation of the interface that 911 // HCL uses to provide "extra information" associated with diagnostics that 912 // describe errors during a function call. 913 type fakeDiagFunctionCallExtra string 914 915 var _ hclsyntax.FunctionCallDiagExtra = fakeDiagFunctionCallExtra("") 916 917 func (e fakeDiagFunctionCallExtra) CalledFunctionName() string { 918 return string(e) 919 } 920 921 func (e fakeDiagFunctionCallExtra) FunctionCallError() error { 922 return nil 923 } 924 925 // diagnosticCausedByUnknown is a testing helper for exercising our logic 926 // for selectively showing unknown values alongside our source snippets for 927 // diagnostics that are explicitly marked as being caused by unknown values. 928 type diagnosticCausedByUnknown bool 929 930 var _ tfdiags.DiagnosticExtraBecauseUnknown = diagnosticCausedByUnknown(true) 931 932 func (e diagnosticCausedByUnknown) DiagnosticCausedByUnknown() bool { 933 return bool(e) 934 } 935 936 // diagnosticCausedBySensitive is a testing helper for exercising our logic 937 // for selectively showing sensitive values alongside our source snippets for 938 // diagnostics that are explicitly marked as being caused by sensitive values. 939 type diagnosticCausedBySensitive bool 940 941 var _ tfdiags.DiagnosticExtraBecauseSensitive = diagnosticCausedBySensitive(true) 942 943 func (e diagnosticCausedBySensitive) DiagnosticCausedBySensitive() bool { 944 return bool(e) 945 }