| package cobra |
| |
| import ( |
| "bytes" |
| "regexp" |
| "strings" |
| "testing" |
| ) |
| |
| func TestGenZshCompletion(t *testing.T) { |
| var debug bool |
| var option string |
| |
| tcs := []struct { |
| name string |
| root *Command |
| expectedExpressions []string |
| invocationArgs []string |
| skip string |
| }{ |
| { |
| name: "simple command", |
| root: func() *Command { |
| r := &Command{ |
| Use: "mycommand", |
| Long: "My Command long description", |
| Run: emptyRun, |
| } |
| r.Flags().BoolVar(&debug, "debug", debug, "description") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `(?s)function _mycommand {\s+_arguments \\\s+'--debug\[description\]'.*--help.*}`, |
| "#compdef _mycommand mycommand", |
| }, |
| }, |
| { |
| name: "flags with both long and short flags", |
| root: func() *Command { |
| r := &Command{ |
| Use: "testcmd", |
| Long: "long description", |
| Run: emptyRun, |
| } |
| r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'\(-d --debug\)'{-d,--debug}'\[debug description\]'`, |
| }, |
| }, |
| { |
| name: "command with subcommands and flags with values", |
| root: func() *Command { |
| r := &Command{ |
| Use: "rootcmd", |
| Long: "Long rootcmd description", |
| } |
| d := &Command{ |
| Use: "subcmd1", |
| Short: "Subcmd1 short description", |
| Run: emptyRun, |
| } |
| e := &Command{ |
| Use: "subcmd2", |
| Long: "Subcmd2 short description", |
| Run: emptyRun, |
| } |
| r.PersistentFlags().BoolVar(&debug, "debug", debug, "description") |
| d.Flags().StringVarP(&option, "option", "o", option, "option description") |
| r.AddCommand(d, e) |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`, |
| `_arguments \\\n.*'--debug\[description]'`, |
| `_arguments -C \\\n.*'--debug\[description]'`, |
| `function _rootcmd_subcmd1 {`, |
| `function _rootcmd_subcmd1 {`, |
| `_arguments \\\n.*'\(-o --option\)'{-o,--option}'\[option description]:' \\\n`, |
| }, |
| }, |
| { |
| name: "filename completion with and without globs", |
| root: func() *Command { |
| var file string |
| r := &Command{ |
| Use: "mycmd", |
| Short: "my command short description", |
| Run: emptyRun, |
| } |
| r.Flags().StringVarP(&file, "config", "c", file, "config file") |
| r.MarkFlagFilename("config") |
| r.Flags().String("output", "", "output file") |
| r.MarkFlagFilename("output", "*.log", "*.txt") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `\n +'\(-c --config\)'{-c,--config}'\[config file]:filename:_files'`, |
| `:_files -g "\*.log" -g "\*.txt"`, |
| }, |
| }, |
| { |
| name: "repeated variables both with and without value", |
| root: func() *Command { |
| r := genTestCommand("mycmd", true) |
| _ = r.Flags().BoolSliceP("debug", "d", []bool{}, "debug usage") |
| _ = r.Flags().StringArray("option", []string{}, "options") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'\*--option\[options]`, |
| `'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`, |
| }, |
| }, |
| { |
| name: "generated flags --help and --version should be created even when not executing root cmd", |
| root: func() *Command { |
| r := &Command{ |
| Use: "mycmd", |
| Short: "mycmd short description", |
| Version: "myversion", |
| } |
| s := genTestCommand("sub1", true) |
| r.AddCommand(s) |
| return s |
| }(), |
| expectedExpressions: []string{ |
| "--version", |
| "--help", |
| }, |
| invocationArgs: []string{ |
| "sub1", |
| }, |
| skip: "--version and --help are currently not generated when not running on root command", |
| }, |
| { |
| name: "zsh generation should run on root command", |
| root: func() *Command { |
| r := genTestCommand("root", false) |
| s := genTestCommand("sub1", true) |
| r.AddCommand(s) |
| return s |
| }(), |
| expectedExpressions: []string{ |
| "function _root {", |
| }, |
| }, |
| { |
| name: "flag description with single quote (') shouldn't break quotes in completion file", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.Flags().Bool("private", false, "Don't show public info") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `--private\[Don'\\''t show public info]`, |
| }, |
| }, |
| { |
| name: "argument completion for file with and without patterns", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.MarkZshCompPositionalArgumentFile(1, "*.log") |
| r.MarkZshCompPositionalArgumentFile(2) |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'1: :_files -g "\*.log"' \\\n\s+'2: :_files`, |
| }, |
| }, |
| { |
| name: "argument zsh completion for words", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.MarkZshCompPositionalArgumentWords(1, "word1", "word2") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'1: :\("word1" "word2"\)`, |
| }, |
| }, |
| { |
| name: "argument completion for words with spaces", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.MarkZshCompPositionalArgumentWords(1, "single", "multiple words") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'1: :\("single" "multiple words"\)'`, |
| }, |
| }, |
| { |
| name: "argument completion when command has ValidArgs and no annotation for argument completion", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.ValidArgs = []string{"word1", "word2"} |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'1: :\("word1" "word2"\)'`, |
| }, |
| }, |
| { |
| name: "argument completion when command has ValidArgs and no annotation for argument at argPosition 1", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.ValidArgs = []string{"word1", "word2"} |
| r.MarkZshCompPositionalArgumentFile(2) |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `'1: :\("word1" "word2"\)' \\`, |
| }, |
| }, |
| { |
| name: "directory completion for flag", |
| root: func() *Command { |
| r := genTestCommand("root", true) |
| r.Flags().String("test", "", "test") |
| r.PersistentFlags().String("ptest", "", "ptest") |
| r.MarkFlagDirname("test") |
| r.MarkPersistentFlagDirname("ptest") |
| return r |
| }(), |
| expectedExpressions: []string{ |
| `--test\[test]:filename:_files -g "-\(/\)"`, |
| `--ptest\[ptest]:filename:_files -g "-\(/\)"`, |
| }, |
| }, |
| } |
| |
| for _, tc := range tcs { |
| t.Run(tc.name, func(t *testing.T) { |
| if tc.skip != "" { |
| t.Skip(tc.skip) |
| } |
| tc.root.Root().SetArgs(tc.invocationArgs) |
| tc.root.Execute() |
| buf := new(bytes.Buffer) |
| if err := tc.root.GenZshCompletion(buf); err != nil { |
| t.Error(err) |
| } |
| output := buf.Bytes() |
| |
| for _, expr := range tc.expectedExpressions { |
| rgx, err := regexp.Compile(expr) |
| if err != nil { |
| t.Errorf("error compiling expression (%s): %v", expr, err) |
| } |
| if !rgx.Match(output) { |
| t.Errorf("expected completion (%s) to match '%s'", buf.String(), expr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestGenZshCompletionHidden(t *testing.T) { |
| tcs := []struct { |
| name string |
| root *Command |
| expectedExpressions []string |
| }{ |
| { |
| name: "hidden commands", |
| root: func() *Command { |
| r := &Command{ |
| Use: "main", |
| Short: "main short description", |
| } |
| s1 := &Command{ |
| Use: "sub1", |
| Hidden: true, |
| Run: emptyRun, |
| } |
| s2 := &Command{ |
| Use: "sub2", |
| Short: "short sub2 description", |
| Run: emptyRun, |
| } |
| r.AddCommand(s1, s2) |
| |
| return r |
| }(), |
| expectedExpressions: []string{ |
| "sub1", |
| }, |
| }, |
| { |
| name: "hidden flags", |
| root: func() *Command { |
| var hidden string |
| r := &Command{ |
| Use: "root", |
| Short: "root short description", |
| Run: emptyRun, |
| } |
| r.Flags().StringVarP(&hidden, "hidden", "H", hidden, "hidden usage") |
| if err := r.Flags().MarkHidden("hidden"); err != nil { |
| t.Errorf("Error setting flag hidden: %v\n", err) |
| } |
| return r |
| }(), |
| expectedExpressions: []string{ |
| "--hidden", |
| }, |
| }, |
| } |
| |
| for _, tc := range tcs { |
| t.Run(tc.name, func(t *testing.T) { |
| tc.root.Execute() |
| buf := new(bytes.Buffer) |
| if err := tc.root.GenZshCompletion(buf); err != nil { |
| t.Error(err) |
| } |
| output := buf.String() |
| |
| for _, expr := range tc.expectedExpressions { |
| if strings.Contains(output, expr) { |
| t.Errorf("Expected completion (%s) not to contain '%s' but it does", output, expr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestMarkZshCompPositionalArgumentFile(t *testing.T) { |
| t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) { |
| c := &Command{} |
| if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil { |
| t.Errorf("Received error when we shouldn't have: %v\n", err) |
| } |
| if err := c.MarkZshCompPositionalArgumentFile(1); err == nil { |
| t.Error("Didn't receive an error when trying to overwrite argument position") |
| } |
| }) |
| |
| t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) { |
| c := &Command{} |
| err := c.MarkZshCompPositionalArgumentFile(0, "*") |
| if err == nil { |
| t.Fatal("Error was not thrown when indicating argument position 0") |
| } |
| if !strings.Contains(err.Error(), "position") { |
| t.Errorf("expected error message '%s' to contain 'position'", err.Error()) |
| } |
| }) |
| } |
| |
| func TestMarkZshCompPositionalArgumentWords(t *testing.T) { |
| t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) { |
| c := &Command{} |
| if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil { |
| t.Errorf("Received error when we shouldn't have: %v\n", err) |
| } |
| if err := c.MarkZshCompPositionalArgumentWords(1, "hello"); err == nil { |
| t.Error("Didn't receive an error when trying to overwrite argument position") |
| } |
| }) |
| |
| t.Run("Doesn't allow calling without words", func(t *testing.T) { |
| c := &Command{} |
| if err := c.MarkZshCompPositionalArgumentWords(0); err == nil { |
| t.Error("Should not allow saving empty word list for annotation") |
| } |
| }) |
| |
| t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) { |
| c := &Command{} |
| err := c.MarkZshCompPositionalArgumentWords(0, "word") |
| if err == nil { |
| t.Fatal("Should not allow setting argument position less then 1") |
| } |
| if !strings.Contains(err.Error(), "position") { |
| t.Errorf("Expected error '%s' to contain 'position' but didn't", err.Error()) |
| } |
| }) |
| } |
| |
| func BenchmarkMediumSizeConstruct(b *testing.B) { |
| root := constructLargeCommandHierarchy() |
| // if err := root.GenZshCompletionFile("_mycmd"); err != nil { |
| // b.Error(err) |
| // } |
| |
| for i := 0; i < b.N; i++ { |
| buf := new(bytes.Buffer) |
| err := root.GenZshCompletion(buf) |
| if err != nil { |
| b.Error(err) |
| } |
| } |
| } |
| |
| func TestExtractFlags(t *testing.T) { |
| var debug, cmdc, cmdd bool |
| c := &Command{ |
| Use: "cmdC", |
| Long: "Command C", |
| } |
| c.PersistentFlags().BoolVarP(&debug, "debug", "d", debug, "debug mode") |
| c.Flags().BoolVar(&cmdc, "cmd-c", cmdc, "Command C") |
| d := &Command{ |
| Use: "CmdD", |
| Long: "Command D", |
| } |
| d.Flags().BoolVar(&cmdd, "cmd-d", cmdd, "Command D") |
| c.AddCommand(d) |
| |
| resC := zshCompExtractFlag(c) |
| resD := zshCompExtractFlag(d) |
| |
| if len(resC) != 2 { |
| t.Errorf("expected Command C to return 2 flags, got %d", len(resC)) |
| } |
| if len(resD) != 2 { |
| t.Errorf("expected Command D to return 2 flags, got %d", len(resD)) |
| } |
| } |
| |
| func constructLargeCommandHierarchy() *Command { |
| var config, st1, st2 string |
| var long, debug bool |
| var in1, in2 int |
| var verbose []bool |
| |
| r := genTestCommand("mycmd", false) |
| r.PersistentFlags().StringVarP(&config, "config", "c", config, "config usage") |
| if err := r.MarkPersistentFlagFilename("config", "*"); err != nil { |
| panic(err) |
| } |
| s1 := genTestCommand("sub1", true) |
| s1.Flags().BoolVar(&long, "long", long, "long description") |
| s1.Flags().BoolSliceVar(&verbose, "verbose", verbose, "verbose description") |
| s1.Flags().StringArray("option", []string{}, "various options") |
| s2 := genTestCommand("sub2", true) |
| s2.PersistentFlags().BoolVar(&debug, "debug", debug, "debug description") |
| s3 := genTestCommand("sub3", true) |
| s3.Hidden = true |
| s1_1 := genTestCommand("sub1sub1", true) |
| s1_1.Flags().StringVar(&st1, "st1", st1, "st1 description") |
| s1_1.Flags().StringVar(&st2, "st2", st2, "st2 description") |
| s1_2 := genTestCommand("sub1sub2", true) |
| s1_3 := genTestCommand("sub1sub3", true) |
| s1_3.Flags().IntVar(&in1, "int1", in1, "int1 description") |
| s1_3.Flags().IntVar(&in2, "int2", in2, "int2 description") |
| s1_3.Flags().StringArrayP("option", "O", []string{}, "more options") |
| s2_1 := genTestCommand("sub2sub1", true) |
| s2_2 := genTestCommand("sub2sub2", true) |
| s2_3 := genTestCommand("sub2sub3", true) |
| s2_4 := genTestCommand("sub2sub4", true) |
| s2_5 := genTestCommand("sub2sub5", true) |
| |
| s1.AddCommand(s1_1, s1_2, s1_3) |
| s2.AddCommand(s2_1, s2_2, s2_3, s2_4, s2_5) |
| r.AddCommand(s1, s2, s3) |
| r.Execute() |
| return r |
| } |
| |
| func genTestCommand(name string, withRun bool) *Command { |
| r := &Command{ |
| Use: name, |
| Short: name + " short description", |
| Long: "Long description for " + name, |
| } |
| if withRun { |
| r.Run = emptyRun |
| } |
| |
| return r |
| } |