| package kingpin |
| |
| import ( |
| "io/ioutil" |
| |
| "github.com/alecthomas/assert" |
| |
| "sort" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| func newTestApp() *Application { |
| return New("test", "").Terminate(nil) |
| } |
| |
| func TestCommander(t *testing.T) { |
| c := newTestApp() |
| ping := c.Command("ping", "Ping an IP address.") |
| pingTTL := ping.Flag("ttl", "TTL for ICMP packets").Short('t').Default("5s").Duration() |
| |
| selected, err := c.Parse([]string{"ping"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "ping", selected) |
| assert.Equal(t, 5*time.Second, *pingTTL) |
| |
| selected, err = c.Parse([]string{"ping", "--ttl=10s"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "ping", selected) |
| assert.Equal(t, 10*time.Second, *pingTTL) |
| } |
| |
| func TestRequiredFlags(t *testing.T) { |
| c := newTestApp() |
| c.Flag("a", "a").String() |
| c.Flag("b", "b").Required().String() |
| |
| _, err := c.Parse([]string{"--a=foo"}) |
| assert.Error(t, err) |
| _, err = c.Parse([]string{"--b=foo"}) |
| assert.NoError(t, err) |
| } |
| |
| func TestRepeatableFlags(t *testing.T) { |
| c := newTestApp() |
| c.Flag("a", "a").String() |
| c.Flag("b", "b").Strings() |
| _, err := c.Parse([]string{"--a=foo", "--a=bar"}) |
| assert.Error(t, err) |
| _, err = c.Parse([]string{"--b=foo", "--b=bar"}) |
| assert.NoError(t, err) |
| } |
| |
| func TestInvalidDefaultFlagValueErrors(t *testing.T) { |
| c := newTestApp() |
| c.Flag("foo", "foo").Default("a").Int() |
| _, err := c.Parse([]string{}) |
| assert.Error(t, err) |
| } |
| |
| func TestInvalidDefaultArgValueErrors(t *testing.T) { |
| c := newTestApp() |
| cmd := c.Command("cmd", "cmd") |
| cmd.Arg("arg", "arg").Default("one").Int() |
| _, err := c.Parse([]string{"cmd"}) |
| assert.Error(t, err) |
| } |
| |
| func TestArgsRequiredAfterNonRequiredErrors(t *testing.T) { |
| c := newTestApp() |
| cmd := c.Command("cmd", "") |
| cmd.Arg("a", "a").String() |
| cmd.Arg("b", "b").Required().String() |
| _, err := c.Parse([]string{"cmd"}) |
| assert.Error(t, err) |
| } |
| |
| func TestArgsMultipleRequiredThenNonRequired(t *testing.T) { |
| c := newTestApp().Writer(ioutil.Discard) |
| cmd := c.Command("cmd", "") |
| cmd.Arg("a", "a").Required().String() |
| cmd.Arg("b", "b").Required().String() |
| cmd.Arg("c", "c").String() |
| cmd.Arg("d", "d").String() |
| _, err := c.Parse([]string{"cmd", "a", "b"}) |
| assert.NoError(t, err) |
| _, err = c.Parse([]string{}) |
| assert.Error(t, err) |
| } |
| |
| func TestDispatchCallbackIsCalled(t *testing.T) { |
| dispatched := false |
| c := newTestApp() |
| c.Command("cmd", "").Action(func(*ParseContext) error { |
| dispatched = true |
| return nil |
| }) |
| |
| _, err := c.Parse([]string{"cmd"}) |
| assert.NoError(t, err) |
| assert.True(t, dispatched) |
| } |
| |
| func TestTopLevelArgWorks(t *testing.T) { |
| c := newTestApp() |
| s := c.Arg("arg", "help").String() |
| _, err := c.Parse([]string{"foo"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "foo", *s) |
| } |
| |
| func TestTopLevelArgCantBeUsedWithCommands(t *testing.T) { |
| c := newTestApp() |
| c.Arg("arg", "help").String() |
| c.Command("cmd", "help") |
| _, err := c.Parse([]string{}) |
| assert.Error(t, err) |
| } |
| |
| func TestTooManyArgs(t *testing.T) { |
| a := newTestApp() |
| a.Arg("a", "").String() |
| _, err := a.Parse([]string{"a", "b"}) |
| assert.Error(t, err) |
| } |
| |
| func TestTooManyArgsAfterCommand(t *testing.T) { |
| a := newTestApp() |
| a.Command("a", "") |
| assert.NoError(t, a.init()) |
| _, err := a.Parse([]string{"a", "b"}) |
| assert.Error(t, err) |
| } |
| |
| func TestArgsLooksLikeFlagsWithConsumeRemainder(t *testing.T) { |
| a := newTestApp() |
| a.Arg("opts", "").Required().Strings() |
| _, err := a.Parse([]string{"hello", "-world"}) |
| assert.Error(t, err) |
| } |
| |
| func TestCommandParseDoesNotResetFlagsToDefault(t *testing.T) { |
| app := newTestApp() |
| flag := app.Flag("flag", "").Default("default").String() |
| app.Command("cmd", "") |
| |
| _, err := app.Parse([]string{"--flag=123", "cmd"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "123", *flag) |
| } |
| |
| func TestCommandParseDoesNotFailRequired(t *testing.T) { |
| app := newTestApp() |
| flag := app.Flag("flag", "").Required().String() |
| app.Command("cmd", "") |
| |
| _, err := app.Parse([]string{"cmd", "--flag=123"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "123", *flag) |
| } |
| |
| func TestSelectedCommand(t *testing.T) { |
| app := newTestApp() |
| c0 := app.Command("c0", "") |
| c0.Command("c1", "") |
| s, err := app.Parse([]string{"c0", "c1"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "c0 c1", s) |
| } |
| |
| func TestSubCommandRequired(t *testing.T) { |
| app := newTestApp() |
| c0 := app.Command("c0", "") |
| c0.Command("c1", "") |
| _, err := app.Parse([]string{"c0"}) |
| assert.Error(t, err) |
| } |
| |
| func TestInterspersedFalse(t *testing.T) { |
| app := newTestApp().Interspersed(false) |
| a1 := app.Arg("a1", "").String() |
| a2 := app.Arg("a2", "").String() |
| f1 := app.Flag("flag", "").String() |
| |
| _, err := app.Parse([]string{"a1", "--flag=flag"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "a1", *a1) |
| assert.Equal(t, "--flag=flag", *a2) |
| assert.Equal(t, "", *f1) |
| } |
| |
| func TestInterspersedTrue(t *testing.T) { |
| // test once with the default value and once with explicit true |
| for i := 0; i < 2; i++ { |
| app := newTestApp() |
| if i != 0 { |
| t.Log("Setting explicit") |
| app.Interspersed(true) |
| } else { |
| t.Log("Using default") |
| } |
| a1 := app.Arg("a1", "").String() |
| a2 := app.Arg("a2", "").String() |
| f1 := app.Flag("flag", "").String() |
| |
| _, err := app.Parse([]string{"a1", "--flag=flag"}) |
| assert.NoError(t, err) |
| assert.Equal(t, "a1", *a1) |
| assert.Equal(t, "", *a2) |
| assert.Equal(t, "flag", *f1) |
| } |
| } |
| |
| func TestDefaultEnvars(t *testing.T) { |
| a := New("some-app", "").Terminate(nil).DefaultEnvars() |
| f0 := a.Flag("some-flag", "") |
| f0.Bool() |
| f1 := a.Flag("some-other-flag", "").NoEnvar() |
| f1.Bool() |
| f2 := a.Flag("a-1-flag", "") |
| f2.Bool() |
| _, err := a.Parse([]string{}) |
| assert.NoError(t, err) |
| assert.Equal(t, "SOME_APP_SOME_FLAG", f0.envar) |
| assert.Equal(t, "", f1.envar) |
| assert.Equal(t, "SOME_APP_A_1_FLAG", f2.envar) |
| } |
| |
| func TestBashCompletionOptionsWithEmptyApp(t *testing.T) { |
| a := newTestApp() |
| context, err := a.ParseContext([]string{"--completion-bash"}) |
| if err != nil { |
| t.Errorf("Unexpected error whilst parsing context: [%v]", err) |
| } |
| args := a.completionOptions(context) |
| assert.Equal(t, []string(nil), args) |
| } |
| |
| func TestBashCompletionOptions(t *testing.T) { |
| a := newTestApp() |
| a.Command("one", "") |
| a.Flag("flag-0", "").String() |
| a.Flag("flag-1", "").HintOptions("opt1", "opt2", "opt3").String() |
| |
| two := a.Command("two", "") |
| two.Flag("flag-2", "").String() |
| two.Flag("flag-3", "").HintOptions("opt4", "opt5", "opt6").String() |
| |
| three := a.Command("three", "") |
| three.Flag("flag-4", "").String() |
| three.Arg("arg-1", "").String() |
| three.Arg("arg-2", "").HintOptions("arg-2-opt-1", "arg-2-opt-2").String() |
| three.Arg("arg-3", "").String() |
| three.Arg("arg-4", "").HintAction(func() []string { |
| return []string{"arg-4-opt-1", "arg-4-opt-2"} |
| }).String() |
| |
| cases := []struct { |
| Args string |
| ExpectedOptions []string |
| }{ |
| { |
| Args: "--completion-bash", |
| ExpectedOptions: []string{"help", "one", "three", "two"}, |
| }, |
| { |
| Args: "--completion-bash --", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--help"}, |
| }, |
| { |
| Args: "--completion-bash --fla", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--help"}, |
| }, |
| { |
| // No options available for flag-0, return to cmd completion |
| Args: "--completion-bash --flag-0", |
| ExpectedOptions: []string{"help", "one", "three", "two"}, |
| }, |
| { |
| Args: "--completion-bash --flag-0 --", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--help"}, |
| }, |
| { |
| Args: "--completion-bash --flag-1", |
| ExpectedOptions: []string{"opt1", "opt2", "opt3"}, |
| }, |
| { |
| Args: "--completion-bash --flag-1 opt", |
| ExpectedOptions: []string{"opt1", "opt2", "opt3"}, |
| }, |
| { |
| Args: "--completion-bash --flag-1 opt1", |
| ExpectedOptions: []string{"help", "one", "three", "two"}, |
| }, |
| { |
| Args: "--completion-bash --flag-1 opt1 --", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--help"}, |
| }, |
| |
| // Try Subcommand |
| { |
| Args: "--completion-bash two", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| Args: "--completion-bash two --", |
| ExpectedOptions: []string{"--help", "--flag-2", "--flag-3", "--flag-0", "--flag-1"}, |
| }, |
| { |
| Args: "--completion-bash two --flag", |
| ExpectedOptions: []string{"--help", "--flag-2", "--flag-3", "--flag-0", "--flag-1"}, |
| }, |
| { |
| Args: "--completion-bash two --flag-2", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| // Top level flags carry downwards |
| Args: "--completion-bash two --flag-1", |
| ExpectedOptions: []string{"opt1", "opt2", "opt3"}, |
| }, |
| { |
| // Top level flags carry downwards |
| Args: "--completion-bash two --flag-1 opt", |
| ExpectedOptions: []string{"opt1", "opt2", "opt3"}, |
| }, |
| { |
| // Top level flags carry downwards |
| Args: "--completion-bash two --flag-1 opt1", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| Args: "--completion-bash two --flag-3", |
| ExpectedOptions: []string{"opt4", "opt5", "opt6"}, |
| }, |
| { |
| Args: "--completion-bash two --flag-3 opt", |
| ExpectedOptions: []string{"opt4", "opt5", "opt6"}, |
| }, |
| { |
| Args: "--completion-bash two --flag-3 opt4", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| Args: "--completion-bash two --flag-3 opt4 --", |
| ExpectedOptions: []string{"--help", "--flag-2", "--flag-3", "--flag-0", "--flag-1"}, |
| }, |
| |
| // Args complete |
| { |
| // After a command with an arg with no options, nothing should be |
| // shown |
| Args: "--completion-bash three ", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| // After a command with an arg, explicitly starting a flag should |
| // complete flags |
| Args: "--completion-bash three --", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--flag-4", "--help"}, |
| }, |
| { |
| // After a command with an arg that does have completions, they |
| // should be shown |
| Args: "--completion-bash three arg1 ", |
| ExpectedOptions: []string{"arg-2-opt-1", "arg-2-opt-2"}, |
| }, |
| { |
| // After a command with an arg that does have completions, but a |
| // flag is started, flag options should be completed |
| Args: "--completion-bash three arg1 --", |
| ExpectedOptions: []string{"--flag-0", "--flag-1", "--flag-4", "--help"}, |
| }, |
| { |
| // After a command with an arg that has no completions, and isn't first, |
| // nothing should be shown |
| Args: "--completion-bash three arg1 arg2 ", |
| ExpectedOptions: []string(nil), |
| }, |
| { |
| // After a command with a different arg that also has completions, |
| // those different options should be shown |
| Args: "--completion-bash three arg1 arg2 arg3 ", |
| ExpectedOptions: []string{"arg-4-opt-1", "arg-4-opt-2"}, |
| }, |
| { |
| // After a command with all args listed, nothing should complete |
| Args: "--completion-bash three arg1 arg2 arg3 arg4", |
| ExpectedOptions: []string(nil), |
| }, |
| } |
| |
| for _, c := range cases { |
| context, _ := a.ParseContext(strings.Split(c.Args, " ")) |
| args := a.completionOptions(context) |
| |
| sort.Strings(args) |
| sort.Strings(c.ExpectedOptions) |
| |
| assert.Equal(t, c.ExpectedOptions, args, "Expected != Actual: [%v] != [%v]. \nInput was: [%v]", c.ExpectedOptions, args, c.Args) |
| } |
| |
| } |