228 lines
7.2 KiB
Go
228 lines
7.2 KiB
Go
|
package cli
|
||
|
|
||
|
import (
|
||
|
"testing"
|
||
|
|
||
|
"time"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
func TestShortcut(t *testing.T) {
|
||
|
pc := &parseContext{}
|
||
|
args := []string{"a", "b"}
|
||
|
ok, nargs := shortcut.match(args, pc)
|
||
|
require.True(t, ok, "shortcut always matches")
|
||
|
require.Equal(t, args, nargs, "shortcut doesn't touch the passed args")
|
||
|
}
|
||
|
|
||
|
func TestOptsEnd(t *testing.T) {
|
||
|
pc := &parseContext{}
|
||
|
args := []string{"a", "b"}
|
||
|
ok, nargs := optsEnd.match(args, pc)
|
||
|
require.True(t, ok, "optsEnd always matches")
|
||
|
require.Equal(t, args, nargs, "optsEnd doesn't touch the passed args")
|
||
|
require.True(t, pc.rejectOptions, "optsEnd sets the rejectOptions flag")
|
||
|
}
|
||
|
|
||
|
func TestArgMatcher(t *testing.T) {
|
||
|
arg := &arg{name: "X"}
|
||
|
|
||
|
{
|
||
|
pc := newParseContext()
|
||
|
args := []string{"a", "b"}
|
||
|
ok, nargs := arg.match(args, &pc)
|
||
|
require.True(t, ok, "arg should match")
|
||
|
require.Equal(t, []string{"b"}, nargs, "arg should consume the matched value")
|
||
|
require.Equal(t, []string{"a"}, pc.args[arg], "arg should stored the matched value")
|
||
|
}
|
||
|
{
|
||
|
pc := newParseContext()
|
||
|
ok, _ := arg.match([]string{"-v"}, &pc)
|
||
|
require.False(t, ok, "arg should not match options")
|
||
|
}
|
||
|
{
|
||
|
pc := newParseContext()
|
||
|
pc.rejectOptions = true
|
||
|
ok, _ := arg.match([]string{"-v"}, &pc)
|
||
|
require.True(t, ok, "arg should match options when the reject flag is set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBoolOptMatcher(t *testing.T) {
|
||
|
forceOpt := &opt{names: []string{"-f", "--force"}, value: newBoolValue(new(bool), false)}
|
||
|
optMatcher := &optMatcher{
|
||
|
theOne: forceOpt,
|
||
|
optionsIdx: map[string]*opt{
|
||
|
"-f": forceOpt,
|
||
|
"--force": forceOpt,
|
||
|
"-g": {names: []string{"-g"}, value: newBoolValue(new(bool), false)},
|
||
|
"-x": {names: []string{"-x"}, value: newBoolValue(new(bool), false)},
|
||
|
"-y": {names: []string{"-y"}, value: newBoolValue(new(bool), false)},
|
||
|
},
|
||
|
}
|
||
|
cases := []struct {
|
||
|
args []string
|
||
|
nargs []string
|
||
|
val []string
|
||
|
}{
|
||
|
{[]string{"-f", "x"}, []string{"x"}, []string{"true"}},
|
||
|
{[]string{"-f=true", "x"}, []string{"x"}, []string{"true"}},
|
||
|
{[]string{"-f=false", "x"}, []string{"x"}, []string{"false"}},
|
||
|
{[]string{"--force", "x"}, []string{"x"}, []string{"true"}},
|
||
|
{[]string{"--force=true", "x"}, []string{"x"}, []string{"true"}},
|
||
|
{[]string{"--force=false", "x"}, []string{"x"}, []string{"false"}},
|
||
|
{[]string{"-fgxy", "x"}, []string{"-gxy", "x"}, []string{"true"}},
|
||
|
{[]string{"-gfxy", "x"}, []string{"-gxy", "x"}, []string{"true"}},
|
||
|
{[]string{"-gxfy", "x"}, []string{"-gxy", "x"}, []string{"true"}},
|
||
|
{[]string{"-gxyf", "x"}, []string{"-gxy", "x"}, []string{"true"}},
|
||
|
}
|
||
|
for _, cas := range cases {
|
||
|
t.Logf("Testing case: %#v", cas)
|
||
|
pc := newParseContext()
|
||
|
ok, nargs := optMatcher.match(cas.args, &pc)
|
||
|
require.True(t, ok, "opt should match")
|
||
|
require.Equal(t, cas.nargs, nargs, "opt should consume the option name")
|
||
|
require.Equal(t, cas.val, pc.opts[forceOpt], "true should stored as the option's value")
|
||
|
|
||
|
pc = newParseContext()
|
||
|
pc.rejectOptions = true
|
||
|
nok, _ := optMatcher.match(cas.args, &pc)
|
||
|
require.False(t, nok, "opt shouldn't match when rejectOptions flag is set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestOptMatcher(t *testing.T) {
|
||
|
names := []string{"-f", "--force"}
|
||
|
opts := []*opt{
|
||
|
{names: names, value: newStringValue(new(string), "")},
|
||
|
{names: names, value: newIntValue(new(int), 0)},
|
||
|
{names: names, value: newStringsValue(new([]string), nil)},
|
||
|
{names: names, value: newIntsValue(new([]int), nil)},
|
||
|
}
|
||
|
|
||
|
cases := []struct {
|
||
|
args []string
|
||
|
nargs []string
|
||
|
val []string
|
||
|
}{
|
||
|
{[]string{"-f", "x"}, []string{}, []string{"x"}},
|
||
|
{[]string{"-f=x", "y"}, []string{"y"}, []string{"x"}},
|
||
|
{[]string{"-fx", "y"}, []string{"y"}, []string{"x"}},
|
||
|
{[]string{"-afx", "y"}, []string{"-a", "y"}, []string{"x"}},
|
||
|
{[]string{"-af", "x", "y"}, []string{"-a", "y"}, []string{"x"}},
|
||
|
{[]string{"--force", "x"}, []string{}, []string{"x"}},
|
||
|
{[]string{"--force=x", "y"}, []string{"y"}, []string{"x"}},
|
||
|
}
|
||
|
|
||
|
for _, cas := range cases {
|
||
|
for _, forceOpt := range opts {
|
||
|
t.Logf("Testing case: %#v with opt: %#v", cas, forceOpt)
|
||
|
optMatcher := &optMatcher{
|
||
|
theOne: forceOpt,
|
||
|
optionsIdx: map[string]*opt{
|
||
|
"-f": forceOpt,
|
||
|
"--force": forceOpt,
|
||
|
"-a": {names: []string{"-a"}, value: newBoolValue(new(bool), false)},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
pc := newParseContext()
|
||
|
ok, nargs := optMatcher.match(cas.args, &pc)
|
||
|
require.True(t, ok, "opt %#v should match args %v, %v", forceOpt, cas.args, forceOpt.isBool())
|
||
|
require.Equal(t, cas.nargs, nargs, "opt should consume the option name")
|
||
|
require.Equal(t, cas.val, pc.opts[forceOpt], "true should stored as the option's value")
|
||
|
|
||
|
pc = newParseContext()
|
||
|
pc.rejectOptions = true
|
||
|
nok, _ := optMatcher.match(cas.args, &pc)
|
||
|
require.False(t, nok, "opt shouldn't match when rejectOptions flag is set")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestOptsMatcher(t *testing.T) {
|
||
|
opts := optsMatcher{
|
||
|
options: []*opt{
|
||
|
{names: []string{"-f", "--force"}, value: newBoolValue(new(bool), false)},
|
||
|
{names: []string{"-g", "--green"}, value: newStringValue(new(string), "")},
|
||
|
},
|
||
|
optionsIndex: map[string]*opt{},
|
||
|
}
|
||
|
|
||
|
for _, o := range opts.options {
|
||
|
for _, n := range o.names {
|
||
|
opts.optionsIndex[n] = o
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cases := []struct {
|
||
|
args []string
|
||
|
nargs []string
|
||
|
val [][]string
|
||
|
}{
|
||
|
{[]string{"-f", "x"}, []string{"x"}, [][]string{{"true"}, nil}},
|
||
|
{[]string{"-f=false", "y"}, []string{"y"}, [][]string{{"false"}, nil}},
|
||
|
{[]string{"--force", "x"}, []string{"x"}, [][]string{{"true"}, nil}},
|
||
|
{[]string{"--force=false", "y"}, []string{"y"}, [][]string{{"false"}, nil}},
|
||
|
|
||
|
{[]string{"-g", "x"}, []string{}, [][]string{nil, {"x"}}},
|
||
|
{[]string{"-g=x", "y"}, []string{"y"}, [][]string{nil, {"x"}}},
|
||
|
{[]string{"-gx", "y"}, []string{"y"}, [][]string{nil, {"x"}}},
|
||
|
{[]string{"--green", "x"}, []string{}, [][]string{nil, {"x"}}},
|
||
|
{[]string{"--green=x", "y"}, []string{"y"}, [][]string{nil, {"x"}}},
|
||
|
|
||
|
{[]string{"-f", "-g", "x", "y"}, []string{"y"}, [][]string{{"true"}, {"x"}}},
|
||
|
{[]string{"-g", "x", "-f", "y"}, []string{"y"}, [][]string{{"true"}, {"x"}}},
|
||
|
{[]string{"-fg", "x", "y"}, []string{"y"}, [][]string{{"true"}, {"x"}}},
|
||
|
{[]string{"-fgxxx", "y"}, []string{"y"}, [][]string{{"true"}, {"xxx"}}},
|
||
|
}
|
||
|
|
||
|
for _, cas := range cases {
|
||
|
t.Logf("testing with args %v", cas.args)
|
||
|
pc := newParseContext()
|
||
|
ok, nargs := opts.match(cas.args, &pc)
|
||
|
require.True(t, ok, "opts should match")
|
||
|
require.Equal(t, cas.nargs, nargs, "opts should consume the option name")
|
||
|
for i, opt := range opts.options {
|
||
|
require.Equal(t, cas.val[i], pc.opts[opt], "the option value for %v should be stored", opt)
|
||
|
}
|
||
|
|
||
|
pc = newParseContext()
|
||
|
pc.rejectOptions = true
|
||
|
nok, _ := opts.match(cas.args, &pc)
|
||
|
require.False(t, nok, "opts shouldn't match when rejectOptions flag is set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Issue 55
|
||
|
func TestOptsMatcherInfiniteLoop(t *testing.T) {
|
||
|
opts := optsMatcher{
|
||
|
options: []*opt{
|
||
|
{names: []string{"-g"}, value: newStringValue(new(string), ""), valueSetFromEnv: true},
|
||
|
},
|
||
|
optionsIndex: map[string]*opt{},
|
||
|
}
|
||
|
|
||
|
for _, o := range opts.options {
|
||
|
for _, n := range o.names {
|
||
|
opts.optionsIndex[n] = o
|
||
|
}
|
||
|
}
|
||
|
|
||
|
done := make(chan struct{}, 1)
|
||
|
pc := newParseContext()
|
||
|
go func() {
|
||
|
opts.match([]string{"-x"}, &pc)
|
||
|
done <- struct{}{}
|
||
|
}()
|
||
|
|
||
|
select {
|
||
|
case <-done:
|
||
|
// nop, everything is good
|
||
|
case <-time.After(5 * time.Second):
|
||
|
t.Fatalf("Timed out after 5 seconds. Infinite loop in optsMatcher.")
|
||
|
}
|
||
|
|
||
|
}
|