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