diff --git a/utils/utils.go b/utils/utils.go index 343f6f8a0..435f8d6a4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -192,40 +192,25 @@ func TruncateString(str string, maxLength int) string { func RunCmdAndWait(name string, args ...string) (string, error) { cmd := exec.Command(name, args...) - - stdout, err := cmd.StdoutPipe() - if err != nil { - return "", err - } - stderr, err := cmd.StderrPipe() - if err != nil { - return "", err - } - - err = cmd.Start() + resp, err := cmd.Output() if err != nil { - return "", err - } - - resp, err := io.ReadAll(stdout) - if err != nil { - return "", err - } - errB, err := io.ReadAll(stderr) - if err != nil { - //nolint - return "", nil - } - - err = cmd.Wait() - if err != nil { - // in case of error, capture the exact message. - if len(errB) > 0 { - return "", errors.New(string(errB)) + cmdStr := name + " " + strings.Join(args, " ") + output := "" + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + output = strings.TrimSpace(string(exitErr.Stderr)) } - return "", err + if output == "" { + output = strings.TrimSpace(string(resp)) + } + if output != "" { + if exitErr != nil { + return "", fmt.Errorf("error running \"%s\" (exit code %d): %s", cmdStr, exitErr.ExitCode(), output) + } + return "", fmt.Errorf("error running \"%s\": %s", cmdStr, output) + } + return "", fmt.Errorf("error running \"%s\": %w", cmdStr, err) } - return string(resp), nil } diff --git a/utils/utils_test.go b/utils/utils_test.go index 95a0d7ec1..0b910b57d 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -504,6 +504,53 @@ func cleanupTempDir(t *testing.T, fileName string) { assert.NoError(t, err) } +func TestRunCmdAndWait(t *testing.T) { + t.Run("successful command returns stdout", func(t *testing.T) { + t.Parallel() + var out string + var err error + if runtime.GOOS == "windows" { + out, err = RunCmdAndWait("cmd", "/c", "echo", "hello") + } else { + out, err = RunCmdAndWait("echo", "hello") + } + assert.NoError(t, err) + assert.Contains(t, out, "hello") + }) + + t.Run("failed command with stderr includes command name and stderr in error", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping on Windows") + } + t.Parallel() + _, err := RunCmdAndWait("sh", "-c", "echo myerror >&2; exit 1") + assert.Error(t, err) + assert.Contains(t, err.Error(), `error running "sh -c echo myerror >&2; exit 1" (exit code 1)`) + assert.Contains(t, err.Error(), "myerror") + }) + + t.Run("failed command with no stderr falls back to stdout in error", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping on Windows") + } + t.Parallel() + _, err := RunCmdAndWait("sh", "-c", "echo myoutput; exit 1") + assert.Error(t, err) + assert.Contains(t, err.Error(), `error running "sh -c echo myoutput; exit 1" (exit code 1)`) + assert.Contains(t, err.Error(), "myoutput") + }) + + t.Run("failed command with no output wraps original error with command name", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping on Windows") + } + t.Parallel() + _, err := RunCmdAndWait("sh", "-c", "exit 1") + assert.Error(t, err) + assert.Contains(t, err.Error(), `error running "sh -c exit 1"`) + }) +} + func TestSanitizeDir(t *testing.T) { testcases := []struct { name string