1use clap::{arg, error::ErrorKind, Arg, ArgAction, Command};
2
3use super::utils;
4
5#[test]
6fn subcommand() {
7    let m = Command::new("test")
8        .subcommand(
9            Command::new("some").arg(
10                Arg::new("test")
11                    .short('t')
12                    .long("test")
13                    .action(ArgAction::Set)
14                    .help("testing testing"),
15            ),
16        )
17        .arg(Arg::new("other").long("other"))
18        .try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
19        .unwrap();
20
21    assert_eq!(m.subcommand_name().unwrap(), "some");
22    let sub_m = m.subcommand_matches("some").unwrap();
23    assert!(sub_m.contains_id("test"));
24    assert_eq!(
25        sub_m.get_one::<String>("test").map(|v| v.as_str()).unwrap(),
26        "testing"
27    );
28}
29
30#[test]
31fn subcommand_none_given() {
32    let m = Command::new("test")
33        .subcommand(
34            Command::new("some").arg(
35                Arg::new("test")
36                    .short('t')
37                    .long("test")
38                    .action(ArgAction::Set)
39                    .help("testing testing"),
40            ),
41        )
42        .arg(Arg::new("other").long("other"))
43        .try_get_matches_from(vec![""])
44        .unwrap();
45
46    assert!(m.subcommand_name().is_none());
47}
48
49#[test]
50fn subcommand_multiple() {
51    let m = Command::new("test")
52        .subcommands(vec![
53            Command::new("some").arg(
54                Arg::new("test")
55                    .short('t')
56                    .long("test")
57                    .action(ArgAction::Set)
58                    .help("testing testing"),
59            ),
60            Command::new("add").arg(Arg::new("roster").short('r')),
61        ])
62        .arg(Arg::new("other").long("other"))
63        .try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
64        .unwrap();
65
66    assert!(m.subcommand_matches("some").is_some());
67    assert!(m.subcommand_matches("add").is_none());
68    assert_eq!(m.subcommand_name().unwrap(), "some");
69    let sub_m = m.subcommand_matches("some").unwrap();
70    assert!(sub_m.contains_id("test"));
71    assert_eq!(
72        sub_m.get_one::<String>("test").map(|v| v.as_str()).unwrap(),
73        "testing"
74    );
75}
76
77#[test]
78fn single_alias() {
79    let m = Command::new("myprog")
80        .subcommand(Command::new("test").alias("do-stuff"))
81        .try_get_matches_from(vec!["myprog", "do-stuff"])
82        .unwrap();
83    assert_eq!(m.subcommand_name(), Some("test"));
84}
85
86#[test]
87fn multiple_aliases() {
88    let m = Command::new("myprog")
89        .subcommand(Command::new("test").aliases(["do-stuff", "test-stuff"]))
90        .try_get_matches_from(vec!["myprog", "test-stuff"])
91        .unwrap();
92    assert_eq!(m.subcommand_name(), Some("test"));
93}
94
95#[test]
96#[cfg(feature = "suggestions")]
97#[cfg(feature = "error-context")]
98fn subcmd_did_you_mean_output() {
99    #[cfg(feature = "suggestions")]
100    static DYM_SUBCMD: &str = "\
101error: unrecognized subcommand 'subcm'
102
103  note: subcommand 'subcmd' exists
104  note: to pass 'subcm' as a value, use 'dym -- subcm'
105
106Usage: dym [COMMAND]
107
108For more information, try '--help'.
109";
110
111    let cmd = Command::new("dym").subcommand(Command::new("subcmd"));
112    utils::assert_output(cmd, "dym subcm", DYM_SUBCMD, true);
113}
114
115#[test]
116#[cfg(feature = "suggestions")]
117#[cfg(feature = "error-context")]
118fn subcmd_did_you_mean_output_ambiguous() {
119    #[cfg(feature = "suggestions")]
120    static DYM_SUBCMD_AMBIGUOUS: &str = "\
121error: unrecognized subcommand 'te'
122
123  note: subcommands 'test', 'temp' exist
124  note: to pass 'te' as a value, use 'dym -- te'
125
126Usage: dym [COMMAND]
127
128For more information, try '--help'.
129";
130
131    let cmd = Command::new("dym")
132        .subcommand(Command::new("test"))
133        .subcommand(Command::new("temp"));
134    utils::assert_output(cmd, "dym te", DYM_SUBCMD_AMBIGUOUS, true);
135}
136
137#[test]
138#[cfg(feature = "suggestions")]
139#[cfg(feature = "error-context")]
140fn subcmd_did_you_mean_output_arg() {
141    static EXPECTED: &str = "\
142error: unexpected argument '--subcmarg' found
143
144  note: 'subcmd --subcmdarg' exists
145
146Usage: dym [COMMAND]
147
148For more information, try '--help'.
149";
150
151    let cmd = Command::new("dym")
152        .subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests")));
153
154    utils::assert_output(cmd, "dym --subcmarg subcmd", EXPECTED, true);
155}
156
157#[test]
158#[cfg(feature = "suggestions")]
159#[cfg(feature = "error-context")]
160fn subcmd_did_you_mean_output_arg_false_positives() {
161    static EXPECTED: &str = "\
162error: unexpected argument '--subcmarg' found
163
164Usage: dym [COMMAND]
165
166For more information, try '--help'.
167";
168
169    let cmd = Command::new("dym")
170        .subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests")));
171
172    utils::assert_output(cmd, "dym --subcmarg foo", EXPECTED, true);
173}
174
175#[test]
176fn alias_help() {
177    let m = Command::new("myprog")
178        .subcommand(Command::new("test").alias("do-stuff"))
179        .try_get_matches_from(vec!["myprog", "help", "do-stuff"]);
180    assert!(m.is_err());
181    assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp);
182}
183
184#[test]
185fn visible_aliases_help_output() {
186    static VISIBLE_ALIAS_HELP: &str = "\
187Usage: clap-test [COMMAND]
188
189Commands:
190  test  Some help [aliases: dongle, done]
191  help  Print this message or the help of the given subcommand(s)
192
193Options:
194  -h, --help     Print help
195  -V, --version  Print version
196";
197
198    let cmd = Command::new("clap-test").version("2.6").subcommand(
199        Command::new("test")
200            .about("Some help")
201            .alias("invisible")
202            .visible_alias("dongle")
203            .visible_alias("done"),
204    );
205    utils::assert_output(cmd, "clap-test --help", VISIBLE_ALIAS_HELP, false);
206}
207
208#[test]
209fn invisible_aliases_help_output() {
210    static INVISIBLE_ALIAS_HELP: &str = "\
211Usage: clap-test [COMMAND]
212
213Commands:
214  test  Some help
215  help  Print this message or the help of the given subcommand(s)
216
217Options:
218  -h, --help     Print help
219  -V, --version  Print version
220";
221
222    let cmd = Command::new("clap-test")
223        .version("2.6")
224        .subcommand(Command::new("test").about("Some help").alias("invisible"));
225    utils::assert_output(cmd, "clap-test --help", INVISIBLE_ALIAS_HELP, false);
226}
227
228#[test]
229#[cfg(feature = "unstable-replace")]
230fn replace() {
231    let m = Command::new("prog")
232        .subcommand(
233            Command::new("module").subcommand(Command::new("install").about("Install module")),
234        )
235        .replace("install", ["module", "install"])
236        .try_get_matches_from(vec!["prog", "install"])
237        .unwrap();
238
239    assert_eq!(m.subcommand_name(), Some("module"));
240    assert_eq!(
241        m.subcommand_matches("module").unwrap().subcommand_name(),
242        Some("install")
243    );
244}
245
246#[test]
247fn issue_1031_args_with_same_name() {
248    let res = Command::new("prog")
249        .arg(arg!(--"ui-path" <PATH>).required(true))
250        .subcommand(Command::new("signer"))
251        .try_get_matches_from(vec!["prog", "--ui-path", "signer"]);
252
253    assert!(res.is_ok(), "{:?}", res.unwrap_err().kind());
254    let m = res.unwrap();
255    assert_eq!(
256        m.get_one::<String>("ui-path").map(|v| v.as_str()),
257        Some("signer")
258    );
259}
260
261#[test]
262fn issue_1031_args_with_same_name_no_more_vals() {
263    let res = Command::new("prog")
264        .arg(arg!(--"ui-path" <PATH>).required(true))
265        .subcommand(Command::new("signer"))
266        .try_get_matches_from(vec!["prog", "--ui-path", "value", "signer"]);
267
268    assert!(res.is_ok(), "{:?}", res.unwrap_err().kind());
269    let m = res.unwrap();
270    assert_eq!(
271        m.get_one::<String>("ui-path").map(|v| v.as_str()),
272        Some("value")
273    );
274    assert_eq!(m.subcommand_name(), Some("signer"));
275}
276
277#[test]
278fn issue_1161_multiple_hyphen_hyphen() {
279    // from example 22
280    let res = Command::new("myprog")
281        .arg(Arg::new("eff").short('f'))
282        .arg(Arg::new("pea").short('p').action(ArgAction::Set))
283        .arg(
284            Arg::new("slop")
285                .action(ArgAction::Set)
286                .num_args(1..)
287                .last(true),
288        )
289        .try_get_matches_from(vec![
290            "-f",
291            "-p=bob",
292            "--",
293            "sloppy",
294            "slop",
295            "-a",
296            "--",
297            "subprogram",
298            "position",
299            "args",
300        ]);
301
302    assert!(res.is_ok(), "{:?}", res.unwrap_err().kind());
303    let m = res.unwrap();
304
305    let expected = Some(vec![
306        "sloppy",
307        "slop",
308        "-a",
309        "--",
310        "subprogram",
311        "position",
312        "args",
313    ]);
314    let actual = m
315        .get_many::<String>("slop")
316        .map(|vals| vals.map(|s| s.as_str()).collect::<Vec<_>>());
317
318    assert_eq!(expected, actual);
319}
320
321#[test]
322fn issue_1722_not_emit_error_when_arg_follows_similar_to_a_subcommand() {
323    let m = Command::new("myprog")
324        .subcommand(Command::new("subcommand"))
325        .arg(Arg::new("argument"))
326        .try_get_matches_from(vec!["myprog", "--", "subcommand"]);
327    assert_eq!(
328        m.unwrap().get_one::<String>("argument").map(|v| v.as_str()),
329        Some("subcommand")
330    );
331}
332
333#[test]
334fn subcommand_placeholder_test() {
335    let mut cmd = Command::new("myprog")
336        .subcommand(Command::new("subcommand"))
337        .subcommand_value_name("TEST_PLACEHOLDER")
338        .subcommand_help_heading("TEST_HEADER");
339
340    assert_eq!(
341        &cmd.render_usage().to_string(),
342        "Usage: myprog [TEST_PLACEHOLDER]"
343    );
344
345    let help_text = cmd.render_help().to_string();
346
347    assert!(help_text.contains("TEST_HEADER:"));
348}
349
350#[test]
351#[cfg(feature = "error-context")]
352fn subcommand_used_after_double_dash() {
353    static SUBCMD_AFTER_DOUBLE_DASH: &str = "\
354error: unexpected argument 'subcmd' found
355
356  note: subcommand 'subcmd' exists; to use it, remove the '--' before it
357
358Usage: cmd [COMMAND]
359
360For more information, try '--help'.
361";
362
363    let cmd = Command::new("cmd").subcommand(Command::new("subcmd"));
364
365    utils::assert_output(cmd, "cmd -- subcmd", SUBCMD_AFTER_DOUBLE_DASH, true);
366}
367
368#[test]
369fn subcommand_after_argument() {
370    let m = Command::new("myprog")
371        .arg(Arg::new("some_text"))
372        .subcommand(Command::new("test"))
373        .try_get_matches_from(vec!["myprog", "teat", "test"])
374        .unwrap();
375    assert_eq!(
376        m.get_one::<String>("some_text").map(|v| v.as_str()),
377        Some("teat")
378    );
379    assert_eq!(m.subcommand().unwrap().0, "test");
380}
381
382#[test]
383fn subcommand_after_argument_looks_like_help() {
384    let m = Command::new("myprog")
385        .arg(Arg::new("some_text"))
386        .subcommand(Command::new("test"))
387        .try_get_matches_from(vec!["myprog", "helt", "test"])
388        .unwrap();
389    assert_eq!(
390        m.get_one::<String>("some_text").map(|v| v.as_str()),
391        Some("helt")
392    );
393    assert_eq!(m.subcommand().unwrap().0, "test");
394}
395
396#[test]
397fn issue_2494_subcommand_is_present() {
398    let cmd = Command::new("opt")
399        .arg(Arg::new("global").long("global").action(ArgAction::SetTrue))
400        .subcommand(Command::new("global"));
401
402    let m = cmd
403        .clone()
404        .try_get_matches_from(["opt", "--global", "global"])
405        .unwrap();
406    assert_eq!(m.subcommand_name().unwrap(), "global");
407    assert!(*m.get_one::<bool>("global").expect("defaulted by clap"));
408
409    let m = cmd
410        .clone()
411        .try_get_matches_from(["opt", "--global"])
412        .unwrap();
413    assert!(m.subcommand_name().is_none());
414    assert!(*m.get_one::<bool>("global").expect("defaulted by clap"));
415
416    let m = cmd.try_get_matches_from(["opt", "global"]).unwrap();
417    assert_eq!(m.subcommand_name().unwrap(), "global");
418    assert!(!*m.get_one::<bool>("global").expect("defaulted by clap"));
419}
420
421#[test]
422#[cfg(feature = "error-context")]
423fn subcommand_not_recognized() {
424    let cmd = Command::new("fake")
425        .subcommand(Command::new("sub"))
426        .disable_help_subcommand(true)
427        .infer_subcommands(true);
428    utils::assert_output(
429        cmd,
430        "fake help",
431        "error: unrecognized subcommand 'help'
432
433Usage: fake [COMMAND]
434
435For more information, try '--help'.
436",
437        true,
438    );
439}
440
441#[test]
442fn busybox_like_multicall() {
443    fn applet_commands() -> [Command; 2] {
444        [Command::new("true"), Command::new("false")]
445    }
446    let cmd = Command::new("busybox")
447        .multicall(true)
448        .subcommand(Command::new("busybox").subcommands(applet_commands()))
449        .subcommands(applet_commands());
450
451    let m = cmd
452        .clone()
453        .try_get_matches_from(["busybox", "true"])
454        .unwrap();
455    assert_eq!(m.subcommand_name(), Some("busybox"));
456    assert_eq!(m.subcommand().unwrap().1.subcommand_name(), Some("true"));
457
458    let m = cmd.clone().try_get_matches_from(["true"]).unwrap();
459    assert_eq!(m.subcommand_name(), Some("true"));
460
461    let m = cmd.clone().try_get_matches_from(["a.out"]);
462    assert!(m.is_err());
463    assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidSubcommand);
464}
465
466#[test]
467fn hostname_like_multicall() {
468    let mut cmd = Command::new("hostname")
469        .multicall(true)
470        .subcommand(Command::new("hostname"))
471        .subcommand(Command::new("dnsdomainname"));
472
473    let m = cmd.clone().try_get_matches_from(["hostname"]).unwrap();
474    assert_eq!(m.subcommand_name(), Some("hostname"));
475
476    let m = cmd.clone().try_get_matches_from(["dnsdomainname"]).unwrap();
477    assert_eq!(m.subcommand_name(), Some("dnsdomainname"));
478
479    let m = cmd.clone().try_get_matches_from(["a.out"]);
480    assert!(m.is_err());
481    assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidSubcommand);
482
483    let m = cmd.try_get_matches_from_mut(["hostname", "hostname"]);
484    assert!(m.is_err());
485    assert_eq!(m.unwrap_err().kind(), ErrorKind::UnknownArgument);
486
487    let m = cmd.try_get_matches_from(["hostname", "dnsdomainname"]);
488    assert!(m.is_err());
489    assert_eq!(m.unwrap_err().kind(), ErrorKind::UnknownArgument);
490}
491
492#[test]
493#[cfg(feature = "error-context")]
494fn bad_multicall_command_error() {
495    let cmd = Command::new("repl")
496        .version("1.0.0")
497        .propagate_version(true)
498        .multicall(true)
499        .subcommand(Command::new("foo"))
500        .subcommand(Command::new("bar"));
501
502    let err = cmd.clone().try_get_matches_from(["world"]).unwrap_err();
503    assert_eq!(err.kind(), ErrorKind::InvalidSubcommand);
504    static HELLO_EXPECTED: &str = "\
505error: unrecognized subcommand 'world'
506
507Usage: <COMMAND>
508
509For more information, try 'help'.
510";
511    utils::assert_eq(HELLO_EXPECTED, err.to_string());
512
513    #[cfg(feature = "suggestions")]
514    {
515        let err = cmd.clone().try_get_matches_from(["baz"]).unwrap_err();
516        assert_eq!(err.kind(), ErrorKind::InvalidSubcommand);
517        static BAZ_EXPECTED: &str = "\
518error: unrecognized subcommand 'baz'
519
520  note: subcommand 'bar' exists
521  note: to pass 'baz' as a value, use ' -- baz'
522
523Usage: <COMMAND>
524
525For more information, try 'help'.
526";
527        utils::assert_eq(BAZ_EXPECTED, err.to_string());
528    }
529
530    // Verify whatever we did to get the above to work didn't disable `--help` and `--version`.
531
532    let err = cmd
533        .clone()
534        .try_get_matches_from(["foo", "--help"])
535        .unwrap_err();
536    assert_eq!(err.kind(), ErrorKind::DisplayHelp);
537
538    let err = cmd
539        .clone()
540        .try_get_matches_from(["foo", "--version"])
541        .unwrap_err();
542    assert_eq!(err.kind(), ErrorKind::DisplayVersion);
543}
544
545#[test]
546#[should_panic = "Command repl: Arguments like oh-no cannot be set on a multicall command"]
547fn cant_have_args_with_multicall() {
548    let mut cmd = Command::new("repl")
549        .version("1.0.0")
550        .propagate_version(true)
551        .multicall(true)
552        .subcommand(Command::new("foo"))
553        .subcommand(Command::new("bar"))
554        .arg(Arg::new("oh-no"));
555    cmd.build();
556}
557
558#[test]
559fn multicall_help_flag() {
560    static EXPECTED: &str = "\
561Usage: foo bar [value]
562
563Arguments:
564  [value]
565
566Options:
567  -h, --help     Print help
568  -V, --version  Print version
569";
570    let cmd = Command::new("repl")
571        .version("1.0.0")
572        .propagate_version(true)
573        .multicall(true)
574        .subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
575    utils::assert_output(cmd, "foo bar --help", EXPECTED, false);
576}
577
578#[test]
579fn multicall_help_subcommand() {
580    static EXPECTED: &str = "\
581Usage: foo bar [value]
582
583Arguments:
584  [value]
585
586Options:
587  -h, --help     Print help
588  -V, --version  Print version
589";
590    let cmd = Command::new("repl")
591        .version("1.0.0")
592        .propagate_version(true)
593        .multicall(true)
594        .subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
595    utils::assert_output(cmd, "help foo bar", EXPECTED, false);
596}
597
598#[test]
599fn multicall_render_help() {
600    static EXPECTED: &str = "\
601Usage: foo bar [value]
602
603Arguments:
604  [value]
605
606Options:
607  -h, --help     Print help
608  -V, --version  Print version
609";
610    let mut cmd = Command::new("repl")
611        .version("1.0.0")
612        .propagate_version(true)
613        .multicall(true)
614        .subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
615    cmd.build();
616    let subcmd = cmd.find_subcommand_mut("foo").unwrap();
617    let subcmd = subcmd.find_subcommand_mut("bar").unwrap();
618
619    let help = subcmd.render_help().to_string();
620    utils::assert_eq(EXPECTED, help);
621}
622
623#[test]
624#[should_panic = "Command test: command name `repeat` is duplicated"]
625fn duplicate_subcommand() {
626    Command::new("test")
627        .subcommand(Command::new("repeat"))
628        .subcommand(Command::new("repeat"))
629        .build()
630}
631
632#[test]
633#[should_panic = "Command test: command `unique` alias `repeat` is duplicated"]
634fn duplicate_subcommand_alias() {
635    Command::new("test")
636        .subcommand(Command::new("repeat"))
637        .subcommand(Command::new("unique").alias("repeat"))
638        .build()
639}
640