If a diagnostic has an associated Expression and EvalContext then we can look up the values of any variables referenced in the expression and show them in the diagnostics message as additional context. This is particularly useful when dealing with situations where a given expression is evaluated multiple times with different variables, such as in a 'for' expression, since each evaluation may produce a different set of diagnostics.
236 lines
4.3 KiB
Go
236 lines
4.3 KiB
Go
package hcl
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestDiagnosticTextWriter(t *testing.T) {
|
|
tests := []struct {
|
|
Input *Diagnostic
|
|
Want string
|
|
}{
|
|
{
|
|
&Diagnostic{
|
|
Severity: DiagError,
|
|
Summary: "Splines not reticulated",
|
|
Detail: "All splines must be pre-reticulated.",
|
|
Subject: &Range{
|
|
Start: Pos{
|
|
Byte: 0,
|
|
Column: 1,
|
|
Line: 1,
|
|
},
|
|
End: Pos{
|
|
Byte: 3,
|
|
Column: 4,
|
|
Line: 1,
|
|
},
|
|
},
|
|
},
|
|
`Error: Splines not reticulated
|
|
|
|
on line 1, in hardcoded-context:
|
|
1: foo = 1
|
|
|
|
All splines must be pre-reticulated.
|
|
|
|
`,
|
|
},
|
|
{
|
|
&Diagnostic{
|
|
Severity: DiagError,
|
|
Summary: "Unsupported attribute",
|
|
Detail: `"baz" is not a supported top-level attribute. Did you mean "bam"?`,
|
|
Subject: &Range{
|
|
Start: Pos{
|
|
Byte: 16,
|
|
Column: 1,
|
|
Line: 3,
|
|
},
|
|
End: Pos{
|
|
Byte: 19,
|
|
Column: 4,
|
|
Line: 3,
|
|
},
|
|
},
|
|
},
|
|
`Error: Unsupported attribute
|
|
|
|
on line 3, in hardcoded-context:
|
|
3: baz = 3
|
|
|
|
"baz" is not a supported top-level
|
|
attribute. Did you mean "bam"?
|
|
|
|
`,
|
|
},
|
|
{
|
|
&Diagnostic{
|
|
Severity: DiagError,
|
|
Summary: "Unsupported attribute",
|
|
Detail: `"pizza" is not a supported attribute. Did you mean "pizzetta"?`,
|
|
Subject: &Range{
|
|
Start: Pos{
|
|
Byte: 42,
|
|
Column: 3,
|
|
Line: 5,
|
|
},
|
|
End: Pos{
|
|
Byte: 47,
|
|
Column: 8,
|
|
Line: 5,
|
|
},
|
|
},
|
|
// This is actually not a great example of a context, but is here to test
|
|
// whether we're able to show a multi-line context when needed.
|
|
Context: &Range{
|
|
Start: Pos{
|
|
Byte: 24,
|
|
Column: 1,
|
|
Line: 4,
|
|
},
|
|
End: Pos{
|
|
Byte: 60,
|
|
Column: 2,
|
|
Line: 6,
|
|
},
|
|
},
|
|
},
|
|
`Error: Unsupported attribute
|
|
|
|
on line 5, in hardcoded-context:
|
|
4: block "party" {
|
|
5: pizza = "cheese"
|
|
6: }
|
|
|
|
"pizza" is not a supported attribute.
|
|
Did you mean "pizzetta"?
|
|
|
|
`,
|
|
},
|
|
{
|
|
&Diagnostic{
|
|
Severity: DiagError,
|
|
Summary: "Test of including relevant variable values",
|
|
Detail: `This diagnostic includes an expression and an evalcontext.`,
|
|
Subject: &Range{
|
|
Start: Pos{
|
|
Byte: 42,
|
|
Column: 3,
|
|
Line: 5,
|
|
},
|
|
End: Pos{
|
|
Byte: 47,
|
|
Column: 8,
|
|
Line: 5,
|
|
},
|
|
},
|
|
Expression: &diagnosticTestExpr{
|
|
vars: []Traversal{
|
|
{
|
|
TraverseRoot{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
{
|
|
TraverseRoot{
|
|
Name: "bar",
|
|
},
|
|
TraverseAttr{
|
|
Name: "baz",
|
|
},
|
|
},
|
|
{
|
|
TraverseRoot{
|
|
Name: "missing",
|
|
},
|
|
},
|
|
{
|
|
TraverseRoot{
|
|
Name: "boz",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
EvalContext: &EvalContext{
|
|
parent: &EvalContext{
|
|
Variables: map[string]cty.Value{
|
|
"foo": cty.StringVal("foo value"),
|
|
},
|
|
},
|
|
Variables: map[string]cty.Value{
|
|
"bar": cty.ObjectVal(map[string]cty.Value{
|
|
"baz": cty.ListValEmpty(cty.String),
|
|
}),
|
|
"boz": cty.NumberIntVal(5),
|
|
"unused": cty.True,
|
|
},
|
|
},
|
|
},
|
|
`Error: Test of including relevant variable values
|
|
|
|
on line 5, in hardcoded-context:
|
|
5: pizza = "cheese"
|
|
|
|
with bar.baz as empty list of string,
|
|
boz as 5,
|
|
foo as "foo value".
|
|
|
|
This diagnostic includes an expression
|
|
and an evalcontext.
|
|
|
|
`,
|
|
},
|
|
}
|
|
|
|
files := map[string]*File{
|
|
"": &File{
|
|
Bytes: []byte(testDiagnosticTextWriterSource),
|
|
Nav: &diagnosticTestNav{},
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
|
bwr := &bytes.Buffer{}
|
|
dwr := NewDiagnosticTextWriter(bwr, files, 40, false)
|
|
err := dwr.WriteDiagnostic(test.Input)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
got := bwr.String()
|
|
if got != test.Want {
|
|
t.Errorf("wrong result\n\ngot:\n%swant:\n%s", got, test.Want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const testDiagnosticTextWriterSource = `foo = 1
|
|
bar = 2
|
|
baz = 3
|
|
block "party" {
|
|
pizza = "cheese"
|
|
}
|
|
`
|
|
|
|
type diagnosticTestNav struct {
|
|
}
|
|
|
|
func (tn *diagnosticTestNav) ContextString(offset int) string {
|
|
return "hardcoded-context"
|
|
}
|
|
|
|
type diagnosticTestExpr struct {
|
|
vars []Traversal
|
|
staticExpr
|
|
}
|
|
|
|
func (e *diagnosticTestExpr) Variables() []Traversal {
|
|
return e.vars
|
|
}
|