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