670 lines
17 KiB
Go
670 lines
17 KiB
Go
![]() |
package json
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/google/go-cmp/cmp"
|
||
|
"github.com/hashicorp/hcl/v2"
|
||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||
|
"github.com/hashicorp/terraform/tfdiags"
|
||
|
"github.com/zclconf/go-cty/cty"
|
||
|
)
|
||
|
|
||
|
func TestNewDiagnostic(t *testing.T) {
|
||
|
// Common HCL for diags with source ranges. This does not have any real
|
||
|
// semantic errors, but we can synthesize fake HCL errors which will
|
||
|
// exercise the diagnostic rendering code using this
|
||
|
sources := map[string][]byte{
|
||
|
"test.tf": []byte(`resource "test_resource" "test" {
|
||
|
foo = var.boop["hello!"]
|
||
|
bar = {
|
||
|
baz = maybe
|
||
|
}
|
||
|
}
|
||
|
`),
|
||
|
"short.tf": []byte("bad source code"),
|
||
|
"values.tf": []byte(`[
|
||
|
var.a,
|
||
|
var.b,
|
||
|
var.c,
|
||
|
var.d,
|
||
|
var.e,
|
||
|
var.f,
|
||
|
var.g,
|
||
|
var.h,
|
||
|
var.i,
|
||
|
var.j,
|
||
|
var.k,
|
||
|
]
|
||
|
`),
|
||
|
}
|
||
|
testCases := map[string]struct {
|
||
|
diag interface{} // allow various kinds of diags
|
||
|
want *Diagnostic
|
||
|
}{
|
||
|
"sourceless warning": {
|
||
|
tfdiags.Sourceless(
|
||
|
tfdiags.Warning,
|
||
|
"Oh no",
|
||
|
"Something is broken",
|
||
|
),
|
||
|
&Diagnostic{
|
||
|
Severity: "warning",
|
||
|
Summary: "Oh no",
|
||
|
Detail: "Something is broken",
|
||
|
},
|
||
|
},
|
||
|
"error with source code unavailable": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Bad news",
|
||
|
Detail: "It went wrong",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "modules/oops/missing.tf",
|
||
|
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||
|
End: hcl.Pos{Line: 2, Column: 12, Byte: 33},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Bad news",
|
||
|
Detail: "It went wrong",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "modules/oops/missing.tf",
|
||
|
Start: Pos{
|
||
|
Line: 1,
|
||
|
Column: 6,
|
||
|
Byte: 5,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 2,
|
||
|
Column: 12,
|
||
|
Byte: 33,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Tiny explosion",
|
||
|
Detail: "Unexpected detonation while parsing",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
|
||
|
End: hcl.Pos{Line: 1, Column: 25, Byte: 24},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Tiny explosion",
|
||
|
Detail: "Unexpected detonation while parsing",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 1,
|
||
|
Column: 10,
|
||
|
Byte: 9,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 1,
|
||
|
Column: 25,
|
||
|
Byte: 24,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: `resource "test_resource" "test" {`,
|
||
|
StartLine: 1,
|
||
|
HighlightStartOffset: 9,
|
||
|
HighlightEndOffset: 24,
|
||
|
Values: []DiagnosticExpressionValue{},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject but no context": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Nonsense input",
|
||
|
Detail: "What you wrote makes no sense",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "short.tf",
|
||
|
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||
|
End: hcl.Pos{Line: 1, Column: 10, Byte: 9},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Nonsense input",
|
||
|
Detail: "What you wrote makes no sense",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "short.tf",
|
||
|
Start: Pos{
|
||
|
Line: 1,
|
||
|
Column: 5,
|
||
|
Byte: 4,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 1,
|
||
|
Column: 10,
|
||
|
Byte: 9,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: nil,
|
||
|
Code: (`bad source code`),
|
||
|
StartLine: (1),
|
||
|
HighlightStartOffset: (4),
|
||
|
HighlightEndOffset: (9),
|
||
|
Values: []DiagnosticExpressionValue{},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with multi-line snippet": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "In this house we respect booleans",
|
||
|
Detail: "True or false, there is no maybe",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 4, Column: 11, Byte: 81},
|
||
|
End: hcl.Pos{Line: 4, Column: 16, Byte: 86},
|
||
|
},
|
||
|
Context: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 3, Column: 3, Byte: 63},
|
||
|
End: hcl.Pos{Line: 5, Column: 4, Byte: 90},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "In this house we respect booleans",
|
||
|
Detail: "True or false, there is no maybe",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 4,
|
||
|
Column: 11,
|
||
|
Byte: 81,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 4,
|
||
|
Column: 16,
|
||
|
Byte: 86,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: " bar = {\n baz = maybe\n }",
|
||
|
StartLine: 3,
|
||
|
HighlightStartOffset: 20,
|
||
|
HighlightEndOffset: 25,
|
||
|
Values: []DiagnosticExpressionValue{},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with empty highlight range at end of source code": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "You forgot something",
|
||
|
Detail: "Please finish your thought",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "short.tf",
|
||
|
Start: hcl.Pos{Line: 1, Column: 16, Byte: 15},
|
||
|
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "You forgot something",
|
||
|
Detail: "Please finish your thought",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "short.tf",
|
||
|
Start: Pos{
|
||
|
Line: 1,
|
||
|
Column: 16,
|
||
|
Byte: 15,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 1,
|
||
|
Column: 17,
|
||
|
Byte: 16,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Code: ("bad source code"),
|
||
|
StartLine: (1),
|
||
|
HighlightStartOffset: (15),
|
||
|
HighlightEndOffset: (15),
|
||
|
Values: []DiagnosticExpressionValue{},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject and known expression": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||
|
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||
|
},
|
||
|
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||
|
hcl.TraverseRoot{Name: "var"},
|
||
|
hcl.TraverseAttr{Name: "boop"},
|
||
|
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||
|
}),
|
||
|
EvalContext: &hcl.EvalContext{
|
||
|
Variables: map[string]cty.Value{
|
||
|
"var": cty.ObjectVal(map[string]cty.Value{
|
||
|
"boop": cty.MapVal(map[string]cty.Value{
|
||
|
"hello!": cty.StringVal("bleurgh"),
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 2,
|
||
|
Column: 9,
|
||
|
Byte: 42,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 2,
|
||
|
Column: 26,
|
||
|
Byte: 59,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: (` foo = var.boop["hello!"]`),
|
||
|
StartLine: (2),
|
||
|
HighlightStartOffset: (8),
|
||
|
HighlightEndOffset: (25),
|
||
|
Values: []DiagnosticExpressionValue{
|
||
|
{
|
||
|
Traversal: `var.boop["hello!"]`,
|
||
|
Statement: `is "bleurgh"`,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject and expression referring to sensitive value": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||
|
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||
|
},
|
||
|
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||
|
hcl.TraverseRoot{Name: "var"},
|
||
|
hcl.TraverseAttr{Name: "boop"},
|
||
|
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||
|
}),
|
||
|
EvalContext: &hcl.EvalContext{
|
||
|
Variables: map[string]cty.Value{
|
||
|
"var": cty.ObjectVal(map[string]cty.Value{
|
||
|
"boop": cty.MapVal(map[string]cty.Value{
|
||
|
"hello!": cty.StringVal("bleurgh").Mark("sensitive"),
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 2,
|
||
|
Column: 9,
|
||
|
Byte: 42,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 2,
|
||
|
Column: 26,
|
||
|
Byte: 59,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: (` foo = var.boop["hello!"]`),
|
||
|
StartLine: (2),
|
||
|
HighlightStartOffset: (8),
|
||
|
HighlightEndOffset: (25),
|
||
|
Values: []DiagnosticExpressionValue{
|
||
|
{
|
||
|
Traversal: `var.boop["hello!"]`,
|
||
|
Statement: `has a sensitive value`,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject and unknown string expression": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||
|
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||
|
},
|
||
|
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||
|
hcl.TraverseRoot{Name: "var"},
|
||
|
hcl.TraverseAttr{Name: "boop"},
|
||
|
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||
|
}),
|
||
|
EvalContext: &hcl.EvalContext{
|
||
|
Variables: map[string]cty.Value{
|
||
|
"var": cty.ObjectVal(map[string]cty.Value{
|
||
|
"boop": cty.MapVal(map[string]cty.Value{
|
||
|
"hello!": cty.UnknownVal(cty.String),
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 2,
|
||
|
Column: 9,
|
||
|
Byte: 42,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 2,
|
||
|
Column: 26,
|
||
|
Byte: 59,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: (` foo = var.boop["hello!"]`),
|
||
|
StartLine: (2),
|
||
|
HighlightStartOffset: (8),
|
||
|
HighlightEndOffset: (25),
|
||
|
Values: []DiagnosticExpressionValue{
|
||
|
{
|
||
|
Traversal: `var.boop["hello!"]`,
|
||
|
Statement: `is a string, known only after apply`,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject and unknown expression of unknown type": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "test.tf",
|
||
|
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||
|
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||
|
},
|
||
|
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||
|
hcl.TraverseRoot{Name: "var"},
|
||
|
hcl.TraverseAttr{Name: "boop"},
|
||
|
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||
|
}),
|
||
|
EvalContext: &hcl.EvalContext{
|
||
|
Variables: map[string]cty.Value{
|
||
|
"var": cty.ObjectVal(map[string]cty.Value{
|
||
|
"boop": cty.MapVal(map[string]cty.Value{
|
||
|
"hello!": cty.UnknownVal(cty.DynamicPseudoType),
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Wrong noises",
|
||
|
Detail: "Biological sounds are not allowed",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "test.tf",
|
||
|
Start: Pos{
|
||
|
Line: 2,
|
||
|
Column: 9,
|
||
|
Byte: 42,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 2,
|
||
|
Column: 26,
|
||
|
Byte: 59,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Context: strPtr(`resource "test_resource" "test"`),
|
||
|
Code: (` foo = var.boop["hello!"]`),
|
||
|
StartLine: (2),
|
||
|
HighlightStartOffset: (8),
|
||
|
HighlightEndOffset: (25),
|
||
|
Values: []DiagnosticExpressionValue{
|
||
|
{
|
||
|
Traversal: `var.boop["hello!"]`,
|
||
|
Statement: `will be known only after apply`,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
"error with source code subject with multiple expression values": {
|
||
|
&hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Catastrophic failure",
|
||
|
Detail: "Basically, everything went wrong",
|
||
|
Subject: &hcl.Range{
|
||
|
Filename: "values.tf",
|
||
|
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||
|
End: hcl.Pos{Line: 13, Column: 2, Byte: 102},
|
||
|
},
|
||
|
Expression: hcltest.MockExprList([]hcl.Expression{
|
||
|
hcltest.MockExprTraversalSrc("var.a"),
|
||
|
hcltest.MockExprTraversalSrc("var.b"),
|
||
|
hcltest.MockExprTraversalSrc("var.c"),
|
||
|
hcltest.MockExprTraversalSrc("var.d"),
|
||
|
hcltest.MockExprTraversalSrc("var.e"),
|
||
|
hcltest.MockExprTraversalSrc("var.f"),
|
||
|
hcltest.MockExprTraversalSrc("var.g"),
|
||
|
hcltest.MockExprTraversalSrc("var.h"),
|
||
|
hcltest.MockExprTraversalSrc("var.i"),
|
||
|
hcltest.MockExprTraversalSrc("var.j"),
|
||
|
hcltest.MockExprTraversalSrc("var.k"),
|
||
|
}),
|
||
|
EvalContext: &hcl.EvalContext{
|
||
|
Variables: map[string]cty.Value{
|
||
|
"var": cty.ObjectVal(map[string]cty.Value{
|
||
|
"a": cty.True,
|
||
|
"b": cty.NumberFloatVal(123.45),
|
||
|
"c": cty.NullVal(cty.String),
|
||
|
"d": cty.StringVal("secret").Mark("sensitive"),
|
||
|
"e": cty.False,
|
||
|
"f": cty.ListValEmpty(cty.String),
|
||
|
"g": cty.MapVal(map[string]cty.Value{
|
||
|
"boop": cty.StringVal("beep"),
|
||
|
}),
|
||
|
"h": cty.ListVal([]cty.Value{
|
||
|
cty.StringVal("boop"),
|
||
|
cty.StringVal("beep"),
|
||
|
cty.StringVal("blorp"),
|
||
|
}),
|
||
|
"i": cty.EmptyObjectVal,
|
||
|
"j": cty.ObjectVal(map[string]cty.Value{
|
||
|
"foo": cty.StringVal("bar"),
|
||
|
}),
|
||
|
"k": cty.ObjectVal(map[string]cty.Value{
|
||
|
"a": cty.True,
|
||
|
"b": cty.False,
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
&Diagnostic{
|
||
|
Severity: "error",
|
||
|
Summary: "Catastrophic failure",
|
||
|
Detail: "Basically, everything went wrong",
|
||
|
Range: &DiagnosticRange{
|
||
|
Filename: "values.tf",
|
||
|
Start: Pos{
|
||
|
Line: 1,
|
||
|
Column: 1,
|
||
|
Byte: 0,
|
||
|
},
|
||
|
End: Pos{
|
||
|
Line: 13,
|
||
|
Column: 2,
|
||
|
Byte: 102,
|
||
|
},
|
||
|
},
|
||
|
Snippet: &DiagnosticSnippet{
|
||
|
Code: `[
|
||
|
var.a,
|
||
|
var.b,
|
||
|
var.c,
|
||
|
var.d,
|
||
|
var.e,
|
||
|
var.f,
|
||
|
var.g,
|
||
|
var.h,
|
||
|
var.i,
|
||
|
var.j,
|
||
|
var.k,
|
||
|
]`,
|
||
|
StartLine: (1),
|
||
|
HighlightStartOffset: (0),
|
||
|
HighlightEndOffset: (102),
|
||
|
Values: []DiagnosticExpressionValue{
|
||
|
{
|
||
|
Traversal: `var.a`,
|
||
|
Statement: `is true`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.b`,
|
||
|
Statement: `is 123.45`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.c`,
|
||
|
Statement: `is null`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.d`,
|
||
|
Statement: `has a sensitive value`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.e`,
|
||
|
Statement: `is false`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.f`,
|
||
|
Statement: `is empty list of string`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.g`,
|
||
|
Statement: `is map of string with 1 element`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.h`,
|
||
|
Statement: `is list of string with 3 elements`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.i`,
|
||
|
Statement: `is object with no attributes`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.j`,
|
||
|
Statement: `is object with 1 attribute "foo"`,
|
||
|
},
|
||
|
{
|
||
|
Traversal: `var.k`,
|
||
|
Statement: `is object with 2 attributes`,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for name, tc := range testCases {
|
||
|
t.Run(name, func(t *testing.T) {
|
||
|
// Convert the diag into a tfdiags.Diagnostic
|
||
|
var diags tfdiags.Diagnostics
|
||
|
diags = diags.Append(tc.diag)
|
||
|
|
||
|
got := NewDiagnostic(diags[0], sources)
|
||
|
if !cmp.Equal(tc.want, got) {
|
||
|
t.Fatalf("wrong result\n:%s", cmp.Diff(tc.want, got))
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run(fmt.Sprintf("golden test for %s", name), func(t *testing.T) {
|
||
|
// Convert the diag into a tfdiags.Diagnostic
|
||
|
var diags tfdiags.Diagnostics
|
||
|
diags = diags.Append(tc.diag)
|
||
|
|
||
|
got := NewDiagnostic(diags[0], sources)
|
||
|
|
||
|
// Render the diagnostic to indented JSON
|
||
|
gotBytes, err := json.MarshalIndent(got, "", " ")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
// Compare against the golden reference
|
||
|
filename := path.Join(
|
||
|
"testdata",
|
||
|
"diagnostic",
|
||
|
fmt.Sprintf("%s.json", strings.ReplaceAll(name, " ", "-")),
|
||
|
)
|
||
|
wantFile, err := os.Open(filename)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to open golden file: %s", err)
|
||
|
}
|
||
|
defer wantFile.Close()
|
||
|
wantBytes, err := ioutil.ReadAll(wantFile)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read output file: %s", err)
|
||
|
}
|
||
|
|
||
|
// Don't care about leading or trailing whitespace
|
||
|
gotString := strings.TrimSpace(string(gotBytes))
|
||
|
wantString := strings.TrimSpace(string(wantBytes))
|
||
|
|
||
|
if !cmp.Equal(wantString, gotString) {
|
||
|
t.Fatalf("wrong result\n:%s", cmp.Diff(wantString, gotString))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper function to make constructing literal Diagnostics easier. There
|
||
|
// are fields which are pointer-to-string to ensure that the rendered JSON
|
||
|
// results in `null` for an empty value, rather than `""`.
|
||
|
func strPtr(s string) *string { return &s }
|