cmd/cue: fail with non-zero exit status
Previously, if a command failed cue would still return a 0 exit status.
This also now passes through stderr and stdout to the system
by default.
Made the error stubbing less hacky.
Fixes #30
Change-Id: Ib6dc3733b557bfe817789e14631fd9da09d7b031
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1844
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/cmd_test.go b/cmd/cue/cmd/cmd_test.go
index cf51dd3..f96e457 100644
--- a/cmd/cue/cmd/cmd_test.go
+++ b/cmd/cue/cmd/cmd_test.go
@@ -15,6 +15,7 @@
package cmd
import (
+ "os"
"testing"
"cuelang.org/go/cue/errors"
@@ -27,12 +28,17 @@
"run",
"run_list",
"baddisplay",
+ "errcode",
"http",
}
+ defer func() {
+ stdout = os.Stdout
+ stderr = os.Stderr
+ }()
for _, name := range testCases {
run := func(cmd *cobra.Command, args []string) error {
- testOut = cmd.OutOrStdout()
- defer func() { testOut = nil }()
+ stdout = cmd.OutOrStdout()
+ stderr = cmd.OutOrStderr()
tools := buildTools(rootCmd, args)
cmd, err := addCustom(rootCmd, "command", name, tools)
@@ -41,7 +47,7 @@
}
err = executeTasks("command", name, tools)
if err != nil {
- errors.Print(testOut, err)
+ errors.Print(stdout, err)
}
return nil
}
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index b31982d..1866ff2 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -33,6 +33,7 @@
"cuelang.org/go/cue"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
+ "golang.org/x/xerrors"
)
const (
@@ -48,6 +49,12 @@
return str
}
+// Variables used for testing.
+var (
+ stdout io.Writer = os.Stdout
+ stderr io.Writer = os.Stderr
+)
+
func addCustom(parent *cobra.Command, typ, name string, tools *cue.Instance) (*cobra.Command, error) {
if tools == nil {
return nil, errors.New("no commands defined")
@@ -68,6 +75,7 @@
Short: lookupString(o, "short"),
Long: lookupString(o, "long"),
RunE: func(cmd *cobra.Command, args []string) error {
+ // TODO:
// - parse flags and env vars
// - constrain current config with config section
@@ -111,10 +119,9 @@
}
func doTasks(cmd *cobra.Command, typ, command string, root *cue.Instance) error {
- if err := executeTasks(typ, command, root); err != nil {
- exitIfErr(cmd, root, err, true)
- }
- return nil
+ err := executeTasks(typ, command, root)
+ exitIfErr(cmd, root, err, true)
+ return err
}
// executeTasks runs user-defined tasks as part of a user-defined command.
@@ -293,19 +300,12 @@
return &printCmd{}, nil
}
-// TODO: get rid of this hack
-var testOut io.Writer
-
func (c *printCmd) Run(ctx context.Context, v cue.Value) (res interface{}, err error) {
str, err := v.Lookup("text").String()
if err != nil {
return nil, err
}
- if testOut != nil {
- fmt.Fprintln(testOut, str)
- } else {
- fmt.Println(str)
- }
+ fmt.Fprintln(stdout, str)
return nil, nil
}
@@ -319,12 +319,14 @@
// TODO: set environment variables, if defined.
var bin string
var args []string
+ doc := ""
switch v := v.Lookup("cmd"); v.Kind() {
case cue.StringKind:
str, _ := v.String()
if str == "" {
return cue.Value{}, errors.New("empty command")
}
+ doc = str
list := strings.Fields(str)
bin = list[0]
for _, s := range list[1:] {
@@ -340,12 +342,14 @@
if err != nil {
return cue.Value{}, err
}
+ doc += bin
for list.Next() {
str, err := list.Value().String()
if err != nil {
return cue.Value{}, err
}
args = append(args, str)
+ doc += " " + str
}
}
@@ -356,18 +360,18 @@
return nil, fmt.Errorf("cue: %v", err)
}
}
- captureOut := !v.Lookup("stdout").IsNull()
+ captureOut := v.Lookup("stdout").Exists()
if !captureOut {
- cmd.Stdout = os.Stdout
+ cmd.Stdout = stdout
}
- captureErr := !v.Lookup("stderr").IsNull()
- if captureErr {
- cmd.Stderr = os.Stderr
+ captureErr := v.Lookup("stderr").Exists()
+ if !captureErr {
+ cmd.Stderr = stderr
}
update := map[string]interface{}{}
- var stdout, stderr []byte
if captureOut {
+ var stdout []byte
stdout, err = cmd.Output()
update["stdout"] = string(stdout)
} else {
@@ -375,16 +379,14 @@
}
update["success"] = err == nil
if err != nil {
- if exit, ok := err.(*exec.ExitError); ok && captureErr {
- stderr = exit.Stderr
+ if exit := (*exec.ExitError)(nil); xerrors.As(err, &exit) && captureErr {
+ update["stderr"] = string(exit.Stderr)
} else {
- return nil, fmt.Errorf("cue: %v", err)
+ update = nil
}
+ err = fmt.Errorf("command %q failed: %v", doc, err)
}
- if captureErr {
- update["stderr"] = string(stderr)
- }
- return update, nil
+ return update, err
}
type httpCmd struct{}
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
index e2dc4ef..08dafc3 100644
--- a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
+++ b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
@@ -1,3 +1,3 @@
not of right kind (number vs string):
- $CWD/testdata/tasks/task_tool.cue:23:9
+ $CWD/testdata/tasks/task_tool.cue:29:9
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_errcode.out b/cmd/cue/cmd/testdata/tasks/cmd_errcode.out
new file mode 100644
index 0000000..b42cb26
--- /dev/null
+++ b/cmd/cue/cmd/testdata/tasks/cmd_errcode.out
@@ -0,0 +1 @@
+command "ls --badflags" failed: exit status 1
diff --git a/cmd/cue/cmd/testdata/tasks/task_tool.cue b/cmd/cue/cmd/testdata/tasks/task_tool.cue
index 85468c1..0c6c737 100644
--- a/cmd/cue/cmd/testdata/tasks/task_tool.cue
+++ b/cmd/cue/cmd/testdata/tasks/task_tool.cue
@@ -8,12 +8,18 @@
task echo cmd: ["echo", message]
}
+command errcode: {
+ task bad: {
+ kind: "exec"
+ cmd: "ls --badflags"
+ stderr: string // suppress error message
+ }}
+
// TODO: capture stdout and stderr for tests.
command runRedirect: {
task echo: {
- kind: "exec"
- cmd: "echo \(message)"
- stdout: null // should be automatic
+ kind: "exec"
+ cmd: "echo \(message)"
}
}
diff --git a/go.mod b/go.mod
index c89414f..00ad7ec 100644
--- a/go.mod
+++ b/go.mod
@@ -14,4 +14,5 @@
golang.org/x/exp/errors v0.0.0-20181221233300-b68661188fbf
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77
+ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373
)
diff --git a/go.sum b/go.sum
index 3d202df..2aa9834 100644
--- a/go.sum
+++ b/go.sum
@@ -59,6 +59,8 @@
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77 h1:s+6psEFi3o1QryeA/qyvUoVaHMCQkYVvZ0i2ZolwSJc=
golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=