cmd/cue/cmd: use testscript for top level command testing

This makes the command tests much more end to end,
and bundles test parameters together with the
test file dependencies.

We also make the actual main function more minimal
to avoid duplicating it inside TestScript, and fix the
code so that it prints errors to standard error and
avoids printing the "terminating because of errors"
sentinel error value.

The test coverage is somewhat improved as a result.

Change-Id: I023c1dc836a0bad42eb2103294ecffb58dfff8e3
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3444
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/cmd_test.go b/cmd/cue/cmd/cmd_test.go
deleted file mode 100644
index 7eec347..0000000
--- a/cmd/cue/cmd/cmd_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2018 The CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
-	"os"
-	"testing"
-
-	"cuelang.org/go/cue/errors"
-	"github.com/spf13/cobra"
-)
-
-func TestCmd(t *testing.T) {
-	cfg := printConfig(t)
-
-	testCases := []string{
-		"echo",
-		"run",
-		"run_list",
-		"baddisplay",
-		"errcode",
-		"http",
-		"print",
-	}
-	defer func() {
-		stdout = os.Stdout
-		stderr = os.Stderr
-	}()
-	for _, name := range testCases {
-		c := newRootCmd()
-		run := func(cmd *cobra.Command, args []string) error {
-			stdout = cmd.OutOrStdout()
-			stderr = c.Stderr()
-
-			tools, _ := buildTools(c, args)
-			cmd, err := addCustom(c, c.root, "command", name, tools)
-			if err != nil {
-				return err
-			}
-			err = executeTasks("command", name, tools)
-			if err != nil {
-				errors.Print(stdout, err, cfg)
-			}
-			return nil
-		}
-		runCommand(t, &cobra.Command{RunE: run}, "cmd_"+name)
-	}
-}
diff --git a/cmd/cue/cmd/common_test.go b/cmd/cue/cmd/common_test.go
index 8348106..018531c 100644
--- a/cmd/cue/cmd/common_test.go
+++ b/cmd/cue/cmd/common_test.go
@@ -16,17 +16,10 @@
 
 import (
 	"flag"
-	"fmt"
-	"io"
-	"io/ioutil"
 	"os"
-	"path/filepath"
-	"regexp"
 	"testing"
 
 	"cuelang.org/go/cue/errors"
-	"github.com/spf13/cobra"
-	"golang.org/x/sync/errgroup"
 )
 
 var _ = errors.Print
@@ -48,77 +41,3 @@
 		ToSlash: true,
 	}
 }
-
-func runCommand(t *testing.T, cmd *cobra.Command, name string, args ...string) {
-	t.Helper()
-
-	const dir = "./testdata"
-
-	cfg := printConfig(t)
-
-	_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
-		t.Run(path, func(t *testing.T) {
-			if err != nil {
-				t.Fatal(err)
-			}
-			if !info.IsDir() || dir == path {
-				return
-			}
-			testfile := filepath.Join(path, name+".out")
-			bWant, err := ioutil.ReadFile(testfile)
-			if err != nil {
-				// Don't write the file if it doesn't exist, even in *update
-				// mode. We don't want to need to support all commands for all
-				// directories. Touch the file and use *update to create it.
-				return
-			}
-
-			extra := args
-			if len(args) == 0 {
-				extra = append(args, "./"+path)
-			}
-			cmd.SetArgs(extra)
-			rOut, wOut := io.Pipe()
-			cmd.SetOutput(wOut)
-			var bOut []byte
-			g := errgroup.Group{}
-			g.Go(func() error {
-				defer wOut.Close()
-				defer func() {
-					switch err := recover().(type) {
-					case nil:
-					case panicError:
-						errors.Print(wOut, err.Err, cfg)
-					case error:
-						errors.Print(wOut, err, cfg)
-					default:
-						fmt.Fprintln(wOut, err)
-					}
-				}()
-				return cmd.Execute()
-			})
-			g.Go(func() error {
-				bOut, err = ioutil.ReadAll(rOut)
-				re := regexp.MustCompile(`exit status \d`)
-				bOut = re.ReplaceAll(bOut, []byte("non-zero exist code"))
-				return err
-			})
-			if err := g.Wait(); err != nil {
-				t.Error(err)
-			}
-			if *update {
-				_ = ioutil.WriteFile(testfile, bOut, 0644)
-				return
-			}
-			got, want := string(bOut), string(bWant)
-			if got != want {
-				t.Errorf("\n got: %v\nwant: %v", got, want)
-			}
-		})
-		return nil
-	})
-}
-
-func TestLoadError(t *testing.T) {
-	runCommand(t, newEvalCmd(newRootCmd()), "loaderr", "non-existing", ".")
-}
diff --git a/cmd/cue/cmd/eval_test.go b/cmd/cue/cmd/eval_test.go
deleted file mode 100644
index 56c4c5a..0000000
--- a/cmd/cue/cmd/eval_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import "testing"
-
-func TestEval(t *testing.T) {
-	runCommand(t, newEvalCmd(newRootCmd()), "eval")
-
-	cmd := newEvalCmd(newRootCmd())
-	mustParseFlags(t, cmd, "-c", "-a")
-	runCommand(t, cmd, "eval_conc")
-
-	cmd = newEvalCmd(newRootCmd())
-	mustParseFlags(t, cmd, "-c", "-e", "b.a.b", "-e", "b.idx")
-	runCommand(t, cmd, "eval_expr")
-}
diff --git a/cmd/cue/cmd/export_test.go b/cmd/cue/cmd/export_test.go
deleted file mode 100644
index 9b9aac5..0000000
--- a/cmd/cue/cmd/export_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import "testing"
-
-func TestExport(t *testing.T) {
-	runCommand(t, newExportCmd(newRootCmd()), "export")
-	runCommand(t, newExportCmd(newRootCmd()), "export_err")
-}
diff --git a/cmd/cue/cmd/fmt_test.go b/cmd/cue/cmd/fmt_test.go
deleted file mode 100644
index 7ecefab..0000000
--- a/cmd/cue/cmd/fmt_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import "testing"
-
-func TestFmt(t *testing.T) {
-	cmd := newFmtCmd(newRootCmd())
-	runCommand(t, cmd, "fmt")
-}
diff --git a/cmd/cue/cmd/import_test.go b/cmd/cue/cmd/import_test.go
deleted file mode 100644
index c892fbd..0000000
--- a/cmd/cue/cmd/import_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
-	"testing"
-)
-
-func TestImport(t *testing.T) {
-	cmd := newImportCmd(newRootCmd())
-	mustParseFlags(t, cmd,
-		"-o", "-", "-f", "--files")
-	runCommand(t, cmd, "import_files")
-
-	cmd = newImportCmd(newRootCmd())
-	mustParseFlags(t, cmd,
-		"-o", "-", "-f", "-l", `"\(strings.ToLower(kind))" "\(name)"`)
-	runCommand(t, cmd, "import_path")
-
-	cmd = newImportCmd(newRootCmd())
-	mustParseFlags(t, cmd,
-		"-o", "-", "-f", "-l", `"\(strings.ToLower(kind))"`, "--list")
-	runCommand(t, cmd, "import_list")
-
-	cmd = newImportCmd(newRootCmd())
-	mustParseFlags(t, cmd,
-		"-o", "-", "-f", "--list",
-		"-l", `"\(strings.ToLower(kind))" "\(name)"`, "--recursive")
-	runCommand(t, cmd, "import_hoiststr")
-}
diff --git a/cmd/cue/cmd/root.go b/cmd/cue/cmd/root.go
index bf13a2a..2f89cb4 100644
--- a/cmd/cue/cmd/root.go
+++ b/cmd/cue/cmd/root.go
@@ -16,7 +16,9 @@
 
 import (
 	"context"
+	"fmt"
 	"io"
+	"os"
 	"strings"
 
 	"cuelang.org/go/cue/errors"
@@ -100,8 +102,19 @@
 	return c
 }
 
