1package cli 2 3import ( 4 "bytes" 5 "flag" 6 "fmt" 7 "runtime" 8 "strings" 9 "testing" 10) 11 12func Test_ShowAppHelp_NoAuthor(t *testing.T) { 13 output := new(bytes.Buffer) 14 app := NewApp() 15 app.Writer = output 16 17 c := NewContext(app, nil, nil) 18 19 ShowAppHelp(c) 20 21 if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { 22 t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") 23 } 24} 25 26func Test_ShowAppHelp_NoVersion(t *testing.T) { 27 output := new(bytes.Buffer) 28 app := NewApp() 29 app.Writer = output 30 31 app.Version = "" 32 33 c := NewContext(app, nil, nil) 34 35 ShowAppHelp(c) 36 37 if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { 38 t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") 39 } 40} 41 42func Test_ShowAppHelp_HideVersion(t *testing.T) { 43 output := new(bytes.Buffer) 44 app := NewApp() 45 app.Writer = output 46 47 app.HideVersion = true 48 49 c := NewContext(app, nil, nil) 50 51 ShowAppHelp(c) 52 53 if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { 54 t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") 55 } 56} 57 58func Test_Help_Custom_Flags(t *testing.T) { 59 oldFlag := HelpFlag 60 defer func() { 61 HelpFlag = oldFlag 62 }() 63 64 HelpFlag = BoolFlag{ 65 Name: "help, x", 66 Usage: "show help", 67 } 68 69 app := App{ 70 Flags: []Flag{ 71 BoolFlag{Name: "foo, h"}, 72 }, 73 Action: func(ctx *Context) error { 74 if ctx.Bool("h") != true { 75 t.Errorf("custom help flag not set") 76 } 77 return nil 78 }, 79 } 80 output := new(bytes.Buffer) 81 app.Writer = output 82 app.Run([]string{"test", "-h"}) 83 if output.Len() > 0 { 84 t.Errorf("unexpected output: %s", output.String()) 85 } 86} 87 88func Test_Version_Custom_Flags(t *testing.T) { 89 oldFlag := VersionFlag 90 defer func() { 91 VersionFlag = oldFlag 92 }() 93 94 VersionFlag = BoolFlag{ 95 Name: "version, V", 96 Usage: "show version", 97 } 98 99 app := App{ 100 Flags: []Flag{ 101 BoolFlag{Name: "foo, v"}, 102 }, 103 Action: func(ctx *Context) error { 104 if ctx.Bool("v") != true { 105 t.Errorf("custom version flag not set") 106 } 107 return nil 108 }, 109 } 110 output := new(bytes.Buffer) 111 app.Writer = output 112 app.Run([]string{"test", "-v"}) 113 if output.Len() > 0 { 114 t.Errorf("unexpected output: %s", output.String()) 115 } 116} 117 118func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { 119 app := NewApp() 120 121 set := flag.NewFlagSet("test", 0) 122 set.Parse([]string{"foo"}) 123 124 c := NewContext(app, set, nil) 125 126 err := helpCommand.Action.(func(*Context) error)(c) 127 128 if err == nil { 129 t.Fatalf("expected error from helpCommand.Action(), but got nil") 130 } 131 132 exitErr, ok := err.(*ExitError) 133 if !ok { 134 t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) 135 } 136 137 if !strings.HasPrefix(exitErr.Error(), "No help topic for") { 138 t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) 139 } 140 141 if exitErr.exitCode != 3 { 142 t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) 143 } 144} 145 146func Test_helpCommand_InHelpOutput(t *testing.T) { 147 app := NewApp() 148 output := &bytes.Buffer{} 149 app.Writer = output 150 app.Run([]string{"test", "--help"}) 151 152 s := output.String() 153 154 if strings.Contains(s, "\nCOMMANDS:\nGLOBAL OPTIONS:\n") { 155 t.Fatalf("empty COMMANDS section detected: %q", s) 156 } 157 158 if !strings.Contains(s, "help, h") { 159 t.Fatalf("missing \"help, h\": %q", s) 160 } 161} 162 163func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { 164 app := NewApp() 165 166 set := flag.NewFlagSet("test", 0) 167 set.Parse([]string{"foo"}) 168 169 c := NewContext(app, set, nil) 170 171 err := helpSubcommand.Action.(func(*Context) error)(c) 172 173 if err == nil { 174 t.Fatalf("expected error from helpCommand.Action(), but got nil") 175 } 176 177 exitErr, ok := err.(*ExitError) 178 if !ok { 179 t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) 180 } 181 182 if !strings.HasPrefix(exitErr.Error(), "No help topic for") { 183 t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) 184 } 185 186 if exitErr.exitCode != 3 { 187 t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) 188 } 189} 190 191func TestShowAppHelp_CommandAliases(t *testing.T) { 192 app := &App{ 193 Commands: []Command{ 194 { 195 Name: "frobbly", 196 Aliases: []string{"fr", "frob"}, 197 Action: func(ctx *Context) error { 198 return nil 199 }, 200 }, 201 }, 202 } 203 204 output := &bytes.Buffer{} 205 app.Writer = output 206 app.Run([]string{"foo", "--help"}) 207 208 if !strings.Contains(output.String(), "frobbly, fr, frob") { 209 t.Errorf("expected output to include all command aliases; got: %q", output.String()) 210 } 211} 212 213func TestShowCommandHelp_CommandAliases(t *testing.T) { 214 app := &App{ 215 Commands: []Command{ 216 { 217 Name: "frobbly", 218 Aliases: []string{"fr", "frob", "bork"}, 219 Action: func(ctx *Context) error { 220 return nil 221 }, 222 }, 223 }, 224 } 225 226 output := &bytes.Buffer{} 227 app.Writer = output 228 app.Run([]string{"foo", "help", "fr"}) 229 230 if !strings.Contains(output.String(), "frobbly") { 231 t.Errorf("expected output to include command name; got: %q", output.String()) 232 } 233 234 if strings.Contains(output.String(), "bork") { 235 t.Errorf("expected output to exclude command aliases; got: %q", output.String()) 236 } 237} 238 239func TestShowSubcommandHelp_CommandAliases(t *testing.T) { 240 app := &App{ 241 Commands: []Command{ 242 { 243 Name: "frobbly", 244 Aliases: []string{"fr", "frob", "bork"}, 245 Action: func(ctx *Context) error { 246 return nil 247 }, 248 }, 249 }, 250 } 251 252 output := &bytes.Buffer{} 253 app.Writer = output 254 app.Run([]string{"foo", "help"}) 255 256 if !strings.Contains(output.String(), "frobbly, fr, frob, bork") { 257 t.Errorf("expected output to include all command aliases; got: %q", output.String()) 258 } 259} 260 261func TestShowCommandHelp_Customtemplate(t *testing.T) { 262 app := &App{ 263 Commands: []Command{ 264 { 265 Name: "frobbly", 266 Action: func(ctx *Context) error { 267 return nil 268 }, 269 HelpName: "foo frobbly", 270 CustomHelpTemplate: `NAME: 271 {{.HelpName}} - {{.Usage}} 272 273USAGE: 274 {{.HelpName}} [FLAGS] TARGET [TARGET ...] 275 276FLAGS: 277 {{range .VisibleFlags}}{{.}} 278 {{end}} 279EXAMPLES: 280 1. Frobbly runs with this param locally. 281 $ {{.HelpName}} wobbly 282`, 283 }, 284 }, 285 } 286 output := &bytes.Buffer{} 287 app.Writer = output 288 app.Run([]string{"foo", "help", "frobbly"}) 289 290 if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") { 291 t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String()) 292 } 293 294 if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") { 295 t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String()) 296 } 297 298 if !strings.Contains(output.String(), "$ foo frobbly wobbly") { 299 t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String()) 300 } 301} 302 303func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { 304 app := &App{ 305 Commands: []Command{ 306 { 307 Name: "frobbly", 308 UsageText: "this is usage text", 309 }, 310 }, 311 } 312 313 output := &bytes.Buffer{} 314 app.Writer = output 315 316 app.Run([]string{"foo", "frobbly", "--help"}) 317 318 if !strings.Contains(output.String(), "this is usage text") { 319 t.Errorf("expected output to include usage text; got: %q", output.String()) 320 } 321} 322 323func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { 324 app := &App{ 325 Commands: []Command{ 326 { 327 Name: "frobbly", 328 Subcommands: []Command{ 329 { 330 Name: "bobbly", 331 UsageText: "this is usage text", 332 }, 333 }, 334 }, 335 }, 336 } 337 338 output := &bytes.Buffer{} 339 app.Writer = output 340 app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) 341 342 if !strings.Contains(output.String(), "this is usage text") { 343 t.Errorf("expected output to include usage text; got: %q", output.String()) 344 } 345} 346 347func TestShowAppHelp_HiddenCommand(t *testing.T) { 348 app := &App{ 349 Commands: []Command{ 350 { 351 Name: "frobbly", 352 Action: func(ctx *Context) error { 353 return nil 354 }, 355 }, 356 { 357 Name: "secretfrob", 358 Hidden: true, 359 Action: func(ctx *Context) error { 360 return nil 361 }, 362 }, 363 }, 364 } 365 366 output := &bytes.Buffer{} 367 app.Writer = output 368 app.Run([]string{"app", "--help"}) 369 370 if strings.Contains(output.String(), "secretfrob") { 371 t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) 372 } 373 374 if !strings.Contains(output.String(), "frobbly") { 375 t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) 376 } 377} 378 379func TestShowAppHelp_CustomAppTemplate(t *testing.T) { 380 app := &App{ 381 Commands: []Command{ 382 { 383 Name: "frobbly", 384 Action: func(ctx *Context) error { 385 return nil 386 }, 387 }, 388 { 389 Name: "secretfrob", 390 Hidden: true, 391 Action: func(ctx *Context) error { 392 return nil 393 }, 394 }, 395 }, 396 ExtraInfo: func() map[string]string { 397 platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH) 398 goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU()) 399 return map[string]string{ 400 "PLATFORM": platform, 401 "RUNTIME": goruntime, 402 } 403 }, 404 CustomAppHelpTemplate: `NAME: 405 {{.Name}} - {{.Usage}} 406 407USAGE: 408 {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...] 409 410COMMANDS: 411 {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 412 {{end}}{{if .VisibleFlags}} 413GLOBAL FLAGS: 414 {{range .VisibleFlags}}{{.}} 415 {{end}}{{end}} 416VERSION: 417 2.0.0 418{{"\n"}}{{range $key, $value := ExtraInfo}} 419{{$key}}: 420 {{$value}} 421{{end}}`, 422 } 423 424 output := &bytes.Buffer{} 425 app.Writer = output 426 app.Run([]string{"app", "--help"}) 427 428 if strings.Contains(output.String(), "secretfrob") { 429 t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) 430 } 431 432 if !strings.Contains(output.String(), "frobbly") { 433 t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) 434 } 435 436 if !strings.Contains(output.String(), "PLATFORM:") || 437 !strings.Contains(output.String(), "OS:") || 438 !strings.Contains(output.String(), "Arch:") { 439 t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String()) 440 } 441 442 if !strings.Contains(output.String(), "RUNTIME:") || 443 !strings.Contains(output.String(), "Version:") || 444 !strings.Contains(output.String(), "CPUs:") { 445 t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String()) 446 } 447 448 if !strings.Contains(output.String(), "VERSION:") || 449 !strings.Contains(output.String(), "2.0.0") { 450 t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String()) 451 } 452} 453