github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/views/json/diagnostic_test.go (about) 1 package json 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/hashicorp/hcl/v2" 14 "github.com/hashicorp/hcl/v2/hcltest" 15 "github.com/hashicorp/terraform/internal/lang/marks" 16 "github.com/hashicorp/terraform/internal/tfdiags" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 func TestNewDiagnostic(t *testing.T) { 21 // Common HCL for diags with source ranges. This does not have any real 22 // semantic errors, but we can synthesize fake HCL errors which will 23 // exercise the diagnostic rendering code using this 24 sources := map[string][]byte{ 25 "test.tf": []byte(`resource "test_resource" "test" { 26 foo = var.boop["hello!"] 27 bar = { 28 baz = maybe 29 } 30 } 31 `), 32 "short.tf": []byte("bad source code"), 33 "odd-comment.tf": []byte("foo\n\n#\n"), 34 "values.tf": []byte(`[ 35 var.a, 36 var.b, 37 var.c, 38 var.d, 39 var.e, 40 var.f, 41 var.g, 42 var.h, 43 var.i, 44 var.j, 45 var.k, 46 ] 47 `), 48 } 49 testCases := map[string]struct { 50 diag interface{} // allow various kinds of diags 51 want *Diagnostic 52 }{ 53 "sourceless warning": { 54 tfdiags.Sourceless( 55 tfdiags.Warning, 56 "Oh no", 57 "Something is broken", 58 ), 59 &Diagnostic{ 60 Severity: "warning", 61 Summary: "Oh no", 62 Detail: "Something is broken", 63 }, 64 }, 65 "error with source code unavailable": { 66 &hcl.Diagnostic{ 67 Severity: hcl.DiagError, 68 Summary: "Bad news", 69 Detail: "It went wrong", 70 Subject: &hcl.Range{ 71 Filename: "modules/oops/missing.tf", 72 Start: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 73 End: hcl.Pos{Line: 2, Column: 12, Byte: 33}, 74 }, 75 }, 76 &Diagnostic{ 77 Severity: "error", 78 Summary: "Bad news", 79 Detail: "It went wrong", 80 Range: &DiagnosticRange{ 81 Filename: "modules/oops/missing.tf", 82 Start: Pos{ 83 Line: 1, 84 Column: 6, 85 Byte: 5, 86 }, 87 End: Pos{ 88 Line: 2, 89 Column: 12, 90 Byte: 33, 91 }, 92 }, 93 }, 94 }, 95 "error with source code subject": { 96 &hcl.Diagnostic{ 97 Severity: hcl.DiagError, 98 Summary: "Tiny explosion", 99 Detail: "Unexpected detonation while parsing", 100 Subject: &hcl.Range{ 101 Filename: "test.tf", 102 Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 103 End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, 104 }, 105 }, 106 &Diagnostic{ 107 Severity: "error", 108 Summary: "Tiny explosion", 109 Detail: "Unexpected detonation while parsing", 110 Range: &DiagnosticRange{ 111 Filename: "test.tf", 112 Start: Pos{ 113 Line: 1, 114 Column: 10, 115 Byte: 9, 116 }, 117 End: Pos{ 118 Line: 1, 119 Column: 25, 120 Byte: 24, 121 }, 122 }, 123 Snippet: &DiagnosticSnippet{ 124 Context: strPtr(`resource "test_resource" "test"`), 125 Code: `resource "test_resource" "test" {`, 126 StartLine: 1, 127 HighlightStartOffset: 9, 128 HighlightEndOffset: 24, 129 Values: []DiagnosticExpressionValue{}, 130 }, 131 }, 132 }, 133 "error with source code subject but no context": { 134 &hcl.Diagnostic{ 135 Severity: hcl.DiagError, 136 Summary: "Nonsense input", 137 Detail: "What you wrote makes no sense", 138 Subject: &hcl.Range{ 139 Filename: "short.tf", 140 Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 141 End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 142 }, 143 }, 144 &Diagnostic{ 145 Severity: "error", 146 Summary: "Nonsense input", 147 Detail: "What you wrote makes no sense", 148 Range: &DiagnosticRange{ 149 Filename: "short.tf", 150 Start: Pos{ 151 Line: 1, 152 Column: 5, 153 Byte: 4, 154 }, 155 End: Pos{ 156 Line: 1, 157 Column: 10, 158 Byte: 9, 159 }, 160 }, 161 Snippet: &DiagnosticSnippet{ 162 Context: nil, 163 Code: (`bad source code`), 164 StartLine: (1), 165 HighlightStartOffset: (4), 166 HighlightEndOffset: (9), 167 Values: []DiagnosticExpressionValue{}, 168 }, 169 }, 170 }, 171 "error with multi-line snippet": { 172 &hcl.Diagnostic{ 173 Severity: hcl.DiagError, 174 Summary: "In this house we respect booleans", 175 Detail: "True or false, there is no maybe", 176 Subject: &hcl.Range{ 177 Filename: "test.tf", 178 Start: hcl.Pos{Line: 4, Column: 11, Byte: 81}, 179 End: hcl.Pos{Line: 4, Column: 16, Byte: 86}, 180 }, 181 Context: &hcl.Range{ 182 Filename: "test.tf", 183 Start: hcl.Pos{Line: 3, Column: 3, Byte: 63}, 184 End: hcl.Pos{Line: 5, Column: 4, Byte: 90}, 185 }, 186 }, 187 &Diagnostic{ 188 Severity: "error", 189 Summary: "In this house we respect booleans", 190 Detail: "True or false, there is no maybe", 191 Range: &DiagnosticRange{ 192 Filename: "test.tf", 193 Start: Pos{ 194 Line: 4, 195 Column: 11, 196 Byte: 81, 197 }, 198 End: Pos{ 199 Line: 4, 200 Column: 16, 201 Byte: 86, 202 }, 203 }, 204 Snippet: &DiagnosticSnippet{ 205 Context: strPtr(`resource "test_resource" "test"`), 206 Code: " bar = {\n baz = maybe\n }", 207 StartLine: 3, 208 HighlightStartOffset: 20, 209 HighlightEndOffset: 25, 210 Values: []DiagnosticExpressionValue{}, 211 }, 212 }, 213 }, 214 "error with empty highlight range at end of source code": { 215 &hcl.Diagnostic{ 216 Severity: hcl.DiagError, 217 Summary: "You forgot something", 218 Detail: "Please finish your thought", 219 Subject: &hcl.Range{ 220 Filename: "short.tf", 221 Start: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 222 End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 223 }, 224 }, 225 &Diagnostic{ 226 Severity: "error", 227 Summary: "You forgot something", 228 Detail: "Please finish your thought", 229 Range: &DiagnosticRange{ 230 Filename: "short.tf", 231 Start: Pos{ 232 Line: 1, 233 Column: 16, 234 Byte: 15, 235 }, 236 End: Pos{ 237 Line: 1, 238 Column: 17, 239 Byte: 16, 240 }, 241 }, 242 Snippet: &DiagnosticSnippet{ 243 Code: ("bad source code"), 244 StartLine: (1), 245 HighlightStartOffset: (15), 246 HighlightEndOffset: (15), 247 Values: []DiagnosticExpressionValue{}, 248 }, 249 }, 250 }, 251 "error with unset highlight end position": { 252 &hcl.Diagnostic{ 253 Severity: hcl.DiagError, 254 Summary: "There is no end", 255 Detail: "But there is a beginning", 256 Subject: &hcl.Range{ 257 Filename: "test.tf", 258 Start: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 259 End: hcl.Pos{Line: 0, Column: 0, Byte: 0}, 260 }, 261 }, 262 &Diagnostic{ 263 Severity: "error", 264 Summary: "There is no end", 265 Detail: "But there is a beginning", 266 Range: &DiagnosticRange{ 267 Filename: "test.tf", 268 Start: Pos{ 269 Line: 1, 270 Column: 16, 271 Byte: 15, 272 }, 273 End: Pos{ 274 Line: 1, 275 Column: 17, 276 Byte: 16, 277 }, 278 }, 279 Snippet: &DiagnosticSnippet{ 280 Context: strPtr(`resource "test_resource" "test"`), 281 Code: `resource "test_resource" "test" {`, 282 StartLine: 1, 283 HighlightStartOffset: 15, 284 HighlightEndOffset: 16, 285 Values: []DiagnosticExpressionValue{}, 286 }, 287 }, 288 }, 289 "error whose range starts at a newline": { 290 &hcl.Diagnostic{ 291 Severity: hcl.DiagError, 292 Summary: "Invalid newline", 293 Detail: "How awkward!", 294 Subject: &hcl.Range{ 295 Filename: "odd-comment.tf", 296 Start: hcl.Pos{Line: 2, Column: 5, Byte: 4}, 297 End: hcl.Pos{Line: 3, Column: 1, Byte: 6}, 298 }, 299 }, 300 &Diagnostic{ 301 Severity: "error", 302 Summary: "Invalid newline", 303 Detail: "How awkward!", 304 Range: &DiagnosticRange{ 305 Filename: "odd-comment.tf", 306 Start: Pos{ 307 Line: 2, 308 Column: 5, 309 Byte: 4, 310 }, 311 End: Pos{ 312 Line: 3, 313 Column: 1, 314 Byte: 6, 315 }, 316 }, 317 Snippet: &DiagnosticSnippet{ 318 Code: `#`, 319 StartLine: 2, 320 Values: []DiagnosticExpressionValue{}, 321 322 // Due to the range starting at a newline on a blank 323 // line, we end up stripping off the initial newline 324 // to produce only a one-line snippet. That would 325 // therefore cause the start offset to naturally be 326 // -1, just before the Code we returned, but then we 327 // force it to zero so that the result will still be 328 // in range for a byte-oriented slice of Code. 329 HighlightStartOffset: 0, 330 HighlightEndOffset: 1, 331 }, 332 }, 333 }, 334 "error with source code subject and known expression": { 335 &hcl.Diagnostic{ 336 Severity: hcl.DiagError, 337 Summary: "Wrong noises", 338 Detail: "Biological sounds are not allowed", 339 Subject: &hcl.Range{ 340 Filename: "test.tf", 341 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 342 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 343 }, 344 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 345 hcl.TraverseRoot{Name: "var"}, 346 hcl.TraverseAttr{Name: "boop"}, 347 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 348 }), 349 EvalContext: &hcl.EvalContext{ 350 Variables: map[string]cty.Value{ 351 "var": cty.ObjectVal(map[string]cty.Value{ 352 "boop": cty.MapVal(map[string]cty.Value{ 353 "hello!": cty.StringVal("bleurgh"), 354 }), 355 }), 356 }, 357 }, 358 }, 359 &Diagnostic{ 360 Severity: "error", 361 Summary: "Wrong noises", 362 Detail: "Biological sounds are not allowed", 363 Range: &DiagnosticRange{ 364 Filename: "test.tf", 365 Start: Pos{ 366 Line: 2, 367 Column: 9, 368 Byte: 42, 369 }, 370 End: Pos{ 371 Line: 2, 372 Column: 26, 373 Byte: 59, 374 }, 375 }, 376 Snippet: &DiagnosticSnippet{ 377 Context: strPtr(`resource "test_resource" "test"`), 378 Code: (` foo = var.boop["hello!"]`), 379 StartLine: (2), 380 HighlightStartOffset: (8), 381 HighlightEndOffset: (25), 382 Values: []DiagnosticExpressionValue{ 383 { 384 Traversal: `var.boop["hello!"]`, 385 Statement: `is "bleurgh"`, 386 }, 387 }, 388 }, 389 }, 390 }, 391 "error with source code subject and expression referring to sensitive value": { 392 &hcl.Diagnostic{ 393 Severity: hcl.DiagError, 394 Summary: "Wrong noises", 395 Detail: "Biological sounds are not allowed", 396 Subject: &hcl.Range{ 397 Filename: "test.tf", 398 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 399 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 400 }, 401 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 402 hcl.TraverseRoot{Name: "var"}, 403 hcl.TraverseAttr{Name: "boop"}, 404 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 405 }), 406 EvalContext: &hcl.EvalContext{ 407 Variables: map[string]cty.Value{ 408 "var": cty.ObjectVal(map[string]cty.Value{ 409 "boop": cty.MapVal(map[string]cty.Value{ 410 "hello!": cty.StringVal("bleurgh").Mark(marks.Sensitive), 411 }), 412 }), 413 }, 414 }, 415 Extra: diagnosticCausedBySensitive(true), 416 }, 417 &Diagnostic{ 418 Severity: "error", 419 Summary: "Wrong noises", 420 Detail: "Biological sounds are not allowed", 421 Range: &DiagnosticRange{ 422 Filename: "test.tf", 423 Start: Pos{ 424 Line: 2, 425 Column: 9, 426 Byte: 42, 427 }, 428 End: Pos{ 429 Line: 2, 430 Column: 26, 431 Byte: 59, 432 }, 433 }, 434 Snippet: &DiagnosticSnippet{ 435 Context: strPtr(`resource "test_resource" "test"`), 436 Code: (` foo = var.boop["hello!"]`), 437 StartLine: (2), 438 HighlightStartOffset: (8), 439 HighlightEndOffset: (25), 440 Values: []DiagnosticExpressionValue{ 441 { 442 Traversal: `var.boop["hello!"]`, 443 Statement: `has a sensitive value`, 444 }, 445 }, 446 }, 447 }, 448 }, 449 "error with source code subject and expression referring to sensitive value when not caused by sensitive values": { 450 &hcl.Diagnostic{ 451 Severity: hcl.DiagError, 452 Summary: "Wrong noises", 453 Detail: "Biological sounds are not allowed", 454 Subject: &hcl.Range{ 455 Filename: "test.tf", 456 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 457 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 458 }, 459 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 460 hcl.TraverseRoot{Name: "var"}, 461 hcl.TraverseAttr{Name: "boop"}, 462 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 463 }), 464 EvalContext: &hcl.EvalContext{ 465 Variables: map[string]cty.Value{ 466 "var": cty.ObjectVal(map[string]cty.Value{ 467 "boop": cty.MapVal(map[string]cty.Value{ 468 "hello!": cty.StringVal("bleurgh").Mark(marks.Sensitive), 469 }), 470 }), 471 }, 472 }, 473 }, 474 &Diagnostic{ 475 Severity: "error", 476 Summary: "Wrong noises", 477 Detail: "Biological sounds are not allowed", 478 Range: &DiagnosticRange{ 479 Filename: "test.tf", 480 Start: Pos{ 481 Line: 2, 482 Column: 9, 483 Byte: 42, 484 }, 485 End: Pos{ 486 Line: 2, 487 Column: 26, 488 Byte: 59, 489 }, 490 }, 491 Snippet: &DiagnosticSnippet{ 492 Context: strPtr(`resource "test_resource" "test"`), 493 Code: (` foo = var.boop["hello!"]`), 494 StartLine: (2), 495 HighlightStartOffset: (8), 496 HighlightEndOffset: (25), 497 Values: []DiagnosticExpressionValue{ 498 // The sensitive value is filtered out because this is 499 // not a sensitive-value-related diagnostic message. 500 }, 501 }, 502 }, 503 }, 504 "error with source code subject and expression referring to a collection containing a sensitive value": { 505 &hcl.Diagnostic{ 506 Severity: hcl.DiagError, 507 Summary: "Wrong noises", 508 Detail: "Biological sounds are not allowed", 509 Subject: &hcl.Range{ 510 Filename: "test.tf", 511 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 512 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 513 }, 514 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 515 hcl.TraverseRoot{Name: "var"}, 516 hcl.TraverseAttr{Name: "boop"}, 517 }), 518 EvalContext: &hcl.EvalContext{ 519 Variables: map[string]cty.Value{ 520 "var": cty.ObjectVal(map[string]cty.Value{ 521 "boop": cty.MapVal(map[string]cty.Value{ 522 "hello!": cty.StringVal("bleurgh").Mark(marks.Sensitive), 523 }), 524 }), 525 }, 526 }, 527 }, 528 &Diagnostic{ 529 Severity: "error", 530 Summary: "Wrong noises", 531 Detail: "Biological sounds are not allowed", 532 Range: &DiagnosticRange{ 533 Filename: "test.tf", 534 Start: Pos{ 535 Line: 2, 536 Column: 9, 537 Byte: 42, 538 }, 539 End: Pos{ 540 Line: 2, 541 Column: 26, 542 Byte: 59, 543 }, 544 }, 545 Snippet: &DiagnosticSnippet{ 546 Context: strPtr(`resource "test_resource" "test"`), 547 Code: (` foo = var.boop["hello!"]`), 548 StartLine: (2), 549 HighlightStartOffset: (8), 550 HighlightEndOffset: (25), 551 Values: []DiagnosticExpressionValue{ 552 { 553 Traversal: `var.boop`, 554 Statement: `is map of string with 1 element`, 555 }, 556 }, 557 }, 558 }, 559 }, 560 "error with source code subject and unknown string expression": { 561 &hcl.Diagnostic{ 562 Severity: hcl.DiagError, 563 Summary: "Wrong noises", 564 Detail: "Biological sounds are not allowed", 565 Subject: &hcl.Range{ 566 Filename: "test.tf", 567 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 568 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 569 }, 570 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 571 hcl.TraverseRoot{Name: "var"}, 572 hcl.TraverseAttr{Name: "boop"}, 573 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 574 }), 575 EvalContext: &hcl.EvalContext{ 576 Variables: map[string]cty.Value{ 577 "var": cty.ObjectVal(map[string]cty.Value{ 578 "boop": cty.MapVal(map[string]cty.Value{ 579 "hello!": cty.UnknownVal(cty.String), 580 }), 581 }), 582 }, 583 }, 584 Extra: diagnosticCausedByUnknown(true), 585 }, 586 &Diagnostic{ 587 Severity: "error", 588 Summary: "Wrong noises", 589 Detail: "Biological sounds are not allowed", 590 Range: &DiagnosticRange{ 591 Filename: "test.tf", 592 Start: Pos{ 593 Line: 2, 594 Column: 9, 595 Byte: 42, 596 }, 597 End: Pos{ 598 Line: 2, 599 Column: 26, 600 Byte: 59, 601 }, 602 }, 603 Snippet: &DiagnosticSnippet{ 604 Context: strPtr(`resource "test_resource" "test"`), 605 Code: (` foo = var.boop["hello!"]`), 606 StartLine: (2), 607 HighlightStartOffset: (8), 608 HighlightEndOffset: (25), 609 Values: []DiagnosticExpressionValue{ 610 { 611 Traversal: `var.boop["hello!"]`, 612 Statement: `is a string, known only after apply`, 613 }, 614 }, 615 }, 616 }, 617 }, 618 "error with source code subject and unknown expression of unknown type": { 619 &hcl.Diagnostic{ 620 Severity: hcl.DiagError, 621 Summary: "Wrong noises", 622 Detail: "Biological sounds are not allowed", 623 Subject: &hcl.Range{ 624 Filename: "test.tf", 625 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 626 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 627 }, 628 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 629 hcl.TraverseRoot{Name: "var"}, 630 hcl.TraverseAttr{Name: "boop"}, 631 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 632 }), 633 EvalContext: &hcl.EvalContext{ 634 Variables: map[string]cty.Value{ 635 "var": cty.ObjectVal(map[string]cty.Value{ 636 "boop": cty.MapVal(map[string]cty.Value{ 637 "hello!": cty.UnknownVal(cty.DynamicPseudoType), 638 }), 639 }), 640 }, 641 }, 642 Extra: diagnosticCausedByUnknown(true), 643 }, 644 &Diagnostic{ 645 Severity: "error", 646 Summary: "Wrong noises", 647 Detail: "Biological sounds are not allowed", 648 Range: &DiagnosticRange{ 649 Filename: "test.tf", 650 Start: Pos{ 651 Line: 2, 652 Column: 9, 653 Byte: 42, 654 }, 655 End: Pos{ 656 Line: 2, 657 Column: 26, 658 Byte: 59, 659 }, 660 }, 661 Snippet: &DiagnosticSnippet{ 662 Context: strPtr(`resource "test_resource" "test"`), 663 Code: (` foo = var.boop["hello!"]`), 664 StartLine: (2), 665 HighlightStartOffset: (8), 666 HighlightEndOffset: (25), 667 Values: []DiagnosticExpressionValue{ 668 { 669 Traversal: `var.boop["hello!"]`, 670 Statement: `will be known only after apply`, 671 }, 672 }, 673 }, 674 }, 675 }, 676 "error with source code subject and unknown expression of unknown type when not caused by unknown values": { 677 &hcl.Diagnostic{ 678 Severity: hcl.DiagError, 679 Summary: "Wrong noises", 680 Detail: "Biological sounds are not allowed", 681 Subject: &hcl.Range{ 682 Filename: "test.tf", 683 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 684 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 685 }, 686 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 687 hcl.TraverseRoot{Name: "var"}, 688 hcl.TraverseAttr{Name: "boop"}, 689 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 690 }), 691 EvalContext: &hcl.EvalContext{ 692 Variables: map[string]cty.Value{ 693 "var": cty.ObjectVal(map[string]cty.Value{ 694 "boop": cty.MapVal(map[string]cty.Value{ 695 "hello!": cty.UnknownVal(cty.DynamicPseudoType), 696 }), 697 }), 698 }, 699 }, 700 }, 701 &Diagnostic{ 702 Severity: "error", 703 Summary: "Wrong noises", 704 Detail: "Biological sounds are not allowed", 705 Range: &DiagnosticRange{ 706 Filename: "test.tf", 707 Start: Pos{ 708 Line: 2, 709 Column: 9, 710 Byte: 42, 711 }, 712 End: Pos{ 713 Line: 2, 714 Column: 26, 715 Byte: 59, 716 }, 717 }, 718 Snippet: &DiagnosticSnippet{ 719 Context: strPtr(`resource "test_resource" "test"`), 720 Code: (` foo = var.boop["hello!"]`), 721 StartLine: (2), 722 HighlightStartOffset: (8), 723 HighlightEndOffset: (25), 724 Values: []DiagnosticExpressionValue{ 725 // The unknown value is filtered out because this is 726 // not an unknown-value-related diagnostic message. 727 }, 728 }, 729 }, 730 }, 731 "error with source code subject with multiple expression values": { 732 &hcl.Diagnostic{ 733 Severity: hcl.DiagError, 734 Summary: "Catastrophic failure", 735 Detail: "Basically, everything went wrong", 736 Subject: &hcl.Range{ 737 Filename: "values.tf", 738 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 739 End: hcl.Pos{Line: 13, Column: 2, Byte: 102}, 740 }, 741 Expression: hcltest.MockExprList([]hcl.Expression{ 742 hcltest.MockExprTraversalSrc("var.a"), 743 hcltest.MockExprTraversalSrc("var.b"), 744 hcltest.MockExprTraversalSrc("var.c"), 745 hcltest.MockExprTraversalSrc("var.d"), 746 hcltest.MockExprTraversalSrc("var.e"), 747 hcltest.MockExprTraversalSrc("var.f"), 748 hcltest.MockExprTraversalSrc("var.g"), 749 hcltest.MockExprTraversalSrc("var.h"), 750 hcltest.MockExprTraversalSrc("var.i"), 751 hcltest.MockExprTraversalSrc("var.j"), 752 hcltest.MockExprTraversalSrc("var.k"), 753 }), 754 EvalContext: &hcl.EvalContext{ 755 Variables: map[string]cty.Value{ 756 "var": cty.ObjectVal(map[string]cty.Value{ 757 "a": cty.True, 758 "b": cty.NumberFloatVal(123.45), 759 "c": cty.NullVal(cty.String), 760 "d": cty.StringVal("secret").Mark(marks.Sensitive), 761 "e": cty.False, 762 "f": cty.ListValEmpty(cty.String), 763 "g": cty.MapVal(map[string]cty.Value{ 764 "boop": cty.StringVal("beep"), 765 }), 766 "h": cty.ListVal([]cty.Value{ 767 cty.StringVal("boop"), 768 cty.StringVal("beep"), 769 cty.StringVal("blorp"), 770 }), 771 "i": cty.EmptyObjectVal, 772 "j": cty.ObjectVal(map[string]cty.Value{ 773 "foo": cty.StringVal("bar"), 774 }), 775 "k": cty.ObjectVal(map[string]cty.Value{ 776 "a": cty.True, 777 "b": cty.False, 778 }), 779 }), 780 }, 781 }, 782 Extra: diagnosticCausedBySensitive(true), 783 }, 784 &Diagnostic{ 785 Severity: "error", 786 Summary: "Catastrophic failure", 787 Detail: "Basically, everything went wrong", 788 Range: &DiagnosticRange{ 789 Filename: "values.tf", 790 Start: Pos{ 791 Line: 1, 792 Column: 1, 793 Byte: 0, 794 }, 795 End: Pos{ 796 Line: 13, 797 Column: 2, 798 Byte: 102, 799 }, 800 }, 801 Snippet: &DiagnosticSnippet{ 802 Code: `[ 803 var.a, 804 var.b, 805 var.c, 806 var.d, 807 var.e, 808 var.f, 809 var.g, 810 var.h, 811 var.i, 812 var.j, 813 var.k, 814 ]`, 815 StartLine: (1), 816 HighlightStartOffset: (0), 817 HighlightEndOffset: (102), 818 Values: []DiagnosticExpressionValue{ 819 { 820 Traversal: `var.a`, 821 Statement: `is true`, 822 }, 823 { 824 Traversal: `var.b`, 825 Statement: `is 123.45`, 826 }, 827 { 828 Traversal: `var.c`, 829 Statement: `is null`, 830 }, 831 { 832 Traversal: `var.d`, 833 Statement: `has a sensitive value`, 834 }, 835 { 836 Traversal: `var.e`, 837 Statement: `is false`, 838 }, 839 { 840 Traversal: `var.f`, 841 Statement: `is empty list of string`, 842 }, 843 { 844 Traversal: `var.g`, 845 Statement: `is map of string with 1 element`, 846 }, 847 { 848 Traversal: `var.h`, 849 Statement: `is list of string with 3 elements`, 850 }, 851 { 852 Traversal: `var.i`, 853 Statement: `is object with no attributes`, 854 }, 855 { 856 Traversal: `var.j`, 857 Statement: `is object with 1 attribute "foo"`, 858 }, 859 { 860 Traversal: `var.k`, 861 Statement: `is object with 2 attributes`, 862 }, 863 }, 864 }, 865 }, 866 }, 867 } 868 869 for name, tc := range testCases { 870 t.Run(name, func(t *testing.T) { 871 // Convert the diag into a tfdiags.Diagnostic 872 var diags tfdiags.Diagnostics 873 diags = diags.Append(tc.diag) 874 875 got := NewDiagnostic(diags[0], sources) 876 if !cmp.Equal(tc.want, got) { 877 t.Fatalf("wrong result\n:%s", cmp.Diff(tc.want, got)) 878 } 879 }) 880 881 t.Run(fmt.Sprintf("golden test for %s", name), func(t *testing.T) { 882 // Convert the diag into a tfdiags.Diagnostic 883 var diags tfdiags.Diagnostics 884 diags = diags.Append(tc.diag) 885 886 got := NewDiagnostic(diags[0], sources) 887 888 // Render the diagnostic to indented JSON 889 gotBytes, err := json.MarshalIndent(got, "", " ") 890 if err != nil { 891 t.Fatal(err) 892 } 893 894 // Compare against the golden reference 895 filename := path.Join( 896 "testdata", 897 "diagnostic", 898 fmt.Sprintf("%s.json", strings.ReplaceAll(name, " ", "-")), 899 ) 900 901 // Generate golden reference by uncommenting the next two lines: 902 // gotBytes = append(gotBytes, '\n') 903 // os.WriteFile(filename, gotBytes, 0644) 904 905 wantFile, err := os.Open(filename) 906 if err != nil { 907 t.Fatalf("failed to open golden file: %s", err) 908 } 909 defer wantFile.Close() 910 wantBytes, err := ioutil.ReadAll(wantFile) 911 if err != nil { 912 t.Fatalf("failed to read output file: %s", err) 913 } 914 915 // Don't care about leading or trailing whitespace 916 gotString := strings.TrimSpace(string(gotBytes)) 917 wantString := strings.TrimSpace(string(wantBytes)) 918 919 if !cmp.Equal(wantString, gotString) { 920 t.Fatalf("wrong result\n:%s", cmp.Diff(wantString, gotString)) 921 } 922 }) 923 } 924 } 925 926 // Helper function to make constructing literal Diagnostics easier. There 927 // are fields which are pointer-to-string to ensure that the rendered JSON 928 // results in `null` for an empty value, rather than `""`. 929 func strPtr(s string) *string { return &s } 930 931 // diagnosticCausedByUnknown is a testing helper for exercising our logic 932 // for selectively showing unknown values alongside our source snippets for 933 // diagnostics that are explicitly marked as being caused by unknown values. 934 type diagnosticCausedByUnknown bool 935 936 var _ tfdiags.DiagnosticExtraBecauseUnknown = diagnosticCausedByUnknown(true) 937 938 func (e diagnosticCausedByUnknown) DiagnosticCausedByUnknown() bool { 939 return bool(e) 940 } 941 942 // diagnosticCausedBySensitive is a testing helper for exercising our logic 943 // for selectively showing sensitive values alongside our source snippets for 944 // diagnostics that are explicitly marked as being caused by sensitive values. 945 type diagnosticCausedBySensitive bool 946 947 var _ tfdiags.DiagnosticExtraBecauseSensitive = diagnosticCausedBySensitive(true) 948 949 func (e diagnosticCausedBySensitive) DiagnosticCausedBySensitive() bool { 950 return bool(e) 951 }