cmd/hclspecsuite: Decode expected traversals and diagnostics
These are not used by the test harness yet, but they can now be decoded from a test spec file.
This commit is contained in:
parent
65f9271b86
commit
95b1859585
@ -5,10 +5,10 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/hashicorp/hcl2/hclparse"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"github.com/hashicorp/hcl2/hclparse"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -10,13 +10,13 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl2/ext/typeexpr"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hclparse"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
|
||||
"github.com/hashicorp/hcl2/ext/typeexpr"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hclparse"
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
|
@ -3,22 +3,37 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/ext/typeexpr"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
|
||||
"github.com/hashicorp/hcl2/ext/typeexpr"
|
||||
"github.com/hashicorp/hcl2/gohcl"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
type TestFile struct {
|
||||
Result cty.Value
|
||||
ResultType cty.Type
|
||||
|
||||
Traversals []hcl.Traversal
|
||||
ChecksTraversals bool
|
||||
ExpectedTraversals []*TestFileExpectTraversal
|
||||
|
||||
ExpectedDiags []*TestFileExpectDiag
|
||||
|
||||
ResultRange hcl.Range
|
||||
ResultTypeRange hcl.Range
|
||||
}
|
||||
|
||||
type TestFileExpectTraversal struct {
|
||||
Traversal hcl.Traversal
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
type TestFileExpectDiag struct {
|
||||
Severity hcl.DiagnosticSeverity
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
func (r *Runner) LoadTestFile(filename string) (*TestFile, hcl.Diagnostics) {
|
||||
f, diags := r.parser.ParseHCLFile(filename)
|
||||
if diags.HasErrors() {
|
||||
@ -63,9 +78,193 @@ func (r *Runner) LoadTestFile(filename string) (*TestFile, hcl.Diagnostics) {
|
||||
ret.ResultRange = resultAttr.Expr.Range()
|
||||
}
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
|
||||
case "traversals":
|
||||
if ret.ChecksTraversals {
|
||||
// Indicates a duplicate traversals block
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate \"traversals\" block",
|
||||
Detail: fmt.Sprintf("Only one traversals block is expected."),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
expectTraversals, moreDiags := r.decodeTraversalsBlock(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
if !moreDiags.HasErrors() {
|
||||
ret.ChecksTraversals = true
|
||||
ret.ExpectedTraversals = expectTraversals
|
||||
}
|
||||
|
||||
case "diagnostics":
|
||||
if len(ret.ExpectedDiags) > 0 {
|
||||
// Indicates a duplicate diagnostics block
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate \"diagnostics\" block",
|
||||
Detail: fmt.Sprintf("Only one diagnostics block is expected."),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
expectDiags, moreDiags := r.decodeDiagnosticsBlock(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
ret.ExpectedDiags = expectDiags
|
||||
|
||||
default:
|
||||
// Shouldn't get here, because the above cases are exhaustive for
|
||||
// our test file schema.
|
||||
panic(fmt.Sprintf("unsupported block type %q", block.Type))
|
||||
}
|
||||
}
|
||||
|
||||
if ret.Result != cty.NilVal && len(ret.ExpectedDiags) > 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Conflicting spec expectations",
|
||||
Detail: "This test spec includes expected diagnostics, so it may not also include an expected result.",
|
||||
Subject: &content.Attributes["result"].Range,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (r *Runner) decodeTraversalsBlock(block *hcl.Block) ([]*TestFileExpectTraversal, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := block.Body.Content(testFileTraversalsSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
var ret []*TestFileExpectTraversal
|
||||
for _, block := range content.Blocks {
|
||||
// There's only one block type in our schema, so we can assume all
|
||||
// blocks are of that type.
|
||||
expectTraversal, moreDiags := r.decodeTraversalExpectBlock(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
if expectTraversal != nil {
|
||||
ret = append(ret, expectTraversal)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (r *Runner) decodeTraversalExpectBlock(block *hcl.Block) (*TestFileExpectTraversal, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
rng, body, moreDiags := r.decodeRangeFromBody(block.Body)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
content, moreDiags := body.Content(testFileTraversalExpectSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
var traversal hcl.Traversal
|
||||
{
|
||||
refAttr := content.Attributes["ref"]
|
||||
traversal, moreDiags = hcl.AbsTraversalForExpr(refAttr.Expr)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
}
|
||||
|
||||
return &TestFileExpectTraversal{
|
||||
Traversal: traversal,
|
||||
Range: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (r *Runner) decodeDiagnosticsBlock(block *hcl.Block) ([]*TestFileExpectDiag, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := block.Body.Content(testFileDiagnosticsSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if len(content.Blocks) == 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Empty diagnostics block",
|
||||
Detail: "If a diagnostics block is present, at least one expectation statement (\"error\" or \"warning\" block) must be included.",
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
ret := make([]*TestFileExpectDiag, 0, len(content.Blocks))
|
||||
for _, block := range content.Blocks {
|
||||
rng, remain, moreDiags := r.decodeRangeFromBody(block.Body)
|
||||
diags = append(diags, moreDiags...)
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Should have nothing else in the block aside from the range definition.
|
||||
_, moreDiags = remain.Content(&hcl.BodySchema{})
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
var severity hcl.DiagnosticSeverity
|
||||
switch block.Type {
|
||||
case "error":
|
||||
severity = hcl.DiagError
|
||||
case "warning":
|
||||
severity = hcl.DiagWarning
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported block type %q", block.Type))
|
||||
}
|
||||
|
||||
ret = append(ret, &TestFileExpectDiag{
|
||||
Severity: severity,
|
||||
Range: rng,
|
||||
})
|
||||
}
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (r *Runner) decodeRangeFromBody(body hcl.Body) (hcl.Range, hcl.Body, hcl.Diagnostics) {
|
||||
type RawPos struct {
|
||||
Line int `hcl:"line"`
|
||||
Column int `hcl:"column"`
|
||||
Byte int `hcl:"byte"`
|
||||
}
|
||||
type RawRange struct {
|
||||
From RawPos `hcl:"from,block"`
|
||||
To RawPos `hcl:"to,block"`
|
||||
Remain hcl.Body `hcl:",remain"`
|
||||
}
|
||||
|
||||
var raw RawRange
|
||||
diags := gohcl.DecodeBody(body, nil, &raw)
|
||||
|
||||
return hcl.Range{
|
||||
// We intentionally omit Filename here, because the test spec doesn't
|
||||
// need to specify that explicitly: we can infer it to be the file
|
||||
// path we pass to hcldec.
|
||||
Start: hcl.Pos{
|
||||
Line: raw.From.Line,
|
||||
Column: raw.From.Line,
|
||||
Byte: raw.From.Line,
|
||||
},
|
||||
End: hcl.Pos{
|
||||
Line: raw.To.Line,
|
||||
Column: raw.To.Line,
|
||||
Byte: raw.To.Line,
|
||||
},
|
||||
}, raw.Remain, diags
|
||||
}
|
||||
|
||||
var testFileSchema = &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{
|
||||
@ -79,5 +278,69 @@ var testFileSchema = &hcl.BodySchema{
|
||||
{
|
||||
Type: "traversals",
|
||||
},
|
||||
{
|
||||
Type: "diagnostics",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testFileTraversalsSchema = &hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "expect",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testFileTraversalExpectSchema = &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{
|
||||
Name: "ref",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "range",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testFileDiagnosticsSchema = &hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "error",
|
||||
},
|
||||
{
|
||||
Type: "warning",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testFileRangeSchema = &hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "from",
|
||||
},
|
||||
{
|
||||
Type: "to",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testFilePosSchema = &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{
|
||||
Name: "line",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "column",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "byte",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user