summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorMartin Atkins <mart@degeneration.co.uk>2018-08-09 19:29:32 -0700
committerMartin Atkins <mart@degeneration.co.uk>2018-08-09 19:29:32 -0700
commit0956c193b7144f92c97bb18d657fb4e066687eae (patch)
treed55c714ffe2da12680eeea805f2e8f679860a3a3 /cmd
parent6743a2254ba3d642b7d3a0be506259a0842819ac (diff)
specsuite: Start of the harness for the specification test suite
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hclspecsuite/README.md4
-rw-r--r--cmd/hclspecsuite/log.go3
-rw-r--r--cmd/hclspecsuite/main.go58
-rw-r--r--cmd/hclspecsuite/runner.go164
-rw-r--r--cmd/hclspecsuite/test_file.go78
5 files changed, 307 insertions, 0 deletions
diff --git a/cmd/hclspecsuite/README.md b/cmd/hclspecsuite/README.md
new file mode 100644
index 0000000..0f7badc
--- /dev/null
+++ b/cmd/hclspecsuite/README.md
@@ -0,0 +1,4 @@
+# `hclspecsuite`
+
+`hclspecsuite` is the test harness for
+[the HCL specification test suite](../../specsuite/README.md).
diff --git a/cmd/hclspecsuite/log.go b/cmd/hclspecsuite/log.go
new file mode 100644
index 0000000..f2af650
--- /dev/null
+++ b/cmd/hclspecsuite/log.go
@@ -0,0 +1,3 @@
+package main
+
+type LogCallback func(testName string, testFile *TestFile)
diff --git a/cmd/hclspecsuite/main.go b/cmd/hclspecsuite/main.go
new file mode 100644
index 0000000..e64a053
--- /dev/null
+++ b/cmd/hclspecsuite/main.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/hashicorp/hcl2/hclparse"
+
+ "github.com/hashicorp/hcl2/hcl"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+func main() {
+ os.Exit(realMain(os.Args[1:]))
+}
+
+func realMain(args []string) int {
+ if len(args) != 2 {
+ fmt.Fprintf(os.Stderr, "Usage: hclspecsuite <tests-dir> <hcldec-file>\n")
+ return 2
+ }
+
+ testsDir := args[0]
+ hcldecPath := args[1]
+
+ hcldecPath, err := exec.LookPath(hcldecPath)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ return 2
+ }
+
+ parser := hclparse.NewParser()
+
+ runner := &Runner{
+ parser: parser,
+ hcldecPath: hcldecPath,
+ baseDir: testsDir,
+ log: func(name string, file *TestFile) {
+ fmt.Printf("- %s\n", name)
+ },
+ }
+ diags := runner.Run()
+
+ if len(diags) != 0 {
+ os.Stderr.WriteString("\n")
+ color := terminal.IsTerminal(int(os.Stderr.Fd()))
+ w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
+ if err != nil {
+ w = 80
+ }
+ diagWr := hcl.NewDiagnosticTextWriter(os.Stderr, parser.Files(), uint(w), color)
+ diagWr.WriteDiagnostics(diags)
+ return 2
+ }
+
+ return 0
+}
diff --git a/cmd/hclspecsuite/runner.go b/cmd/hclspecsuite/runner.go
new file mode 100644
index 0000000..0be1822
--- /dev/null
+++ b/cmd/hclspecsuite/runner.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/hashicorp/hcl2/hcl"
+ "github.com/hashicorp/hcl2/hclparse"
+ "github.com/zclconf/go-cty/cty"
+)
+
+type Runner struct {
+ parser *hclparse.Parser
+ hcldecPath string
+ baseDir string
+ log LogCallback
+}
+
+func (r *Runner) Run() hcl.Diagnostics {
+ return r.runDir(r.baseDir)
+}
+
+func (r *Runner) runDir(dir string) hcl.Diagnostics {
+ var diags hcl.Diagnostics
+
+ infos, err := ioutil.ReadDir(dir)
+ if err != nil {
+ diags = append(diags, &hcl.Diagnostic{
+ Severity: hcl.DiagError,
+ Summary: "Failed to read test directory",
+ Detail: fmt.Sprintf("The directory %q could not be opened: %s.", dir, err),
+ })
+ return diags
+ }
+
+ var tests []string
+ var subDirs []string
+ for _, info := range infos {
+ name := info.Name()
+ if strings.HasPrefix(name, ".") {
+ continue
+ }
+
+ if info.IsDir() {
+ subDirs = append(subDirs, name)
+ }
+ if strings.HasSuffix(name, ".t") {
+ tests = append(tests, name)
+ }
+ }
+ sort.Strings(tests)
+ sort.Strings(subDirs)
+
+ for _, filename := range tests {
+ filename = filepath.Join(r.baseDir, filename)
+ testDiags := r.runTest(filename)
+ diags = append(diags, testDiags...)
+ }
+
+ for _, dirName := range subDirs {
+ dir := filepath.Join(r.baseDir, dirName)
+ dirDiags := r.runDir(dir)
+ diags = append(diags, dirDiags...)
+ }
+
+ return diags
+}
+
+func (r *Runner) runTest(filename string) hcl.Diagnostics {
+ prettyName := r.prettyTestName(filename)
+ tf, diags := r.LoadTestFile(filename)
+ if diags.HasErrors() {
+ // We'll still log, so it's clearer which test the diagnostics belong to.
+ if r.log != nil {
+ r.log(prettyName, nil)
+ }
+ return diags
+ }
+
+ if r.log != nil {
+ r.log(prettyName, tf)
+ }
+
+ basePath := filename[:len(filename)-2]
+ specFilename := basePath + ".hcldec"
+ nativeFilename := basePath + ".hcl"
+ //jsonFilename := basePath + ".hcl.json"
+
+ if _, err := os.Stat(specFilename); err != nil {
+ diags = append(diags, &hcl.Diagnostic{
+ Severity: hcl.DiagError,
+ Summary: "Missing .hcldec file",
+ Detail: fmt.Sprintf("No specification file for test %s: %s.", prettyName, err),
+ })
+ return diags
+ }
+
+ if _, err := os.Stat(nativeFilename); err == nil {
+
+ }
+
+ return diags
+}
+
+func (r *Runner) hcldecTransform(specFile, inputFile string) (cty.Value, hcl.Diagnostics) {
+ var diags hcl.Diagnostics
+ var outBuffer bytes.Buffer
+ var errBuffer bytes.Buffer
+
+ cmd := &exec.Cmd{
+ Path: r.hcldecPath,
+ Args: []string{
+ "--spec=" + specFile,
+ "--diags=json",
+ inputFile,
+ },
+ Stdout: &outBuffer,
+ Stderr: &errBuffer,
+ }
+ err := cmd.Run()
+ if _, isExit := err.(*exec.ExitError); !isExit {
+ diags = append(diags, &hcl.Diagnostic{
+ Severity: hcl.DiagError,
+ Summary: "Failed to run hcldec",
+ Detail: fmt.Sprintf("Sub-program hcldec failed to start: %s.", err),
+ })
+ return cty.DynamicVal, diags
+ }
+
+ if err != nil {
+ // If we exited unsuccessfully then we'll expect diagnostics on stderr
+ // TODO: implement that
+ } else {
+ // Otherwise, we expect a JSON result value on stdout
+ // TODO: implement that
+ }
+
+ return cty.DynamicVal, diags
+}
+
+func (r *Runner) prettyDirName(dir string) string {
+ rel, err := filepath.Rel(r.baseDir, dir)
+ if err != nil {
+ return filepath.ToSlash(dir)
+ }
+ return filepath.ToSlash(rel)
+}
+
+func (r *Runner) prettyTestName(filename string) string {
+ dir := filepath.Dir(filename)
+ dirName := r.prettyDirName(dir)
+ filename = filepath.Base(filename)
+ testName := filename[:len(filename)-2]
+ if dirName == "." {
+ return testName
+ }
+ return fmt.Sprintf("%s/%s", dirName, testName)
+}
diff --git a/cmd/hclspecsuite/test_file.go b/cmd/hclspecsuite/test_file.go
new file mode 100644
index 0000000..98371ee
--- /dev/null
+++ b/cmd/hclspecsuite/test_file.go
@@ -0,0 +1,78 @@
+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"
+)
+
+type TestFile struct {
+ Result cty.Value
+ ResultType cty.Type
+
+ Traversals []hcl.Traversal
+}
+
+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
+ }
+ }
+
+ 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
+ }
+ }
+ }
+
+ return ret, diags
+}
+
+var testFileSchema = &hcl.BodySchema{
+ Attributes: []hcl.AttributeSchema{
+ {
+ Name: "result",
+ },
+ {
+ Name: "result_type",
+ },
+ },
+ Blocks: []hcl.BlockHeaderSchema{
+ {
+ Type: "traversals",
+ },
+ },
+}