...

Source file src/github.com/urfave/cli/v2/command_test.go

Documentation: github.com/urfave/cli/v2

     1  package cli
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  func TestCommandFlagParsing(t *testing.T) {
    15  	cases := []struct {
    16  		testArgs               []string
    17  		skipFlagParsing        bool
    18  		useShortOptionHandling bool
    19  		expectedErr            error
    20  	}{
    21  		// Test normal "not ignoring flags" flow
    22  		{testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: errors.New("flag provided but not defined: -break")},
    23  		{testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil},   // Test SkipFlagParsing without any args that look like flags
    24  		{testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with random flag arg
    25  		{testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil},  // Test SkipFlagParsing with "special" help flag arg
    26  		{testArgs: []string{"test-cmd", "blah", "-h"}, skipFlagParsing: false, useShortOptionHandling: true, expectedErr: nil},     // Test UseShortOptionHandling
    27  	}
    28  
    29  	for _, c := range cases {
    30  		app := &App{Writer: io.Discard}
    31  		set := flag.NewFlagSet("test", 0)
    32  		_ = set.Parse(c.testArgs)
    33  
    34  		cCtx := NewContext(app, set, nil)
    35  
    36  		command := Command{
    37  			Name:            "test-cmd",
    38  			Aliases:         []string{"tc"},
    39  			Usage:           "this is for testing",
    40  			Description:     "testing",
    41  			Action:          func(_ *Context) error { return nil },
    42  			SkipFlagParsing: c.skipFlagParsing,
    43  			isRoot:          true,
    44  		}
    45  
    46  		err := command.Run(cCtx, c.testArgs...)
    47  
    48  		expect(t, err, c.expectedErr)
    49  		//expect(t, cCtx.Args().Slice(), c.testArgs)
    50  	}
    51  }
    52  
    53  func TestParseAndRunShortOpts(t *testing.T) {
    54  	cases := []struct {
    55  		testArgs     args
    56  		expectedErr  error
    57  		expectedArgs Args
    58  	}{
    59  		{testArgs: args{"foo", "test", "-a"}, expectedErr: nil, expectedArgs: &args{}},
    60  		{testArgs: args{"foo", "test", "-c", "arg1", "arg2"}, expectedErr: nil, expectedArgs: &args{"arg1", "arg2"}},
    61  		{testArgs: args{"foo", "test", "-f"}, expectedErr: nil, expectedArgs: &args{}},
    62  		{testArgs: args{"foo", "test", "-ac", "--fgh"}, expectedErr: nil, expectedArgs: &args{}},
    63  		{testArgs: args{"foo", "test", "-af"}, expectedErr: nil, expectedArgs: &args{}},
    64  		{testArgs: args{"foo", "test", "-cf"}, expectedErr: nil, expectedArgs: &args{}},
    65  		{testArgs: args{"foo", "test", "-acf"}, expectedErr: nil, expectedArgs: &args{}},
    66  		{testArgs: args{"foo", "test", "--acf"}, expectedErr: errors.New("flag provided but not defined: -acf"), expectedArgs: nil},
    67  		{testArgs: args{"foo", "test", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil},
    68  		{testArgs: args{"foo", "test", "-acf", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil},
    69  		{testArgs: args{"foo", "test", "--invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil},
    70  		{testArgs: args{"foo", "test", "-acf", "--invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil},
    71  		{testArgs: args{"foo", "test", "-acf", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}},
    72  		{testArgs: args{"foo", "test", "-acf", "arg1", "--invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "--invalid"}},
    73  		{testArgs: args{"foo", "test", "-acfi", "not-arg", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}},
    74  		{testArgs: args{"foo", "test", "-i", "ivalue"}, expectedErr: nil, expectedArgs: &args{}},
    75  		{testArgs: args{"foo", "test", "-i", "ivalue", "arg1"}, expectedErr: nil, expectedArgs: &args{"arg1"}},
    76  		{testArgs: args{"foo", "test", "-i"}, expectedErr: errors.New("flag needs an argument: -i"), expectedArgs: nil},
    77  	}
    78  
    79  	for _, c := range cases {
    80  		var args Args
    81  		cmd := &Command{
    82  			Name:        "test",
    83  			Usage:       "this is for testing",
    84  			Description: "testing",
    85  			Action: func(c *Context) error {
    86  				args = c.Args()
    87  				return nil
    88  			},
    89  			UseShortOptionHandling: true,
    90  			Flags: []Flag{
    91  				&BoolFlag{Name: "abc", Aliases: []string{"a"}},
    92  				&BoolFlag{Name: "cde", Aliases: []string{"c"}},
    93  				&BoolFlag{Name: "fgh", Aliases: []string{"f"}},
    94  				&StringFlag{Name: "ijk", Aliases: []string{"i"}},
    95  			},
    96  		}
    97  
    98  		app := newTestApp()
    99  		app.Commands = []*Command{cmd}
   100  
   101  		err := app.Run(c.testArgs)
   102  
   103  		expect(t, err, c.expectedErr)
   104  		expect(t, args, c.expectedArgs)
   105  	}
   106  }
   107  
   108  func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
   109  	app := &App{
   110  		Commands: []*Command{
   111  			{
   112  				Name: "bar",
   113  				Before: func(c *Context) error {
   114  					return fmt.Errorf("before error")
   115  				},
   116  				After: func(c *Context) error {
   117  					return fmt.Errorf("after error")
   118  				},
   119  			},
   120  		},
   121  		Writer: io.Discard,
   122  	}
   123  
   124  	err := app.Run([]string{"foo", "bar"})
   125  	if err == nil {
   126  		t.Fatalf("expected to receive error from Run, got none")
   127  	}
   128  
   129  	if !strings.Contains(err.Error(), "before error") {
   130  		t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
   131  	}
   132  	if !strings.Contains(err.Error(), "after error") {
   133  		t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
   134  	}
   135  }
   136  
   137  func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
   138  	var receivedMsgFromAction string
   139  	var receivedMsgFromAfter string
   140  
   141  	app := &App{
   142  		Commands: []*Command{
   143  			{
   144  				Name: "bar",
   145  				Before: func(c *Context) error {
   146  					c.App.Metadata["msg"] = "hello world"
   147  					return nil
   148  				},
   149  				Action: func(c *Context) error {
   150  					msg, ok := c.App.Metadata["msg"]
   151  					if !ok {
   152  						return errors.New("msg not found")
   153  					}
   154  					receivedMsgFromAction = msg.(string)
   155  					return nil
   156  				},
   157  				After: func(c *Context) error {
   158  					msg, ok := c.App.Metadata["msg"]
   159  					if !ok {
   160  						return errors.New("msg not found")
   161  					}
   162  					receivedMsgFromAfter = msg.(string)
   163  					return nil
   164  				},
   165  			},
   166  		},
   167  	}
   168  
   169  	err := app.Run([]string{"foo", "bar"})
   170  	if err != nil {
   171  		t.Fatalf("expected no error from Run, got %s", err)
   172  	}
   173  
   174  	expectedMsg := "hello world"
   175  
   176  	if receivedMsgFromAction != expectedMsg {
   177  		t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q",
   178  			receivedMsgFromAction, expectedMsg)
   179  	}
   180  	if receivedMsgFromAfter != expectedMsg {
   181  		t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q",
   182  			receivedMsgFromAction, expectedMsg)
   183  	}
   184  }
   185  
   186  func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
   187  	app := &App{
   188  		Commands: []*Command{
   189  			{
   190  				Name: "bar",
   191  				Flags: []Flag{
   192  					&IntFlag{Name: "flag"},
   193  				},
   194  				OnUsageError: func(c *Context, err error, _ bool) error {
   195  					return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
   196  				},
   197  			},
   198  		},
   199  	}
   200  
   201  	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
   202  	if err == nil {
   203  		t.Fatalf("expected to receive error from Run, got none")
   204  	}
   205  
   206  	if !strings.HasPrefix(err.Error(), "intercepted in bar") {
   207  		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
   208  	}
   209  }
   210  
   211  func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
   212  	app := &App{
   213  		Commands: []*Command{
   214  			{
   215  				Name: "bar",
   216  				Flags: []Flag{
   217  					&IntFlag{Name: "flag"},
   218  				},
   219  				OnUsageError: func(c *Context, err error, _ bool) error {
   220  					if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
   221  						t.Errorf("Expect an invalid value error, but got \"%v\"", err)
   222  					}
   223  					return errors.New("intercepted: " + err.Error())
   224  				},
   225  			},
   226  		},
   227  	}
   228  
   229  	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
   230  	if err == nil {
   231  		t.Fatalf("expected to receive error from Run, got none")
   232  	}
   233  
   234  	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
   235  		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
   236  	}
   237  }
   238  
   239  func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
   240  	app := &App{
   241  		Commands: []*Command{
   242  			{
   243  				Name: "bar",
   244  				Subcommands: []*Command{
   245  					{
   246  						Name: "baz",
   247  					},
   248  				},
   249  				Flags: []Flag{
   250  					&IntFlag{Name: "flag"},
   251  				},
   252  				OnUsageError: func(c *Context, err error, _ bool) error {
   253  					if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
   254  						t.Errorf("Expect an invalid value error, but got \"%v\"", err)
   255  					}
   256  					return errors.New("intercepted: " + err.Error())
   257  				},
   258  			},
   259  		},
   260  	}
   261  
   262  	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
   263  	if err == nil {
   264  		t.Fatalf("expected to receive error from Run, got none")
   265  	}
   266  
   267  	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
   268  		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
   269  	}
   270  }
   271  
   272  func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
   273  	app := &App{
   274  		ErrWriter: io.Discard,
   275  		Commands: []*Command{
   276  			{
   277  				Name:  "bar",
   278  				Usage: "this is for testing",
   279  				Subcommands: []*Command{
   280  					{
   281  						Name:  "baz",
   282  						Usage: "this is for testing",
   283  						Action: func(c *Context) error {
   284  							if c.App.ErrWriter != io.Discard {
   285  								return fmt.Errorf("ErrWriter not passed")
   286  							}
   287  
   288  							return nil
   289  						},
   290  					},
   291  				},
   292  			},
   293  		},
   294  	}
   295  
   296  	err := app.Run([]string{"foo", "bar", "baz"})
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  }
   301  
   302  func TestCommandSkipFlagParsing(t *testing.T) {
   303  	cases := []struct {
   304  		testArgs     args
   305  		expectedArgs *args
   306  		expectedErr  error
   307  	}{
   308  		{testArgs: args{"some-exec", "some-command", "some-arg", "--flag", "foo"}, expectedArgs: &args{"some-arg", "--flag", "foo"}, expectedErr: nil},
   309  		{testArgs: args{"some-exec", "some-command", "some-arg", "--flag=foo"}, expectedArgs: &args{"some-arg", "--flag=foo"}, expectedErr: nil},
   310  	}
   311  
   312  	for _, c := range cases {
   313  		var args Args
   314  		app := &App{
   315  			Commands: []*Command{
   316  				{
   317  					SkipFlagParsing: true,
   318  					Name:            "some-command",
   319  					Flags: []Flag{
   320  						&StringFlag{Name: "flag"},
   321  					},
   322  					Action: func(c *Context) error {
   323  						args = c.Args()
   324  						return nil
   325  					},
   326  				},
   327  			},
   328  			Writer: io.Discard,
   329  		}
   330  
   331  		err := app.Run(c.testArgs)
   332  		expect(t, err, c.expectedErr)
   333  		expect(t, args, c.expectedArgs)
   334  	}
   335  }
   336  
   337  func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
   338  	cases := []struct {
   339  		testArgs    args
   340  		expectedOut string
   341  	}{
   342  		{testArgs: args{"--undefined"}, expectedOut: "found 0 args"},
   343  		{testArgs: args{"--number"}, expectedOut: "found 0 args"},
   344  		{testArgs: args{"--number", "forty-two"}, expectedOut: "found 0 args"},
   345  		{testArgs: args{"--number", "42"}, expectedOut: "found 0 args"},
   346  		{testArgs: args{"--number", "42", "newArg"}, expectedOut: "found 1 args"},
   347  	}
   348  
   349  	for _, c := range cases {
   350  		var outputBuffer bytes.Buffer
   351  		app := &App{
   352  			Writer:               &outputBuffer,
   353  			EnableBashCompletion: true,
   354  			Commands: []*Command{
   355  				{
   356  					Name:  "bar",
   357  					Usage: "this is for testing",
   358  					Flags: []Flag{
   359  						&IntFlag{
   360  							Name:  "number",
   361  							Usage: "A number to parse",
   362  						},
   363  					},
   364  					BashComplete: func(c *Context) {
   365  						fmt.Fprintf(c.App.Writer, "found %d args", c.NArg())
   366  					},
   367  				},
   368  			},
   369  		}
   370  
   371  		osArgs := args{"foo", "bar"}
   372  		osArgs = append(osArgs, c.testArgs...)
   373  		osArgs = append(osArgs, "--generate-bash-completion")
   374  
   375  		err := app.Run(osArgs)
   376  		stdout := outputBuffer.String()
   377  		expect(t, err, nil)
   378  		expect(t, stdout, c.expectedOut)
   379  	}
   380  
   381  }
   382  
   383  func TestCommand_NoVersionFlagOnCommands(t *testing.T) {
   384  	app := &App{
   385  		Version: "some version",
   386  		Commands: []*Command{
   387  			{
   388  				Name:        "bar",
   389  				Usage:       "this is for testing",
   390  				Subcommands: []*Command{{}}, // some subcommand
   391  				HideHelp:    true,
   392  				Action: func(c *Context) error {
   393  					if len(c.Command.VisibleFlags()) != 0 {
   394  						t.Fatal("unexpected flag on command")
   395  					}
   396  					return nil
   397  				},
   398  			},
   399  		},
   400  	}
   401  
   402  	err := app.Run([]string{"foo", "bar"})
   403  	expect(t, err, nil)
   404  }
   405  
   406  func TestCommand_CanAddVFlagOnCommands(t *testing.T) {
   407  	app := &App{
   408  		Version: "some version",
   409  		Writer:  io.Discard,
   410  		Commands: []*Command{
   411  			{
   412  				Name:        "bar",
   413  				Usage:       "this is for testing",
   414  				Subcommands: []*Command{{}}, // some subcommand
   415  				Flags: []Flag{
   416  					&BoolFlag{
   417  						Name: "v",
   418  					},
   419  				},
   420  			},
   421  		},
   422  	}
   423  
   424  	err := app.Run([]string{"foo", "bar"})
   425  	expect(t, err, nil)
   426  }
   427  
   428  func TestCommand_VisibleSubcCommands(t *testing.T) {
   429  
   430  	subc1 := &Command{
   431  		Name:  "subc1",
   432  		Usage: "subc1 command1",
   433  	}
   434  	subc3 := &Command{
   435  		Name:  "subc3",
   436  		Usage: "subc3 command2",
   437  	}
   438  	c := &Command{
   439  		Name:  "bar",
   440  		Usage: "this is for testing",
   441  		Subcommands: []*Command{
   442  			subc1,
   443  			{
   444  				Name:   "subc2",
   445  				Usage:  "subc2 command2",
   446  				Hidden: true,
   447  			},
   448  			subc3,
   449  		},
   450  	}
   451  
   452  	expect(t, c.VisibleCommands(), []*Command{subc1, subc3})
   453  }
   454  
   455  func TestCommand_VisibleFlagCategories(t *testing.T) {
   456  
   457  	c := &Command{
   458  		Name:  "bar",
   459  		Usage: "this is for testing",
   460  		Flags: []Flag{
   461  			&StringFlag{
   462  				Name: "strd", // no category set
   463  			},
   464  			&Int64Flag{
   465  				Name:     "intd",
   466  				Aliases:  []string{"altd1", "altd2"},
   467  				Category: "cat1",
   468  			},
   469  		},
   470  	}
   471  
   472  	vfc := c.VisibleFlagCategories()
   473  	if len(vfc) != 2 {
   474  		t.Fatalf("unexpected visible flag categories %+v", vfc)
   475  	}
   476  	if vfc[1].Name() != "cat1" {
   477  		t.Errorf("expected category name cat1 got %s", vfc[0].Name())
   478  	}
   479  	if len(vfc[1].Flags()) != 1 {
   480  		t.Fatalf("expected flag category to have just one flag got %+v", vfc[0].Flags())
   481  	}
   482  
   483  	fl := vfc[1].Flags()[0]
   484  	if !reflect.DeepEqual(fl.Names(), []string{"intd", "altd1", "altd2"}) {
   485  		t.Errorf("unexpected flag %+v", fl.Names())
   486  	}
   487  }
   488  
   489  func TestCommand_RunSubcommandWithDefault(t *testing.T) {
   490  	app := &App{
   491  		Version:        "some version",
   492  		Name:           "app",
   493  		DefaultCommand: "foo",
   494  		Commands: []*Command{
   495  			{
   496  				Name: "foo",
   497  				Action: func(ctx *Context) error {
   498  					return errors.New("should not run this subcommand")
   499  				},
   500  			},
   501  			{
   502  				Name:        "bar",
   503  				Usage:       "this is for testing",
   504  				Subcommands: []*Command{{}}, // some subcommand
   505  				Action: func(*Context) error {
   506  					return nil
   507  				},
   508  			},
   509  		},
   510  	}
   511  
   512  	err := app.Run([]string{"app", "bar"})
   513  	expect(t, err, nil)
   514  
   515  	err = app.Run([]string{"app"})
   516  	expect(t, err, errors.New("should not run this subcommand"))
   517  }
   518  
   519  func TestCommand_PreservesSeparatorOnSubcommands(t *testing.T) {
   520  	var values []string
   521  	subcommand := &Command{
   522  		Name: "bar",
   523  		Flags: []Flag{
   524  			&StringSliceFlag{Name: "my-flag"},
   525  		},
   526  		Action: func(c *Context) error {
   527  			values = c.StringSlice("my-flag")
   528  			return nil
   529  		},
   530  	}
   531  	app := &App{
   532  		Commands: []*Command{
   533  			{
   534  				Name:        "foo",
   535  				Subcommands: []*Command{subcommand},
   536  			},
   537  		},
   538  		SliceFlagSeparator: ";",
   539  	}
   540  
   541  	err := app.Run([]string{"app", "foo", "bar", "--my-flag", "1;2;3"})
   542  	expect(t, err, nil)
   543  
   544  	expect(t, values, []string{"1", "2", "3"})
   545  }
   546  

View as plain text