Browse Source

Add --debug flag to return full stack trace on error

pull/1842/head
Ethan Frey 8 years ago
committed by Ethan Buchman
parent
commit
8efeeb5f38
2 changed files with 80 additions and 20 deletions
  1. +31
    -8
      cli/setup.go
  2. +49
    -12
      cli/setup_test.go

+ 31
- 8
cli/setup.go View File

@ -16,25 +16,36 @@ import (
const ( const (
RootFlag = "root" RootFlag = "root"
HomeFlag = "home" HomeFlag = "home"
DebugFlag = "debug"
OutputFlag = "output" OutputFlag = "output"
EncodingFlag = "encoding" EncodingFlag = "encoding"
) )
// Executable is the minimal interface to *corba.Command, so we can
// wrap if desired before the test
type Executable interface {
Execute() error
}
// PrepareBaseCmd is meant for tendermint and other servers // PrepareBaseCmd is meant for tendermint and other servers
func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() {
func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defautRoot string) Executor {
cobra.OnInitialize(func() { initEnv(envPrefix) }) cobra.OnInitialize(func() { initEnv(envPrefix) })
cmd.PersistentFlags().StringP(RootFlag, "r", defautRoot, "DEPRECATED. Use --home") cmd.PersistentFlags().StringP(RootFlag, "r", defautRoot, "DEPRECATED. Use --home")
// -h is already reserved for --help as part of the cobra framework // -h is already reserved for --help as part of the cobra framework
// do you want to try something else??
// also, default must be empty, so we can detect this unset and fall back
// to --root / TM_ROOT / TMROOT
cmd.PersistentFlags().String(HomeFlag, "", "root directory for config and data") cmd.PersistentFlags().String(HomeFlag, "", "root directory for config and data")
cmd.PersistentFlags().Bool(DebugFlag, false, "print out full stack trace on errors")
cmd.PersistentPreRunE = concatCobraCmdFuncs(bindFlagsLoadViper, cmd.PersistentPreRunE) cmd.PersistentPreRunE = concatCobraCmdFuncs(bindFlagsLoadViper, cmd.PersistentPreRunE)
return func() { execute(cmd) }
return Executor{cmd}
} }
// PrepareMainCmd is meant for client side libs that want some more flags // PrepareMainCmd is meant for client side libs that want some more flags
// //
// This adds --encoding (hex, btc, base64) and --output (text, json) to // This adds --encoding (hex, btc, base64) and --output (text, json) to
// the command. These only really make sense in interactive commands. // the command. These only really make sense in interactive commands.
func PrepareMainCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() {
func PrepareMainCmd(cmd *cobra.Command, envPrefix, defautRoot string) Executor {
cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)") cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)") cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
cmd.PersistentPreRunE = concatCobraCmdFuncs(setEncoding, validateOutput, cmd.PersistentPreRunE) cmd.PersistentPreRunE = concatCobraCmdFuncs(setEncoding, validateOutput, cmd.PersistentPreRunE)
@ -68,14 +79,26 @@ func copyEnvVars(prefix string) {
} }
} }
// Executor wraps the cobra Command with a nicer Execute method
type Executor struct {
*cobra.Command
}
// execute adds all child commands to the root command sets flags appropriately. // execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func execute(cmd *cobra.Command) {
// TODO: this can do something cooler with debug and log-levels
if err := cmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
func (e Executor) Execute() error {
e.SilenceUsage = true
e.SilenceErrors = true
err := e.Command.Execute()
if err != nil {
// TODO: something cooler with log-levels
if viper.GetBool(DebugFlag) {
fmt.Printf("ERROR: %+v\n", err)
} else {
fmt.Println("ERROR:", err.Error())
}
} }
return err
} }
type cobraCmdFunc func(cmd *cobra.Command, args []string) error type cobraCmdFunc func(cmd *cobra.Command, args []string) error


+ 49
- 12
cli/setup_test.go View File

