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