1 package cli
2
3 import (
4 "bytes"
5 "flag"
6 "fmt"
7 "io"
8 "os"
9 "runtime"
10 "strings"
11 "testing"
12 )
13
14 func Test_ShowAppHelp_NoAuthor(t *testing.T) {
15 output := new(bytes.Buffer)
16 app := &App{Writer: output}
17
18 c := NewContext(app, nil, nil)
19
20 _ = ShowAppHelp(c)
21
22 if bytes.Contains(output.Bytes(), []byte("AUTHOR(S):")) {
23 t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
24 }
25 }
26
27 func Test_ShowAppHelp_NoVersion(t *testing.T) {
28 output := new(bytes.Buffer)
29 app := &App{Writer: output}
30
31 app.Version = ""
32
33 c := NewContext(app, nil, nil)
34
35 _ = ShowAppHelp(c)
36
37 if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
38 t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
39 }
40 }
41
42 func Test_ShowAppHelp_HideVersion(t *testing.T) {
43 output := new(bytes.Buffer)
44 app := &App{Writer: output}
45
46 app.HideVersion = true
47
48 c := NewContext(app, nil, nil)
49
50 _ = ShowAppHelp(c)
51
52 if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
53 t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
54 }
55 }
56
57 func Test_ShowAppHelp_MultiLineDescription(t *testing.T) {
58 output := new(bytes.Buffer)
59 app := &App{Writer: output}
60
61 app.HideVersion = true
62 app.Description = "multi\n line"
63
64 c := NewContext(app, nil, nil)
65
66 _ = ShowAppHelp(c)
67
68 if !bytes.Contains(output.Bytes(), []byte("DESCRIPTION:\n multi\n line")) {
69 t.Errorf("expected\n%s\nto include\n%s", output.String(), "DESCRIPTION:\n multi\n line")
70 }
71 }
72
73 func Test_Help_Custom_Flags(t *testing.T) {
74 oldFlag := HelpFlag
75 defer func() {
76 HelpFlag = oldFlag
77 }()
78
79 HelpFlag = &BoolFlag{
80 Name: "help",
81 Aliases: []string{"x"},
82 Usage: "show help",
83 }
84
85 app := App{
86 Flags: []Flag{
87 &BoolFlag{Name: "foo", Aliases: []string{"h"}},
88 },
89 Action: func(ctx *Context) error {
90 if ctx.Bool("h") != true {
91 t.Errorf("custom help flag not set")
92 }
93 return nil
94 },
95 }
96 output := new(bytes.Buffer)
97 app.Writer = output
98 _ = app.Run([]string{"test", "-h"})
99 if output.Len() > 0 {
100 t.Errorf("unexpected output: %s", output.String())
101 }
102 }
103
104 func Test_Help_Nil_Flags(t *testing.T) {
105 oldFlag := HelpFlag
106 defer func() {
107 HelpFlag = oldFlag
108 }()
109 HelpFlag = nil
110
111 app := App{
112 Action: func(context *Context) error {
113 return nil
114 },
115 }
116 output := new(bytes.Buffer)
117 app.Writer = output
118 _ = app.Run([]string{"test"})
119 if output.Len() > 0 {
120 t.Errorf("unexpected output: %s", output.String())
121 }
122 }
123
124 func Test_Version_Custom_Flags(t *testing.T) {
125 oldFlag := VersionFlag
126 defer func() {
127 VersionFlag = oldFlag
128 }()
129
130 VersionFlag = &BoolFlag{
131 Name: "version",
132 Aliases: []string{"V"},
133 Usage: "show version",
134 }
135
136 app := App{
137 Flags: []Flag{
138 &BoolFlag{Name: "foo", Aliases: []string{"v"}},
139 },
140 Action: func(ctx *Context) error {
141 if ctx.Bool("v") != true {
142 t.Errorf("custom version flag not set")
143 }
144 return nil
145 },
146 }
147 output := new(bytes.Buffer)
148 app.Writer = output
149 _ = app.Run([]string{"test", "-v"})
150 if output.Len() > 0 {
151 t.Errorf("unexpected output: %s", output.String())
152 }
153 }
154
155 func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
156 app := &App{}
157
158 set := flag.NewFlagSet("test", 0)
159 _ = set.Parse([]string{"foo"})
160
161 c := NewContext(app, set, nil)
162
163 err := helpCommand.Action(c)
164
165 if err == nil {
166 t.Fatalf("expected error from helpCommand.Action(), but got nil")
167 }
168
169 exitErr, ok := err.(*exitError)
170 if !ok {
171 t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
172 }
173
174 if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
175 t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
176 }
177
178 if exitErr.exitCode != 3 {
179 t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
180 }
181 }
182
183 func Test_helpCommand_InHelpOutput(t *testing.T) {
184 app := &App{}
185 output := &bytes.Buffer{}
186 app.Writer = output
187 _ = app.Run([]string{"test", "--help"})
188
189 s := output.String()
190
191 if strings.Contains(s, "\nCOMMANDS:\nGLOBAL OPTIONS:\n") {
192 t.Fatalf("empty COMMANDS section detected: %q", s)
193 }
194
195 if !strings.Contains(s, "help, h") {
196 t.Fatalf("missing \"help, h\": %q", s)
197 }
198 }
199
200 func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
201 app := &App{}
202
203 set := flag.NewFlagSet("test", 0)
204 _ = set.Parse([]string{"foo"})
205
206 c := NewContext(app, set, nil)
207
208 err := helpCommand.Action(c)
209
210 if err == nil {
211 t.Fatalf("expected error from helpCommand.Action(), but got nil")
212 }
213
214 exitErr, ok := err.(*exitError)
215 if !ok {
216 t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
217 }
218
219 if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
220 t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
221 }
222
223 if exitErr.exitCode != 3 {
224 t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
225 }
226 }
227
228 func TestShowAppHelp_CommandAliases(t *testing.T) {
229 app := &App{
230 Commands: []*Command{
231 {
232 Name: "frobbly",
233 Aliases: []string{"fr", "frob"},
234 Action: func(ctx *Context) error {
235 return nil
236 },
237 },
238 },
239 }
240
241 output := &bytes.Buffer{}
242 app.Writer = output
243 _ = app.Run([]string{"foo", "--help"})
244
245 if !strings.Contains(output.String(), "frobbly, fr, frob") {
246 t.Errorf("expected output to include all command aliases; got: %q", output.String())
247 }
248 }
249
250 func TestShowCommandHelp_HelpPrinter(t *testing.T) {
251
254
255 tests := []struct {
256 name string
257 template string
258 printer helpPrinter
259 command string
260 wantTemplate string
261 wantOutput string
262 }{
263 {
264 name: "no-command",
265 template: "",
266 printer: func(w io.Writer, templ string, data interface{}) {
267 fmt.Fprint(w, "yo")
268 },
269 command: "",
270 wantTemplate: AppHelpTemplate,
271 wantOutput: "yo",
272 },
273
295 }
296
297 for _, tt := range tests {
298 t.Run(tt.name, func(t *testing.T) {
299 defer func(old helpPrinter) {
300 HelpPrinter = old
301 }(HelpPrinter)
302 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
303 if templ != tt.wantTemplate {
304 t.Errorf("want template:\n%s\ngot template:\n%s", tt.wantTemplate, templ)
305 }
306
307 tt.printer(w, templ, data)
308 }
309
310 var buf bytes.Buffer
311 app := &App{
312 Name: "my-app",
313 Writer: &buf,
314 Commands: []*Command{
315 {
316 Name: "my-command",
317 CustomHelpTemplate: tt.template,
318 },
319 },
320 }
321
322 err := app.Run([]string{"my-app", "help", tt.command})
323 if err != nil {
324 t.Fatal(err)
325 }
326
327 got := buf.String()
328 if got != tt.wantOutput {
329 t.Errorf("want output %q, got %q", tt.wantOutput, got)
330 }
331 })
332 }
333 }
334
335 func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) {
336 doublecho := func(text string) string {
337 return text + " " + text
338 }
339
340 tests := []struct {
341 name string
342 template string
343 printer helpPrinterCustom
344 command string
345 wantTemplate string
346 wantOutput string
347 }{
348 {
349 name: "no-command",
350 template: "",
351 printer: func(w io.Writer, templ string, data interface{}, fm map[string]interface{}) {
352 fmt.Fprint(w, "yo")
353 },
354 command: "",
355 wantTemplate: AppHelpTemplate,
356 wantOutput: "yo",
357 },
358 {
359 name: "standard-command",
360 template: "",
361 printer: func(w io.Writer, templ string, data interface{}, fm map[string]interface{}) {
362 fmt.Fprint(w, "yo")
363 },
364 command: "my-command",
365 wantTemplate: CommandHelpTemplate,
366 wantOutput: "yo",
367 },
368 {
369 name: "custom-template-command",
370 template: "{{doublecho .Name}}",
371 printer: func(w io.Writer, templ string, data interface{}, _ map[string]interface{}) {
372
373 fm := map[string]interface{}{"doublecho": doublecho}
374 printHelpCustom(w, templ, data, fm)
375 },
376 command: "my-command",
377 wantTemplate: "{{doublecho .Name}}",
378 wantOutput: "my-command my-command",
379 },
380 }
381
382 for _, tt := range tests {
383 t.Run(tt.name, func(t *testing.T) {
384 defer func(old helpPrinterCustom) {
385 HelpPrinterCustom = old
386 }(HelpPrinterCustom)
387 HelpPrinterCustom = func(w io.Writer, templ string, data interface{}, fm map[string]interface{}) {
388 if fm != nil {
389 t.Error("unexpected function map passed")
390 }
391
392 if templ != tt.wantTemplate {
393 t.Errorf("want template:\n%s\ngot template:\n%s", tt.wantTemplate, templ)
394 }
395
396 tt.printer(w, templ, data, fm)
397 }
398
399 var buf bytes.Buffer
400 app := &App{
401 Name: "my-app",
402 Writer: &buf,
403 Commands: []*Command{
404 {
405 Name: "my-command",
406 CustomHelpTemplate: tt.template,
407 },
408 },
409 }
410
411 err := app.Run([]string{"my-app", "help", tt.command})
412 if err != nil {
413 t.Fatal(err)
414 }
415
416 got := buf.String()
417 if got != tt.wantOutput {
418 t.Errorf("want output %q, got %q", tt.wantOutput, got)
419 }
420 })
421 }
422 }
423
424 func TestShowCommandHelp_CommandAliases(t *testing.T) {
425 app := &App{
426 Commands: []*Command{
427 {
428 Name: "frobbly",
429 Aliases: []string{"fr", "frob", "bork"},
430 Action: func(ctx *Context) error {
431 return nil
432 },
433 },
434 },
435 }
436
437 output := &bytes.Buffer{}
438 app.Writer = output
439 _ = app.Run([]string{"foo", "help", "fr"})
440
441 if !strings.Contains(output.String(), "frobbly") {
442 t.Errorf("expected output to include command name; got: %q", output.String())
443 }
444
445 if strings.Contains(output.String(), "bork") {
446 t.Errorf("expected output to exclude command aliases; got: %q", output.String())
447 }
448 }
449
450 func TestHelpNameConsistency(t *testing.T) {
451
452
453
454
455 tmpTemplate := SubcommandHelpTemplate
456 SubcommandHelpTemplate = `{{.HelpName}}`
457 defer func() {
458 SubcommandHelpTemplate = tmpTemplate
459 }()
460
461 app := NewApp()
462 app.Name = "bar"
463 app.CustomAppHelpTemplate = `{{.HelpName}}`
464 app.Commands = []*Command{
465 {
466 Name: "command1",
467 CustomHelpTemplate: `{{.HelpName}}`,
468 Subcommands: []*Command{
469 {
470 Name: "subcommand1",
471 CustomHelpTemplate: `{{.HelpName}}`,
472 },
473 },
474 },
475 }
476
477 tests := []struct {
478 name string
479 args []string
480 }{
481 {
482 name: "App help",
483 args: []string{"foo"},
484 },
485 {
486 name: "Command help",
487 args: []string{"foo", "command1"},
488 },
489 {
490 name: "Subcommand help",
491 args: []string{"foo", "command1", "subcommand1"},
492 },
493 }
494
495 for _, tt := range tests {
496 output := &bytes.Buffer{}
497 app.Writer = output
498 if err := app.Run(tt.args); err != nil {
499 t.Error(err)
500 }
501 if !strings.Contains(output.String(), "bar") {
502 t.Errorf("expected output to contain bar; got: %q", output.String())
503 }
504 }
505 }
506
507 func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
508 app := &App{
509 Commands: []*Command{
510 {
511 Name: "frobbly",
512 Aliases: []string{"fr", "frob", "bork"},
513 Action: func(ctx *Context) error {
514 return nil
515 },
516 },
517 },
518 }
519
520 output := &bytes.Buffer{}
521 app.Writer = output
522 _ = app.Run([]string{"foo", "help"})
523
524 if !strings.Contains(output.String(), "frobbly, fr, frob, bork") {
525 t.Errorf("expected output to include all command aliases; got: %q", output.String())
526 }
527 }
528
529 func TestShowCommandHelp_Customtemplate(t *testing.T) {
530 app := &App{
531 Name: "foo",
532 Commands: []*Command{
533 {
534 Name: "frobbly",
535 Action: func(ctx *Context) error {
536 return nil
537 },
538 CustomHelpTemplate: `NAME:
539 {{.HelpName}} - {{.Usage}}
540
541 USAGE:
542 {{.HelpName}} [FLAGS] TARGET [TARGET ...]
543
544 FLAGS:
545 {{range .VisibleFlags}}{{.}}
546 {{end}}
547 EXAMPLES:
548 1. Frobbly runs with this param locally.
549 $ {{.HelpName}} wobbly
550 `,
551 },
552 },
553 }
554 output := &bytes.Buffer{}
555 app.Writer = output
556 _ = app.Run([]string{"foo", "help", "frobbly"})
557
558 if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
559 t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
560 }
561
562 if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") {
563 t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String())
564 }
565
566 if !strings.Contains(output.String(), "$ foo frobbly wobbly") {
567 t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String())
568 }
569 }
570
571 func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
572 app := &App{
573 Commands: []*Command{
574 {
575 Name: "frobbly",
576 UsageText: "this is usage text",
577 },
578 },
579 }
580
581 output := &bytes.Buffer{}
582 app.Writer = output
583
584 _ = app.Run([]string{"foo", "frobbly", "--help"})
585
586 if !strings.Contains(output.String(), "this is usage text") {
587 t.Errorf("expected output to include usage text; got: %q", output.String())
588 }
589 }
590
591 func TestShowSubcommandHelp_MultiLine_CommandUsageText(t *testing.T) {
592 app := &App{
593 Commands: []*Command{
594 {
595 Name: "frobbly",
596 UsageText: `This is a
597 multi
598 line
599 UsageText`,
600 },
601 },
602 }
603
604 output := &bytes.Buffer{}
605 app.Writer = output
606
607 _ = app.Run([]string{"foo", "frobbly", "--help"})
608
609 expected := `USAGE:
610 This is a
611 multi
612 line
613 UsageText
614 `
615
616 if !strings.Contains(output.String(), expected) {
617 t.Errorf("expected output to include usage text; got: %q", output.String())
618 }
619 }
620
621 func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
622 app := &App{
623 Commands: []*Command{
624 {
625 Name: "frobbly",
626 Subcommands: []*Command{
627 {
628 Name: "bobbly",
629 UsageText: "this is usage text",
630 },
631 },
632 },
633 },
634 }
635
636 output := &bytes.Buffer{}
637 app.Writer = output
638 _ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
639
640 if !strings.Contains(output.String(), "this is usage text") {
641 t.Errorf("expected output to include usage text; got: %q", output.String())
642 }
643 }
644
645 func TestShowSubcommandHelp_MultiLine_SubcommandUsageText(t *testing.T) {
646 app := &App{
647 Commands: []*Command{
648 {
649 Name: "frobbly",
650 Subcommands: []*Command{
651 {
652 Name: "bobbly",
653 UsageText: `This is a
654 multi
655 line
656 UsageText`,
657 },
658 },
659 },
660 },
661 }
662
663 output := &bytes.Buffer{}
664 app.Writer = output
665 _ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
666
667 expected := `USAGE:
668 This is a
669 multi
670 line
671 UsageText
672 `
673
674 if !strings.Contains(output.String(), expected) {
675 t.Errorf("expected output to include usage text; got: %q", output.String())
676 }
677 }
678
679 func TestShowAppHelp_HiddenCommand(t *testing.T) {
680 app := &App{
681 Commands: []*Command{
682 {
683 Name: "frobbly",
684 Action: func(ctx *Context) error {
685 return nil
686 },
687 },
688 {
689 Name: "secretfrob",
690 Hidden: true,
691 Action: func(ctx *Context) error {
692 return nil
693 },
694 },
695 },
696 }
697
698 output := &bytes.Buffer{}
699 app.Writer = output
700 _ = app.Run([]string{"app", "--help"})
701
702 if strings.Contains(output.String(), "secretfrob") {
703 t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
704 }
705
706 if !strings.Contains(output.String(), "frobbly") {
707 t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
708 }
709 }
710
711 func TestShowAppHelp_HelpPrinter(t *testing.T) {
712 doublecho := func(text string) string {
713 return text + " " + text
714 }
715
716 tests := []struct {
717 name string
718 template string
719 printer helpPrinter
720 wantTemplate string
721 wantOutput string
722 }{
723 {
724 name: "standard-command",
725 template: "",
726 printer: func(w io.Writer, templ string, data interface{}) {
727 fmt.Fprint(w, "yo")
728 },
729 wantTemplate: AppHelpTemplate,
730 wantOutput: "yo",
731 },
732 {
733 name: "custom-template-command",
734 template: "{{doublecho .Name}}",
735 printer: func(w io.Writer, templ string, data interface{}) {
736
737 fm := map[string]interface{}{"doublecho": doublecho}
738 printHelpCustom(w, templ, data, fm)
739 },
740 wantTemplate: "{{doublecho .Name}}",
741 wantOutput: "my-app my-app",
742 },
743 }
744
745 for _, tt := range tests {
746 t.Run(tt.name, func(t *testing.T) {
747 defer func(old helpPrinter) {
748 HelpPrinter = old
749 }(HelpPrinter)
750 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
751 if templ != tt.wantTemplate {
752 t.Errorf("want template:\n%s\ngot template:\n%s", tt.wantTemplate, templ)
753 }
754
755 tt.printer(w, templ, data)
756 }
757
758 var buf bytes.Buffer
759 app := &App{
760 Name: "my-app",
761 Writer: &buf,
762 CustomAppHelpTemplate: tt.template,
763 }
764
765 err := app.Run([]string{"my-app", "help"})
766 if err != nil {
767 t.Fatal(err)
768 }
769
770 got := buf.String()
771 if got != tt.wantOutput {
772 t.Errorf("want output %q, got %q", tt.wantOutput, got)
773 }
774 })
775 }
776 }
777
778 func TestShowAppHelp_HelpPrinterCustom(t *testing.T) {
779 doublecho := func(text string) string {
780 return text + " " + text
781 }
782
783 tests := []struct {
784 name string
785 template string
786 printer helpPrinterCustom
787 wantTemplate string
788 wantOutput string
789 }{
790 {
791 name: "standard-command",
792 template: "",
793 printer: func(w io.Writer, templ string, data interface{}, fm map[string]interface{}) {
794 fmt.Fprint(w, "yo")
795 },
796 wantTemplate: AppHelpTemplate,
797 wantOutput: "yo",
798 },
799 {
800 name: "custom-template-command",
801 template: "{{doublecho .Name}}",
802 printer: func(w io.Writer, templ string, data interface{}, _ map[string]interface{}) {
803
804 fm := map[string]interface{}{"doublecho": doublecho}
805 printHelpCustom(w, templ, data, fm)
806 },
807 wantTemplate: "{{doublecho .Name}}",
808 wantOutput: "my-app my-app",
809 },
810 }
811
812 for _, tt := range tests {
813 t.Run(tt.name, func(t *testing.T) {
814 defer func(old helpPrinterCustom) {
815 HelpPrinterCustom = old
816 }(HelpPrinterCustom)
817 HelpPrinterCustom = func(w io.Writer, templ string, data interface{}, fm map[string]interface{}) {
818 if fm != nil {
819 t.Error("unexpected function map passed")
820 }
821
822 if templ != tt.wantTemplate {
823 t.Errorf("want template:\n%s\ngot template:\n%s", tt.wantTemplate, templ)
824 }
825
826 tt.printer(w, templ, data, fm)
827 }
828
829 var buf bytes.Buffer
830 app := &App{
831 Name: "my-app",
832 Writer: &buf,
833 CustomAppHelpTemplate: tt.template,
834 }
835
836 err := app.Run([]string{"my-app", "help"})
837 if err != nil {
838 t.Fatal(err)
839 }
840
841 got := buf.String()
842 if got != tt.wantOutput {
843 t.Errorf("want output %q, got %q", tt.wantOutput, got)
844 }
845 })
846 }
847 }
848
849 func TestShowAppHelp_CustomAppTemplate(t *testing.T) {
850 app := &App{
851 Commands: []*Command{
852 {
853 Name: "frobbly",
854 Action: func(ctx *Context) error {
855 return nil
856 },
857 },
858 {
859 Name: "secretfrob",
860 Hidden: true,
861 Action: func(ctx *Context) error {
862 return nil
863 },
864 },
865 },
866 ExtraInfo: func() map[string]string {
867 platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH)
868 goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU())
869 return map[string]string{
870 "PLATFORM": platform,
871 "RUNTIME": goruntime,
872 }
873 },
874 CustomAppHelpTemplate: `NAME:
875 {{.Name}} - {{.Usage}}
876
877 USAGE:
878 {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]
879
880 COMMANDS:
881 {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
882 {{end}}{{if .VisibleFlags}}
883 GLOBAL FLAGS:
884 {{range .VisibleFlags}}{{.}}
885 {{end}}{{end}}
886 VERSION:
887 2.0.0
888 {{"\n"}}{{range $key, $value := ExtraInfo}}
889 {{$key}}:
890 {{$value}}
891 {{end}}`,
892 }
893
894 output := &bytes.Buffer{}
895 app.Writer = output
896 _ = app.Run([]string{"app", "--help"})
897
898 if strings.Contains(output.String(), "secretfrob") {
899 t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
900 }
901
902 if !strings.Contains(output.String(), "frobbly") {
903 t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
904 }
905
906 if !strings.Contains(output.String(), "PLATFORM:") ||
907 !strings.Contains(output.String(), "OS:") ||
908 !strings.Contains(output.String(), "Arch:") {
909 t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String())
910 }
911
912 if !strings.Contains(output.String(), "RUNTIME:") ||
913 !strings.Contains(output.String(), "Version:") ||
914 !strings.Contains(output.String(), "CPUs:") {
915 t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String())
916 }
917
918 if !strings.Contains(output.String(), "VERSION:") ||
919 !strings.Contains(output.String(), "2.0.0") {
920 t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String())
921 }
922 }
923
924 func TestShowAppHelp_UsageText(t *testing.T) {
925 app := &App{
926 UsageText: "This is a single line of UsageText",
927 Commands: []*Command{
928 {
929 Name: "frobbly",
930 },
931 },
932 }
933
934 output := &bytes.Buffer{}
935 app.Writer = output
936
937 _ = app.Run([]string{"foo"})
938
939 if !strings.Contains(output.String(), "This is a single line of UsageText") {
940 t.Errorf("expected output to include usage text; got: %q", output.String())
941 }
942 }
943
944 func TestShowAppHelp_MultiLine_UsageText(t *testing.T) {
945 app := &App{
946 UsageText: `This is a
947 multi
948 line
949 App UsageText`,
950 Commands: []*Command{
951 {
952 Name: "frobbly",
953 },
954 },
955 }
956
957 output := &bytes.Buffer{}
958 app.Writer = output
959
960 _ = app.Run([]string{"foo"})
961
962 expected := `USAGE:
963 This is a
964 multi
965 line
966 App UsageText
967 `
968
969 if !strings.Contains(output.String(), expected) {
970 t.Errorf("expected output to include usage text; got: %q", output.String())
971 }
972 }
973
974 func TestShowAppHelp_CommandMultiLine_UsageText(t *testing.T) {
975 app := &App{
976 UsageText: `This is a
977 multi
978 line
979 App UsageText`,
980 Commands: []*Command{
981 {
982 Name: "frobbly",
983 Aliases: []string{"frb1", "frbb2", "frl2"},
984 Usage: "this is a long help output for the run command, long usage \noutput, long usage output, long usage output, long usage output\noutput, long usage output, long usage output",
985 },
986 {
987 Name: "grobbly",
988 Aliases: []string{"grb1", "grbb2"},
989 Usage: "this is another long help output for the run command, long usage \noutput, long usage output",
990 },
991 },
992 }
993
994 output := &bytes.Buffer{}
995 app.Writer = output
996
997 _ = app.Run([]string{"foo"})
998
999 expected := "COMMANDS:\n" +
1000 " frobbly, frb1, frbb2, frl2 this is a long help output for the run command, long usage \n" +
1001 " output, long usage output, long usage output, long usage output\n" +
1002 " output, long usage output, long usage output\n" +
1003 " grobbly, grb1, grbb2 this is another long help output for the run command, long usage \n" +
1004 " output, long usage output"
1005 if !strings.Contains(output.String(), expected) {
1006 t.Errorf("expected output to include usage text; got: %q", output.String())
1007 }
1008 }
1009
1010 func TestHideHelpCommand(t *testing.T) {
1011 app := &App{
1012 HideHelpCommand: true,
1013 Writer: io.Discard,
1014 }
1015
1016 err := app.Run([]string{"foo", "help"})
1017 if err == nil {
1018 t.Fatalf("expected a non-nil error")
1019 }
1020 if !strings.Contains(err.Error(), "No help topic for 'help'") {
1021 t.Errorf("Run returned unexpected error: %v", err)
1022 }
1023
1024 err = app.Run([]string{"foo", "--help"})
1025 if err != nil {
1026 t.Errorf("Run returned unexpected error: %v", err)
1027 }
1028 }
1029
1030 func TestHideHelpCommand_False(t *testing.T) {
1031 app := &App{
1032 HideHelpCommand: false,
1033 Writer: io.Discard,
1034 }
1035
1036 err := app.Run([]string{"foo", "help"})
1037 if err != nil {
1038 t.Errorf("Run returned unexpected error: %v", err)
1039 }
1040
1041 err = app.Run([]string{"foo", "--help"})
1042 if err != nil {
1043 t.Errorf("Run returned unexpected error: %v", err)
1044 }
1045 }
1046
1047 func TestHideHelpCommand_WithHideHelp(t *testing.T) {
1048 app := &App{
1049 HideHelp: true,
1050 HideHelpCommand: true,
1051 Writer: io.Discard,
1052 }
1053
1054 err := app.Run([]string{"foo", "help"})
1055 if err == nil {
1056 t.Fatalf("expected a non-nil error")
1057 }
1058 if !strings.Contains(err.Error(), "No help topic for 'help'") {
1059 t.Errorf("Run returned unexpected error: %v", err)
1060 }
1061
1062 err = app.Run([]string{"foo", "--help"})
1063 if err == nil {
1064 t.Fatalf("expected a non-nil error")
1065 }
1066 if !strings.Contains(err.Error(), "flag: help requested") {
1067 t.Errorf("Run returned unexpected error: %v", err)
1068 }
1069 }
1070
1071 func newContextFromStringSlice(ss []string) *Context {
1072 set := flag.NewFlagSet("", flag.ContinueOnError)
1073 _ = set.Parse(ss)
1074 return &Context{flagSet: set}
1075 }
1076
1077 func TestHideHelpCommand_RunAsSubcommand(t *testing.T) {
1078 app := &App{
1079 HideHelpCommand: true,
1080 Writer: io.Discard,
1081 Commands: []*Command{
1082 {
1083 Name: "dummy",
1084 },
1085 },
1086 }
1087
1088 err := app.RunAsSubcommand(newContextFromStringSlice([]string{"", "help"}))
1089 if err == nil {
1090 t.Fatalf("expected a non-nil error")
1091 }
1092 if !strings.Contains(err.Error(), "No help topic for 'help'") {
1093 t.Errorf("Run returned unexpected error: %v", err)
1094 }
1095
1096 err = app.RunAsSubcommand(newContextFromStringSlice([]string{"", "--help"}))
1097 if err != nil {
1098 t.Errorf("Run returned unexpected error: %v", err)
1099 }
1100 }
1101
1102 func TestHideHelpCommand_RunAsSubcommand_False(t *testing.T) {
1103 app := &App{
1104 HideHelpCommand: false,
1105 Writer: io.Discard,
1106 Commands: []*Command{
1107 {
1108 Name: "dummy",
1109 },
1110 },
1111 }
1112
1113 err := app.RunAsSubcommand(newContextFromStringSlice([]string{"", "help"}))
1114 if err != nil {
1115 t.Errorf("Run returned unexpected error: %v", err)
1116 }
1117
1118 err = app.RunAsSubcommand(newContextFromStringSlice([]string{"", "--help"}))
1119 if err != nil {
1120 t.Errorf("Run returned unexpected error: %v", err)
1121 }
1122 }
1123
1124 func TestHideHelpCommand_WithSubcommands(t *testing.T) {
1125 app := &App{
1126 Writer: io.Discard,
1127 Commands: []*Command{
1128 {
1129 Name: "dummy",
1130 Subcommands: []*Command{
1131 {
1132 Name: "dummy2",
1133 },
1134 },
1135 HideHelpCommand: true,
1136 },
1137 },
1138 }
1139
1140 err := app.Run([]string{"foo", "dummy", "help"})
1141 if err == nil {
1142 t.Fatalf("expected a non-nil error")
1143 }
1144 if !strings.Contains(err.Error(), "No help topic for 'help'") {
1145 t.Errorf("Run returned unexpected error: %v", err)
1146 }
1147
1148 err = app.Run([]string{"foo", "dummy", "--help"})
1149 if err != nil {
1150 t.Errorf("Run returned unexpected error: %v", err)
1151 }
1152
1153 var buf bytes.Buffer
1154 app.Writer = &buf
1155
1156 err = app.Run([]string{"foo", "dummy"})
1157 if err != nil {
1158 t.Errorf("Run returned unexpected error: %v", err)
1159 }
1160
1161 if !strings.Contains(buf.String(), "dummy2") {
1162 t.Errorf("Expected out to contain \"dummy2\" %v", buf.String())
1163 }
1164 }
1165
1166 func TestHideHelpCommand_RunAsSubcommand_True_CustomTemplate(t *testing.T) {
1167 var buf bytes.Buffer
1168
1169 app := &App{
1170 Writer: &buf,
1171 Commands: []*Command{
1172 {
1173 Name: "dummy",
1174 CustomHelpTemplate: "TEMPLATE",
1175 HideHelpCommand: true,
1176 },
1177 },
1178 }
1179
1180 err := app.RunAsSubcommand(newContextFromStringSlice([]string{"", "dummy", "-h"}))
1181 if err != nil {
1182 t.Errorf("Run returned unexpected error: %v", err)
1183 }
1184 if !strings.Contains(buf.String(), "TEMPLATE") {
1185 t.Errorf("Custom Help template ignored")
1186 }
1187 }
1188
1189 func TestDefaultCompleteWithFlags(t *testing.T) {
1190 origEnv := os.Environ()
1191 origArgv := os.Args
1192
1193 t.Cleanup(func() {
1194 os.Args = origArgv
1195 resetEnv(origEnv)
1196 })
1197
1198 os.Setenv("SHELL", "bash")
1199
1200 for _, tc := range []struct {
1201 name string
1202 a *App
1203 argv []string
1204 expected string
1205 }{
1206 {
1207 name: "empty",
1208 a: &App{},
1209 argv: []string{"prog", "cmd"},
1210 expected: "",
1211 },
1212 {
1213 name: "typical-flag-suggestion",
1214 a: &App{
1215 Name: "cmd",
1216 Flags: []Flag{
1217 &BoolFlag{Name: "happiness"},
1218 &Int64Flag{Name: "everybody-jump-on"},
1219 },
1220 Commands: []*Command{
1221 {Name: "putz"},
1222 },
1223 },
1224 argv: []string{"cmd", "--e", "--generate-bash-completion"},
1225 expected: "--everybody-jump-on\n",
1226 },
1227 {
1228 name: "typical-command-suggestion",
1229 a: &App{
1230 Name: "cmd",
1231 Flags: []Flag{
1232 &BoolFlag{Name: "happiness"},
1233 &Int64Flag{Name: "everybody-jump-on"},
1234 },
1235 Commands: []*Command{
1236 {
1237 Name: "putz",
1238 Subcommands: []*Command{
1239 {Name: "futz"},
1240 },
1241 Flags: []Flag{
1242 &BoolFlag{Name: "excitement"},
1243 &StringFlag{Name: "hat-shape"},
1244 },
1245 },
1246 },
1247 },
1248 argv: []string{"cmd", "--generate-bash-completion"},
1249 expected: "putz\n",
1250 },
1251 {
1252 name: "typical-subcommand-suggestion",
1253 a: &App{
1254 Name: "cmd",
1255 Flags: []Flag{
1256 &BoolFlag{Name: "happiness"},
1257 &Int64Flag{Name: "everybody-jump-on"},
1258 },
1259 Commands: []*Command{
1260 {
1261 Name: "putz",
1262 Subcommands: []*Command{
1263 {Name: "futz"},
1264 },
1265 Flags: []Flag{
1266 &BoolFlag{Name: "excitement"},
1267 &StringFlag{Name: "hat-shape"},
1268 },
1269 },
1270 },
1271 },
1272 argv: []string{"cmd", "--happiness", "putz", "--generate-bash-completion"},
1273 expected: "futz\nhelp\nh\n",
1274 },
1275 {
1276 name: "typical-subcommand-subcommand-suggestion",
1277 a: &App{
1278 Name: "cmd",
1279 Flags: []Flag{
1280 &BoolFlag{Name: "happiness"},
1281 &Int64Flag{Name: "everybody-jump-on"},
1282 },
1283 Commands: []*Command{
1284 {
1285 Name: "putz",
1286 Subcommands: []*Command{
1287 {
1288 Name: "futz",
1289 Flags: []Flag{
1290 &BoolFlag{Name: "excitement"},
1291 &StringFlag{Name: "hat-shape"},
1292 },
1293 },
1294 },
1295 },
1296 },
1297 },
1298 argv: []string{"cmd", "--happiness", "putz", "futz", "-e", "--generate-bash-completion"},
1299 expected: "--excitement\n",
1300 },
1301 {
1302 name: "autocomplete-with-spaces",
1303 a: &App{
1304 Name: "cmd",
1305 Flags: []Flag{
1306 &BoolFlag{Name: "happiness"},
1307 &Int64Flag{Name: "everybody-jump-on"},
1308 },
1309 Commands: []*Command{
1310 {
1311 Name: "putz",
1312 Subcommands: []*Command{
1313 {Name: "help"},
1314 },
1315 Flags: []Flag{
1316 &BoolFlag{Name: "excitement"},
1317 &StringFlag{Name: "hat-shape"},
1318 },
1319 },
1320 },
1321 },
1322 argv: []string{"cmd", "--happiness", "putz", "h", "--generate-bash-completion"},
1323 expected: "help\n",
1324 },
1325 } {
1326 t.Run(tc.name, func(ct *testing.T) {
1327 writer := &bytes.Buffer{}
1328
1329 tc.a.EnableBashCompletion = true
1330 tc.a.HideHelp = true
1331 tc.a.Writer = writer
1332 os.Args = tc.argv
1333 _ = tc.a.Run(tc.argv)
1334
1335 written := writer.String()
1336
1337 if written != tc.expected {
1338 ct.Errorf("written help does not match expected %q != %q", written, tc.expected)
1339 }
1340 })
1341 }
1342 }
1343
1344 func TestWrap(t *testing.T) {
1345 emptywrap := wrap("", 4, 16)
1346 if emptywrap != "" {
1347 t.Errorf("Wrapping empty line should return empty line. Got '%s'.", emptywrap)
1348 }
1349 }
1350
1351 func TestWrappedHelp(t *testing.T) {
1352
1353
1354 defer func(old helpPrinter) {
1355 HelpPrinter = old
1356 }(HelpPrinter)
1357
1358 output := new(bytes.Buffer)
1359 app := &App{
1360 Writer: output,
1361 Flags: []Flag{
1362 &BoolFlag{Name: "foo",
1363 Aliases: []string{"h"},
1364 Usage: "here's a really long help text line, let's see where it wraps. blah blah blah and so on.",
1365 },
1366 },
1367 Usage: "here's a sample App.Usage string long enough that it should be wrapped in this test",
1368 UsageText: "i'm not sure how App.UsageText differs from App.Usage, but this should also be wrapped in this test",
1369
1370 Description: `here's a sample App.Description string long enough that it should be wrapped in this test
1371
1372 with a newline
1373 and an indented line`,
1374 Copyright: `Here's a sample copyright text string long enough that it should be wrapped.
1375 Including newlines.
1376 And also indented lines.
1377
1378
1379 And then another long line. Blah blah blah does anybody ever read these things?`,
1380 }
1381
1382 c := NewContext(app, nil, nil)
1383
1384 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
1385 funcMap := map[string]interface{}{
1386 "wrapAt": func() int {
1387 return 30
1388 },
1389 }
1390
1391 HelpPrinterCustom(w, templ, data, funcMap)
1392 }
1393
1394 _ = ShowAppHelp(c)
1395
1396 expected := `NAME:
1397 - here's a sample
1398 App.Usage string long
1399 enough that it should be
1400 wrapped in this test
1401
1402 USAGE:
1403 i'm not sure how
1404 App.UsageText differs from
1405 App.Usage, but this should
1406 also be wrapped in this
1407 test
1408
1409 DESCRIPTION:
1410 here's a sample
1411 App.Description string long
1412 enough that it should be
1413 wrapped in this test
1414
1415 with a newline
1416 and an indented line
1417
1418 GLOBAL OPTIONS:
1419 --foo, -h here's a
1420 really long help text
1421 line, let's see where it
1422 wraps. blah blah blah
1423 and so on. (default:
1424 false)
1425
1426 COPYRIGHT:
1427 Here's a sample copyright
1428 text string long enough
1429 that it should be wrapped.
1430 Including newlines.
1431 And also indented lines.
1432
1433
1434 And then another long line.
1435 Blah blah blah does anybody
1436 ever read these things?
1437 `
1438
1439 if output.String() != expected {
1440 t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
1441 output.String(), expected)
1442 }
1443 }
1444
1445 func TestWrappedCommandHelp(t *testing.T) {
1446
1447
1448 defer func(old helpPrinter) {
1449 HelpPrinter = old
1450 }(HelpPrinter)
1451
1452 output := new(bytes.Buffer)
1453 app := &App{
1454 Writer: output,
1455 Commands: []*Command{
1456 {
1457 Name: "add",
1458 Aliases: []string{"a"},
1459 Usage: "add a task to the list",
1460 UsageText: "this is an even longer way of describing adding a task to the list",
1461 Description: "and a description long enough to wrap in this test case",
1462 Action: func(c *Context) error {
1463 return nil
1464 },
1465 },
1466 },
1467 }
1468
1469 c := NewContext(app, nil, nil)
1470
1471 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
1472 funcMap := map[string]interface{}{
1473 "wrapAt": func() int {
1474 return 30
1475 },
1476 }
1477
1478 HelpPrinterCustom(w, templ, data, funcMap)
1479 }
1480
1481 _ = ShowCommandHelp(c, "add")
1482
1483 expected := `NAME:
1484 - add a task to the list
1485
1486 USAGE:
1487 this is an even longer way
1488 of describing adding a task
1489 to the list
1490
1491 DESCRIPTION:
1492 and a description long
1493 enough to wrap in this test
1494 case
1495
1496 OPTIONS:
1497 --help, -h show help
1498 `
1499
1500 if output.String() != expected {
1501 t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s",
1502 output.String(), expected)
1503 }
1504 }
1505
1506 func TestWrappedSubcommandHelp(t *testing.T) {
1507
1508
1509 defer func(old helpPrinter) {
1510 HelpPrinter = old
1511 }(HelpPrinter)
1512
1513 output := new(bytes.Buffer)
1514 app := &App{
1515 Name: "cli.test",
1516 Writer: output,
1517 Commands: []*Command{
1518 {
1519 Name: "bar",
1520 Aliases: []string{"a"},
1521 Usage: "add a task to the list",
1522 UsageText: "this is an even longer way of describing adding a task to the list",
1523 Description: "and a description long enough to wrap in this test case",
1524 Action: func(c *Context) error {
1525 return nil
1526 },
1527 Subcommands: []*Command{
1528 {
1529 Name: "grok",
1530 Usage: "remove an existing template",
1531 UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more",
1532 Action: func(c *Context) error {
1533 return nil
1534 },
1535 },
1536 },
1537 },
1538 },
1539 }
1540
1541 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
1542 funcMap := map[string]interface{}{
1543 "wrapAt": func() int {
1544 return 30
1545 },
1546 }
1547
1548 HelpPrinterCustom(w, templ, data, funcMap)
1549 }
1550
1551 _ = app.Run([]string{"foo", "bar", "grok", "--help"})
1552
1553 expected := `NAME:
1554 cli.test bar grok - remove
1555 an
1556 existing
1557 template
1558
1559 USAGE:
1560 longer usage text goes
1561 here, la la la, hopefully
1562 this is long enough to wrap
1563 even more
1564
1565 OPTIONS:
1566 --help, -h show help
1567 `
1568
1569 if output.String() != expected {
1570 t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
1571 output.String(), expected)
1572 }
1573 }
1574
1575 func TestWrappedHelpSubcommand(t *testing.T) {
1576
1577
1578 defer func(old helpPrinter) {
1579 HelpPrinter = old
1580 }(HelpPrinter)
1581
1582 output := new(bytes.Buffer)
1583 app := &App{
1584 Name: "cli.test",
1585 Writer: output,
1586 Commands: []*Command{
1587 {
1588 Name: "bar",
1589 Aliases: []string{"a"},
1590 Usage: "add a task to the list",
1591 UsageText: "this is an even longer way of describing adding a task to the list",
1592 Description: "and a description long enough to wrap in this test case",
1593 Action: func(c *Context) error {
1594 return nil
1595 },
1596 Subcommands: []*Command{
1597 {
1598 Name: "grok",
1599 Usage: "remove an existing template",
1600 UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more",
1601 Action: func(c *Context) error {
1602 return nil
1603 },
1604 Flags: []Flag{
1605 &StringFlag{
1606 Name: "test-f",
1607 Usage: "my test usage",
1608 },
1609 },
1610 },
1611 },
1612 },
1613 },
1614 }
1615
1616 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
1617 funcMap := map[string]interface{}{
1618 "wrapAt": func() int {
1619 return 30
1620 },
1621 }
1622
1623 HelpPrinterCustom(w, templ, data, funcMap)
1624 }
1625
1626 _ = app.Run([]string{"foo", "bar", "help", "grok"})
1627
1628 expected := `NAME:
1629 cli.test bar grok - remove
1630 an
1631 existing
1632 template
1633
1634 USAGE:
1635 longer usage text goes
1636 here, la la la, hopefully
1637 this is long enough to wrap
1638 even more
1639
1640 OPTIONS:
1641 --test-f value my test
1642 usage
1643 --help, -h show help
1644 `
1645
1646 if output.String() != expected {
1647 t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
1648 output.String(), expected)
1649 }
1650 }
1651
1652 func TestCategorizedHelp(t *testing.T) {
1653
1654 defer func(old helpPrinter) {
1655 HelpPrinter = old
1656 }(HelpPrinter)
1657
1658 output := new(bytes.Buffer)
1659 app := &App{
1660 Name: "cli.test",
1661 Args: true,
1662 Writer: output,
1663 Action: func(ctx *Context) error { return nil },
1664 Flags: []Flag{
1665 &StringFlag{
1666 Name: "strd",
1667 },
1668 &Int64Flag{
1669 Name: "intd",
1670 Aliases: []string{"altd1", "altd2"},
1671 Category: "cat1",
1672 },
1673 },
1674 }
1675
1676 c := NewContext(app, nil, nil)
1677 app.Setup()
1678
1679 HelpPrinter = func(w io.Writer, templ string, data interface{}) {
1680 funcMap := map[string]interface{}{
1681 "wrapAt": func() int {
1682 return 30
1683 },
1684 }
1685
1686 HelpPrinterCustom(w, templ, data, funcMap)
1687 }
1688
1689 _ = ShowAppHelp(c)
1690
1691 expected := `NAME:
1692 cli.test - A new cli
1693 application
1694
1695 USAGE:
1696 cli.test [global options] command [command options] [arguments...]
1697
1698 COMMANDS:
1699 help, h Shows a list of
1700 commands or help
1701 for one command
1702
1703 GLOBAL OPTIONS:
1704 --help, -h show help
1705 --strd value
1706
1707 cat1
1708
1709 --intd value, --altd1 value, --altd2 value (default: 0)
1710
1711 `
1712 if output.String() != expected {
1713 t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s",
1714 output.String(), expected)
1715 }
1716 }
1717
View as plain text