github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/terraform/lang/marks" 16 "github.com/cycloidio/terraform/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 }, 416 &Diagnostic{ 417 Severity: "error", 418 Summary: "Wrong noises", 419 Detail: "Biological sounds are not allowed", 420 Range: &DiagnosticRange{ 421 Filename: "test.tf", 422 Start: Pos{ 423 Line: 2, 424 Column: 9, 425 Byte: 42, 426 }, 427 End: Pos{ 428 Line: 2, 429 Column: 26, 430 Byte: 59, 431 }, 432 }, 433 Snippet: &DiagnosticSnippet{ 434 Context: strPtr(`resource "test_resource" "test"`), 435 Code: (` foo = var.boop["hello!"]`), 436 StartLine: (2), 437 HighlightStartOffset: (8), 438 HighlightEndOffset: (25), 439 Values: []DiagnosticExpressionValue{ 440 { 441 Traversal: `var.boop["hello!"]`, 442 Statement: `has a sensitive value`, 443 }, 444 }, 445 }, 446 }, 447 }, 448 "error with source code subject and expression referring to a collection containing a sensitive value": { 449 &hcl.Diagnostic{ 450 Severity: hcl.DiagError, 451 Summary: "Wrong noises", 452 Detail: "Biological sounds are not allowed", 453 Subject: &hcl.Range{ 454 Filename: "test.tf", 455 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 456 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 457 }, 458 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 459 hcl.TraverseRoot{Name: "var"}, 460 hcl.TraverseAttr{Name: "boop"}, 461 }), 462 EvalContext: &hcl.EvalContext{ 463 Variables: map[string]cty.Value{ 464 "var": cty.ObjectVal(map[string]cty.Value{ 465 "boop": cty.MapVal(map[string]cty.Value{ 466 "hello!": cty.StringVal("bleurgh").Mark(marks.Sensitive), 467 }), 468 }), 469 }, 470 }, 471 }, 472 &Diagnostic{ 473 Severity: "error", 474 Summary: "Wrong noises", 475 Detail: "Biological sounds are not allowed", 476 Range: &DiagnosticRange{ 477 Filename: "test.tf", 478 Start: Pos{ 479 Line: 2, 480 Column: 9, 481 Byte: 42, 482 }, 483 End: Pos{ 484 Line: 2, 485 Column: 26, 486 Byte: 59, 487 }, 488 }, 489 Snippet: &DiagnosticSnippet{ 490 Context: strPtr(`resource "test_resource" "test"`), 491 Code: (` foo = var.boop["hello!"]`), 492 StartLine: (2), 493 HighlightStartOffset: (8), 494 HighlightEndOffset: (25), 495 Values: []DiagnosticExpressionValue{ 496 { 497 Traversal: `var.boop`, 498 Statement: `is map of string with 1 element`, 499 }, 500 }, 501 }, 502 }, 503 }, 504 "error with source code subject and unknown string expression": { 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 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 518 }), 519 EvalContext: &hcl.EvalContext{ 520 Variables: map[string]cty.Value{ 521 "var": cty.ObjectVal(map[string]cty.Value{ 522 "boop": cty.MapVal(map[string]cty.Value{ 523 "hello!": cty.UnknownVal(cty.String), 524 }), 525 }), 526 }, 527 }, 528 }, 529 &Diagnostic{ 530 Severity: "error", 531 Summary: "Wrong noises", 532 Detail: "Biological sounds are not allowed", 533 Range: &DiagnosticRange{ 534 Filename: "test.tf", 535 Start: Pos{ 536 Line: 2, 537 Column: 9, 538 Byte: 42, 539 }, 540 End: Pos{ 541 Line: 2, 542 Column: 26, 543 Byte: 59, 544 }, 545 }, 546 Snippet: &DiagnosticSnippet{ 547 Context: strPtr(`resource "test_resource" "test"`), 548 Code: (` foo = var.boop["hello!"]`), 549 StartLine: (2), 550 HighlightStartOffset: (8), 551 HighlightEndOffset: (25), 552 Values: []DiagnosticExpressionValue{ 553 { 554 Traversal: `var.boop["hello!"]`, 555 Statement: `is a string, known only after apply`, 556 }, 557 }, 558 }, 559 }, 560 }, 561 "error with source code subject and unknown expression of unknown type": { 562 &hcl.Diagnostic{ 563 Severity: hcl.DiagError, 564 Summary: "Wrong noises", 565 Detail: "Biological sounds are not allowed", 566 Subject: &hcl.Range{ 567 Filename: "test.tf", 568 Start: hcl.Pos{Line: 2, Column: 9, Byte: 42}, 569 End: hcl.Pos{Line: 2, Column: 26, Byte: 59}, 570 }, 571 Expression: hcltest.MockExprTraversal(hcl.Traversal{ 572 hcl.TraverseRoot{Name: "var"}, 573 hcl.TraverseAttr{Name: "boop"}, 574 hcl.TraverseIndex{Key: cty.StringVal("hello!")}, 575 }), 576 EvalContext: &hcl.EvalContext{ 577 Variables: map[string]cty.Value{ 578 "var": cty.ObjectVal(map[string]cty.Value{ 579 "boop": cty.MapVal(map[string]cty.Value{ 580 "hello!": cty.UnknownVal(cty.DynamicPseudoType), 581 }), 582 }), 583 }, 584 }, 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: `will be known only after apply`, 613 }, 614 }, 615 }, 616 }, 617 }, 618 "error with source code subject with multiple expression values": { 619 &hcl.Diagnostic{ 620 Severity: hcl.DiagError, 621 Summary: "Catastrophic failure", 622 Detail: "Basically, everything went wrong", 623 Subject: &hcl.Range{ 624 Filename: "values.tf", 625 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 626 End: hcl.Pos{Line: 13, Column: 2, Byte: 102}, 627 }, 628 Expression: hcltest.MockExprList([]hcl.Expression{ 629 hcltest.MockExprTraversalSrc("var.a"), 630 hcltest.MockExprTraversalSrc("var.b"), 631 hcltest.MockExprTraversalSrc("var.c"), 632 hcltest.MockExprTraversalSrc("var.d"), 633 hcltest.MockExprTraversalSrc("var.e"), 634 hcltest.MockExprTraversalSrc("var.f"), 635 hcltest.MockExprTraversalSrc("var.g"), 636 hcltest.MockExprTraversalSrc("var.h"), 637 hcltest.MockExprTraversalSrc("var.i"), 638 hcltest.MockExprTraversalSrc("var.j"), 639 hcltest.MockExprTraversalSrc("var.k"), 640 }), 641 EvalContext: &hcl.EvalContext{ 642 Variables: map[string]cty.Value{ 643 "var": cty.ObjectVal(map[string]cty.Value{ 644 "a": cty.True, 645 "b": cty.NumberFloatVal(123.45), 646 "c": cty.NullVal(cty.String), 647 "d": cty.StringVal("secret").Mark(marks.Sensitive), 648 "e": cty.False, 649 "f": cty.ListValEmpty(cty.String), 650 "g": cty.MapVal(map[string]cty.Value{ 651 "boop": cty.StringVal("beep"), 652 }), 653 "h": cty.ListVal([]cty.Value{ 654 cty.StringVal("boop"), 655 cty.StringVal("beep"), 656 cty.StringVal("blorp"), 657 }), 658 "i": cty.EmptyObjectVal, 659 "j": cty.ObjectVal(map[string]cty.Value{ 660 "foo": cty.StringVal("bar"), 661 }), 662 "k": cty.ObjectVal(map[string]cty.Value{ 663 "a": cty.True, 664 "b": cty.False, 665 }), 666 }), 667 }, 668 }, 669 }, 670 &Diagnostic{ 671 Severity: "error", 672 Summary: "Catastrophic failure", 673 Detail: "Basically, everything went wrong", 674 Range: &DiagnosticRange{ 675 Filename: "values.tf", 676 Start: Pos{ 677 Line: 1, 678 Column: 1, 679 Byte: 0, 680 }, 681 End: Pos{ 682 Line: 13, 683 Column: 2, 684 Byte: 102, 685 }, 686 }, 687 Snippet: &DiagnosticSnippet{ 688 Code: `[ 689 var.a, 690 var.b, 691 var.c, 692 var.d, 693 var.e, 694 var.f, 695 var.g, 696 var.h, 697 var.i, 698 var.j, 699 var.k, 700 ]`, 701 StartLine: (1), 702 HighlightStartOffset: (0), 703 HighlightEndOffset: (102), 704 Values: []DiagnosticExpressionValue{ 705 { 706 Traversal: `var.a`, 707 Statement: `is true`, 708 }, 709 { 710 Traversal: `var.b`, 711 Statement: `is 123.45`, 712 }, 713 { 714 Traversal: `var.c`, 715 Statement: `is null`, 716 }, 717 { 718 Traversal: `var.d`, 719 Statement: `has a sensitive value`, 720 }, 721 { 722 Traversal: `var.e`, 723 Statement: `is false`, 724 }, 725 { 726 Traversal: `var.f`, 727 Statement: `is empty list of string`, 728 }, 729 { 730 Traversal: `var.g`, 731 Statement: `is map of string with 1 element`, 732 }, 733 { 734 Traversal: `var.h`, 735 Statement: `is list of string with 3 elements`, 736 }, 737 { 738 Traversal: `var.i`, 739 Statement: `is object with no attributes`, 740 }, 741 { 742 Traversal: `var.j`, 743 Statement: `is object with 1 attribute "foo"`, 744 }, 745 { 746 Traversal: `var.k`, 747 Statement: `is object with 2 attributes`, 748 }, 749 }, 750 }, 751 }, 752 }, 753 } 754 755 for name, tc := range testCases { 756 t.Run(name, func(t *testing.T) { 757 // Convert the diag into a tfdiags.Diagnostic 758 var diags tfdiags.Diagnostics 759 diags = diags.Append(tc.diag) 760 761 got := NewDiagnostic(diags[0], sources) 762 if !cmp.Equal(tc.want, got) { 763 t.Fatalf("wrong result\n:%s", cmp.Diff(tc.want, got)) 764 } 765 }) 766 767 t.Run(fmt.Sprintf("golden test for %s", name), func(t *testing.T) { 768 // Convert the diag into a tfdiags.Diagnostic 769 var diags tfdiags.Diagnostics 770 diags = diags.Append(tc.diag) 771 772 got := NewDiagnostic(diags[0], sources) 773 774 // Render the diagnostic to indented JSON 775 gotBytes, err := json.MarshalIndent(got, "", " ") 776 if err != nil { 777 t.Fatal(err) 778 } 779 780 // Compare against the golden reference 781 filename := path.Join( 782 "testdata", 783 "diagnostic", 784 fmt.Sprintf("%s.json", strings.ReplaceAll(name, " ", "-")), 785 ) 786 787 // Generate golden reference by uncommenting the next two lines: 788 // gotBytes = append(gotBytes, '\n') 789 // os.WriteFile(filename, gotBytes, 0644) 790 791 wantFile, err := os.Open(filename) 792 if err != nil { 793 t.Fatalf("failed to open golden file: %s", err) 794 } 795 defer wantFile.Close() 796 wantBytes, err := ioutil.ReadAll(wantFile) 797 if err != nil { 798 t.Fatalf("failed to read output file: %s", err) 799 } 800 801 // Don't care about leading or trailing whitespace 802 gotString := strings.TrimSpace(string(gotBytes)) 803 wantString := strings.TrimSpace(string(wantBytes)) 804 805 if !cmp.Equal(wantString, gotString) { 806 t.Fatalf("wrong result\n:%s", cmp.Diff(wantString, gotString)) 807 } 808 }) 809 } 810 } 811 812 // Helper function to make constructing literal Diagnostics easier. There 813 // are fields which are pointer-to-string to ensure that the rendered JSON 814 // results in `null` for an empty value, rather than `""`. 815 func strPtr(s string) *string { return &s }