a21a3f1908
When we did the automatic rewriting from the hashicorp/hcl2 import paths to hashicorp/hcl/v2 import paths, our automatic rewrite script did not run "go fmt" afterwards and so left some imports misordered as a result of the slightly-differently-shaped directory structure in the new repository.
351 lines
8.1 KiB
Go
351 lines
8.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
|
"github.com/hashicorp/hcl/v2/gohcl"
|
|
)
|
|
|
|
type TestFile struct {
|
|
Result cty.Value
|
|
ResultType cty.Type
|
|
|
|
ChecksTraversals bool
|
|
ExpectedTraversals []*TestFileExpectTraversal
|
|
|
|
ExpectedDiags []*TestFileExpectDiag
|
|
|
|
ResultRange hcl.Range
|
|
ResultTypeRange hcl.Range
|
|
}
|
|
|
|
type TestFileExpectTraversal struct {
|
|
Traversal hcl.Traversal
|
|
Range hcl.Range
|
|
DeclRange hcl.Range
|
|
}
|
|
|
|
type TestFileExpectDiag struct {
|
|
Severity hcl.DiagnosticSeverity
|
|
Range hcl.Range
|
|
DeclRange hcl.Range
|
|
}
|
|
|
|
func (r *Runner) LoadTestFile(filename string) (*TestFile, hcl.Diagnostics) {
|
|
f, diags := r.parser.ParseHCLFile(filename)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
content, moreDiags := f.Body.Content(testFileSchema)
|
|
diags = append(diags, moreDiags...)
|
|
if moreDiags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
ret := &TestFile{
|
|
ResultType: cty.DynamicPseudoType,
|
|
}
|
|
|
|
if typeAttr, exists := content.Attributes["result_type"]; exists {
|
|
ty, moreDiags := typeexpr.TypeConstraint(typeAttr.Expr)
|
|
diags = append(diags, moreDiags...)
|
|
if !moreDiags.HasErrors() {
|
|
ret.ResultType = ty
|
|
}
|
|
ret.ResultTypeRange = typeAttr.Expr.Range()
|
|
}
|
|
|
|
if resultAttr, exists := content.Attributes["result"]; exists {
|
|
resultVal, moreDiags := resultAttr.Expr.Value(nil)
|
|
diags = append(diags, moreDiags...)
|
|
if !moreDiags.HasErrors() {
|
|
resultVal, err := convert.Convert(resultVal, ret.ResultType)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid result value",
|
|
Detail: fmt.Sprintf("The result value does not conform to the given result type: %s.", err),
|
|
Subject: resultAttr.Expr.Range().Ptr(),
|
|
})
|
|
} else {
|
|
ret.Result = resultVal
|
|
}
|
|
}
|
|
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,
|
|
DeclRange: block.DefRange,
|
|
}, 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,
|
|
DeclRange: block.TypeRange,
|
|
})
|
|
}
|
|
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.Column,
|
|
Byte: raw.From.Byte,
|
|
},
|
|
End: hcl.Pos{
|
|
Line: raw.To.Line,
|
|
Column: raw.To.Column,
|
|
Byte: raw.To.Byte,
|
|
},
|
|
}, raw.Remain, diags
|
|
}
|
|
|
|
var testFileSchema = &hcl.BodySchema{
|
|
Attributes: []hcl.AttributeSchema{
|
|
{
|
|
Name: "result",
|
|
},
|
|
{
|
|
Name: "result_type",
|
|
},
|
|
},
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
{
|
|
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,
|
|
},
|
|
},
|
|
}
|