-// Main runs the cue tool. It loads the tool flags.
-func Main(ctx context.Context, args []string) (err error) {
+// Main runs the cue tool and returns the code for passing to os.Exit.
+func Main() int {
+	err := mainErr(context.Background(), os.Args[1:])
+	if err != nil {
+		if err != ErrPrintedError {
+			fmt.Fprintln(os.Stderr, err)
+		}
+		return 1
+	}
+	return 0
+}
+
+func mainErr(ctx context.Context, args []string) error {
 	cmd, err := New(args)
 	if err != nil {
 		return err
@@ -296,5 +309,5 @@
 }
 
 func exit() {
-	panic(panicError{errors.New("terminating because of errors")})
+	panic(panicError{ErrPrintedError})
 }
diff --git a/cmd/cue/cmd/script_test.go b/cmd/cue/cmd/script_test.go
new file mode 100644
index 0000000..0bb50a4
--- /dev/null
+++ b/cmd/cue/cmd/script_test.go
@@ -0,0 +1,21 @@
+package cmd
+
+import (
+	"os"
+	"testing"
+
+	"github.com/rogpeppe/testscript"
+)
+
+func TestScript(t *testing.T) {
+	testscript.Run(t, testscript.Params{
+		Dir:           "testdata/script",
+		UpdateScripts: *update,
+	})
+}
+
+func TestMain(m *testing.M) {
+	os.Exit(testscript.RunMain(m, map[string]func() int{
+		"cue": Main,
+	}))
+}
diff --git a/cmd/cue/cmd/testdata/cue.mod b/cmd/cue/cmd/testdata/cue.mod
deleted file mode 100644
index ab9fbdd..0000000
--- a/cmd/cue/cmd/testdata/cue.mod
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2019 CUE Authors
-// 
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// 
-//     http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
diff --git a/cmd/cue/cmd/testdata/export/export.cue b/cmd/cue/cmd/testdata/export/export.cue
deleted file mode 100644
index 5110493..0000000
--- a/cmd/cue/cmd/testdata/export/export.cue
+++ /dev/null
@@ -1,13 +0,0 @@
-package export
-
-One :: 1
-
-def: *One | int
-
-a: string
-a: "foo"
-
-s t u: "bar"
-s t v: {a: 1} | *{b: 2}
-
-l: [1, 2, 3]
diff --git a/cmd/cue/cmd/testdata/export/export.out b/cmd/cue/cmd/testdata/export/export.out
deleted file mode 100644
index 5911b5c..0000000
--- a/cmd/cue/cmd/testdata/export/export.out
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-    "def": 1,
-    "a": "foo",
-    "s": {
-        "t": {
-            "u": "bar",
-            "v": {
-                "b": 2
-            }
-        }
-    },
-    "l": [
-        1,
-        2,
-        3
-    ]
-}
diff --git a/cmd/cue/cmd/testdata/exporterr/export_err.cue b/cmd/cue/cmd/testdata/exporterr/export_err.cue
deleted file mode 100644
index 480a942..0000000
--- a/cmd/cue/cmd/testdata/exporterr/export_err.cue
+++ /dev/null
@@ -1,5 +0,0 @@
-package exporterr
-
-a: {
-	b: [0, 1, {c: int}, 3]
-}
diff --git a/cmd/cue/cmd/testdata/exporterr/export_err.out b/cmd/cue/cmd/testdata/exporterr/export_err.out
deleted file mode 100644
index 8ce55eb..0000000
--- a/cmd/cue/cmd/testdata/exporterr/export_err.out
+++ /dev/null
@@ -1 +0,0 @@
-cue: marshal error at path a.b.2.c: cannot convert incomplete value "int" to JSON
diff --git a/cmd/cue/cmd/testdata/fmt/error.cue b/cmd/cue/cmd/testdata/fmt/error.cue
deleted file mode 100644
index 60465d6..0000000
--- a/cmd/cue/cmd/testdata/fmt/error.cue
+++ /dev/null
@@ -1,4 +0,0 @@
-import a.b "foo"
-
-a: 2
-bb: 3
\ No newline at end of file
diff --git a/cmd/cue/cmd/testdata/fmt/fmt.out b/cmd/cue/cmd/testdata/fmt/fmt.out
deleted file mode 100644
index 231cb6d..0000000
--- a/cmd/cue/cmd/testdata/fmt/fmt.out
+++ /dev/null
@@ -1,2 +0,0 @@
-expected 'STRING', found '.':
-    ./testdata/fmt/error.cue:1:9
diff --git a/cmd/cue/cmd/testdata/hello/cmd_echo.out b/cmd/cue/cmd/testdata/hello/cmd_echo.out
deleted file mode 100644
index ea2fd5c..0000000
--- a/cmd/cue/cmd/testdata/hello/cmd_echo.out
+++ /dev/null
@@ -1,2 +0,0 @@
-Hello World!
-
diff --git a/cmd/cue/cmd/testdata/hello/data.cue b/cmd/cue/cmd/testdata/hello/data.cue
deleted file mode 100644
index edbb1b6..0000000
--- a/cmd/cue/cmd/testdata/hello/data.cue
+++ /dev/null
@@ -1,3 +0,0 @@
-package hello
-
-who :: "World"
diff --git a/cmd/cue/cmd/testdata/hello/eval.out b/cmd/cue/cmd/testdata/hello/eval.out
deleted file mode 100644
index f8492ce..0000000
--- a/cmd/cue/cmd/testdata/hello/eval.out
+++ /dev/null
@@ -1,2 +0,0 @@
-who ::   "World"
-message: "Hello World!"
diff --git a/cmd/cue/cmd/testdata/hello/eval_conc.out b/cmd/cue/cmd/testdata/hello/eval_conc.out
deleted file mode 100644
index df67acc..0000000
--- a/cmd/cue/cmd/testdata/hello/eval_conc.out
+++ /dev/null
@@ -1 +0,0 @@
-message: "Hello World!"
diff --git a/cmd/cue/cmd/testdata/hello/export.out b/cmd/cue/cmd/testdata/hello/export.out
deleted file mode 100644
index cf38f26..0000000
--- a/cmd/cue/cmd/testdata/hello/export.out
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "message": "Hello World!"
-}
diff --git a/cmd/cue/cmd/testdata/hello/hello.cue b/cmd/cue/cmd/testdata/hello/hello.cue
deleted file mode 100644
index f8f8654..0000000
--- a/cmd/cue/cmd/testdata/hello/hello.cue
+++ /dev/null
@@ -1,3 +0,0 @@
-package hello
-
-message: "Hello \(who)!" // who declared in data.cue
diff --git a/cmd/cue/cmd/testdata/hello/hello_tool.cue b/cmd/cue/cmd/testdata/hello/hello_tool.cue
deleted file mode 100644
index b3a6f2d..0000000
--- a/cmd/cue/cmd/testdata/hello/hello_tool.cue
+++ /dev/null
@@ -1,14 +0,0 @@
-package hello
-
-command echo: {
-    task echo: {
-        kind:   "exec"
-        cmd:    "echo \(message)"
-        stdout: string
-    }
-
-    task display: {
-        kind: "print"
-        text: task.echo.stdout
-    }
-}
\ No newline at end of file
diff --git a/cmd/cue/cmd/testdata/hello/vet.out b/cmd/cue/cmd/testdata/hello/vet.out
deleted file mode 100644
index e69de29..0000000
--- a/cmd/cue/cmd/testdata/hello/vet.out
+++ /dev/null
diff --git a/cmd/cue/cmd/testdata/import/import_files.out b/cmd/cue/cmd/testdata/import/import_files.out
deleted file mode 100644
index 02c62bd..0000000
--- a/cmd/cue/cmd/testdata/import/import_files.out
+++ /dev/null
@@ -1,11 +0,0 @@
-kind: "Service"
-name: "booster"
-kind:     "Deployment"
-name:     "booster"
-replicas: 1
-kind: "Service"
-name: """
-		supplement
-		foo
-		"""
-json: "[1, 2]"
diff --git a/cmd/cue/cmd/testdata/import/import_hoiststr.out b/cmd/cue/cmd/testdata/import/import_hoiststr.out
deleted file mode 100644
index 4460c9d..0000000
--- a/cmd/cue/cmd/testdata/import/import_hoiststr.out
+++ /dev/null
@@ -1,22 +0,0 @@
-import xjson "encoding/json"
-
-service: {
-	booster: [{
-		kind: "Service"
-		name: "booster"
-	}]
-	"supplement\nfoo": [{
-		kind: "Service"
-		name: """
-		supplement
-		foo
-		"""
-		json: xjson.Marshal(_cue_json)
-		_cue_json = [1, 2]
-	}]
-}
-deployment booster: [{
-	kind:     "Deployment"
-	name:     "booster"
-	replicas: 1
-}]
diff --git a/cmd/cue/cmd/testdata/import/import_list.out b/cmd/cue/cmd/testdata/import/import_list.out
deleted file mode 100644
index c645992..0000000
--- a/cmd/cue/cmd/testdata/import/import_list.out
+++ /dev/null
@@ -1,16 +0,0 @@
-service: [{
-	kind: "Service"
-	name: "booster"
-}, {
-	kind: "Service"
-	name: """
-		supplement
-		foo
-		"""
-	json: "[1, 2]"
-}]
-deployment: [{
-	kind:     "Deployment"
-	name:     "booster"
-	replicas: 1
-}]
diff --git a/cmd/cue/cmd/testdata/import/import_path.out b/cmd/cue/cmd/testdata/import/import_path.out
deleted file mode 100644
index b668a2d..0000000
--- a/cmd/cue/cmd/testdata/import/import_path.out
+++ /dev/null
@@ -1,18 +0,0 @@
-
-service booster: {
-	kind: "Service"
-	name: "booster"
-}
-deployment booster: {
-	kind:     "Deployment"
-	name:     "booster"
-	replicas: 1
-}
-service "supplement\nfoo": {
-	kind: "Service"
-	name: """
-		supplement
-		foo
-		"""
-	json: "[1, 2]"
-}
diff --git a/cmd/cue/cmd/testdata/import/services.cue b/cmd/cue/cmd/testdata/import/services.cue
deleted file mode 100644
index c645992..0000000
--- a/cmd/cue/cmd/testdata/import/services.cue
+++ /dev/null
@@ -1,16 +0,0 @@
-service: [{
-	kind: "Service"
-	name: "booster"
-}, {
-	kind: "Service"
-	name: """
-		supplement
-		foo
-		"""
-	json: "[1, 2]"
-}]
-deployment: [{
-	kind:     "Deployment"
-	name:     "booster"
-	replicas: 1
-}]
diff --git a/cmd/cue/cmd/testdata/import/services.jsonl b/cmd/cue/cmd/testdata/import/services.jsonl
deleted file mode 100644
index e704fe0..0000000
--- a/cmd/cue/cmd/testdata/import/services.jsonl
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "kind": "Service",
-    "name": "booster"
-}
-{
-    "kind": "Deployment",
-    "name": "booster",
-    "replicas": 1
-}
-{
-    "kind": "Service",
-    "name": "supplement\nfoo",
-    "json": "[1, 2]"
-}
\ No newline at end of file
diff --git a/cmd/cue/cmd/testdata/loaderr/loaderr.out b/cmd/cue/cmd/testdata/loaderr/loaderr.out
deleted file mode 100644
index 68b2374..0000000
--- a/cmd/cue/cmd/testdata/loaderr/loaderr.out
+++ /dev/null
@@ -1,2 +0,0 @@
-cannot find package "non-existing"
-terminating because of errors
diff --git a/cmd/cue/cmd/testdata/partial/eval.out b/cmd/cue/cmd/testdata/partial/eval.out
deleted file mode 100644
index 73d0294..0000000
--- a/cmd/cue/cmd/testdata/partial/eval.out
+++ /dev/null
@@ -1,21 +0,0 @@
-def: 1
-sum: 1 | 2
-A = a
-b: {
-    idx: A[str]
-    a: {
-        b: 4
-    }
-    str: string
-}
-a: {
-    b: 3
-    c: 4
-}
-c: {
-    idx: 3
-    a: {
-        b: 4
-    }
-    str: "b"
-}
diff --git a/cmd/cue/cmd/testdata/partial/eval_conc.out b/cmd/cue/cmd/testdata/partial/eval_conc.out
deleted file mode 100644
index eae33c1..0000000
--- a/cmd/cue/cmd/testdata/partial/eval_conc.out
+++ /dev/null
@@ -1,6 +0,0 @@
-sum: incomplete value ((1 | 2)):
-    ./testdata/partial/partial.cue:4:6
-b.idx: invalid non-ground value string (must be concrete int|string):
-    ./testdata/partial/partial.cue:7:9
-b.str: incomplete value (string):
-    ./testdata/partial/partial.cue:8:7
diff --git a/cmd/cue/cmd/testdata/partial/eval_expr.out b/cmd/cue/cmd/testdata/partial/eval_expr.out
deleted file mode 100644
index 0e251dd..0000000
--- a/cmd/cue/cmd/testdata/partial/eval_expr.out
+++ /dev/null
@@ -1,5 +0,0 @@
-// b.a.b
-4
-// b.idx
-invalid non-ground value string (must be concrete int|string):
-    ./testdata/partial/partial.cue:7:9
diff --git a/cmd/cue/cmd/testdata/partial/partial.cue b/cmd/cue/cmd/testdata/partial/partial.cue
deleted file mode 100644
index ce33304..0000000
--- a/cmd/cue/cmd/testdata/partial/partial.cue
+++ /dev/null
@@ -1,15 +0,0 @@
-package partial
-
-def: *1 | int
-sum: 1 | 2
-
-b: {
-	idx: a[str] // should resolve to top-level `a`
-	str: string
-}
-b a b: 4
-a: {
-	b: 3
-	c: 4
-}
-c: b & {str: "b"}
diff --git a/cmd/cue/cmd/testdata/partial/vet.out b/cmd/cue/cmd/testdata/partial/vet.out
deleted file mode 100644
index e696e99..0000000
--- a/cmd/cue/cmd/testdata/partial/vet.out
+++ /dev/null
@@ -1 +0,0 @@
-some instances are incomplete; use the -c flag to show errors or suppress this message
diff --git a/cmd/cue/cmd/testdata/partial/vet_conc.out b/cmd/cue/cmd/testdata/partial/vet_conc.out
deleted file mode 100644
index eae33c1..0000000
--- a/cmd/cue/cmd/testdata/partial/vet_conc.out
+++ /dev/null
@@ -1,6 +0,0 @@
-sum: incomplete value ((1 | 2)):
-    ./testdata/partial/partial.cue:4:6
-b.idx: invalid non-ground value string (must be concrete int|string):
-    ./testdata/partial/partial.cue:7:9
-b.str: incomplete value (string):
-    ./testdata/partial/partial.cue:8:7
diff --git a/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
new file mode 100644
index 0000000..3302f7e
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
@@ -0,0 +1,21 @@
+! cue cmd baddisplay
+! stdout .
+cmp stderr cmd_baddisplay.out
+
+-- cmd_baddisplay.out --
+text: conflicting values 42 and string (mismatched types int and string):
+    ./task_tool.cue:6:9
+    tool/cli:4:9
+-- task.cue --
+package home
+message: "Hello world!"
+
+-- task_tool.cue --
+package home
+
+command baddisplay: {
+	task display: {
+		kind: "print"
+		text: 42
+	}
+}
diff --git a/cmd/cue/cmd/testdata/script/cmd_echo.txt b/cmd/cue/cmd/testdata/script/cmd_echo.txt
new file mode 100644
index 0000000..c47fa89
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_echo.txt
@@ -0,0 +1,29 @@
+cue cmd echo
+cmp stdout expect-stdout
+-- expect-stdout --
+Hello World!
+
+-- data.cue --
+package hello
+
+who :: "World"
+-- hello.cue --
+package hello
+
+message: "Hello \(who)!" // who declared in data.cue
+-- hello_tool.cue --
+package hello
+
+command echo: {
+    task echo: {
+        kind:   "exec"
+        cmd:    "echo \(message)"
+        stdout: string
+    }
+
+    task display: {
+        kind: "print"
+        text: task.echo.stdout
+    }
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_errcode.txt b/cmd/cue/cmd/testdata/script/cmd_errcode.txt
new file mode 100644
index 0000000..352d8c3
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_errcode.txt
@@ -0,0 +1,19 @@
+! cue cmd errcode
+! stdout .
+cmp stderr cmd_baddisplay.out
+
+-- cmd_baddisplay.out --
+command "ls --badflags" failed: exit status 2
+-- task.cue --
+package home
+message: "Hello world!"
+
+-- task_tool.cue --
+package home
+
+command errcode: {
+	task bad: exec.Run & {
+		kind:   "exec"
+		cmd:    "ls --badflags"
+		stderr: string // suppress error message
+	}}
diff --git a/cmd/cue/cmd/testdata/script/cmd_http.txt b/cmd/cue/cmd/testdata/script/cmd_http.txt
new file mode 100644
index 0000000..2ce28ee
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_http.txt
@@ -0,0 +1,32 @@
+cue cmd http
+cmp stdout cmd_http.out
+-- cmd_http.out --
+{"data":"I'll be back!","when":"now"}
+
+-- task_tool.cue --
+
+package home
+
+command http: {
+	task testserver: {
+		kind: "testserver"
+		url:  string
+	}
+	task http: {
+		kind:   "http"
+		method: "POST"
+		url:    task.testserver.url
+
+		request body:  "I'll be back!"
+		response body: string // TODO: allow this to be a struct, parsing the body.
+	}
+	task print: {
+		kind: "print"
+		text: task.http.response.body
+	}
+}
+
+-- task.cue --
+package home
+
+message: "Hello world!"
diff --git a/cmd/cue/cmd/testdata/script/cmd_print.txt b/cmd/cue/cmd/testdata/script/cmd_print.txt
new file mode 100644
index 0000000..5ebf1c0
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_print.txt
@@ -0,0 +1,38 @@
+cue cmd print
+cmp stdout expect-stdout
+
+-- expect-stdout --
+t.1.
+.t.2.
+
+-- task_tool.cue --
+package home
+
+import (
+	"tool/exec"
+	"strings"
+)
+
+command print: {
+	task: {
+		t1: exec.Run & {
+			cmd: ["sh", "-c", "sleep 1; echo t1"]
+			stdout: string
+		}
+		t2: exec.Run & {
+			cmd: ["sh", "-c", "sleep 1; echo t2"]
+			stdout: string
+		}
+		t3: cli.Print & {
+			text: (f & {arg: t1.stdout + t2.stdout}).result
+		}
+	}
+}
+
+f :: {
+    arg: string
+    result: strings.Join(strings.Split(arg, ""), ".")
+}
+-- task.cue --
+package home
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_run.txt b/cmd/cue/cmd/testdata/script/cmd_run.txt
new file mode 100644
index 0000000..51abff7
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_run.txt
@@ -0,0 +1,33 @@
+cue cmd run
+cmp stdout run.out
+
+-- run.out --
+Hello world!
+
+-- task.cue --
+package home
+message: "Hello world!"
+
+-- task_tool.cue --
+package home
+
+command run: runBase & {
+	task echo cmd: "echo \(message)"
+}
+
+-- base_tool.cue --
+package home
+
+// deliberately put in another file to test resolving top-level identifiers
+// in different files.
+runBase: {
+	task echo: {
+		kind:   "exec"
+		stdout: string
+	}
+
+	task display: {
+		kind: "print"
+		text: task.echo.stdout
+	}
+}
diff --git a/cmd/cue/cmd/testdata/script/cmd_run_list.txt b/cmd/cue/cmd/testdata/script/cmd_run_list.txt
new file mode 100644
index 0000000..66681e2
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_run_list.txt
@@ -0,0 +1,33 @@
+cue cmd run_list
+cmp stdout run_list.out
+
+-- run_list.out --
+Hello world!
+
+-- task.cue --
+package home
+message: "Hello world!"
+
+-- task_tool.cue --
+package home
+
+command run_list: runBase & {
+	task echo cmd: ["echo", message]
+}
+
+-- base_tool.cue --
+package home
+
+// deliberately put in another file to test resolving top-level identifiers
+// in different files.
+runBase: {
+	task echo: {
+		kind:   "exec"
+		stdout: string
+	}
+
+	task display: {
+		kind: "print"
+		text: task.echo.stdout
+	}
+}
diff --git a/cmd/cue/cmd/testdata/script/eval_concrete.txt b/cmd/cue/cmd/testdata/script/eval_concrete.txt
new file mode 100644
index 0000000..ccc755e
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/eval_concrete.txt
@@ -0,0 +1,16 @@
+cue eval -c -a
+cmp stdout eval_conc.out
+
+-- eval_conc.out --
+message: "Hello World!"
+-- cmd_echo.out --
+Hello World!
+
+-- data.cue --
+package hello
+
+who :: "World"
+-- hello.cue --
+package hello
+
+message: "Hello \(who)!" // who declared in data.cue
diff --git a/cmd/cue/cmd/testdata/script/eval_expr.txt b/cmd/cue/cmd/testdata/script/eval_expr.txt
new file mode 100644
index 0000000..6475b28
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/eval_expr.txt
@@ -0,0 +1,28 @@
+! cue eval -c -e b.a.b -e b.idx
+cmp stderr expect-stderr
+cmp stdout expect-stdout
+
+-- expect-stdout --
+// b.a.b
+4
+// b.idx
+-- expect-stderr --
+invalid non-ground value string (must be concrete int|string):
+    ./partial.cue:7:9
+-- partial.cue --
+package partial
+
+def: *1 | int
+sum: 1 | 2
+
+b: {
+	idx: a[str] // should resolve to top-level `a`
+	str: string
+}
+b a b: 4
+a: {
+	b: 3
+	c: 4
+}
+c: b & {str: "b"}
+
diff --git a/cmd/cue/cmd/testdata/script/eval_loaderr.txt b/cmd/cue/cmd/testdata/script/eval_loaderr.txt
new file mode 100644
index 0000000..3edd62b
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/eval_loaderr.txt
@@ -0,0 +1,6 @@
+! cue eval non-existing .
+! stdout .
+cmp stderr expect-stderr
+
+-- expect-stderr --
+cannot find package "non-existing"
diff --git a/cmd/cue/cmd/testdata/script/eval_tool.txt b/cmd/cue/cmd/testdata/script/eval_tool.txt
new file mode 100644
index 0000000..43aa9d6
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/eval_tool.txt
@@ -0,0 +1,15 @@
+cue eval
+cmp stdout expect-stdout
+
+-- expect-stdout --
+message: "Hello world!"
+-- task.cue --
+package home
+message: "Hello world!"
+
+-- task_tool.cue --
+package home
+
+command run_list: runBase & {
+	task echo cmd: ["echo", message]
+}
diff --git a/cmd/cue/cmd/testdata/script/export.txt b/cmd/cue/cmd/testdata/script/export.txt
new file mode 100644
index 0000000..6d70421
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/export.txt
@@ -0,0 +1,15 @@
+cue export ./hello
+cmp stdout expect-stdout
+-- expect-stdout --
+{
+    "message": "Hello World!"
+}
+-- hello/data.cue --
+package hello
+
+who :: "World"
+-- hello/hello.cue --
+package hello
+
+message: "Hello \(who)!" // who declared in data.cue
+-- hello/cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/export_err.txt b/cmd/cue/cmd/testdata/script/export_err.txt
new file mode 100644
index 0000000..7cd9c72
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/export_err.txt
@@ -0,0 +1,11 @@
+cue export ./exporterr
+cmp stdout expect-stdout
+-- expect-stdout --
+cue: marshal error at path a.b.2.c: cannot convert incomplete value "int" to JSON
+-- exporterr/export_err.cue --
+package exporterr
+
+a: {
+	b: [0, 1, {c: int}, 3]
+}
+-- exporterr/cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/fmt.txt b/cmd/cue/cmd/testdata/script/fmt.txt
new file mode 100644
index 0000000..2ce05d6
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/fmt.txt
@@ -0,0 +1,11 @@
+! cue fmt ./fmt
+cmp stderr expect-stderr
+-- expect-stderr --
+expected 'STRING', found '.':
+    ./fmt/error.cue:1:9
+-- fmt/error.cue --
+import a.b "foo"
+
+a: 2
+bb: 3
+-- fmt/cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/import_files.txt b/cmd/cue/cmd/testdata/script/import_files.txt
new file mode 100644
index 0000000..76be294
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_files.txt
@@ -0,0 +1,30 @@
+cue import -o - -f --files ./import
+cmp stdout expect-stdout
+-- expect-stdout --
+kind: "Service"
+name: "booster"
+kind:     "Deployment"
+name:     "booster"
+replicas: 1
+kind: "Service"
+name: """
+		supplement
+		foo
+		"""
+json: "[1, 2]"
+-- import/services.jsonl --
+{
+    "kind": "Service",
+    "name": "booster"
+}
+{
+    "kind": "Deployment",
+    "name": "booster",
+    "replicas": 1
+}
+{
+    "kind": "Service",
+    "name": "supplement\nfoo",
+    "json": "[1, 2]"
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/import_hoiststr.txt b/cmd/cue/cmd/testdata/script/import_hoiststr.txt
new file mode 100644
index 0000000..266e99e
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_hoiststr.txt
@@ -0,0 +1,58 @@
+cue import -o - -f --list -l '"\(strings.ToLower(kind))" "\(name)"' --recursive ./import
+cmp stdout expect-stdout
+-- expect-stdout --
+import xjson "encoding/json"
+
+service: {
+	booster: [{
+		kind: "Service"
+		name: "booster"
+	}]
+	"supplement\nfoo": [{
+		kind: "Service"
+		name: """
+		supplement
+		foo
+		"""
+		json: xjson.Marshal(_cue_json)
+		_cue_json = [1, 2]
+	}]
+}
+deployment booster: [{
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}]
+-- import/services.cue --
+service: [{
+	kind: "Service"
+	name: "booster"
+}, {
+	kind: "Service"
+	name: """
+		supplement
+		foo
+		"""
+	json: "[1, 2]"
+}]
+deployment: [{
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}]
+-- import/services.jsonl --
+{
+    "kind": "Service",
+    "name": "booster"
+}
+{
+    "kind": "Deployment",
+    "name": "booster",
+    "replicas": 1
+}
+{
+    "kind": "Service",
+    "name": "supplement\nfoo",
+    "json": "[1, 2]"
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/import_list.txt b/cmd/cue/cmd/testdata/script/import_list.txt
new file mode 100644
index 0000000..8985511
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_list.txt
@@ -0,0 +1,52 @@
+cue import -o - -f -l "\(strings.ToLower(kind))" --list ./import
+cmp stdout expect-stdout
+-- expect-stdout --
+service: [{
+	kind: "Service"
+	name: "booster"
+}, {
+	kind: "Service"
+	name: """
+		supplement
+		foo
+		"""
+	json: "[1, 2]"
+}]
+deployment: [{
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}]
+-- import/services.cue --
+service: [{
+	kind: "Service"
+	name: "booster"
+}, {
+	kind: "Service"
+	name: """
+		supplement
+		foo
+		"""
+	json: "[1, 2]"
+}]
+deployment: [{
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}]
+-- import/services.jsonl --
+{
+    "kind": "Service",
+    "name": "booster"
+}
+{
+    "kind": "Deployment",
+    "name": "booster",
+    "replicas": 1
+}
+{
+    "kind": "Service",
+    "name": "supplement\nfoo",
+    "json": "[1, 2]"
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/import_path.txt b/cmd/cue/cmd/testdata/script/import_path.txt
new file mode 100644
index 0000000..80618e2
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_path.txt
@@ -0,0 +1,54 @@
+cue import -o - -f -l '"\(strings.ToLower(kind))" "\(name)"' ./import
+cmp stdout expect-stdout
+-- expect-stdout --
+
+service booster: {
+	kind: "Service"
+	name: "booster"
+}
+deployment booster: {
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}
+service "supplement\nfoo": {
+	kind: "Service"
+	name: """
+		supplement
+		foo
+		"""
+	json: "[1, 2]"
+}
+-- import/services.cue --
+service: [{
+	kind: "Service"
+	name: "booster"
+}, {
+	kind: "Service"
+	name: """
+		supplement
+		foo
+		"""
+	json: "[1, 2]"
+}]
+deployment: [{
+	kind:     "Deployment"
+	name:     "booster"
+	replicas: 1
+}]
+-- import/services.jsonl --
+{
+    "kind": "Service",
+    "name": "booster"
+}
+{
+    "kind": "Deployment",
+    "name": "booster",
+    "replicas": 1
+}
+{
+    "kind": "Service",
+    "name": "supplement\nfoo",
+    "json": "[1, 2]"
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/trim.txt b/cmd/cue/cmd/testdata/script/trim.txt
new file mode 100644
index 0000000..b437ddf
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/trim.txt
@@ -0,0 +1,117 @@
+cue trim -o - ./trim
+cmp stdout expect-stdout
+-- expect-stdout --
+package trim
+
+foo <Name>: {
+	_value: string
+
+	a: 4
+	b: string
+	d: 8
+	e: "foo"
+	f: ">> \( _value) <<"
+	n: 5
+
+	list: ["foo", 8.0]
+
+	struct: {a: 3.0}
+
+	sList: [{a: 8, b: string}, {a: 9, b: *"foo" | string}]
+	rList: [{a: "a"}]
+	rcList: [{a: "a", c: b}]
+
+	t <Name>: {
+		x: >=0 & <=5
+	}
+}
+
+foo bar: {
+	_value: "here"
+	b:      "foo"
+	c:      45
+
+	sList: [{b: "foo"}, {}]
+}
+
+foo baz: {}
+
+foo multipath: {
+	t <Name>: {
+		// Combined with the other template, we know the value must be 5 and
+		// thus the entry below can be eliminated.
+		x: >=5 & <=8
+	}
+
+	t u: {
+	}
+}
+
+// TODO: top-level fields are currently not removed.
+group: {
+	for k, v in foo {
+		comp "\(k)": v
+	}
+
+	comp bar: {
+		aa: 8 // new value
+	}
+}
+-- trim/trim.cue --
+package trim
+
+foo <Name>: {
+	_value: string
+
+	a: 4
+	b: string
+	d: 8
+	e: "foo"
+	f: ">> \( _value) <<"
+	n: 5
+
+	list: ["foo", 8.0]
+
+	struct: {a: 3.0}
+
+	sList: [{a: 8, b: string}, {a: 9, b: *"foo" | string}]
+	rList: [{a: "a"}]
+	rcList: [{a: "a", c: b}]
+
+	t <Name>: {
+		x: >=0 & <=5
+	}
+}
+
+foo bar: {
+	_value: "here"
+	b:      "foo"
+	c:      45
+
+	sList: [{b: "foo"}, {}]
+}
+
+foo baz: {}
+
+foo multipath: {
+	t <Name>: {
+		// Combined with the other template, we know the value must be 5 and
+		// thus the entry below can be eliminated.
+		x: >=5 & <=8
+	}
+
+	t u: {
+	}
+}
+
+// TODO: top-level fields are currently not removed.
+group: {
+	for k, v in foo {
+		comp "\(k)": v
+	}
+
+	comp bar: {
+		aa: 8 // new value
+	}
+}
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/vet.txt b/cmd/cue/cmd/testdata/script/vet.txt
new file mode 100644
index 0000000..68e3664
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/vet.txt
@@ -0,0 +1,21 @@
+! cue vet
+cmp stderr expect-stderr
+
+-- expect-stderr --
+some instances are incomplete; use the -c flag to show errors or suppress this message
+-- partial.cue --
+package partial
+
+def: *1 | int
+sum: 1 | 2
+
+b: {
+	idx: a[str] // should resolve to top-level `a`
+	str: string
+}
+b a b: 4
+a: {
+	b: 3
+	c: 4
+}
+c: b & {str: "b"}
diff --git a/cmd/cue/cmd/testdata/script/vet_concrete.txt b/cmd/cue/cmd/testdata/script/vet_concrete.txt
new file mode 100644
index 0000000..6d557da
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/vet_concrete.txt
@@ -0,0 +1,25 @@
+! cue vet -c
+cmp stderr expect-stderr
+-- expect-stderr --
+sum: incomplete value ((1 | 2)):
+    ./partial.cue:4:6
+b.idx: invalid non-ground value string (must be concrete int|string):
+    ./partial.cue:7:9
+b.str: incomplete value (string):
+    ./partial.cue:8:7
+-- partial.cue --
+package partial
+
+def: *1 | int
+sum: 1 | 2
+
+b: {
+	idx: a[str] // should resolve to top-level `a`
+	str: string
+}
+b a b: 4
+a: {
+	b: 3
+	c: 4
+}
+c: b & {str: "b"}
diff --git a/cmd/cue/cmd/testdata/script/vet_expr.txt b/cmd/cue/cmd/testdata/script/vet_expr.txt
new file mode 100644
index 0000000..e96888c
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/vet_expr.txt
@@ -0,0 +1,34 @@
+! cue vet -e File vet.cue data.yaml
+cmp stderr expect-stderr
+
+-- expect-stderr --
+field "skip" not allowed in closed struct:
+    ./vet.cue:4:9
+-- vet.cue --
+
+translations <_> lang: string
+
+File :: {
+	translations: {...}
+}
+-- data.yaml --
+# translated messages
+translations:
+  hello:
+    lang: gsw
+    text: Grüetzi
+---
+translations:
+  hello:
+    text: Hoi
+---
+translations:
+  hello:
+    lang: no
+    text: Hallo
+---
+translations:
+  hello:
+    lang: nl
+    text: Hallo
+skip: true
diff --git a/cmd/cue/cmd/testdata/script/vet_file.txt b/cmd/cue/cmd/testdata/script/vet_file.txt
new file mode 100644
index 0000000..b000daf
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/vet_file.txt
@@ -0,0 +1,37 @@
+! cue vet ./vet.cue ./data.yaml
+cmp stderr expect-stderr
+
+-- expect-stderr --
+translations.hello.lang: incomplete value (string):
+    ./vet.cue:2:24
+translations.hello.lang: conflicting values false and string (mismatched types bool and string):
+    ./data.yaml:13:11
+    ./vet.cue:2:24
+-- vet.cue --
+
+translations <_> lang: string
+
+File :: {
+	translations: {...}
+}
+-- data.yaml --
+# translated messages
+translations:
+  hello:
+    lang: gsw
+    text: Grüetzi
+---
+translations:
+  hello:
+    text: Hoi
+---
+translations:
+  hello:
+    lang: no
+    text: Hallo
+---
+translations:
+  hello:
+    lang: nl
+    text: Hallo
+skip: true
diff --git a/cmd/cue/cmd/testdata/tasks/base_tool.cue b/cmd/cue/cmd/testdata/tasks/base_tool.cue
deleted file mode 100644
index 1787a94..0000000
--- a/cmd/cue/cmd/testdata/tasks/base_tool.cue
+++ /dev/null
@@ -1,15 +0,0 @@
-package home
-
-// deliberately put in another file to test resolving top-level identifiers
-// in different files.
-runBase: {
-	task echo: {
-		kind:   "exec"
-		stdout: string
-	}
-
-	task display: {
-		kind: "print"
-		text: task.echo.stdout
-	}
-}
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
deleted file mode 100644
index 2d64bfb..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
+++ /dev/null
@@ -1,3 +0,0 @@
-text: conflicting values 42 and string (mismatched types int and string):
-    ./testdata/tasks/task_tool.cue:31:9
-    tool/cli:4:9
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_errcode.out b/cmd/cue/cmd/testdata/tasks/cmd_errcode.out
deleted file mode 100644
index 08e9a2b..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_errcode.out
+++ /dev/null
@@ -1 +0,0 @@
-command "ls --badflags" failed: non-zero exist code
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_http.out b/cmd/cue/cmd/testdata/tasks/cmd_http.out
deleted file mode 100644
index 058b2c2..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_http.out
+++ /dev/null
@@ -1,2 +0,0 @@
-{"data":"I'll be back!","when":"now"}
-
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_print.out b/cmd/cue/cmd/testdata/tasks/cmd_print.out
deleted file mode 100644
index 5572191..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_print.out
+++ /dev/null
@@ -1,3 +0,0 @@
-t.1.
-.t.2.
-
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_run.out b/cmd/cue/cmd/testdata/tasks/cmd_run.out
deleted file mode 100644
index 47ee769..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_run.out
+++ /dev/null
@@ -1,2 +0,0 @@
-Hello world!
-
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_run_list.out b/cmd/cue/cmd/testdata/tasks/cmd_run_list.out
deleted file mode 100644
index 47ee769..0000000
--- a/cmd/cue/cmd/testdata/tasks/cmd_run_list.out
+++ /dev/null
@@ -1,2 +0,0 @@
-Hello world!
-
diff --git a/cmd/cue/cmd/testdata/tasks/eval.out b/cmd/cue/cmd/testdata/tasks/eval.out
deleted file mode 100644
index aefec3e..0000000
--- a/cmd/cue/cmd/testdata/tasks/eval.out
+++ /dev/null
@@ -1 +0,0 @@
-message: "Hello world!"
diff --git a/cmd/cue/cmd/testdata/tasks/task.cue b/cmd/cue/cmd/testdata/tasks/task.cue
deleted file mode 100644
index cb91d4f..0000000
--- a/cmd/cue/cmd/testdata/tasks/task.cue
+++ /dev/null
@@ -1,3 +0,0 @@
-package home
-
-message: "Hello world!"
diff --git a/cmd/cue/cmd/testdata/tasks/task_tool.cue b/cmd/cue/cmd/testdata/tasks/task_tool.cue
deleted file mode 100644
index 05383ec..0000000
--- a/cmd/cue/cmd/testdata/tasks/task_tool.cue
+++ /dev/null
@@ -1,73 +0,0 @@
-package home
-
-import "tool/exec"
-
-command run: runBase & {
-	task echo cmd: "echo \(message)"
-}
-
-command run_list: runBase & {
-	task echo cmd: ["echo", message]
-}
-
-command errcode: {
-	task bad: exec.Run & {
-		kind:   "exec"
-		cmd:    "ls --badflags"
-		stderr: string // suppress error message
-	}}
-
-// TODO: capture stdout and stderr for tests.
-command runRedirect: {
-	task echo: exec.Run & {
-		kind: "exec"
-		cmd:  "echo \(message)"
-	}
-}
-
-command baddisplay: {
-	task display: {
-		kind: "print"
-		text: 42
-	}
-}
-
-command http: {
-	task testserver: {
-		kind: "testserver"
-		url:  string
-	}
-	task http: {
-		kind:   "http"
-		method: "POST"
-		url:    task.testserver.url
-
-		request body:  "I'll be back!"
-		response body: string // TODO: allow this to be a struct, parsing the body.
-	}
-	task print: {
-		kind: "print"
-		text: task.http.response.body
-	}
-}
-
-command print: {
-	task: {
-		t1: exec.Run & {
-			cmd: ["sh", "-c", "sleep 1; echo t1"]
-			stdout: string
-		}
-		t2: exec.Run & {
-			cmd: ["sh", "-c", "sleep 1; echo t2"]
-			stdout: string
-		}
-		t3: cli.Print & {
-			text: (f & {arg: t1.stdout + t2.stdout}).result
-		}
-	}
-}
-
-f :: {
-    arg: string
-    result: strings.Join(strings.Split(arg, ""), ".")
-}
\ No newline at end of file
diff --git a/cmd/cue/cmd/testdata/trim/trim.cue b/cmd/cue/cmd/testdata/trim/trim.cue
deleted file mode 100644
index e5d5da2..0000000
--- a/cmd/cue/cmd/testdata/trim/trim.cue
+++ /dev/null
@@ -1,56 +0,0 @@
-package trim
-
-foo <Name>: {
-	_value: string
-
-	a: 4
-	b: string
-	d: 8
-	e: "foo"
-	f: ">> \( _value) <<"
-	n: 5
-
-	list: ["foo", 8.0]
-
-	struct: {a: 3.0}
-
-	sList: [{a: 8, b: string}, {a: 9, b: *"foo" | string}]
-	rList: [{a: "a"}]
-	rcList: [{a: "a", c: b}]
-
-	t <Name>: {
-		x: >=0 & <=5
-	}
-}
-
-foo bar: {
-	_value: "here"
-	b:      "foo"
-	c:      45
-
-	sList: [{b: "foo"}, {}]
-}
-
-foo baz: {}
-
-foo multipath: {
-	t <Name>: {
-		// Combined with the other template, we know the value must be 5 and
-		// thus the entry below can be eliminated.
-		x: >=5 & <=8
-	}
-
-	t u: {
-	}
-}
-
-// TODO: top-level fields are currently not removed.
-group: {
-	for k, v in foo {
-		comp "\(k)": v
-	}
-
-	comp bar: {
-		aa: 8 // new value
-	}
-}
diff --git a/cmd/cue/cmd/testdata/trim/trim.out b/cmd/cue/cmd/testdata/trim/trim.out
deleted file mode 100644
index e5d5da2..0000000
--- a/cmd/cue/cmd/testdata/trim/trim.out
+++ /dev/null
@@ -1,56 +0,0 @@
-package trim
-
-foo <Name>: {
-	_value: string
-
-	a: 4
-	b: string
-	d: 8
-	e: "foo"
-	f: ">> \( _value) <<"
-	n: 5
-
-	list: ["foo", 8.0]
-
-	struct: {a: 3.0}
-
-	sList: [{a: 8, b: string}, {a: 9, b: *"foo" | string}]
-	rList: [{a: "a"}]
-	rcList: [{a: "a", c: b}]
-
-	t <Name>: {
-		x: >=0 & <=5
-	}
-}
-
-foo bar: {
-	_value: "here"
-	b:      "foo"
-	c:      45
-
-	sList: [{b: "foo"}, {}]
-}
-
-foo baz: {}
-
-foo multipath: {
-	t <Name>: {
-		// Combined with the other template, we know the value must be 5 and
-		// thus the entry below can be eliminated.
-		x: >=5 & <=8
-	}
-
-	t u: {
-	}
-}
-
-// TODO: top-level fields are currently not removed.
-group: {
-	for k, v in foo {
-		comp "\(k)": v
-	}
-
-	comp bar: {
-		aa: 8 // new value
-	}
-}
diff --git a/cmd/cue/cmd/testdata/vet/data.yaml b/cmd/cue/cmd/testdata/vet/data.yaml
deleted file mode 100644
index 1d53144..0000000
--- a/cmd/cue/cmd/testdata/vet/data.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# translated messages
-translations:
-  hello:
-    lang: gsw
-    text: Grüetzi
----
-translations:
-  hello:
-    text: Hoi
----
-translations:
-  hello:
-    lang: no
-    text: Hallo
----
-translations:
-  hello:
-    lang: nl
-    text: Hallo
-skip: true
diff --git a/cmd/cue/cmd/testdata/vet/vet.cue b/cmd/cue/cmd/testdata/vet/vet.cue
deleted file mode 100644
index 8bffdf0..0000000
--- a/cmd/cue/cmd/testdata/vet/vet.cue
+++ /dev/null
@@ -1,6 +0,0 @@
-
-translations <_> lang: string
-
-File :: {
-	translations: {...}
-}
diff --git a/cmd/cue/cmd/testdata/vet/vet_expr.out b/cmd/cue/cmd/testdata/vet/vet_expr.out
deleted file mode 100644
index 82323b8..0000000
--- a/cmd/cue/cmd/testdata/vet/vet_expr.out
+++ /dev/null
@@ -1,2 +0,0 @@
-field "skip" not allowed in closed struct:
-    ./testdata/vet/vet.cue:4:9
diff --git a/cmd/cue/cmd/testdata/vet/vet_file.out b/cmd/cue/cmd/testdata/vet/vet_file.out
deleted file mode 100644
index 8d8a23d..0000000
--- a/cmd/cue/cmd/testdata/vet/vet_file.out
+++ /dev/null
@@ -1,5 +0,0 @@
-translations.hello.lang: incomplete value (string):
-    ./testdata/vet/vet.cue:2:24
-translations.hello.lang: conflicting values false and string (mismatched types bool and string):
-    ./testdata/vet/data.yaml:13:11
-    ./testdata/vet/vet.cue:2:24
diff --git a/cmd/cue/cmd/trim_test.go b/cmd/cue/cmd/trim_test.go
deleted file mode 100644
index 6326f31..0000000
--- a/cmd/cue/cmd/trim_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
-	"testing"
-)
-
-func TestTrim(t *testing.T) {
-	cmd := newTrimCmd(newRootCmd())
-	mustParseFlags(t, cmd, "-o", "-")
-	runCommand(t, cmd, "trim")
-}
diff --git a/cmd/cue/cmd/vet_test.go b/cmd/cue/cmd/vet_test.go
deleted file mode 100644
index 6856daa..0000000
--- a/cmd/cue/cmd/vet_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import "testing"
-
-func TestVet(t *testing.T) {
-	runCommand(t, newVetCmd(newRootCmd()), "vet")
-
-	cmd := newVetCmd(newRootCmd())
-	mustParseFlags(t, cmd, "-c")
-	runCommand(t, cmd, "vet_conc")
-
-	cmd = newVetCmd(newRootCmd())
-	runCommand(t, cmd, "vet_file", "./testdata/vet/vet.cue", "./testdata/vet/data.yaml")
-
-	cmd = newVetCmd(newRootCmd())
-	mustParseFlags(t, cmd, "-e", "File")
-	runCommand(t, cmd, "vet_expr", "./testdata/vet/vet.cue", "./testdata/vet/data.yaml")
-
-}
diff --git a/cmd/cue/main.go b/cmd/cue/main.go
index 77c34bb..643dfbd 100644
--- a/cmd/cue/main.go
+++ b/cmd/cue/main.go
@@ -15,19 +15,11 @@
 package main
 
 import (
-	"context"
-	"fmt"
 	"os"
 
 	"cuelang.org/go/cmd/cue/cmd"
 )
 
 func main() {
-	err := cmd.Main(context.Background(), os.Args[1:])
-	if err != nil {
-		if err != cmd.ErrPrintedError {
-			fmt.Println(err)
-		}
-		os.Exit(1)
-	}
+	os.Exit(cmd.Main())
 }
diff --git a/go.mod b/go.mod
index 0b23e5c..68b2e98 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@
 	github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de
 	github.com/pkg/errors v0.8.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/rogpeppe/testscript v1.1.0
 	github.com/spf13/cobra v0.0.3
 	github.com/spf13/pflag v1.0.3
 	github.com/stretchr/testify v1.2.0
diff --git a/go.sum b/go.sum
index e3af646..6cc3c30 100644
--- a/go.sum
+++ b/go.sum
@@ -29,6 +29,8 @@
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/testscript v1.1.0 h1:NxTsoOBQ1zibxf6NDtzrjPbK56hDAteIcOTSINZHtow=
+github.com/rogpeppe/testscript v1.1.0/go.mod h1:lzMlnW8LS56mcdJoQYkrlzqOoTFCOemzt5LusJ93bDM=
 github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
@@ -51,5 +53,9 @@
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=