splitKeyVal sliced the value from index+1, skipping only one byte of the key/value separator. With a multi-character sep tag (e.g. sep:"=>") the remaining separator bytes leaked into the value. Slice from index+len(sep) so the whole separator is consumed.
752 lines
18 KiB
Go
752 lines
18 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/labstack/gommon/color"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var donothing = func(*Context) error { return nil }
|
|
|
|
func TestTree(t *testing.T) {
|
|
cmd := &Command{Name: "cmd", Fn: donothing}
|
|
subcmd := &Command{Name: "subcmd", Fn: donothing}
|
|
|
|
tree := Tree(cmd, Tree(subcmd))
|
|
assert.Equal(t, tree.command.Name, "cmd")
|
|
require.Len(t, tree.forest, 1)
|
|
assert.Equal(t, tree.forest[0].command.Name, "subcmd")
|
|
assert.Len(t, tree.forest[0].forest, 0)
|
|
}
|
|
|
|
func TestRoot(t *testing.T) {
|
|
donothing := func(*Context) error { return nil }
|
|
root := Root(&Command{Name: "cmd", Fn: donothing},
|
|
Tree(&Command{Name: "subcmd", Fn: donothing}),
|
|
)
|
|
assert.Equal(t, root.Name, "cmd")
|
|
require.Len(t, root.children, 1)
|
|
assert.Equal(t, root.children[0].Name, "subcmd")
|
|
}
|
|
|
|
type argT struct {
|
|
Short bool `cli:"s" usage:"short flag"`
|
|
Short2 bool `cli:"2" usage:"another short flag"`
|
|
ShortAndLong string `cli:"S,long" usage:"short and long flags"`
|
|
ShortsAndLong int `cli:"x,y,abcd,omitof" usage:"many short and long flags"`
|
|
Long uint `cli:"long-flag" usage:"long flag"`
|
|
Required int8 `cli:"*required" usage:"required flag, note the *"`
|
|
Default uint8 `cli:"dft,default" usage:"default value" dft:"102"`
|
|
UnName uint16 `usage:"unname field"`
|
|
|
|
Int8 int8 `cli:"i8" usage:"type int8"`
|
|
Uint8 uint8 `cli:"u8" usage:"type uint8"`
|
|
Int16 int16 `cli:"i16" usage:"type int16"`
|
|
Uint16 uint16 `cli:"u16" usage:"type uint16"`
|
|
Int32 int32 `cli:"i32" usage:"type int32"`
|
|
Uint32 uint32 `cli:"u32" usage:"type uint32"`
|
|
Int64 int64 `cli:"i64" usage:"type int64"`
|
|
Uint64 uint64 `cli:"u64" usage:"type uint64"`
|
|
Float32 float32 `cli:"f32" usage:"type float32"`
|
|
Float64 float64 `cli:"f64" usage:"type float64"`
|
|
}
|
|
|
|
func toStr(i interface{}) string {
|
|
return fmt.Sprintf("%v", i)
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
clr := color.Color{}
|
|
for i, tab := range []struct {
|
|
args []string
|
|
want argT
|
|
isErr bool
|
|
freedomArgs []string
|
|
}{
|
|
//Case: ENV
|
|
{
|
|
args: []string{"--required=0"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: missing required
|
|
{
|
|
args: []string{},
|
|
isErr: true,
|
|
},
|
|
//Case: undefined flag
|
|
{
|
|
args: []string{"--required=0", "-Q"},
|
|
isErr: true,
|
|
},
|
|
//Case: undefined flag
|
|
{
|
|
args: []string{"--required=0", "--KdjiiejdfwkHJH"},
|
|
isErr: true,
|
|
},
|
|
//Case: short flag group
|
|
{
|
|
args: []string{"--required=0", "-s2"},
|
|
want: argT{Default: 102, Short: true, Short2: true},
|
|
},
|
|
//Case: check default
|
|
{
|
|
args: []string{"--required=0"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: modify default
|
|
{
|
|
args: []string{"--required=0", "--dft", "55"},
|
|
want: argT{Default: 55},
|
|
},
|
|
//Case: modify default
|
|
{
|
|
args: []string{"--required=0", "--default", "55"},
|
|
want: argT{Default: 55},
|
|
},
|
|
//Case: UnName
|
|
{
|
|
args: []string{"--required=0", "--UnName", "64"},
|
|
want: argT{Default: 102, UnName: 64},
|
|
},
|
|
//Case: not a bool
|
|
{
|
|
args: []string{"--required=0", "-s=not-a-bool"},
|
|
isErr: true,
|
|
},
|
|
//Case: "" -> bool
|
|
{
|
|
args: []string{"--required=0", "-s"},
|
|
want: argT{Default: 102, Short: true},
|
|
},
|
|
//Case: "true" -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=true"},
|
|
want: argT{Default: 102, Short: true},
|
|
},
|
|
//Case: non-zero integer -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=1"},
|
|
want: argT{Default: 102, Short: true},
|
|
},
|
|
//Case: zero -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=0"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: no -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=no"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: not -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=not"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: none -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=none"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: false -> bool
|
|
{
|
|
args: []string{"--required=0", "-s=false"},
|
|
want: argT{Default: 102},
|
|
},
|
|
//Case: int64
|
|
{
|
|
args: []string{"--required=0", "--i64", toStr(12)},
|
|
want: argT{Default: 102, Int64: 12},
|
|
},
|
|
//Case: int64 overflow
|
|
{
|
|
args: []string{"--required=0", "--i64", toStr(uint64(math.MaxUint64))},
|
|
isErr: true,
|
|
},
|
|
//Case: uint64
|
|
{
|
|
args: []string{"--required=0", "--u64", toStr(12)},
|
|
want: argT{Default: 102, Uint64: 12},
|
|
},
|
|
//Case: max uint64
|
|
{
|
|
args: []string{"--required=0", "--u64", toStr(uint64(math.MaxUint64))},
|
|
want: argT{Default: 102, Uint64: uint64(math.MaxUint64)},
|
|
},
|
|
//Case: negative -> uint64
|
|
{
|
|
args: []string{"--required=0", "--u64", "-1"},
|
|
isErr: true,
|
|
},
|
|
//Case: int32
|
|
{
|
|
args: []string{"--required=0", "--i32", toStr(12)},
|
|
want: argT{Default: 102, Int32: 12},
|
|
},
|
|
//Case: int32 overflow
|
|
{
|
|
args: []string{"--required=0", "--i32", toStr(uint32(math.MaxUint32))},
|
|
isErr: true,
|
|
},
|
|
//Case: uint32
|
|
{
|
|
args: []string{"--required=0", "--u32", toStr(12)},
|
|
want: argT{Default: 102, Uint32: 12},
|
|
},
|
|
//Case: max uint32
|
|
{
|
|
args: []string{"--required=0", "--u32", toStr(uint32(math.MaxUint32))},
|
|
want: argT{Default: 102, Uint32: uint32(math.MaxUint32)},
|
|
},
|
|
//Case: negative -> uint32
|
|
{
|
|
args: []string{"--required=0", "--u32", "-1"},
|
|
isErr: true,
|
|
},
|
|
//Case: int16
|
|
{
|
|
args: []string{"--required=0", "--i16", toStr(12)},
|
|
want: argT{Default: 102, Int16: 12},
|
|
},
|
|
//Case: int16 overflow
|
|
{
|
|
args: []string{"--required=0", "--i16", toStr(uint16(math.MaxUint16))},
|
|
isErr: true,
|
|
},
|
|
//Case: uint16
|
|
{
|
|
args: []string{"--required=0", "--u16", toStr(12)},
|
|
want: argT{Default: 102, Uint16: 12},
|
|
},
|
|
//Case: max uint16
|
|
{
|
|
args: []string{"--required=0", "--u16", toStr(uint16(math.MaxUint16))},
|
|
want: argT{Default: 102, Uint16: uint16(math.MaxUint16)},
|
|
},
|
|
//Case: negative -> uint16
|
|
{
|
|
args: []string{"--required=0", "--u16", "-1"},
|
|
isErr: true,
|
|
},
|
|
//Case: int8
|
|
{
|
|
args: []string{"--required=0", "--i8", toStr(12)},
|
|
want: argT{Default: 102, Int8: 12},
|
|
},
|
|
//Case: int8 overflow
|
|
{
|
|
args: []string{"--required=0", "--i8", toStr(uint8(math.MaxUint8))},
|
|
isErr: true,
|
|
},
|
|
//Case: uint8
|
|
{
|
|
args: []string{"--required=0", "--u8", toStr(12)},
|
|
want: argT{Default: 102, Uint8: 12},
|
|
},
|
|
//Case: max uint8
|
|
{
|
|
args: []string{"--required=0", "--u8", toStr(uint8(math.MaxUint8))},
|
|
want: argT{Default: 102, Uint8: uint8(math.MaxUint8)},
|
|
},
|
|
//Case: negative -> uint8
|
|
{
|
|
args: []string{"--required=0", "--u8", "-1"},
|
|
isErr: true,
|
|
},
|
|
//Case: many invalid value
|
|
{
|
|
args: []string{"--required=0", "--u8", "-1", "--u8", "256"},
|
|
isErr: true,
|
|
},
|
|
//Case: too many value
|
|
{
|
|
args: []string{"--required=0=1"},
|
|
isErr: true,
|
|
},
|
|
//Case: float32
|
|
{
|
|
args: []string{"--required=0", "--f32", "12.34"},
|
|
want: argT{Default: 102, Float32: 12.34},
|
|
},
|
|
//Case: not a float32
|
|
{
|
|
args: []string{"--required=0", "--f32", "not-a-float32"},
|
|
isErr: true,
|
|
},
|
|
//Case: float32 overflow
|
|
{
|
|
args: []string{"--required=0", "--f32", "123456789123456789123456789123456789123456789"},
|
|
isErr: true,
|
|
},
|
|
//Case: float32 overflow
|
|
{
|
|
args: []string{"--required=0", "--f32", "-123456789123456789123456789123456789123456789"},
|
|
isErr: true,
|
|
},
|
|
//Case: float64
|
|
{
|
|
args: []string{"--required=0", "--f64=-1234.5678"},
|
|
want: argT{Default: 102, Float64: -1234.5678},
|
|
},
|
|
//Case: not a float64
|
|
{
|
|
args: []string{"--required=0", "--f64=not-a-float64"},
|
|
isErr: true,
|
|
},
|
|
//Case: fold flag not boolean
|
|
{
|
|
args: []string{"--required=0", "-2y"},
|
|
isErr: true,
|
|
},
|
|
//Case: test -F<value>
|
|
{
|
|
args: []string{"--required=0", "-Sshort-and-long"},
|
|
want: argT{Default: 102, ShortAndLong: "short-and-long"},
|
|
},
|
|
//Case: test `--`
|
|
{
|
|
args: []string{"--required=0", "--", "-Sshort-and-long"},
|
|
want: argT{Default: 102, ShortAndLong: ""},
|
|
freedomArgs: []string{"-Sshort-and-long"},
|
|
},
|
|
//Case: test flags and args
|
|
{
|
|
args: []string{"--required=0", "-Sshort-and-long", "abc"},
|
|
want: argT{Default: 102, ShortAndLong: "short-and-long"},
|
|
freedomArgs: []string{"abc"},
|
|
},
|
|
{
|
|
args: []string{"--required=0", "-Sshort-and-long", "abc", "-s", "xyz"},
|
|
want: argT{Default: 102, Short: true, ShortAndLong: "short-and-long"},
|
|
freedomArgs: []string{"abc", "xyz"},
|
|
},
|
|
} {
|
|
if tab.args == nil {
|
|
tab.args = []string{}
|
|
}
|
|
v := new(argT)
|
|
flagSet := parseArgv(tab.args, v, clr)
|
|
if tab.isErr {
|
|
if flagSet.err == nil {
|
|
t.Errorf("[%2d] want error, got nil", i)
|
|
}
|
|
continue
|
|
}
|
|
if flagSet.err != nil {
|
|
t.Errorf("[%2d] parseArgv error: %v", i, flagSet.err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(*v, tab.want) {
|
|
t.Errorf("[%2d] want %v, got %v", i, tab.want, *v)
|
|
}
|
|
if !stringsEqual(tab.freedomArgs, flagSet.args) {
|
|
t.Errorf("[%2d] want %v, got %v", i, tab.freedomArgs, flagSet.args)
|
|
}
|
|
}
|
|
|
|
//Case parse non-pointer object
|
|
if flagSet := parseArgv([]string{}, argT{}, clr); flagSet.err != errNotAPointer {
|
|
t.Errorf("want %v, got %v", errNotAPointer, flagSet.err)
|
|
}
|
|
if usage([]interface{}{argT{}}, clr, NormalStyle) != "" {
|
|
t.Errorf("want usage empty, but not")
|
|
}
|
|
|
|
//Case parse pointer, but not indirect a struct
|
|
tmp := 0
|
|
ptrInt := &tmp
|
|
if flagSet := parseArgv([]string{}, ptrInt, clr); flagSet.err != errNotAPointerToStruct {
|
|
t.Errorf("want %v, got %v", errNotAPointerToStruct, flagSet.err)
|
|
}
|
|
if usage([]interface{}{ptrInt}, clr, NormalStyle) != "" {
|
|
t.Errorf("want usage empty, but not")
|
|
}
|
|
|
|
//Case repeat tag
|
|
type tmpT struct {
|
|
A bool `cli:"a"`
|
|
B bool `cli:"a"`
|
|
}
|
|
if flagSet := parseArgv([]string{}, new(tmpT), clr); flagSet.err == nil {
|
|
t.Errorf("want error, got nil")
|
|
}
|
|
if usage([]interface{}{new(tmpT)}, clr, NormalStyle) != "" {
|
|
t.Errorf("want usage empty, but not")
|
|
}
|
|
|
|
type envT struct {
|
|
DefaultEnv string `cli:"default-env" usage:"default value" dft:"$ENV_CLI_TEST"`
|
|
}
|
|
envV := new(envT)
|
|
if flagSet := parseArgv([]string{}, envV, clr); flagSet.err != nil {
|
|
t.Errorf(flagSet.err.Error())
|
|
} else {
|
|
if want := os.Getenv("ENV_CLI_TEST"); want != envV.DefaultEnv {
|
|
t.Errorf("ENV_CLI_TEST want `%s`, got `%s`", want, envV.DefaultEnv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUsage(t *testing.T) {
|
|
clr := color.Color{}
|
|
clr.Disable()
|
|
got := usage([]interface{}{new(argT)}, clr, NormalStyle)
|
|
want := fmt.Sprintf(
|
|
` -s short flag
|
|
-2 another short flag
|
|
-S, --long short and long flags
|
|
-x, -y, --abcd, --omitof many short and long flags
|
|
--long-flag long flag
|
|
--required *required flag, note the *
|
|
--dft, --default[=102] default value
|
|
--UnName unname field
|
|
--i8 type int8
|
|
--u8 type uint8
|
|
--i16 type int16
|
|
--u16 type uint16
|
|
--i32 type int32
|
|
--u32 type uint32
|
|
--i64 type int64
|
|
--u64 type uint64
|
|
--f32 type float32
|
|
--f64 type float64
|
|
`)
|
|
assert.Equal(t, got, want)
|
|
|
|
got = usage([]interface{}{new(argT)}, clr, ManualStyle)
|
|
want = ` -s
|
|
short flag
|
|
|
|
-2
|
|
another short flag
|
|
|
|
-S, --long
|
|
short and long flags
|
|
|
|
-x, -y, --abcd, --omitof
|
|
many short and long flags
|
|
|
|
--long-flag
|
|
long flag
|
|
|
|
--required
|
|
*required flag, note the *
|
|
|
|
--dft, --default[=102]
|
|
default value
|
|
|
|
--UnName
|
|
unname field
|
|
|
|
--i8
|
|
type int8
|
|
|
|
--u8
|
|
type uint8
|
|
|
|
--i16
|
|
type int16
|
|
|
|
--u16
|
|
type uint16
|
|
|
|
--i32
|
|
type int32
|
|
|
|
--u32
|
|
type uint32
|
|
|
|
--i64
|
|
type int64
|
|
|
|
--u64
|
|
type uint64
|
|
|
|
--f32
|
|
type float32
|
|
|
|
--f64
|
|
type float64
|
|
`
|
|
assert.Equal(t, got, want)
|
|
}
|
|
|
|
func TestStructField(t *testing.T) {
|
|
type BaseT struct {
|
|
Help bool `cli:"!h,help" usage:"display help"`
|
|
}
|
|
type subT struct {
|
|
BaseT
|
|
Version string `cli:"v" usage:"display version" dft:"v0.0.1"`
|
|
}
|
|
args := []string{
|
|
"-h",
|
|
"-v=v1.1.1",
|
|
}
|
|
argv := new(subT)
|
|
clr := color.Color{}
|
|
flagSet := parseArgv(args, argv, clr)
|
|
if flagSet.err != nil {
|
|
t.Errorf("parseArgv error: %v", flagSet.err)
|
|
} else {
|
|
if argv.BaseT.Help != true {
|
|
t.Errorf("Help want true, but got false")
|
|
}
|
|
if argv.Version != "v1.1.1" {
|
|
t.Errorf("Version want v1.1.1, but got %s", argv.Version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func stringsEqual(ss1, ss2 []string) bool {
|
|
if len(ss1) != len(ss2) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(ss1); i++ {
|
|
if ss1[i] != ss2[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func intsEqual(ss1, ss2 []uint32) bool {
|
|
if ss1 == nil && ss2 == nil {
|
|
return true
|
|
}
|
|
if len(ss1) != len(ss2) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(ss1); i++ {
|
|
if ss1[i] != ss2[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func mapEqual(m1, m2 map[uint32]string) bool {
|
|
if m1 == nil && m2 == nil {
|
|
return true
|
|
}
|
|
for k, v := range m1 {
|
|
if v2, ok := m2[k]; !ok {
|
|
return false
|
|
} else if v2 != v {
|
|
return false
|
|
}
|
|
}
|
|
for k, v := range m2 {
|
|
if v1, ok := m1[k]; !ok {
|
|
return false
|
|
} else if v1 != v {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestSliceAndMap(t *testing.T) {
|
|
clr := color.Color{}
|
|
type T struct {
|
|
Slice []uint32 `cli:"s,slice"`
|
|
Map map[uint32]string `cli:"m,map"`
|
|
}
|
|
for _, tab := range []struct {
|
|
args []string
|
|
want T
|
|
isErr bool
|
|
}{
|
|
{
|
|
args: []string{"--slice=12", "--slice", "23"},
|
|
want: T{Slice: []uint32{12, 23}},
|
|
},
|
|
{
|
|
args: []string{"-s12", "-s23"},
|
|
want: T{Slice: []uint32{12, 23}},
|
|
},
|
|
{
|
|
args: []string{"-s=12", "-s=23"},
|
|
want: T{Slice: []uint32{12, 23}},
|
|
},
|
|
{
|
|
args: []string{"-s", "12", "-s", "23"},
|
|
want: T{Slice: []uint32{12, 23}},
|
|
},
|
|
{
|
|
args: []string{"-s", "12", "-s", "not-a-number"},
|
|
isErr: true,
|
|
},
|
|
{
|
|
args: []string{"-m2=s", "-m3=y"},
|
|
want: T{Map: map[uint32]string{
|
|
2: "s",
|
|
3: "y",
|
|
}},
|
|
},
|
|
{
|
|
args: []string{"-m2=s", "-m", "3=y"},
|
|
want: T{Map: map[uint32]string{
|
|
2: "s",
|
|
3: "y",
|
|
}},
|
|
},
|
|
{
|
|
args: []string{"-m"},
|
|
isErr: true,
|
|
},
|
|
{
|
|
args: []string{"-ms=2", "-my=3"},
|
|
isErr: true,
|
|
},
|
|
} {
|
|
v := new(T)
|
|
flagSet := parseArgv(tab.args, v, clr)
|
|
if tab.isErr {
|
|
if flagSet.err == nil {
|
|
t.Errorf("want error, but not. tab=%v", tab)
|
|
}
|
|
continue
|
|
}
|
|
if flagSet.err != nil {
|
|
t.Errorf("TestSliceAndMap: error: %v", flagSet.err)
|
|
continue
|
|
}
|
|
if !intsEqual(tab.want.Slice, v.Slice) {
|
|
t.Errorf("want %v, got %v", tab.want.Slice, v.Slice)
|
|
}
|
|
if !mapEqual(tab.want.Map, v.Map) {
|
|
t.Errorf("want %v, got %v", tab.want.Map, v.Map)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestErrorSliceType(t *testing.T) {
|
|
type A struct {
|
|
Value string
|
|
}
|
|
type T struct {
|
|
Slice []A `cli:"s"`
|
|
}
|
|
clr := color.Color{}
|
|
v := new(T)
|
|
flagSet := parseArgv([]string{"-s4"}, v, clr)
|
|
if flagSet.err == nil {
|
|
t.Errorf("want error, but not")
|
|
}
|
|
}
|
|
|
|
func TestErrorMapType(t *testing.T) {
|
|
type A struct {
|
|
Value string
|
|
}
|
|
type T struct {
|
|
Map map[string]A `cli:"m"`
|
|
}
|
|
clr := color.Color{}
|
|
v := new(T)
|
|
flagSet := parseArgv([]string{"-mkey=val"}, v, clr)
|
|
if flagSet.err == nil {
|
|
t.Errorf("want error, but not")
|
|
}
|
|
}
|
|
|
|
func TestIsSet(t *testing.T) {
|
|
type argT struct {
|
|
A int `cli:"a,aa" dft:"1"`
|
|
B int `cli:"b"`
|
|
}
|
|
for i, tt := range []struct {
|
|
args []string
|
|
isSetA bool
|
|
isSetB bool
|
|
}{
|
|
{[]string{"app", "-a=1", "-b=1"}, true, true},
|
|
{[]string{"app", "--aa=1", "-b=1"}, true, true},
|
|
{[]string{"app", "-a=1"}, true, false},
|
|
{[]string{"app", "-b=1"}, false, true},
|
|
{[]string{"app"}, false, false},
|
|
} {
|
|
RunWithArgs(new(argT), tt.args, func(ctx *Context) error {
|
|
assert.Equal(t, ctx.IsSet("-a"), tt.isSetA, "case %d", i)
|
|
assert.Equal(t, ctx.IsSet("-a", "--aa"), tt.isSetA, "case %d", i)
|
|
assert.Equal(t, ctx.IsSet("-b"), tt.isSetB, "case %d", i)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelpCommand(t *testing.T) {
|
|
w := bytes.NewBufferString("")
|
|
root := &Command{Name: "root"}
|
|
help := HelpCommand("help command")
|
|
root.Register(help)
|
|
assert.Nil(t, root.RunWith([]string{"help"}, w, nil))
|
|
assert.Equal(t, w.String(), "Commands:\n\n help help command\n")
|
|
assert.Error(t, root.RunWith([]string{"help", "not-found"}, nil, nil))
|
|
}
|
|
|
|
func TestError(t *testing.T) {
|
|
assert.Equal(t, ExitError.Error(), "exit")
|
|
assert.Equal(t, throwCommandNotFound("cmd").Error(), "command cmd not found")
|
|
assert.Equal(t, throwMethodNotAllowed("POST").Error(), "method POST not allowed")
|
|
assert.Equal(t, throwRouterRepeat("R").Error(), "router R repeat")
|
|
clr := color.Color{}
|
|
clr.Disable()
|
|
assert.Equal(t, wrapErr(throwCommandNotFound("cmd"), "_end", clr).Error(), `ERR! command cmd not found_end`)
|
|
|
|
assert.Equal(t, argvError{isEmpty: true}.Error(), "argv list is empty")
|
|
assert.Equal(t, argvError{isOutOfRange: true}.Error(), "argv list out of range")
|
|
assert.Equal(t, argvError{ith: 1, msg: "ERROR MSG"}.Error(), "1th argv: ERROR MSG")
|
|
}
|
|
|
|
type customT struct {
|
|
K1 string
|
|
K2 int
|
|
}
|
|
|
|
func (t *customT) Decode(s string) error {
|
|
return json.Unmarshal([]byte(s), t)
|
|
}
|
|
|
|
func TestDecoder(t *testing.T) {
|
|
type argT struct {
|
|
D customT `cli:"d"`
|
|
}
|
|
v := new(argT)
|
|
clr := color.Color{}
|
|
flagSet := parseArgv([]string{`-d`, `{"k1": "string", "k2": 2}`}, v, clr)
|
|
assert.Nil(t, flagSet.err)
|
|
assert.Equal(t, v.D, customT{K1: "string", K2: 2})
|
|
}
|
|
|
|
func TestMapMultiCharSep(t *testing.T) {
|
|
// White-box: split on a multi-character separator must not leak the
|
|
// separator tail into the value.
|
|
if k, v, _ := splitKeyVal("key=>val", "=>"); k != "key" || v != "val" {
|
|
t.Errorf(`splitKeyVal("key=>val", "=>") = (%q, %q), want ("key", "val")`, k, v)
|
|
}
|
|
|
|
// Public API: a map flag configured with a multi-char sep.
|
|
type T struct {
|
|
Map map[string]string `cli:"m" sep:"=>"`
|
|
}
|
|
clr := color.Color{}
|
|
v := new(T)
|
|
flagSet := parseArgv([]string{"-mkey=>val"}, v, clr)
|
|
if flagSet.err != nil {
|
|
t.Fatalf("unexpected error: %v", flagSet.err)
|
|
}
|
|
if got := v.Map["key"]; got != "val" {
|
|
t.Errorf(`map value = %q, want "val"`, got)
|
|
}
|
|
}
|