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", ""), ) 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"), ) }) }