f585ec7cb8
This was introduced by #36, but only worked in the specific case of all arguments being passed before all flags. If the user mixed them, they ended up with odd parsing behavior where the arguments were reordered (causing #103 and #355). Given the tradeoffs I think we should remove support for flag reordering. Fixes #103 Fixes #355
1287 lines
28 KiB
Go
1287 lines
28 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type opCounts struct {
|
|
Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int
|
|
}
|
|
|
|
func ExampleApp_Run() {
|
|
// set args for examples sake
|
|
os.Args = []string{"greet", "--name", "Jeremy"}
|
|
|
|
app := NewApp()
|
|
app.Name = "greet"
|
|
app.Flags = []Flag{
|
|
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
|
}
|
|
app.Action = func(c *Context) error {
|
|
fmt.Printf("Hello %v\n", c.String("name"))
|
|
return nil
|
|
}
|
|
app.UsageText = "app [first_arg] [second_arg]"
|
|
app.Author = "Harrison"
|
|
app.Email = "harrison@lolwut.com"
|
|
app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
|
|
app.Run(os.Args)
|
|
// Output:
|
|
// Hello Jeremy
|
|
}
|
|
|
|
func ExampleApp_Run_subcommand() {
|
|
// set args for examples sake
|
|
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
|
app := NewApp()
|
|
app.Name = "say"
|
|
app.Commands = []Command{
|
|
{
|
|
Name: "hello",
|
|
Aliases: []string{"hi"},
|
|
Usage: "use it to see a description",
|
|
Description: "This is how we describe hello the function",
|
|
Subcommands: []Command{
|
|
{
|
|
Name: "english",
|
|
Aliases: []string{"en"},
|
|
Usage: "sends a greeting in english",
|
|
Description: "greets someone in english",
|
|
Flags: []Flag{
|
|
StringFlag{
|
|
Name: "name",
|
|
Value: "Bob",
|
|
Usage: "Name of the person to greet",
|
|
},
|
|
},
|
|
Action: func(c *Context) error {
|
|
fmt.Println("Hello,", c.String("name"))
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Run(os.Args)
|
|
// Output:
|
|
// Hello, Jeremy
|
|
}
|
|
|
|
func ExampleApp_Run_help() {
|
|
// set args for examples sake
|
|
os.Args = []string{"greet", "h", "describeit"}
|
|
|
|
app := NewApp()
|
|
app.Name = "greet"
|
|
app.Flags = []Flag{
|
|
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
|
}
|
|
app.Commands = []Command{
|
|
{
|
|
Name: "describeit",
|
|
Aliases: []string{"d"},
|
|
Usage: "use it to see a description",
|
|
Description: "This is how we describe describeit the function",
|
|
Action: func(c *Context) error {
|
|
fmt.Printf("i like to describe things")
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
app.Run(os.Args)
|
|
// Output:
|
|
// NAME:
|
|
// greet describeit - use it to see a description
|
|
//
|
|
// USAGE:
|
|
// greet describeit [arguments...]
|
|
//
|
|
// DESCRIPTION:
|
|
// This is how we describe describeit the function
|
|
}
|
|
|
|
func ExampleApp_Run_bashComplete() {
|
|
// set args for examples sake
|
|
os.Args = []string{"greet", "--generate-bash-completion"}
|
|
|
|
app := NewApp()
|
|
app.Name = "greet"
|
|
app.EnableBashCompletion = true
|
|
app.Commands = []Command{
|
|
{
|
|
Name: "describeit",
|
|
Aliases: []string{"d"},
|
|
Usage: "use it to see a description",
|
|
Description: "This is how we describe describeit the function",
|
|
Action: func(c *Context) error {
|
|
fmt.Printf("i like to describe things")
|
|
return nil
|
|
},
|
|
}, {
|
|
Name: "next",
|
|
Usage: "next example",
|
|
Description: "more stuff to see when generating bash completion",
|
|
Action: func(c *Context) error {
|
|
fmt.Printf("the next example")
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Run(os.Args)
|
|
// Output:
|
|
// describeit
|
|
// d
|
|
// next
|
|
// help
|
|
// h
|
|
}
|
|
|
|
func TestApp_Run(t *testing.T) {
|
|
s := ""
|
|
|
|
app := NewApp()
|
|
app.Action = func(c *Context) error {
|
|
s = s + c.Args().First()
|
|
return nil
|
|
}
|
|
|
|
err := app.Run([]string{"command", "foo"})
|
|
expect(t, err, nil)
|
|
err = app.Run([]string{"command", "bar"})
|
|
expect(t, err, nil)
|
|
expect(t, s, "foobar")
|
|
}
|
|
|
|
var commandAppTests = []struct {
|
|
name string
|
|
expected bool
|
|
}{
|
|
{"foobar", true},
|
|
{"batbaz", true},
|
|
{"b", true},
|
|
{"f", true},
|
|
{"bat", false},
|
|
{"nothing", false},
|
|
}
|
|
|
|
func TestApp_Command(t *testing.T) {
|
|
app := NewApp()
|
|
fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
|
|
batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
|
|
app.Commands = []Command{
|
|
fooCommand,
|
|
batCommand,
|
|
}
|
|
|
|
for _, test := range commandAppTests {
|
|
expect(t, app.Command(test.name) != nil, test.expected)
|
|
}
|
|
}
|
|
|
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
|
var context *Context
|
|
|
|
a := NewApp()
|
|
a.Commands = []Command{
|
|
{
|
|
Name: "foo",
|
|
Action: func(c *Context) error {
|
|
context = c
|
|
return nil
|
|
},
|
|
Flags: []Flag{
|
|
StringFlag{
|
|
Name: "lang",
|
|
Value: "english",
|
|
Usage: "language for the greeting",
|
|
},
|
|
},
|
|
Before: func(_ *Context) error { return nil },
|
|
},
|
|
}
|
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
|
|
|
expect(t, context.Args().Get(0), "abcd")
|
|
expect(t, context.String("lang"), "spanish")
|
|
}
|
|
|
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
|
var parsedOption string
|
|
var args []string
|
|
|
|
app := NewApp()
|
|
command := Command{
|
|
Name: "cmd",
|
|
Flags: []Flag{
|
|
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
|
},
|
|
Action: func(c *Context) error {
|
|
parsedOption = c.String("option")
|
|
args = c.Args()
|
|
return nil
|
|
},
|
|
}
|
|
app.Commands = []Command{command}
|
|
|
|
app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"})
|
|
|
|
expect(t, parsedOption, "my-option")
|
|
expect(t, args[0], "my-arg")
|
|
expect(t, args[1], "--")
|
|
expect(t, args[2], "--notARealFlag")
|
|
}
|
|
|
|
func TestApp_CommandWithDash(t *testing.T) {
|
|
var args []string
|
|
|
|
app := NewApp()
|
|
command := Command{
|
|
Name: "cmd",
|
|
Action: func(c *Context) error {
|
|
args = c.Args()
|
|
return nil
|
|
},
|
|
}
|
|
app.Commands = []Command{command}
|
|
|
|
app.Run([]string{"", "cmd", "my-arg", "-"})
|
|
|
|
expect(t, args[0], "my-arg")
|
|
expect(t, args[1], "-")
|
|
}
|
|
|
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
|
var args []string
|
|
|
|
app := NewApp()
|
|
command := Command{
|
|
Name: "cmd",
|
|
Action: func(c *Context) error {
|
|
args = c.Args()
|
|
return nil
|
|
},
|
|
}
|
|
app.Commands = []Command{command}
|
|
|
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
|
|
|
|
expect(t, args[0], "my-arg")
|
|
expect(t, args[1], "--")
|
|
expect(t, args[2], "notAFlagAtAll")
|
|
}
|
|
|
|
func TestApp_Float64Flag(t *testing.T) {
|
|
var meters float64
|
|
|
|
app := NewApp()
|
|
app.Flags = []Flag{
|
|
Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
|
}
|
|
app.Action = func(c *Context) error {
|
|
meters = c.Float64("height")
|
|
return nil
|
|
}
|
|
|
|
app.Run([]string{"", "--height", "1.93"})
|
|
expect(t, meters, 1.93)
|
|
}
|
|
|
|
func TestApp_ParseSliceFlags(t *testing.T) {
|
|
var parsedOption, firstArg string
|
|
var parsedIntSlice []int
|
|
var parsedStringSlice []string
|
|
|
|
app := NewApp()
|
|
command := Command{
|
|
Name: "cmd",
|
|
Flags: []Flag{
|
|
IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"},
|
|
StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"},
|
|
},
|
|
Action: func(c *Context) error {
|
|
parsedIntSlice = c.IntSlice("p")
|
|
parsedStringSlice = c.StringSlice("ip")
|
|
parsedOption = c.String("option")
|
|
firstArg = c.Args().First()
|
|
return nil
|
|
},
|
|
}
|
|
app.Commands = []Command{command}
|
|
|
|
app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"})
|
|
|
|
IntsEquals := func(a, b []int) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, v := range a {
|
|
if v != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
StrsEquals := func(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, v := range a {
|
|
if v != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
var expectedIntSlice = []int{22, 80}
|
|
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
|
|
|
|
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
|
|
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
|
|
}
|
|
|
|
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
|
|
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
|
|
}
|
|
}
|
|
|
|
func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
|
|
var parsedIntSlice []int
|
|
var parsedStringSlice []string
|
|
|
|
app := NewApp()
|
|
command := Command{
|
|
Name: "cmd",
|
|
Flags: []Flag{
|
|
IntSliceFlag{Name: "a", Usage: "set numbers"},
|
|
StringSliceFlag{Name: "str", Usage: "set strings"},
|
|
},
|
|
Action: func(c *Context) error {
|
|
parsedIntSlice = c.IntSlice("a")
|
|
parsedStringSlice = c.StringSlice("str")
|
|
return nil
|
|
},
|
|
}
|
|
app.Commands = []Command{command}
|
|
|
|
app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"})
|
|
|
|
var expectedIntSlice = []int{2}
|
|
var expectedStringSlice = []string{"A"}
|
|
|
|
if parsedIntSlice[0] != expectedIntSlice[0] {
|
|
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
|
}
|
|
|
|
if parsedStringSlice[0] != expectedStringSlice[0] {
|
|
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
|
}
|
|
}
|
|
|
|
func TestApp_DefaultStdout(t *testing.T) {
|
|
app := NewApp()
|
|
|
|
if app.Writer != os.Stdout {
|
|
t.Error("Default output writer not set.")
|
|
}
|
|
}
|
|
|
|
type mockWriter struct {
|
|
written []byte
|
|
}
|
|
|
|
func (fw *mockWriter) Write(p []byte) (n int, err error) {
|
|
if fw.written == nil {
|
|
fw.written = p
|
|
} else {
|
|
fw.written = append(fw.written, p...)
|
|
}
|
|
|
|
return len(p), nil
|
|
}
|
|
|
|
func (fw *mockWriter) GetWritten() (b []byte) {
|
|
return fw.written
|
|
}
|
|
|
|
func TestApp_SetStdout(t *testing.T) {
|
|
w := &mockWriter{}
|
|
|
|
app := NewApp()
|
|
app.Name = "test"
|
|
app.Writer = w
|
|
|
|
err := app.Run([]string{"help"})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Run error: %s", err)
|
|
}
|
|
|
|
if len(w.written) == 0 {
|
|
t.Error("App did not write output to desired writer.")
|
|
}
|
|
}
|
|
|
|
func TestApp_BeforeFunc(t *testing.T) {
|
|
counts := &opCounts{}
|
|
beforeError := fmt.Errorf("fail")
|
|
var err error
|
|
|
|
app := NewApp()
|
|
|
|
app.Before = func(c *Context) error {
|
|
counts.Total++
|
|
counts.Before = counts.Total
|
|
s := c.String("opt")
|
|
if s == "fail" {
|
|
return beforeError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "sub",
|
|
Action: func(c *Context) error {
|
|
counts.Total++
|
|
counts.SubCommand = counts.Total
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Flags = []Flag{
|
|
StringFlag{Name: "opt"},
|
|
}
|
|
|
|
// run with the Before() func succeeding
|
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Run error: %s", err)
|
|
}
|
|
|
|
if counts.Before != 1 {
|
|
t.Errorf("Before() not executed when expected")
|
|
}
|
|
|
|
if counts.SubCommand != 2 {
|
|
t.Errorf("Subcommand not executed when expected")
|
|
}
|
|
|
|
// reset
|
|
counts = &opCounts{}
|
|
|
|
// run with the Before() func failing
|
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
|
|
|
// should be the same error produced by the Before func
|
|
if err != beforeError {
|
|
t.Errorf("Run error expected, but not received")
|
|
}
|
|
|
|
if counts.Before != 1 {
|
|
t.Errorf("Before() not executed when expected")
|
|
}
|
|
|
|
if counts.SubCommand != 0 {
|
|
t.Errorf("Subcommand executed when NOT expected")
|
|
}
|
|
|
|
// reset
|
|
counts = &opCounts{}
|
|
|
|
afterError := errors.New("fail again")
|
|
app.After = func(_ *Context) error {
|
|
return afterError
|
|
}
|
|
|
|
// run with the Before() func failing, wrapped by After()
|
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
|
|
|
// should be the same error produced by the Before func
|
|
if _, ok := err.(MultiError); !ok {
|
|
t.Errorf("MultiError expected, but not received")
|
|
}
|
|
|
|
if counts.Before != 1 {
|
|
t.Errorf("Before() not executed when expected")
|
|
}
|
|
|
|
if counts.SubCommand != 0 {
|
|
t.Errorf("Subcommand executed when NOT expected")
|
|
}
|
|
}
|
|
|
|
func TestApp_AfterFunc(t *testing.T) {
|
|
counts := &opCounts{}
|
|
afterError := fmt.Errorf("fail")
|
|
var err error
|
|
|
|
app := NewApp()
|
|
|
|
app.After = func(c *Context) error {
|
|
counts.Total++
|
|
counts.After = counts.Total
|
|
s := c.String("opt")
|
|
if s == "fail" {
|
|
return afterError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "sub",
|
|
Action: func(c *Context) error {
|
|
counts.Total++
|
|
counts.SubCommand = counts.Total
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Flags = []Flag{
|
|
StringFlag{Name: "opt"},
|
|
}
|
|
|
|
// run with the After() func succeeding
|
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Run error: %s", err)
|
|
}
|
|
|
|
if counts.After != 2 {
|
|
t.Errorf("After() not executed when expected")
|
|
}
|
|
|
|
if counts.SubCommand != 1 {
|
|
t.Errorf("Subcommand not executed when expected")
|
|
}
|
|
|
|
// reset
|
|
counts = &opCounts{}
|
|
|
|
// run with the Before() func failing
|
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
|
|
|
// should be the same error produced by the Before func
|
|
if err != afterError {
|
|
t.Errorf("Run error expected, but not received")
|
|
}
|
|
|
|
if counts.After != 2 {
|
|
t.Errorf("After() not executed when expected")
|
|
}
|
|
|
|
if counts.SubCommand != 1 {
|
|
t.Errorf("Subcommand not executed when expected")
|
|
}
|
|
}
|
|
|
|
func TestAppNoHelpFlag(t *testing.T) {
|
|
oldFlag := HelpFlag
|
|
defer func() {
|
|
HelpFlag = oldFlag
|
|
}()
|
|
|
|
HelpFlag = BoolFlag{}
|
|
|
|
app := NewApp()
|
|
app.Writer = ioutil.Discard
|
|
err := app.Run([]string{"test", "-h"})
|
|
|
|
if err != flag.ErrHelp {
|
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
|
|
}
|
|
}
|
|
|
|
func TestAppHelpPrinter(t *testing.T) {
|
|
oldPrinter := HelpPrinter
|
|
defer func() {
|
|
HelpPrinter = oldPrinter
|
|
}()
|
|
|
|
var wasCalled = false
|
|
HelpPrinter = func(w io.Writer, template string, data interface{}) {
|
|
wasCalled = true
|
|
}
|
|
|
|
app := NewApp()
|
|
app.Run([]string{"-h"})
|
|
|
|
if wasCalled == false {
|
|
t.Errorf("Help printer expected to be called, but was not")
|
|
}
|
|
}
|
|
|
|
func TestApp_VersionPrinter(t *testing.T) {
|
|
oldPrinter := VersionPrinter
|
|
defer func() {
|
|
VersionPrinter = oldPrinter
|
|
}()
|
|
|
|
var wasCalled = false
|
|
VersionPrinter = func(c *Context) {
|
|
wasCalled = true
|
|
}
|
|
|
|
app := NewApp()
|
|
ctx := NewContext(app, nil, nil)
|
|
ShowVersion(ctx)
|
|
|
|
if wasCalled == false {
|
|
t.Errorf("Version printer expected to be called, but was not")
|
|
}
|
|
}
|
|
|
|
func TestApp_CommandNotFound(t *testing.T) {
|
|
counts := &opCounts{}
|
|
app := NewApp()
|
|
|
|
app.CommandNotFound = func(c *Context, command string) {
|
|
counts.Total++
|
|
counts.CommandNotFound = counts.Total
|
|
}
|
|
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "bar",
|
|
Action: func(c *Context) error {
|
|
counts.Total++
|
|
counts.SubCommand = counts.Total
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Run([]string{"command", "foo"})
|
|
|
|
expect(t, counts.CommandNotFound, 1)
|
|
expect(t, counts.SubCommand, 0)
|
|
expect(t, counts.Total, 1)
|
|
}
|
|
|
|
func TestApp_OrderOfOperations(t *testing.T) {
|
|
counts := &opCounts{}
|
|
|
|
resetCounts := func() { counts = &opCounts{} }
|
|
|
|
app := NewApp()
|
|
app.EnableBashCompletion = true
|
|
app.BashComplete = func(c *Context) {
|
|
counts.Total++
|
|
counts.BashComplete = counts.Total
|
|
}
|
|
|
|
app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
|
|
counts.Total++
|
|
counts.OnUsageError = counts.Total
|
|
return errors.New("hay OnUsageError")
|
|
}
|
|
|
|
beforeNoError := func(c *Context) error {
|
|
counts.Total++
|
|
counts.Before = counts.Total
|
|
return nil
|
|
}
|
|
|
|
beforeError := func(c *Context) error {
|
|
counts.Total++
|
|
counts.Before = counts.Total
|
|
return errors.New("hay Before")
|
|
}
|
|
|
|
app.Before = beforeNoError
|
|
app.CommandNotFound = func(c *Context, command string) {
|
|
counts.Total++
|
|
counts.CommandNotFound = counts.Total
|
|
}
|
|
|
|
afterNoError := func(c *Context) error {
|
|
counts.Total++
|
|
counts.After = counts.Total
|
|
return nil
|
|
}
|
|
|
|
afterError := func(c *Context) error {
|
|
counts.Total++
|
|
counts.After = counts.Total
|
|
return errors.New("hay After")
|
|
}
|
|
|
|
app.After = afterNoError
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "bar",
|
|
Action: func(c *Context) error {
|
|
counts.Total++
|
|
counts.SubCommand = counts.Total
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
app.Action = func(c *Context) error {
|
|
counts.Total++
|
|
counts.Action = counts.Total
|
|
return nil
|
|
}
|
|
|
|
_ = app.Run([]string{"command", "--nope"})
|
|
expect(t, counts.OnUsageError, 1)
|
|
expect(t, counts.Total, 1)
|
|
|
|
resetCounts()
|
|
|
|
_ = app.Run([]string{"command", "--generate-bash-completion"})
|
|
expect(t, counts.BashComplete, 1)
|
|
expect(t, counts.Total, 1)
|
|
|
|
resetCounts()
|
|
|
|
oldOnUsageError := app.OnUsageError
|
|
app.OnUsageError = nil
|
|
_ = app.Run([]string{"command", "--nope"})
|
|
expect(t, counts.Total, 0)
|
|
app.OnUsageError = oldOnUsageError
|
|
|
|
resetCounts()
|
|
|
|
_ = app.Run([]string{"command", "foo"})
|
|
expect(t, counts.OnUsageError, 0)
|
|
expect(t, counts.Before, 1)
|
|
expect(t, counts.CommandNotFound, 0)
|
|
expect(t, counts.Action, 2)
|
|
expect(t, counts.After, 3)
|
|
expect(t, counts.Total, 3)
|
|
|
|
resetCounts()
|
|
|
|
app.Before = beforeError
|
|
_ = app.Run([]string{"command", "bar"})
|
|
expect(t, counts.OnUsageError, 0)
|
|
expect(t, counts.Before, 1)
|
|
expect(t, counts.After, 2)
|
|
expect(t, counts.Total, 2)
|
|
app.Before = beforeNoError
|
|
|
|
resetCounts()
|
|
|
|
app.After = nil
|
|
_ = app.Run([]string{"command", "bar"})
|
|
expect(t, counts.OnUsageError, 0)
|
|
expect(t, counts.Before, 1)
|
|
expect(t, counts.SubCommand, 2)
|
|
expect(t, counts.Total, 2)
|
|
app.After = afterNoError
|
|
|
|
resetCounts()
|
|
|
|
app.After = afterError
|
|
err := app.Run([]string{"command", "bar"})
|
|
if err == nil {
|
|
t.Fatalf("expected a non-nil error")
|
|
}
|
|
expect(t, counts.OnUsageError, 0)
|
|
expect(t, counts.Before, 1)
|
|
expect(t, counts.SubCommand, 2)
|
|
expect(t, counts.After, 3)
|
|
expect(t, counts.Total, 3)
|
|
app.After = afterNoError
|
|
|
|
resetCounts()
|
|
|
|
oldCommands := app.Commands
|
|
app.Commands = nil
|
|
_ = app.Run([]string{"command"})
|
|
expect(t, counts.OnUsageError, 0)
|
|
expect(t, counts.Before, 1)
|
|
expect(t, counts.Action, 2)
|
|
expect(t, counts.After, 3)
|
|
expect(t, counts.Total, 3)
|
|
app.Commands = oldCommands
|
|
}
|
|
|
|
func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
|
|
var subcommandHelpTopics = [][]string{
|
|
{"command", "foo", "--help"},
|
|
{"command", "foo", "-h"},
|
|
{"command", "foo", "help"},
|
|
}
|
|
|
|
for _, flagSet := range subcommandHelpTopics {
|
|
t.Logf("==> checking with flags %v", flagSet)
|
|
|
|
app := NewApp()
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
|
|
subCmdBar := Command{
|
|
Name: "bar",
|
|
Usage: "does bar things",
|
|
}
|
|
subCmdBaz := Command{
|
|
Name: "baz",
|
|
Usage: "does baz things",
|
|
}
|
|
cmd := Command{
|
|
Name: "foo",
|
|
Description: "descriptive wall of text about how it does foo things",
|
|
Subcommands: []Command{subCmdBar, subCmdBaz},
|
|
}
|
|
|
|
app.Commands = []Command{cmd}
|
|
err := app.Run(flagSet)
|
|
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
t.Logf("output: %q\n", buf.Bytes())
|
|
|
|
if strings.Contains(output, "No help topic for") {
|
|
t.Errorf("expect a help topic, got none: \n%q", output)
|
|
}
|
|
|
|
for _, shouldContain := range []string{
|
|
cmd.Name, cmd.Description,
|
|
subCmdBar.Name, subCmdBar.Usage,
|
|
subCmdBaz.Name, subCmdBaz.Usage,
|
|
} {
|
|
if !strings.Contains(output, shouldContain) {
|
|
t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_SubcommandFullPath(t *testing.T) {
|
|
app := NewApp()
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
app.Name = "command"
|
|
subCmd := Command{
|
|
Name: "bar",
|
|
Usage: "does bar things",
|
|
}
|
|
cmd := Command{
|
|
Name: "foo",
|
|
Description: "foo commands",
|
|
Subcommands: []Command{subCmd},
|
|
}
|
|
app.Commands = []Command{cmd}
|
|
|
|
err := app.Run([]string{"command", "foo", "bar", "--help"})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "command foo bar - does bar things") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
if !strings.Contains(output, "command foo bar [arguments...]") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_SubcommandHelpName(t *testing.T) {
|
|
app := NewApp()
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
app.Name = "command"
|
|
subCmd := Command{
|
|
Name: "bar",
|
|
HelpName: "custom",
|
|
Usage: "does bar things",
|
|
}
|
|
cmd := Command{
|
|
Name: "foo",
|
|
Description: "foo commands",
|
|
Subcommands: []Command{subCmd},
|
|
}
|
|
app.Commands = []Command{cmd}
|
|
|
|
err := app.Run([]string{"command", "foo", "bar", "--help"})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "custom - does bar things") {
|
|
t.Errorf("expected HelpName for subcommand: %s", output)
|
|
}
|
|
if !strings.Contains(output, "custom [arguments...]") {
|
|
t.Errorf("expected HelpName to subcommand: %s", output)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_CommandHelpName(t *testing.T) {
|
|
app := NewApp()
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
app.Name = "command"
|
|
subCmd := Command{
|
|
Name: "bar",
|
|
Usage: "does bar things",
|
|
}
|
|
cmd := Command{
|
|
Name: "foo",
|
|
HelpName: "custom",
|
|
Description: "foo commands",
|
|
Subcommands: []Command{subCmd},
|
|
}
|
|
app.Commands = []Command{cmd}
|
|
|
|
err := app.Run([]string{"command", "foo", "bar", "--help"})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "command foo bar - does bar things") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
if !strings.Contains(output, "command foo bar [arguments...]") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
|
|
app := NewApp()
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
app.Name = "base"
|
|
subCmd := Command{
|
|
Name: "bar",
|
|
HelpName: "custom",
|
|
Usage: "does bar things",
|
|
}
|
|
cmd := Command{
|
|
Name: "foo",
|
|
Description: "foo commands",
|
|
Subcommands: []Command{subCmd},
|
|
}
|
|
app.Commands = []Command{cmd}
|
|
|
|
err := app.Run([]string{"command", "foo", "--help"})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "base foo - foo commands") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
if !strings.Contains(output, "base foo command [command options] [arguments...]") {
|
|
t.Errorf("expected full path to subcommand: %s", output)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_Help(t *testing.T) {
|
|
var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}
|
|
|
|
for _, args := range helpArguments {
|
|
buf := new(bytes.Buffer)
|
|
|
|
t.Logf("==> checking with arguments %v", args)
|
|
|
|
app := NewApp()
|
|
app.Name = "boom"
|
|
app.Usage = "make an explosive entrance"
|
|
app.Writer = buf
|
|
app.Action = func(c *Context) error {
|
|
buf.WriteString("boom I say!")
|
|
return nil
|
|
}
|
|
|
|
err := app.Run(args)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
t.Logf("output: %q\n", buf.Bytes())
|
|
|
|
if !strings.Contains(output, "boom - make an explosive entrance") {
|
|
t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_Version(t *testing.T) {
|
|
var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}}
|
|
|
|
for _, args := range versionArguments {
|
|
buf := new(bytes.Buffer)
|
|
|
|
t.Logf("==> checking with arguments %v", args)
|
|
|
|
app := NewApp()
|
|
app.Name = "boom"
|
|
app.Usage = "make an explosive entrance"
|
|
app.Version = "0.1.0"
|
|
app.Writer = buf
|
|
app.Action = func(c *Context) error {
|
|
buf.WriteString("boom I say!")
|
|
return nil
|
|
}
|
|
|
|
err := app.Run(args)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
output := buf.String()
|
|
t.Logf("output: %q\n", buf.Bytes())
|
|
|
|
if !strings.Contains(output, "0.1.0") {
|
|
t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_Categories(t *testing.T) {
|
|
app := NewApp()
|
|
app.Name = "categories"
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "command1",
|
|
Category: "1",
|
|
},
|
|
Command{
|
|
Name: "command2",
|
|
Category: "1",
|
|
},
|
|
Command{
|
|
Name: "command3",
|
|
Category: "2",
|
|
},
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
app.Writer = buf
|
|
|
|
app.Run([]string{"categories"})
|
|
|
|
expect := CommandCategories{
|
|
&CommandCategory{
|
|
Name: "1",
|
|
Commands: []Command{
|
|
app.Commands[0],
|
|
app.Commands[1],
|
|
},
|
|
},
|
|
&CommandCategory{
|
|
Name: "2",
|
|
Commands: []Command{
|
|
app.Commands[2],
|
|
},
|
|
},
|
|
}
|
|
if !reflect.DeepEqual(app.Categories(), expect) {
|
|
t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect)
|
|
}
|
|
|
|
output := buf.String()
|
|
t.Logf("output: %q\n", buf.Bytes())
|
|
|
|
if !strings.Contains(output, "1:\n command1") {
|
|
t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
|
app := NewApp()
|
|
app.Action = func(c *Context) error { return nil }
|
|
app.Before = func(c *Context) error { return fmt.Errorf("before error") }
|
|
app.After = func(c *Context) error { return fmt.Errorf("after error") }
|
|
|
|
err := app.Run([]string{"foo"})
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "before error") {
|
|
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "after error") {
|
|
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
|
}
|
|
}
|
|
|
|
func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
|
|
app := NewApp()
|
|
app.Commands = []Command{
|
|
Command{
|
|
Subcommands: []Command{
|
|
Command{
|
|
Name: "sub",
|
|
},
|
|
},
|
|
Name: "bar",
|
|
Before: func(c *Context) error { return fmt.Errorf("before error") },
|
|
After: func(c *Context) error { return fmt.Errorf("after error") },
|
|
},
|
|
}
|
|
|
|
err := app.Run([]string{"foo", "bar"})
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "before error") {
|
|
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "after error") {
|
|
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
|
}
|
|
}
|
|
|
|
func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) {
|
|
app := NewApp()
|
|
app.Flags = []Flag{
|
|
IntFlag{Name: "flag"},
|
|
}
|
|
app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
|
|
if isSubcommand {
|
|
t.Errorf("Expect no subcommand")
|
|
}
|
|
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
|
|
t.Errorf("Expect an invalid value error, but got \"%v\"", err)
|
|
}
|
|
return errors.New("intercepted: " + err.Error())
|
|
}
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "bar",
|
|
},
|
|
}
|
|
|
|
err := app.Run([]string{"foo", "--flag=wrong"})
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
|
|
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
|
}
|
|
}
|
|
|
|
func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
|
|
app := NewApp()
|
|
app.Flags = []Flag{
|
|
IntFlag{Name: "flag"},
|
|
}
|
|
app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
|
|
if isSubcommand {
|
|
t.Errorf("Expect subcommand")
|
|
}
|
|
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
|
|
t.Errorf("Expect an invalid value error, but got \"%v\"", err)
|
|
}
|
|
return errors.New("intercepted: " + err.Error())
|
|
}
|
|
app.Commands = []Command{
|
|
Command{
|
|
Name: "bar",
|
|
},
|
|
}
|
|
|
|
err := app.Run([]string{"foo", "--flag=wrong", "bar"})
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
|
|
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
|
}
|
|
}
|
|
|
|
func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
|
app := NewApp()
|
|
app.Action = 42
|
|
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
|
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
exitErr, ok := err.(*ExitError)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected to receive a *ExitError")
|
|
}
|
|
|
|
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
|
|
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
|
|
}
|
|
|
|
if exitErr.ExitCode() != 2 {
|
|
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
|
|
}
|
|
}
|
|
|
|
func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
|
|
app := NewApp()
|
|
app.Action = func() string { return "" }
|
|
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
|
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
exitErr, ok := err.(*ExitError)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected to receive a *ExitError")
|
|
}
|
|
|
|
if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") {
|
|
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
|
|
}
|
|
|
|
if exitErr.ExitCode() != 2 {
|
|
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
|
|
}
|
|
}
|
|
|
|
func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
|
|
app := NewApp()
|
|
app.Action = func(_ *Context) (int, error) { return 0, nil }
|
|
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
|
|
|
if err == nil {
|
|
t.Fatalf("expected to receive error from Run, got none")
|
|
}
|
|
|
|
exitErr, ok := err.(*ExitError)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected to receive a *ExitError")
|
|
}
|
|
|
|
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") {
|
|
t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
|
|
}
|
|
|
|
if exitErr.ExitCode() != 2 {
|
|
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
|
|
}
|
|
}
|