@ -8,20 +8,16 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"testing" "testing"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// Executable is the minimal interface to *corba.Command, so we can
// wrap if desired before the test
type Executable interface {
Execute() error
}
func TestSetupEnv(t *testing.T) { func TestSetupEnv(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
@ -46,15 +42,15 @@ func TestSetupEnv(t *testing.T) {
i := strconv.Itoa(idx) i := strconv.Itoa(idx)
// test command that store value of foobar in local variable // test command that store value of foobar in local variable
var foo string var foo string
cmd := &cobra.Command{
demo := &cobra.Command{
Use: "demo", Use: "demo",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
foo = viper.GetString("foobar") foo = viper.GetString("foobar")
return nil return nil
}, },
} }
cmd.Flags().String("foobar", "", "Some test value from config")
PrepareBaseCmd(cmd, "DEMO", "/qwerty/asdfgh") // some missing dir..
demo.Flags().String("foobar", "", "Some test value from config")
cmd := PrepareBaseCmd(demo, "DEMO", "/qwerty/asdfgh") // some missing dir..
viper.Reset() viper.Reset()
args := append([]string{cmd.Use}, tc.args...) args := append([]string{cmd.Use}, tc.args...)
@ -112,15 +108,15 @@ func TestSetupConfig(t *testing.T) {
i := strconv.Itoa(idx) i := strconv.Itoa(idx)
// test command that store value of foobar in local variable // test command that store value of foobar in local variable
var foo string var foo string
cmd := &cobra.Command{
boo := &cobra.Command{
Use: "reader", Use: "reader",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
foo = viper.GetString("boo") foo = viper.GetString("boo")
return nil return nil
}, },
} }
cmd.Flags().String("boo", "", "Some test value from config")
PrepareBaseCmd(cmd, "RD", "/qwerty/asdfgh") // some missing dir...
boo.Flags().String("boo", "", "Some test value from config")
cmd := PrepareBaseCmd(boo, "RD", "/qwerty/asdfgh") // some missing dir...
viper.Reset() viper.Reset()
args := append([]string{cmd.Use}, tc.args...) args := append([]string{cmd.Use}, tc.args...)
@ -130,6 +126,47 @@ func TestSetupConfig(t *testing.T) {
} }
} }
func TestSetupDebug(t *testing.T) {
assert, require := assert.New(t), require.New(t)
cases := []struct {
args []string
env map[string]string
long bool
expected string
}{
{nil, nil, false, "Debug flag = false"},
{[]string{"--debug"}, nil, true, "Debug flag = true"},
{[]string{"--no-such-flag"}, nil, false, "unknown flag: --no-such-flag"},
{nil, map[string]string{"DBG_DEBUG": "true"}, true, "Debug flag = true"},
}
for idx, tc := range cases {
i := strconv.Itoa(idx)
// test command that store value of foobar in local variable
debug := &cobra.Command{
Use: "debug",
RunE: func(cmd *cobra.Command, args []string) error {
return errors.Errorf("Debug flag = %t", viper.GetBool(DebugFlag))
},
}
cmd := PrepareBaseCmd(debug, "DBG", "/qwerty/asdfgh") // some missing dir..
viper.Reset()
args := append([]string{cmd.Use}, tc.args...)
out, err := runCaptureWithArgs(cmd, args, tc.env)
require.NotNil(err, i)
msg := strings.Split(out, "\n")
desired := fmt.Sprintf("ERROR: %s", tc.expected)
assert.Equal(desired, msg[0], i)
if tc.long && assert.True(len(msg) > 2, i) {
// the next line starts the stack trace...
assert.Contains(msg[1], "TestSetupDebug", i)
assert.Contains(msg[2], "setup_test.go", i)
}
}
}
// runWithArgs executes the given command with the specified command line args // runWithArgs executes the given command with the specified command line args
// and environmental variables set. It returns any error returned from cmd.Execute() // and environmental variables set. It returns any error returned from cmd.Execute()
func runWithArgs(cmd Executable, args []string, env map[string]string) error { func runWithArgs(cmd Executable, args []string, env map[string]string) error {


Loading…
Cancel
Save