375 lines
9.5 KiB
Go
375 lines
9.5 KiB
Go
package wand
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
)
|
|
|
|
func TestCommand(t *testing.T) {
|
|
t.Run("fields", func(t *testing.T) {
|
|
type s0 struct {
|
|
FooBar int
|
|
Foo struct{ Bar bool }
|
|
}
|
|
|
|
type s1 struct {
|
|
Config string
|
|
}
|
|
|
|
f0 := func(a s0) int { return a.FooBar }
|
|
t.Run(
|
|
"incompatible field types",
|
|
testExec(testCase{impl: f0, command: "foo"}, "duplicate fields with different types", ""),
|
|
)
|
|
|
|
f1 := func(a s1) string { return a.Config }
|
|
t.Run(
|
|
"config option shadowed",
|
|
testExec(
|
|
testCase{impl: f1, mergeConfTyped: []Config{ConfigFromOption()}, command: "foo"},
|
|
"option reserved for config",
|
|
"",
|
|
),
|
|
)
|
|
})
|
|
|
|
t.Run("positional", func(t *testing.T) {
|
|
f := func(a, b, c int) int { return a + b + c }
|
|
t.Run("ok", testExec(testCase{impl: Args(Command("foo", f), 3, 3), command: "foo 1 2 3"}, "", "6"))
|
|
t.Run(
|
|
"min less than fixed",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", f), 2, 3), command: "foo 1 2 3"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"min more than fixed",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", f), 4, 4), command: "foo 1 2 3"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"max less than fixed",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", f), 0, 2), command: "foo 1 2 3"},
|
|
"maximum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
fv := func(a, b int, c ...int) int { return a + b + len(c) }
|
|
t.Run("ok variadic", testExec(testCase{impl: Args(Command("foo", fv), 3, 5), command: "foo 1 2 3 4 5"}, "", "6"))
|
|
|
|
t.Run(
|
|
"min less than fixed variadic",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fv), 1, 5), command: "foo 1 2 3"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"max less than fixed variadic",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fv), 0, 1), command: "foo 1 2 3"},
|
|
"maximum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"min more than max variadic",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fv), 4, 3), command: "foo 1 2 3 4 5"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
fvio := func(a int, in io.Reader, out io.Writer, b int, c ...int) int { return a + b + len(c) }
|
|
t.Run("ok io", testExec(testCase{impl: Args(Command("foo", fvio), 3, 5), command: "foo 1 2 3 4 5"}, "", "6"))
|
|
|
|
t.Run(
|
|
"min less than fixed io",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fvio), 1, 5), command: "foo 1 2 3"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"max less than fixed io",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fvio), 0, 1), command: "foo 1 2 3"},
|
|
"maximum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"min more than max io",
|
|
testExec(
|
|
testCase{impl: Args(Command("foo", fvio), 4, 3), command: "foo 1 2 3 4 5"},
|
|
"minimum positional",
|
|
"",
|
|
),
|
|
)
|
|
|
|
fio := func(a, b io.Reader) {}
|
|
t.Run(
|
|
"max one io of a kind",
|
|
testExec(testCase{impl: fio, command: "foo"}, "only zero or one reader", ""),
|
|
)
|
|
})
|
|
|
|
t.Run("impl", func(t *testing.T) {
|
|
t.Run(
|
|
"must be func",
|
|
testExec(
|
|
testCase{impl: []func(){func() {}}, command: "foo"},
|
|
"implementation must be a function",
|
|
"",
|
|
),
|
|
)
|
|
|
|
t.Run(
|
|
"paramters must be bindable",
|
|
testExec(testCase{impl: func(chan int) {}, command: "foo"}, "unsupported parameter type", ""),
|
|
)
|
|
})
|
|
|
|
t.Run("command tree", func(t *testing.T) {
|
|
f := func(a, b int) int { return a + b }
|
|
cmd := Command("foo", f)
|
|
cmd = Version(cmd, "v42")
|
|
t.Run("version pass", testExec(testCase{impl: cmd, command: "foo 1 2"}, "", "3"))
|
|
cmd = Command("foo", nil)
|
|
t.Run(
|
|
"no impl and not group",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "command does not have an implementation", ""),
|
|
)
|
|
|
|
cmd = Group("foo")
|
|
t.Run(
|
|
"no impl and no subcommands",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "empty command category", ""),
|
|
)
|
|
|
|
cmd = Group("foo", Command("bar-baz", func() {}), Command("qux quux", func() {}))
|
|
t.Run(
|
|
"invalid subcommand name",
|
|
testExec(testCase{impl: cmd, command: "foo bar-baz"}, "command name is not a valid", ""),
|
|
)
|
|
|
|
cmd = Group("foo", Command("bar", func() {}), Command("bar", func() {}))
|
|
t.Run(
|
|
"duplicate subcommand name",
|
|
testExec(testCase{impl: cmd, command: "foo bar"}, "subcommand name conflict", ""),
|
|
)
|
|
|
|
cmd = Command("foo", func() {}, Default(Command("bar", func() {})), Command("bar", func() {}))
|
|
t.Run(
|
|
"implementation conflict",
|
|
testExec(
|
|
testCase{impl: cmd, command: "foo bar"},
|
|
"default subcommand defined for a command that has an explicit implementation",
|
|
"",
|
|
),
|
|
)
|
|
|
|
cmd = Group("foo", Default(Command("bar", func() {})), Default(Command("baz", func() {})))
|
|
t.Run(
|
|
"multiple defaults",
|
|
testExec(
|
|
testCase{impl: cmd, command: "foo bar"},
|
|
"multiple default subcommands",
|
|
"",
|
|
),
|
|
)
|
|
|
|
cmd = Command("foo", func() {}, Command("bar", func() {}), Command("baz", func(chan int) {}))
|
|
t.Run(
|
|
"invalid subcommand",
|
|
testExec(
|
|
testCase{impl: cmd, command: "foo bar"},
|
|
"foo:",
|
|
"",
|
|
),
|
|
)
|
|
})
|
|
|
|
t.Run("short forms", func(t *testing.T) {
|
|
type (
|
|
s0 struct {
|
|
Foo int
|
|
Qux int
|
|
}
|
|
s1 struct{ Bar int }
|
|
s2 struct{ Baz int }
|
|
)
|
|
|
|
f0 := func(a s0) int { return a.Foo }
|
|
f1 := func(a s1) int { return a.Bar }
|
|
f2 := func(a s2) int { return a.Baz }
|
|
cmd := ShortForm(Command("foo", f0, ShortForm(Command("bar", f1), "a", "bar")), "a", "foo")
|
|
t.Run(
|
|
"same short form for different options",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0), "a", "foo", "a", "qux")
|
|
t.Run(
|
|
"same short form for different options on the same command",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0, Command("bar", f1)), "a", "foo", "a", "qux")
|
|
t.Run(
|
|
"same short form for different options on the same command with subcommand",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
|
|
)
|
|
|
|
f3 := func(o struct{ Foo map[string]string }) string { return o.Foo["bar"] }
|
|
cmd = Command("foo", f3)
|
|
t.Run(
|
|
"free form",
|
|
testExec(testCase{impl: cmd, command: "foo --foo-bar baz"}, "option not supported"),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f3), "b", "foo-bar")
|
|
t.Run(
|
|
"short form for free form",
|
|
testExec(testCase{impl: cmd, command: "foo -b baz"}, "unmapped short form"),
|
|
)
|
|
|
|
f4 := func(o map[string]string) string { return o["foo"] }
|
|
cmd = ShortForm(Command("foo", f4), "f", "foo")
|
|
t.Run(
|
|
"short form for free form root",
|
|
testExec(testCase{impl: cmd, command: "foo -f baz"}, "unmapped short form"),
|
|
)
|
|
|
|
f5 := func(o struct{ Foo struct{ Bar string } }) string { return o.Foo.Bar }
|
|
cmd = ShortForm(Command("foo", f5), "b", "foo-bar")
|
|
t.Run(
|
|
"short form for child field",
|
|
testExec(testCase{impl: cmd, command: "foo -b baz"}, "", "baz"),
|
|
)
|
|
|
|
cmd = Command(
|
|
"foo",
|
|
f0,
|
|
ShortForm(Command("bar", f1), "a", "bar"),
|
|
ShortForm(Command("baz", f2), "a", "baz"),
|
|
)
|
|
|
|
t.Run(
|
|
"same short form for different options on different branches in the tree",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0, ShortForm(Command("bar", f1), "b", "baz")), "a", "foo")
|
|
t.Run(
|
|
"unmapped short form",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "unmapped short forms", ""),
|
|
)
|
|
|
|
cmd = ShortForm(
|
|
Command(
|
|
"foo",
|
|
f0,
|
|
ShortForm(Command("bar", f1), "b", "baz"),
|
|
ShortForm(Command("qux", f1), "b", "qux"),
|
|
),
|
|
"a",
|
|
"foo",
|
|
)
|
|
|
|
t.Run(
|
|
"conflicting unmapped",
|
|
testExec(
|
|
testCase{impl: cmd, command: "foo"},
|
|
"using the same short form for different options",
|
|
"",
|
|
),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0), "ab", "foo")
|
|
t.Run(
|
|
"short form not a single character",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "invalid short form", ""),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0), "6", "foo")
|
|
t.Run(
|
|
"short form not a single character",
|
|
testExec(testCase{impl: cmd, command: "foo"}, "invalid short form", ""),
|
|
)
|
|
|
|
cmd = ShortForm(Group("foo", Command("bar", f1)), "b", "bar")
|
|
t.Run(
|
|
"short form with command category",
|
|
testExec(testCase{impl: cmd, command: "foo bar"}, "", "0"),
|
|
)
|
|
})
|
|
|
|
t.Run("bool options", func(t *testing.T) {
|
|
type s0 struct {
|
|
Foo int
|
|
Bar bool
|
|
Baz bool
|
|
}
|
|
type s1 struct {
|
|
Foo int
|
|
Bar bool
|
|
Baz bool
|
|
Help bool
|
|
}
|
|
f0 := func(a s0, b, c string) string {
|
|
return fmt.Sprint(a.Foo) + fmt.Sprint(a.Bar) + fmt.Sprint(a.Baz) + b + c
|
|
}
|
|
|
|
f1 := func(a s1, b, c string) string {
|
|
return fmt.Sprint(a.Foo) + fmt.Sprint(a.Bar) + fmt.Sprint(a.Baz) + fmt.Sprint(a.Help) + b + c
|
|
}
|
|
|
|
cmd := ShortForm(Command("foo", f0), "b", "bar")
|
|
t.Run(
|
|
"no help in fields and no help in short forms",
|
|
testExec(testCase{impl: cmd, command: "foo --foo 42 -b qux --baz quux"}, "", "42truetruequxquux"),
|
|
)
|
|
|
|
t.Run(
|
|
"no help in fields and no help in short forms help",
|
|
testExec(testCase{impl: cmd, command: "foo --help", contains: true}, "", "Synopsis"),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f0), "h", "help")
|
|
t.Run(
|
|
"help from short forms",
|
|
testExec(testCase{impl: cmd, command: "foo -h", contains: true}, "", "Synopsis"),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f1), "b", "bar")
|
|
t.Run(
|
|
"help taken by a field",
|
|
testExec(testCase{impl: cmd, command: "foo --help qux quux"}, "", "0falsefalsetruequxquux"),
|
|
)
|
|
|
|
cmd = ShortForm(Command("foo", f1), "h", "bar")
|
|
t.Run(
|
|
"help taken by a field and help short form used for another field",
|
|
testExec(testCase{impl: cmd, command: "foo --help qux -h quux"}, "", "0truefalsetruequxquux"),
|
|
)
|
|
})
|
|
}
|