Fix stderr printing functions (#894)

* Fix stderr printing functions

Follow-up of #822

* Errors go to stderr as per POSIX

* use PrintErrf() instead of extra call to Sprintf()

* Error messages should always be printed to os.Stderr.

* add test case for Print* redirection

Thanks: @bukowa for the patch.
This commit is contained in:
Alessio Treglia 2020-10-01 17:28:00 +02:00 committed by GitHub
parent 0bc8bfbe59
commit 40d34bca1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 8 deletions

View File

@ -177,7 +177,7 @@ var rootCmd = &cobra.Command{
func Execute() { func Execute() {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -367,7 +367,7 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
c.mergePersistentFlags() c.mergePersistentFlags()
err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c) err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
if err != nil { if err != nil {
c.Println(err) c.PrintErrln(err)
} }
return err return err
} }
@ -395,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
// See https://github.com/spf13/cobra/issues/1002 // See https://github.com/spf13/cobra/issues/1002
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
if err != nil { if err != nil {
c.Println(err) c.PrintErrln(err)
} }
} }
} }
@ -938,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
c = cmd c = cmd
} }
if !c.SilenceErrors { if !c.SilenceErrors {
c.Println("Error:", err.Error()) c.PrintErrln("Error:", err.Error())
c.Printf("Run '%v --help' for usage.\n", c.CommandPath()) c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
} }
return c, err return c, err
} }
@ -967,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
// If root command has SilentErrors flagged, // If root command has SilentErrors flagged,
// all subcommands should respect it // all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors { if !cmd.SilenceErrors && !c.SilenceErrors {
c.Println("Error:", err.Error()) c.PrintErrln("Error:", err.Error())
} }
// If root command has SilentUsage flagged, // If root command has SilentUsage flagged,
@ -1209,12 +1209,12 @@ func (c *Command) PrintErr(i ...interface{}) {
// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. // PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set.
func (c *Command) PrintErrln(i ...interface{}) { func (c *Command) PrintErrln(i ...interface{}) {
c.Print(fmt.Sprintln(i...)) c.PrintErr(fmt.Sprintln(i...))
} }
// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. // PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set.
func (c *Command) PrintErrf(format string, i ...interface{}) { func (c *Command) PrintErrf(format string, i ...interface{}) {
c.Print(fmt.Sprintf(format, i...)) c.PrintErr(fmt.Sprintf(format, i...))
} }
// CommandPath returns the full path to this command. // CommandPath returns the full path to this command.

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@ -1636,6 +1637,47 @@ func TestUsageStringRedirected(t *testing.T) {
} }
} }
func TestCommandPrintRedirection(t *testing.T) {
errBuff, outBuff := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
root := &Command{
Run: func(cmd *Command, args []string) {
cmd.PrintErr("PrintErr")
cmd.PrintErrln("PrintErr", "line")
cmd.PrintErrf("PrintEr%s", "r")
cmd.Print("Print")
cmd.Println("Print", "line")
cmd.Printf("Prin%s", "t")
},
}
root.SetErr(errBuff)
root.SetOut(outBuff)
if err := root.Execute(); err != nil {
t.Error(err)
}
gotErrBytes, err := ioutil.ReadAll(errBuff)
if err != nil {
t.Error(err)
}
gotOutBytes, err := ioutil.ReadAll(outBuff)
if err != nil {
t.Error(err)
}
if wantErr := []byte("PrintErrPrintErr line\nPrintErr"); !bytes.Equal(gotErrBytes, wantErr) {
t.Errorf("got: '%s' want: '%s'", gotErrBytes, wantErr)
}
if wantOut := []byte("PrintPrint line\nPrint"); !bytes.Equal(gotOutBytes, wantOut) {
t.Errorf("got: '%s' want: '%s'", gotOutBytes, wantOut)
}
}
func TestFlagErrorFunc(t *testing.T) { func TestFlagErrorFunc(t *testing.T) {
c := &Command{Use: "c", Run: emptyRun} c := &Command{Use: "c", Run: emptyRun}