args.rs (88989B)
1 use super::{ 2 cargo::{CargoErr, Check, Clippy, Options, TestKind, Tests, Toolchain}, 3 manifest::PowerSet, 4 }; 5 use core::{ 6 fmt::{self, Display, Formatter}, 7 ops::IndexMut as _, 8 }; 9 use std::{ 10 ffi::OsString, 11 io::{self, Error, StderrLock, StdoutLock, Write as _}, 12 path::PathBuf, 13 time::Instant, 14 }; 15 /// Help message. 16 pub(crate) const HELP_MSG: &str = "Continuous integration of all features using cargo 17 18 Usage: ci-cargo [COMMAND] [OPTIONS] 19 20 Commands: 21 <none> cargo clippy and cargo test 22 help This message 23 version Prints version info 24 check cargo check 25 clippy cargo clippy 26 tests cargo test --tests 27 doc-tests cargo test --doc 28 29 Options: 30 --all-targets --all-targets is passed to cargo clippy or cargo check 31 --allow-implied features Allow implied features from optional dependencies 32 --cargo-home <PATH> Set the storage directory used by cargo 33 --cargo-path <PATH> Set the path cargo is in. Defaults to cargo 34 --color --color always is passed to each command; otherwise --color never is 35 --default-toolchain cargo is invoked as is (i.e., cargo +stable is never used) 36 --deny-warnings -Dwarnings is passed to cargo clippy 37 --dir <PATH> Set the working directory 38 --ignore-compile-errors compile_error!s are ignored 39 --ignore-features <feats> Ignore the provided comma-separated features 40 --ignore-msrv --ignore-rust-version is passed to each command for the default toolchain 41 --ignored -- --ignored is passed to cargo test 42 --include-ignored -- --include-ignored is passed to cargo test 43 --progress Writes the progress to stdout 44 --rustup-home <PATH> Set the storage directory used by rustup 45 --skip-msrv cargo +<MSRV> is not used 46 --summary Writes the toolchain(s) and combinations of features used to stdout on success 47 48 Any unique sequence of the above options are allowed so long as the following 49 conditions are met: 50 51 * no options are allowed for the help or version commands 52 * --all-targets is allowed iff check, clippy, or no command is passed 53 * --deny-warnings is allowed iff clippy or no command is passed 54 * --ignored is allowed iff tests or no command is passed and --include-ignored 55 is not passed 56 * --include-ignored is allowed iff tests or no command is passed and --ignored 57 is not passed 58 * --ignore-msrv is not allowed if the stable toolchain is used 59 60 cargo +stable will be used to run the command(s) if all of the following condtions are met: 61 62 * --default-toolchain was not passed 63 * rust-toolchain.toml does not exist in the package directory nor its ancestor directories 64 * --rustup-home was not passed for platforms that don't support rustup 65 66 If the above are not met, cargo will be used instead. cargo +<MSRV> will also be used 67 if all of the following conditions are met: 68 69 * --skip-msrv was not passed 70 * Package has an MSRV defined via rust-version that is semantically less than the stable or not 71 equivalent to the default toolchain used 72 * --rustup-home was passed or the platform supports rustup 73 74 For the toolchain(s) used, the command(s) are run for each combination of features sans any provided 75 with --ignore-features. Features provided with --ignore-features must be unique and represent valid 76 features in the package. An empty value is interpreted as the empty set of features. 77 "; 78 /// `"help"`. 79 const HELP: &str = "help"; 80 /// `"version"`. 81 const VERSION: &str = "version"; 82 /// `"check"`. 83 const CHECK: &str = "check"; 84 /// `"clippy"`. 85 const CLIPPY: &str = "clippy"; 86 /// `"tests"`. 87 const TESTS: &str = "tests"; 88 /// `"doc-tests"`. 89 const DOC_TESTS: &str = "doc-tests"; 90 /// `"--all-targets"`. 91 const ALL_TARGETS: &str = "--all-targets"; 92 /// `"--allow-implied-features"`. 93 const ALLOW_IMPLIED_FEATURES: &str = "--allow-implied-features"; 94 /// `"--cargo-home"`. 95 const CARGO_HOME: &str = "--cargo-home"; 96 /// `"--cargo-path"`. 97 const CARGO_PATH: &str = "--cargo-path"; 98 /// `"--color"`. 99 const COLOR: &str = "--color"; 100 /// `"--default-toolchain"`. 101 const DEFAULT_TOOLCHAIN: &str = "--default-toolchain"; 102 /// `"--deny-warnings"`. 103 const DENY_WARNINGS: &str = "--deny-warnings"; 104 /// `"--dir"`. 105 const DIR: &str = "--dir"; 106 /// `"--ignore-compile-errors"`. 107 const IGNORE_COMPILE_ERRORS: &str = "--ignore-compile-errors"; 108 /// `"--ignore-features"`. 109 const IGNORE_FEATURES: &str = "--ignore-features"; 110 /// `"--ignore-msrv"`. 111 const IGNORE_MSRV: &str = "--ignore-msrv"; 112 /// `"--ignored"`. 113 const IGNORED: &str = "--ignored"; 114 /// `"--include-ignored"`. 115 const INCLUDE_IGNORED: &str = "--include-ignored"; 116 /// `"--progress"`. 117 const PROGRESS: &str = "--progress"; 118 /// `"--rustup-home"`. 119 const RUSTUP_HOME: &str = "--rustup-home"; 120 /// `"--skip-msrv"`. 121 const SKIP_MSRV: &str = "--skip-msrv"; 122 /// `"--summary"`. 123 const SUMMARY: &str = "--summary"; 124 /// Error returned when parsing arguments passed to the application. 125 #[cfg_attr(test, derive(Debug, PartialEq))] 126 pub(crate) enum ArgsErr { 127 /// Error when no arguments exist. 128 NoArgs, 129 /// Error when an unknown argument is passed. The contained [`OsString`] is the value of the unknown command 130 /// or option. 131 UnknownArg(OsString), 132 /// Error when an option is passed more than once. The contained [`OsString`] is the duplicate argument. 133 DuplicateOption(OsString), 134 /// Error when `help` is passed followed by a non-empty sequence of arguments. 135 HelpWithArgs, 136 /// Error when `version` is passed followed by a non-empty sequence of arguments. 137 VersionWithArgs, 138 /// Error when `--dir` is passed with no file path to the directory `ci-cargo` should run in. 139 MissingDirPath, 140 /// Error when `--cargo-path` is passed with no file path to the directory `cargo` should be located in. 141 MissingCargoPath, 142 /// Error when `--cargo-home` is passed with no file path to the storage directory `cargo` uses. 143 MissingCargoHome, 144 /// Error when `--rustup-home` is passed with no file path to the storage directory `rustup` uses. 145 MissingRustupHome, 146 /// Error when `--all-targets` is passed for `tests` or `doc-tests`. 147 AllTargetsTests, 148 /// Error when `--deny-warnings` is passed for `check`, `tests`, or `doc-tests`. 149 DenyWarningsCheckTests, 150 /// Error when `--ignored` or `--include-ignored` is passed for `check`, `clippy`, or `doc-tests`. 151 IgnoredCheckClippyDoc, 152 /// Error when `--ignored` and `--include-ignored` are passed. 153 IgnoredIncludeIgnored, 154 /// Error when `--ignore-features` was not passed any features. 155 /// 156 /// Note to _only_ pass in the empty set in an interactive terminal, 157 /// you likely will need to use quotes. 158 MissingIgnoredFeatures, 159 /// Error when `--ignore-features` is passed duplicate features. 160 DuplicateIgnoredFeatures(OsString), 161 } 162 impl ArgsErr { 163 /// Writes `self` to `stderr`. 164 pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> { 165 const FINAL_SENTENCE: &str = " See ci-cargo help for more information."; 166 match self { 167 Self::NoArgs => writeln!( 168 stderr, 169 "No arguments exist including the name of the process itself.{FINAL_SENTENCE}" 170 ), 171 Self::UnknownArg(arg) => { 172 writeln!( 173 stderr, 174 "{} is an unknown argument.{FINAL_SENTENCE}", 175 arg.display() 176 ) 177 } 178 Self::DuplicateOption(arg) => { 179 writeln!( 180 stderr, 181 "{} was passed more than once.{FINAL_SENTENCE}", 182 arg.display() 183 ) 184 } 185 Self::HelpWithArgs => { 186 writeln!( 187 stderr, 188 "{HELP} was passed with one or more arguments.{FINAL_SENTENCE}", 189 ) 190 } 191 Self::VersionWithArgs => { 192 writeln!( 193 stderr, 194 "{VERSION} was passed with one or more arguments.{FINAL_SENTENCE}", 195 ) 196 } 197 Self::MissingDirPath => { 198 writeln!( 199 stderr, 200 "{DIR} was passed without a path to the directory ci-cargo should run in.{FINAL_SENTENCE}" 201 ) 202 } 203 Self::MissingCargoPath => { 204 writeln!( 205 stderr, 206 "{CARGO_PATH} was passed without a path to the directory cargo is located in.{FINAL_SENTENCE}" 207 ) 208 } 209 Self::MissingCargoHome => { 210 writeln!( 211 stderr, 212 "{CARGO_HOME} was passed without a path to the cargo storage directory.{FINAL_SENTENCE}" 213 ) 214 } 215 Self::MissingRustupHome => { 216 writeln!( 217 stderr, 218 "{RUSTUP_HOME} was passed without a path to the rustup storage directory.{FINAL_SENTENCE}" 219 ) 220 } 221 Self::AllTargetsTests => { 222 writeln!( 223 stderr, 224 "{ALL_TARGETS} was passed with {TESTS} or {DOC_TESTS}.{FINAL_SENTENCE}" 225 ) 226 } 227 Self::DenyWarningsCheckTests => { 228 writeln!( 229 stderr, 230 "{DENY_WARNINGS} was passed with {CHECK}, {TESTS}, or {DOC_TESTS}.{FINAL_SENTENCE}" 231 ) 232 } 233 Self::IgnoredCheckClippyDoc => { 234 writeln!( 235 stderr, 236 "{IGNORED} or {INCLUDE_IGNORED} was passed with {CHECK}, {CLIPPY}, or {DOC_TESTS}.{FINAL_SENTENCE}" 237 ) 238 } 239 Self::IgnoredIncludeIgnored => { 240 writeln!( 241 stderr, 242 "{IGNORED} and {INCLUDE_IGNORED} were both passed.{FINAL_SENTENCE}" 243 ) 244 } 245 Self::MissingIgnoredFeatures => { 246 writeln!( 247 stderr, 248 "{IGNORE_FEATURES} was passed without any features to ignore.{FINAL_SENTENCE}" 249 ) 250 } 251 Self::DuplicateIgnoredFeatures(feats) => { 252 writeln!( 253 stderr, 254 "{IGNORE_FEATURES} was passed {} which contains at least one duplicate feature.{FINAL_SENTENCE}", 255 feats.display() 256 ) 257 } 258 } 259 } 260 } 261 /// Options to use for `cargo`. 262 #[expect( 263 clippy::struct_excessive_bools, 264 reason = "not a problem. arguable false positive based on its use" 265 )] 266 #[cfg_attr(test, derive(Debug, PartialEq))] 267 pub(crate) struct Opts { 268 /// The directory to run `ci-cargo` in. 269 pub exec_dir: Option<PathBuf>, 270 /// Storage directory for `rustup`. 271 pub rustup_home: Option<PathBuf>, 272 /// Path to `cargo`. 273 pub cargo_path: PathBuf, 274 /// Storage directory for `cargo`. 275 pub cargo_home: Option<PathBuf>, 276 /// `true` iff color should be outputted. 277 pub color: bool, 278 /// `true` iff `cargo` should be used instead of `cargo +stable`. 279 pub default_toolchain: bool, 280 /// `true` iff implied features should be allowed an tested. 281 pub allow_implied_features: bool, 282 /// `true` iff `compile_error`s should be ignored. 283 pub ignore_compile_errors: bool, 284 /// `true` iff `--ignore-rust-version` should be passed. 285 pub ignore_msrv: bool, 286 /// `true` iff progress should be written to `stdout`. 287 pub progress: bool, 288 /// `true` iff the MSRV toolchain should not be used. 289 pub skip_msrv: bool, 290 /// `true` iff the toolchains used and combinations of features run on should be written 291 /// to `stdout` upon success. 292 pub summary: bool, 293 /// The features to ignore. 294 /// 295 /// Note this is empty iff there are no features to ignore. The contained features 296 /// are distinct. The empty `String` corresponds to the empty set of features to 297 /// ignore (i.e., --no-default-features). 298 pub ignore_features: Vec<String>, 299 } 300 impl Default for Opts { 301 fn default() -> Self { 302 Self { 303 exec_dir: None, 304 rustup_home: None, 305 cargo_path: cargo_path(), 306 cargo_home: None, 307 color: false, 308 default_toolchain: false, 309 allow_implied_features: false, 310 ignore_compile_errors: false, 311 ignore_msrv: false, 312 progress: false, 313 skip_msrv: false, 314 summary: false, 315 ignore_features: Vec::new(), 316 } 317 } 318 } 319 /// Controls if `cargo test --tests -- --ignored` or `cargo test --tests --include-ignored` should be run. 320 #[cfg_attr(test, derive(Debug, PartialEq))] 321 #[derive(Clone, Copy)] 322 pub(crate) enum Ignored { 323 /// Don't run any `ignore` tests. 324 None, 325 /// Only run `ignore` tests. 326 Only, 327 /// Run all tests. 328 Include, 329 } 330 /// Positive `usize` or `usize::MAX + 1`. 331 /// 332 /// Since we don't use 0, it's repurposed as `usize::MAX + 1`. 333 #[cfg_attr(test, derive(Debug, PartialEq))] 334 #[derive(Clone, Copy)] 335 pub(crate) struct NonZeroUsizePlus1(usize); 336 impl NonZeroUsizePlus1 { 337 /// Returns `Self` containing `val`. 338 /// 339 /// Note calling code must know that `0` is treated liked 340 /// `usize::MAX + 1`. 341 pub(crate) const fn new(val: usize) -> Self { 342 Self(val) 343 } 344 } 345 impl Display for NonZeroUsizePlus1 { 346 #[expect(unsafe_code, reason = "comment justifies correctness")] 347 #[expect( 348 clippy::arithmetic_side_effects, 349 reason = "comment justifies correctness" 350 )] 351 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 352 /// Helper for `unlikely`. 353 #[inline(always)] 354 #[cold] 355 const fn cold_path() {} 356 /// Hint that a branch is unlikely. 357 #[expect( 358 clippy::inline_always, 359 reason = "purpose is for the compiler to not optimize" 360 )] 361 #[inline(always)] 362 const fn unlikely(b: bool) -> bool { 363 if b { 364 cold_path(); 365 true 366 } else { 367 false 368 } 369 } 370 if unlikely(self.0 == 0) { 371 let mut val = usize::MAX.to_string(); 372 // This won't underflow since the length is at least 1. 373 let idx = val.len() - 1; 374 // SAFETY: 375 // 2^n is even for all n != 0. When n = 0, 2^n = 1. This means we can always increment the last 376 // digit without carrying. 377 // We only mutate the last digit which is guaranteed to be valid ASCII; thuse we can increment 378 // the `u8` since digits are consecutive in ASCII. 379 *unsafe { val.as_bytes_mut() }.index_mut(idx) += 1; 380 write!(f, "{val}") 381 } else { 382 write!(f, "{}", self.0) 383 } 384 } 385 } 386 /// Progress tracker for when `--progress` was passed. 387 struct Progress<'package, 'toolchain> { 388 /// The name of the package. 389 package: &'package str, 390 /// The current toolchain counter. 391 toolchain_counter: &'static str, 392 /// The total toolchains that will be used. 393 toolchain_total: &'static str, 394 /// `"cargo"` or `"cargo "`. 395 /// 396 /// Exists for consistent formatting with [`Self::toolchain`]. 397 cargo_cmd: &'toolchain str, 398 /// The current toolchain. 399 toolchain: &'toolchain str, 400 /// The current command counter. 401 cmd_counter: &'static str, 402 /// The total commands that will be used. 403 cmd_total: &'static str, 404 /// The current command. 405 cmd: &'static str, 406 /// The total number of features in the power set. 407 features_total: String, 408 /// The time in which we started. 409 time_started: Instant, 410 /// `stdout` stream. 411 /// 412 /// None iff we encountered any error when writing 413 /// to it. 414 stdout: Option<StdoutLock<'static>>, 415 } 416 impl<'package> Progress<'package, '_> { 417 /// Returns `Self` based on running both clippy and tests. 418 fn all( 419 package: &'package str, 420 toolchain: Toolchain<'_>, 421 use_msrv: bool, 422 features_total: NonZeroUsizePlus1, 423 ) -> Self { 424 Self::inner_new(package, "clippy", "2", toolchain, use_msrv, features_total) 425 } 426 /// Returns `Self` based on running clippy. 427 fn clippy( 428 package: &'package str, 429 toolchain: Toolchain<'_>, 430 use_msrv: bool, 431 features_total: NonZeroUsizePlus1, 432 ) -> Self { 433 Self::inner_new(package, "clippy", "1", toolchain, use_msrv, features_total) 434 } 435 /// Returns `Self` based on running tests --tests. 436 fn tests( 437 package: &'package str, 438 toolchain: Toolchain<'_>, 439 use_msrv: bool, 440 features_total: NonZeroUsizePlus1, 441 ) -> Self { 442 Self::inner_new( 443 package, 444 "test --tests", 445 "1", 446 toolchain, 447 use_msrv, 448 features_total, 449 ) 450 } 451 /// Returns `Self` based on running tests --doc. 452 fn doc_tests( 453 package: &'package str, 454 toolchain: Toolchain<'_>, 455 use_msrv: bool, 456 features_total: NonZeroUsizePlus1, 457 ) -> Self { 458 Self::inner_new( 459 package, 460 "test --doc", 461 "1", 462 toolchain, 463 use_msrv, 464 features_total, 465 ) 466 } 467 /// Returns `Self` based on running check. 468 fn check( 469 package: &'package str, 470 toolchain: Toolchain<'_>, 471 use_msrv: bool, 472 features_total: NonZeroUsizePlus1, 473 ) -> Self { 474 Self::inner_new(package, "check", "1", toolchain, use_msrv, features_total) 475 } 476 /// Returns `Self` based on the passed arguments. 477 fn inner_new( 478 package: &'package str, 479 cmd: &'static str, 480 cmd_total: &'static str, 481 tool: Toolchain<'_>, 482 use_msrv: bool, 483 features_total: NonZeroUsizePlus1, 484 ) -> Self { 485 let (cargo_cmd, toolchain) = if matches!(tool, Toolchain::Stable) { 486 ("cargo ", "+stable") 487 } else { 488 ("cargo", "") 489 }; 490 Self { 491 package, 492 toolchain_counter: "1", 493 toolchain_total: if use_msrv { "2" } else { "1" }, 494 cargo_cmd, 495 toolchain, 496 cmd_counter: "1", 497 cmd_total, 498 cmd, 499 features_total: features_total.to_string(), 500 time_started: Instant::now(), 501 stdout: Some(io::stdout().lock()), 502 } 503 } 504 /// Writes the progress so far to `stdout`. 505 /// 506 /// If writing to `stdout` errors, then `stdout` will never be written to. 507 fn write_to_stdout( 508 &mut self, 509 features: &str, 510 features_counter: usize, 511 features_skipped: usize, 512 ) { 513 if let Some(ref mut std) = self.stdout { 514 // Example: 515 // "Package: foo. Toolchain (1/2): cargo +stable. Features (18/128, 3 skipped): foo,bar. Command (1/2): clippy. Time running: 49 s."); 516 // Note `features_skipped` maxes at `usize::MAX` since the empty set is never skipped. 517 if writeln!(std, "Package: {}. Toolchain ({}/{}): {}{}. Features ({}/{}, {} skipped): {}. Command ({}/{}): {}. Time running: {} s.", self.package, self.toolchain_counter, self.toolchain_total, self.cargo_cmd, self.toolchain, NonZeroUsizePlus1(features_counter), self.features_total, features_skipped, if features.is_empty() { "<none>" } else { features }, self.cmd_counter, self.cmd_total, self.cmd, self.time_started.elapsed().as_secs()).is_err() { 518 drop(self.stdout.take()); 519 } 520 } 521 } 522 } 523 /// `cargo` command(s) we should run. 524 #[cfg_attr(test, derive(Debug, PartialEq))] 525 pub(crate) enum Cmd { 526 /// Execute `cargo clippy` and `cargo test` commands. 527 /// 528 /// The first `bool` is `true` iff `--all-targets` was passed, 529 /// the second `bool` is `true` iff `--deny-warnings` was passed, and 530 /// the `Ignored` represents if `--ignored` or `--include-ignored` were 531 /// passed. 532 All(bool, bool, Ignored), 533 /// `cargo clippy`. 534 /// 535 /// The first `bool` is `true` iff `--all-targets` was passed, 536 /// and the second `bool` is `true` iff `--deny-warnings` was passed. 537 Clippy(bool, bool), 538 /// `cargo test --tests`. 539 Tests(Ignored), 540 /// `cargo test --doc`. 541 DocTests, 542 /// `cargo check`. 543 /// 544 /// The contained `bool` is `true` iff `--all-targets` was passed, 545 Check(bool), 546 } 547 impl Cmd { 548 /// Runs the appropriate `cargo` command(s) for all features in `power_set`. 549 /// 550 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 551 /// later used. 552 pub(crate) fn run<'a>( 553 self, 554 options: Options<'a, '_, '_>, 555 msrv: Option<&'a str>, 556 power_set: &mut PowerSet<'_>, 557 progress: bool, 558 ) -> Result<(), Box<CargoErr>> { 559 match self { 560 Self::All(all_targets, deny_warning, ignored_tests) => Self::run_all( 561 progress.then(|| { 562 Progress::all( 563 options.package_name, 564 options.toolchain, 565 msrv.is_some(), 566 power_set.len(), 567 ) 568 }), 569 msrv, 570 options, 571 all_targets, 572 deny_warning, 573 ignored_tests, 574 power_set, 575 ), 576 Self::Clippy(all_targets, deny_warnings) => Self::run_clippy( 577 progress.then(|| { 578 Progress::clippy( 579 options.package_name, 580 options.toolchain, 581 msrv.is_some(), 582 power_set.len(), 583 ) 584 }), 585 msrv, 586 options, 587 all_targets, 588 deny_warnings, 589 power_set, 590 ), 591 Self::Tests(ignored_tests) => Self::run_unit_tests( 592 progress.then(|| { 593 Progress::tests( 594 options.package_name, 595 options.toolchain, 596 msrv.is_some(), 597 power_set.len(), 598 ) 599 }), 600 msrv, 601 options, 602 ignored_tests, 603 power_set, 604 ), 605 Self::DocTests => Self::run_doc_tests( 606 progress.then(|| { 607 Progress::doc_tests( 608 options.package_name, 609 options.toolchain, 610 msrv.is_some(), 611 power_set.len(), 612 ) 613 }), 614 msrv, 615 options, 616 power_set, 617 ), 618 Self::Check(all_targets) => Self::run_check( 619 progress.then(|| { 620 Progress::check( 621 options.package_name, 622 options.toolchain, 623 msrv.is_some(), 624 power_set.len(), 625 ) 626 }), 627 msrv, 628 options, 629 all_targets, 630 power_set, 631 ), 632 } 633 } 634 /// Runs `cargo clippy` and `cargo test` for all features in `power_set`. 635 /// 636 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 637 /// later used. 638 #[expect(clippy::else_if_without_else, reason = "don't want an empty else")] 639 fn run_all<'a>( 640 mut progress: Option<Progress<'_, 'a>>, 641 msrv: Option<&'a str>, 642 mut options: Options<'a, '_, '_>, 643 all_targets: bool, 644 deny_warnings: bool, 645 ignored_tests: Ignored, 646 power_set: &mut PowerSet<'_>, 647 ) -> Result<(), Box<CargoErr>> { 648 if let Some(ref mut prog) = progress { 649 let mut feat_counter = 1; 650 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 651 prog.cmd_counter = "1"; 652 prog.cmd = "clippy"; 653 prog.write_to_stdout(set, feat_counter, skip_count); 654 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 655 return Err(e); 656 } 657 // Note we run tests even if a `compile_error` occurred since it may not occur for tests. 658 prog.cmd_counter = "2"; 659 prog.cmd = "test"; 660 prog.write_to_stdout(set, feat_counter, skip_count); 661 if let Err(e) = Tests::run(&mut options, TestKind::All(ignored_tests), set) { 662 return Err(e); 663 } 664 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 665 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 666 // [`NonZeroUsizePlus1::fmt`]. 667 feat_counter = feat_counter.wrapping_add(1); 668 } 669 if let Some(msrv_val) = msrv { 670 feat_counter = 1; 671 prog.toolchain_counter = "2"; 672 prog.cargo_cmd = "cargo "; 673 prog.toolchain = msrv_val; 674 options.toolchain = Toolchain::Msrv(msrv_val); 675 power_set.reset(); 676 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 677 prog.cmd_counter = "1"; 678 prog.cmd = "clippy"; 679 prog.write_to_stdout(set, feat_counter, skip_count); 680 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 681 return Err(e); 682 } 683 // Note we run tests even if a `compile_error` occurred since it may not occur for tests. 684 prog.cmd_counter = "2"; 685 prog.cmd = "test"; 686 prog.write_to_stdout(set, feat_counter, skip_count); 687 if let Err(e) = Tests::run(&mut options, TestKind::All(ignored_tests), set) { 688 return Err(e); 689 } 690 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 691 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 692 // [`NonZeroUsizePlus1::fmt`]. 693 feat_counter = feat_counter.wrapping_add(1); 694 } 695 } 696 } else { 697 while let Some(set) = power_set.next_set() { 698 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 699 return Err(e); 700 // Note we run tests even if a `compile_error` occurred since it may not occur for tests. 701 } else if let Err(e) = Tests::run(&mut options, TestKind::All(ignored_tests), set) { 702 return Err(e); 703 } 704 } 705 if let Some(msrv_val) = msrv { 706 options.toolchain = Toolchain::Msrv(msrv_val); 707 power_set.reset(); 708 while let Some(set) = power_set.next_set() { 709 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 710 return Err(e); 711 // Note we run tests even if a `compile_error` occurred since it may not occur for tests. 712 } else if let Err(e) = 713 Tests::run(&mut options, TestKind::All(ignored_tests), set) 714 { 715 return Err(e); 716 } 717 } 718 } 719 } 720 Ok(()) 721 } 722 /// Runs `cargo clippy` for all features in `power_set`. 723 /// 724 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 725 /// later used. 726 fn run_clippy<'a>( 727 mut progress: Option<Progress<'_, 'a>>, 728 msrv: Option<&'a str>, 729 mut options: Options<'a, '_, '_>, 730 all_targets: bool, 731 deny_warnings: bool, 732 power_set: &mut PowerSet<'_>, 733 ) -> Result<(), Box<CargoErr>> { 734 if let Some(ref mut prog) = progress { 735 let mut feat_counter = 1; 736 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 737 prog.write_to_stdout(set, feat_counter, skip_count); 738 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 739 return Err(e); 740 } 741 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 742 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 743 // [`NonZeroUsizePlus1::fmt`]. 744 feat_counter = feat_counter.wrapping_add(1); 745 } 746 if let Some(msrv_val) = msrv { 747 feat_counter = 1; 748 prog.toolchain_counter = "2"; 749 prog.cargo_cmd = "cargo "; 750 prog.toolchain = msrv_val; 751 options.toolchain = Toolchain::Msrv(msrv_val); 752 power_set.reset(); 753 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 754 prog.write_to_stdout(set, feat_counter, skip_count); 755 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 756 return Err(e); 757 } 758 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 759 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 760 // [`NonZeroUsizePlus1::fmt`]. 761 feat_counter = feat_counter.wrapping_add(1); 762 } 763 } 764 } else { 765 while let Some(set) = power_set.next_set() { 766 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 767 return Err(e); 768 } 769 } 770 if let Some(msrv_val) = msrv { 771 options.toolchain = Toolchain::Msrv(msrv_val); 772 power_set.reset(); 773 while let Some(set) = power_set.next_set() { 774 if let Err(e) = Clippy::run(&mut options, all_targets, deny_warnings, set) { 775 return Err(e); 776 } 777 } 778 } 779 } 780 Ok(()) 781 } 782 /// Runs `cargo test --tests` for all features in `power_set`. 783 /// 784 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 785 /// later used. 786 fn run_unit_tests<'a>( 787 mut progress: Option<Progress<'_, 'a>>, 788 msrv: Option<&'a str>, 789 mut options: Options<'a, '_, '_>, 790 ignored_tests: Ignored, 791 power_set: &mut PowerSet<'_>, 792 ) -> Result<(), Box<CargoErr>> { 793 if let Some(ref mut prog) = progress { 794 let mut feat_counter = 1; 795 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 796 prog.write_to_stdout(set, feat_counter, skip_count); 797 if let Err(e) = Tests::run(&mut options, TestKind::Unit(ignored_tests), set) { 798 return Err(e); 799 } 800 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 801 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 802 // [`NonZeroUsizePlus1::fmt`]. 803 feat_counter = feat_counter.wrapping_add(1); 804 } 805 if let Some(msrv_val) = msrv { 806 feat_counter = 1; 807 prog.toolchain_counter = "2"; 808 prog.cargo_cmd = "cargo "; 809 prog.toolchain = msrv_val; 810 options.toolchain = Toolchain::Msrv(msrv_val); 811 power_set.reset(); 812 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 813 prog.write_to_stdout(set, feat_counter, skip_count); 814 if let Err(e) = Tests::run(&mut options, TestKind::Unit(ignored_tests), set) { 815 return Err(e); 816 } 817 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 818 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 819 // [`NonZeroUsizePlus1::fmt`]. 820 feat_counter = feat_counter.wrapping_add(1); 821 } 822 } 823 } else { 824 while let Some(set) = power_set.next_set() { 825 if let Err(e) = Tests::run(&mut options, TestKind::Unit(ignored_tests), set) { 826 return Err(e); 827 } 828 } 829 if let Some(msrv_val) = msrv { 830 options.toolchain = Toolchain::Msrv(msrv_val); 831 power_set.reset(); 832 while let Some(set) = power_set.next_set() { 833 if let Err(e) = Tests::run(&mut options, TestKind::Unit(ignored_tests), set) { 834 return Err(e); 835 } 836 } 837 } 838 } 839 Ok(()) 840 } 841 /// Runs `cargo test --doc` for all features in `power_set`. 842 /// 843 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 844 /// later used. 845 fn run_doc_tests<'a>( 846 mut progress: Option<Progress<'_, 'a>>, 847 msrv: Option<&'a str>, 848 mut options: Options<'a, '_, '_>, 849 power_set: &mut PowerSet<'_>, 850 ) -> Result<(), Box<CargoErr>> { 851 if let Some(ref mut prog) = progress { 852 let mut feat_counter = 1; 853 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 854 prog.write_to_stdout(set, feat_counter, skip_count); 855 match Tests::run(&mut options, TestKind::Doc, set) { 856 Ok(no_library_target) => { 857 if no_library_target { 858 return Ok(()); 859 } 860 } 861 Err(e) => return Err(e), 862 } 863 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 864 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 865 // [`NonZeroUsizePlus1::fmt`]. 866 feat_counter = feat_counter.wrapping_add(1); 867 } 868 if let Some(msrv_val) = msrv { 869 feat_counter = 1; 870 prog.toolchain_counter = "2"; 871 prog.cargo_cmd = "cargo "; 872 prog.toolchain = msrv_val; 873 options.toolchain = Toolchain::Msrv(msrv_val); 874 power_set.reset(); 875 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 876 prog.write_to_stdout(set, feat_counter, skip_count); 877 // If there is no library target, then we would have been informed above. 878 if let Err(e) = Tests::run(&mut options, TestKind::Doc, set) { 879 return Err(e); 880 } 881 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 882 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 883 // [`NonZeroUsizePlus1::fmt`]. 884 feat_counter = feat_counter.wrapping_add(1); 885 } 886 } 887 } else { 888 while let Some(set) = power_set.next_set() { 889 match Tests::run(&mut options, TestKind::Doc, set) { 890 Ok(no_library_target) => { 891 if no_library_target { 892 // We don't want to continue invoking `cargo test --doc` once we know this is not a 893 // library target. 894 return Ok(()); 895 } 896 } 897 Err(e) => return Err(e), 898 } 899 } 900 if let Some(msrv_val) = msrv { 901 options.toolchain = Toolchain::Msrv(msrv_val); 902 power_set.reset(); 903 while let Some(set) = power_set.next_set() { 904 // If there is no library target, then we would have been informed above. 905 if let Err(e) = Tests::run(&mut options, TestKind::Doc, set) { 906 return Err(e); 907 } 908 } 909 } 910 } 911 Ok(()) 912 } 913 /// Runs `cargo check` for all features in `power_set`. 914 /// 915 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 916 /// later used. 917 fn run_check<'a>( 918 mut progress: Option<Progress<'_, 'a>>, 919 msrv: Option<&'a str>, 920 mut options: Options<'a, '_, '_>, 921 all_targets: bool, 922 power_set: &mut PowerSet<'_>, 923 ) -> Result<(), Box<CargoErr>> { 924 if let Some(ref mut prog) = progress { 925 let mut feat_counter = 1; 926 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 927 prog.write_to_stdout(set, feat_counter, skip_count); 928 if let Err(e) = Check::run(&mut options, all_targets, set) { 929 return Err(e); 930 } 931 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 932 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 933 // [`NonZeroUsizePlus1::fmt`]. 934 feat_counter = feat_counter.wrapping_add(1); 935 } 936 if let Some(msrv_val) = msrv { 937 feat_counter = 1; 938 prog.toolchain_counter = "2"; 939 prog.cargo_cmd = "cargo "; 940 prog.toolchain = msrv_val; 941 options.toolchain = Toolchain::Msrv(msrv_val); 942 power_set.reset(); 943 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 944 prog.write_to_stdout(set, feat_counter, skip_count); 945 if let Err(e) = Check::run(&mut options, all_targets, set) { 946 return Err(e); 947 } 948 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 949 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 950 // [`NonZeroUsizePlus1::fmt`]. 951 feat_counter = feat_counter.wrapping_add(1); 952 } 953 } 954 } else { 955 while let Some(set) = power_set.next_set() { 956 if let Err(e) = Check::run(&mut options, all_targets, set) { 957 return Err(e); 958 } 959 } 960 if let Some(msrv_val) = msrv { 961 options.toolchain = Toolchain::Msrv(msrv_val); 962 power_set.reset(); 963 while let Some(set) = power_set.next_set() { 964 if let Err(e) = Check::run(&mut options, all_targets, set) { 965 return Err(e); 966 } 967 } 968 } 969 } 970 Ok(()) 971 } 972 } 973 /// `ci-cargo` command to run. 974 #[cfg_attr(test, derive(Debug, PartialEq))] 975 pub(crate) enum MetaCmd { 976 /// Write help to stdout. 977 Help, 978 /// Write version info to stdout. 979 Version, 980 /// Execute `cargo` command(s). 981 Cargo(Cmd, Opts), 982 } 983 /// Helper to store options from the command line. 984 #[expect( 985 clippy::struct_excessive_bools, 986 reason = "used exclusively in the recursive function MetaCmd::from_args::extract_options" 987 )] 988 #[derive(Default)] 989 struct ArgOpts { 990 /// `--all-targets`. 991 all_targets: bool, 992 /// `--allow-implied-features`. 993 allow_implied_features: bool, 994 /// `--cargo-home` along with the path. 995 cargo_home: Option<PathBuf>, 996 /// `--cargo-path` along with the path. 997 cargo_path: Option<PathBuf>, 998 /// `--color`. 999 color: bool, 1000 /// `--default-toolchain`. 1001 default_toolchain: bool, 1002 /// `--deny-warnings`. 1003 deny_warnings: bool, 1004 /// `--dir` along with the path. 1005 dir: Option<PathBuf>, 1006 /// `--ignore-compile-errors`. 1007 ignore_compile_errors: bool, 1008 /// `--ignore-msrv`. 1009 ignore_msrv: bool, 1010 /// `--ignore-features` along with the features to ignore. 1011 ignore_features: Vec<String>, 1012 /// `--ignored`. 1013 ignored: bool, 1014 /// `--include-ignored`. 1015 include_ignored: bool, 1016 /// `--progress`. 1017 progress: bool, 1018 /// `--rustup-home` along with the path. 1019 rustup_home: Option<PathBuf>, 1020 /// `--skip-msrv`. 1021 skip_msrv: bool, 1022 /// `--summary`. 1023 summary: bool, 1024 } 1025 impl ArgOpts { 1026 /// Returns `Ignored` based on [`Self::ignored`] and [`Self::include_ignored`]. 1027 const fn ignored(&self) -> Ignored { 1028 if self.ignored { 1029 Ignored::Only 1030 } else if self.include_ignored { 1031 Ignored::Include 1032 } else { 1033 Ignored::None 1034 } 1035 } 1036 } 1037 /// `"cargo"`. 1038 const CARGO: &str = "cargo"; 1039 /// Returns `"cargo"`. 1040 fn cargo_path() -> PathBuf { 1041 CARGO.to_owned().into() 1042 } 1043 impl From<ArgOpts> for Opts { 1044 fn from(value: ArgOpts) -> Self { 1045 Self { 1046 exec_dir: value.dir, 1047 rustup_home: value.rustup_home, 1048 cargo_path: value.cargo_path.unwrap_or_else(cargo_path), 1049 cargo_home: value.cargo_home, 1050 color: value.color, 1051 default_toolchain: value.default_toolchain, 1052 allow_implied_features: value.allow_implied_features, 1053 ignore_compile_errors: value.ignore_compile_errors, 1054 ignore_msrv: value.ignore_msrv, 1055 progress: value.progress, 1056 skip_msrv: value.skip_msrv, 1057 summary: value.summary, 1058 ignore_features: value.ignore_features, 1059 } 1060 } 1061 } 1062 impl MetaCmd { 1063 /// Recursively extracts options from `args`. 1064 /// 1065 /// This must only be called from [`Self::from_args`]. 1066 #[expect(unsafe_code, reason = "comment justifies correctness")] 1067 #[expect( 1068 clippy::arithmetic_side_effects, 1069 reason = "comment justifies correctness" 1070 )] 1071 #[expect(clippy::else_if_without_else, reason = "more concise")] 1072 #[expect( 1073 clippy::too_many_lines, 1074 reason = "expected since we need to extract all the passed options" 1075 )] 1076 fn extract_options<T: Iterator<Item = OsString>>( 1077 opts: &mut ArgOpts, 1078 arg: Option<OsString>, 1079 mut args: T, 1080 ) -> Result<(), ArgsErr> { 1081 arg.map_or_else( 1082 || Ok(()), 1083 |val| { 1084 if let Some(val_str) = val.to_str() { 1085 match val_str { 1086 ALL_TARGETS => { 1087 if opts.all_targets { 1088 return Err(ArgsErr::DuplicateOption(val)); 1089 } 1090 opts.all_targets = true; 1091 } 1092 ALLOW_IMPLIED_FEATURES => { 1093 if opts.allow_implied_features { 1094 return Err(ArgsErr::DuplicateOption(val)); 1095 } 1096 opts.allow_implied_features = true; 1097 } 1098 CARGO_HOME => { 1099 if opts.cargo_home.is_some() { 1100 return Err(ArgsErr::DuplicateOption(val)); 1101 } else if let Some(path) = args.next() { 1102 opts.cargo_home = Some(path.into()); 1103 } else { 1104 return Err(ArgsErr::MissingCargoHome); 1105 } 1106 } 1107 CARGO_PATH => { 1108 if opts.cargo_path.is_some() { 1109 return Err(ArgsErr::DuplicateOption(val)); 1110 } else if let Some(p) = args.next() { 1111 // This won't overflow since `p.len() + 6 < usize::MAX` since 1112 // `p.len() <= isize::MAX`, `usize::MAX >= u16::MAX`, and 1113 // `i16::MAX + 6 < u16::MAX`. 1114 let mut path = PathBuf::with_capacity(CARGO.len() + 1 + p.len()); 1115 path.push(p); 1116 path.push(CARGO); 1117 opts.cargo_path = Some(path); 1118 } else { 1119 return Err(ArgsErr::MissingCargoPath); 1120 } 1121 } 1122 COLOR => { 1123 if opts.color { 1124 return Err(ArgsErr::DuplicateOption(val)); 1125 } 1126 opts.color = true; 1127 } 1128 DEFAULT_TOOLCHAIN => { 1129 if opts.default_toolchain { 1130 return Err(ArgsErr::DuplicateOption(val)); 1131 } 1132 opts.default_toolchain = true; 1133 } 1134 DENY_WARNINGS => { 1135 if opts.deny_warnings { 1136 return Err(ArgsErr::DuplicateOption(val)); 1137 } 1138 opts.deny_warnings = true; 1139 } 1140 DIR => { 1141 if opts.dir.is_some() { 1142 return Err(ArgsErr::DuplicateOption(val)); 1143 } else if let Some(path) = args.next() { 1144 opts.dir = Some(path.into()); 1145 } else { 1146 return Err(ArgsErr::MissingDirPath); 1147 } 1148 } 1149 IGNORE_COMPILE_ERRORS => { 1150 if opts.ignore_compile_errors { 1151 return Err(ArgsErr::DuplicateOption(val)); 1152 } 1153 opts.ignore_compile_errors = true; 1154 } 1155 IGNORE_FEATURES => { 1156 if opts.ignore_features.is_empty() { 1157 if let Some(feats_os) = args.next() { 1158 if let Some(feats) = feats_os.to_str() { 1159 if feats 1160 .as_bytes() 1161 .split(|b| *b == b',') 1162 .try_fold((), |(), feat| { 1163 if opts 1164 .ignore_features 1165 .iter() 1166 .any(|f| f.as_bytes() == feat) 1167 { 1168 Err(()) 1169 } else { 1170 let utf8 = feat.to_owned(); 1171 // SAFETY: 1172 // `feats` is a valid `str` and was split by 1173 // a single UTF-8 code unit; thus `utf8` is also 1174 // valid UTF-8. 1175 opts.ignore_features.push(unsafe { 1176 String::from_utf8_unchecked(utf8) 1177 }); 1178 Ok(()) 1179 } 1180 }) 1181 .is_err() 1182 { 1183 return Err(ArgsErr::DuplicateIgnoredFeatures( 1184 feats_os, 1185 )); 1186 } 1187 } else { 1188 return Err(ArgsErr::UnknownArg(val)); 1189 } 1190 } else { 1191 return Err(ArgsErr::MissingIgnoredFeatures); 1192 } 1193 } else { 1194 return Err(ArgsErr::DuplicateOption(val)); 1195 } 1196 } 1197 IGNORE_MSRV => { 1198 if opts.ignore_msrv { 1199 return Err(ArgsErr::DuplicateOption(val)); 1200 } 1201 opts.ignore_msrv = true; 1202 } 1203 IGNORED => { 1204 if opts.ignored { 1205 return Err(ArgsErr::DuplicateOption(val)); 1206 } else if opts.include_ignored { 1207 return Err(ArgsErr::IgnoredIncludeIgnored); 1208 } 1209 opts.ignored = true; 1210 } 1211 INCLUDE_IGNORED => { 1212 if opts.include_ignored { 1213 return Err(ArgsErr::DuplicateOption(val)); 1214 } else if opts.ignored { 1215 return Err(ArgsErr::IgnoredIncludeIgnored); 1216 } 1217 opts.include_ignored = true; 1218 } 1219 PROGRESS => { 1220 if opts.progress { 1221 return Err(ArgsErr::DuplicateOption(val)); 1222 } 1223 opts.progress = true; 1224 } 1225 RUSTUP_HOME => { 1226 if opts.rustup_home.is_some() { 1227 return Err(ArgsErr::DuplicateOption(val)); 1228 } else if let Some(path) = args.next() { 1229 opts.rustup_home = Some(path.into()); 1230 } else { 1231 return Err(ArgsErr::MissingRustupHome); 1232 } 1233 } 1234 SKIP_MSRV => { 1235 if opts.skip_msrv { 1236 return Err(ArgsErr::DuplicateOption(val)); 1237 } 1238 opts.skip_msrv = true; 1239 } 1240 SUMMARY => { 1241 if opts.summary { 1242 return Err(ArgsErr::DuplicateOption(val)); 1243 } 1244 opts.summary = true; 1245 } 1246 _ => return Err(ArgsErr::UnknownArg(val)), 1247 } 1248 Self::extract_options(opts, args.next(), args) 1249 } else { 1250 Err(ArgsErr::UnknownArg(val)) 1251 } 1252 }, 1253 ) 1254 } 1255 /// Returns data we need by reading the supplied CLI arguments. 1256 #[expect( 1257 clippy::too_many_lines, 1258 reason = "expected even if we extract options separately" 1259 )] 1260 pub(crate) fn from_args<T: Iterator<Item = OsString>>(mut args: T) -> Result<Self, ArgsErr> { 1261 args.next().ok_or(ArgsErr::NoArgs).and_then(|_| { 1262 args.next().map_or_else( 1263 || { 1264 Ok(Self::Cargo( 1265 Cmd::All(false, false, Ignored::None), 1266 Opts::default(), 1267 )) 1268 }, 1269 |arg| { 1270 if let Some(arg_str) = arg.to_str() { 1271 match arg_str { 1272 HELP => { 1273 if args.next().is_none() { 1274 Ok(Self::Help) 1275 } else { 1276 Err(ArgsErr::HelpWithArgs) 1277 } 1278 } 1279 VERSION => { 1280 if args.next().is_none() { 1281 Ok(Self::Version) 1282 } else { 1283 Err(ArgsErr::VersionWithArgs) 1284 } 1285 } 1286 CLIPPY => { 1287 let mut opts = ArgOpts::default(); 1288 Self::extract_options(&mut opts, args.next(), &mut args).and_then( 1289 |()| { 1290 if opts.ignored || opts.include_ignored { 1291 Err(ArgsErr::IgnoredCheckClippyDoc) 1292 } else { 1293 Ok(Self::Cargo( 1294 Cmd::Clippy(opts.all_targets, opts.deny_warnings), 1295 opts.into(), 1296 )) 1297 } 1298 }, 1299 ) 1300 } 1301 TESTS => { 1302 let mut opts = ArgOpts::default(); 1303 Self::extract_options(&mut opts, args.next(), &mut args).and_then( 1304 |()| { 1305 if opts.all_targets { 1306 Err(ArgsErr::AllTargetsTests) 1307 } else if opts.deny_warnings { 1308 Err(ArgsErr::DenyWarningsCheckTests) 1309 } else { 1310 let ignored = opts.ignored(); 1311 Ok(Self::Cargo(Cmd::Tests(ignored), opts.into())) 1312 } 1313 }, 1314 ) 1315 } 1316 DOC_TESTS => { 1317 let mut opts = ArgOpts::default(); 1318 Self::extract_options(&mut opts, args.next(), &mut args).and_then( 1319 |()| { 1320 if opts.all_targets { 1321 Err(ArgsErr::AllTargetsTests) 1322 } else if opts.deny_warnings { 1323 Err(ArgsErr::DenyWarningsCheckTests) 1324 } else if opts.ignored || opts.include_ignored { 1325 Err(ArgsErr::IgnoredCheckClippyDoc) 1326 } else { 1327 Ok(Self::Cargo(Cmd::DocTests, opts.into())) 1328 } 1329 }, 1330 ) 1331 } 1332 CHECK => { 1333 let mut opts = ArgOpts::default(); 1334 Self::extract_options(&mut opts, args.next(), &mut args).and_then( 1335 |()| { 1336 if opts.deny_warnings { 1337 Err(ArgsErr::DenyWarningsCheckTests) 1338 } else if opts.ignored || opts.include_ignored { 1339 Err(ArgsErr::IgnoredCheckClippyDoc) 1340 } else { 1341 Ok(Self::Cargo( 1342 Cmd::Check(opts.all_targets), 1343 opts.into(), 1344 )) 1345 } 1346 }, 1347 ) 1348 } 1349 _ => { 1350 let mut opts = ArgOpts::default(); 1351 Self::extract_options(&mut opts, Some(arg), &mut args).map(|()| { 1352 Self::Cargo( 1353 Cmd::All( 1354 opts.all_targets, 1355 opts.deny_warnings, 1356 opts.ignored(), 1357 ), 1358 opts.into(), 1359 ) 1360 }) 1361 } 1362 } 1363 } else { 1364 Err(ArgsErr::UnknownArg(arg)) 1365 } 1366 }, 1367 ) 1368 }) 1369 } 1370 } 1371 #[cfg(test)] 1372 mod tests { 1373 use super::{ArgsErr, Cmd, Ignored, MetaCmd, NonZeroUsizePlus1, Opts, OsString, PathBuf}; 1374 use core::iter; 1375 #[cfg(unix)] 1376 use std::os::unix::ffi::OsStringExt as _; 1377 #[expect( 1378 clippy::cognitive_complexity, 1379 clippy::too_many_lines, 1380 reason = "want to test for a lot of things" 1381 )] 1382 #[test] 1383 fn arg_parsing() { 1384 assert_eq!(MetaCmd::from_args(iter::empty()), Err(ArgsErr::NoArgs)); 1385 // We always ignore the first argument. 1386 assert_eq!( 1387 MetaCmd::from_args(iter::once(OsString::new())), 1388 Ok(MetaCmd::Cargo( 1389 Cmd::All(false, false, Ignored::None), 1390 Opts { 1391 exec_dir: None, 1392 rustup_home: None, 1393 cargo_path: "cargo".to_owned().into(), 1394 cargo_home: None, 1395 color: false, 1396 default_toolchain: false, 1397 allow_implied_features: false, 1398 ignore_compile_errors: false, 1399 ignore_msrv: false, 1400 progress: false, 1401 skip_msrv: false, 1402 summary: false, 1403 ignore_features: Vec::new(), 1404 } 1405 )), 1406 ); 1407 assert_eq!( 1408 MetaCmd::from_args([OsString::new(), OsString::new()].into_iter()), 1409 Err(ArgsErr::UnknownArg(OsString::new())) 1410 ); 1411 assert_eq!( 1412 MetaCmd::from_args([OsString::new(), OsString::new()].into_iter()), 1413 Err(ArgsErr::UnknownArg(OsString::new())) 1414 ); 1415 // Invalid UTF-8 errors gracefully. 1416 #[cfg(unix)] 1417 assert_eq!( 1418 MetaCmd::from_args([OsString::new(), OsString::from_vec(vec![255])].into_iter()), 1419 Err(ArgsErr::UnknownArg(OsString::from_vec(vec![255]))) 1420 ); 1421 // Whitespace is not ignored. 1422 assert_eq!( 1423 MetaCmd::from_args([OsString::new(), " clippy".to_owned().into()].into_iter()), 1424 Err(ArgsErr::UnknownArg(" clippy".to_owned().into())) 1425 ); 1426 // We parse in a case-sensitive way. 1427 assert_eq!( 1428 MetaCmd::from_args([OsString::new(), "Clippy".to_owned().into()].into_iter()), 1429 Err(ArgsErr::UnknownArg("Clippy".to_owned().into())) 1430 ); 1431 // We require options to be after the command (if one was passed). 1432 assert_eq!( 1433 MetaCmd::from_args( 1434 [ 1435 OsString::new(), 1436 "--summary".to_owned().into(), 1437 "clippy".to_owned().into() 1438 ] 1439 .into_iter() 1440 ), 1441 Err(ArgsErr::UnknownArg("clippy".to_owned().into())) 1442 ); 1443 // `ArgsErr::DuplicateOption` has higher priority than disallowed option errors. 1444 assert_eq!( 1445 MetaCmd::from_args( 1446 [ 1447 OsString::new(), 1448 "tests".to_owned().into(), 1449 "--deny-warnings".to_owned().into(), 1450 "--deny-warnings".to_owned().into() 1451 ] 1452 .into_iter() 1453 ), 1454 Err(ArgsErr::DuplicateOption( 1455 "--deny-warnings".to_owned().into() 1456 )) 1457 ); 1458 assert_eq!( 1459 MetaCmd::from_args( 1460 [ 1461 OsString::new(), 1462 "help".to_owned().into(), 1463 "--summary".to_owned().into() 1464 ] 1465 .into_iter() 1466 ), 1467 Err(ArgsErr::HelpWithArgs) 1468 ); 1469 assert_eq!( 1470 MetaCmd::from_args( 1471 [ 1472 OsString::new(), 1473 "version".to_owned().into(), 1474 "foo".to_owned().into() 1475 ] 1476 .into_iter() 1477 ), 1478 Err(ArgsErr::VersionWithArgs) 1479 ); 1480 assert_eq!( 1481 MetaCmd::from_args([OsString::new(), "--cargo-path".to_owned().into()].into_iter()), 1482 Err(ArgsErr::MissingCargoPath) 1483 ); 1484 assert_eq!( 1485 MetaCmd::from_args([OsString::new(), "--cargo-home".to_owned().into()].into_iter()), 1486 Err(ArgsErr::MissingCargoHome) 1487 ); 1488 assert_eq!( 1489 MetaCmd::from_args([OsString::new(), "--rustup-home".to_owned().into()].into_iter()), 1490 Err(ArgsErr::MissingRustupHome) 1491 ); 1492 assert_eq!( 1493 MetaCmd::from_args( 1494 [ 1495 OsString::new(), 1496 "tests".to_owned().into(), 1497 "--all-targets".to_owned().into() 1498 ] 1499 .into_iter() 1500 ), 1501 Err(ArgsErr::AllTargetsTests) 1502 ); 1503 assert_eq!( 1504 MetaCmd::from_args( 1505 [ 1506 OsString::new(), 1507 "doc-tests".to_owned().into(), 1508 "--all-targets".to_owned().into() 1509 ] 1510 .into_iter() 1511 ), 1512 Err(ArgsErr::AllTargetsTests) 1513 ); 1514 assert_eq!( 1515 MetaCmd::from_args( 1516 [ 1517 OsString::new(), 1518 "tests".to_owned().into(), 1519 "--deny-warnings".to_owned().into() 1520 ] 1521 .into_iter() 1522 ), 1523 Err(ArgsErr::DenyWarningsCheckTests) 1524 ); 1525 assert_eq!( 1526 MetaCmd::from_args( 1527 [ 1528 OsString::new(), 1529 "doc-tests".to_owned().into(), 1530 "--deny-warnings".to_owned().into() 1531 ] 1532 .into_iter() 1533 ), 1534 Err(ArgsErr::DenyWarningsCheckTests) 1535 ); 1536 assert_eq!( 1537 MetaCmd::from_args( 1538 [ 1539 OsString::new(), 1540 "check".to_owned().into(), 1541 "--deny-warnings".to_owned().into() 1542 ] 1543 .into_iter() 1544 ), 1545 Err(ArgsErr::DenyWarningsCheckTests) 1546 ); 1547 assert_eq!( 1548 MetaCmd::from_args( 1549 [ 1550 OsString::new(), 1551 "clippy".to_owned().into(), 1552 "--ignored".to_owned().into() 1553 ] 1554 .into_iter() 1555 ), 1556 Err(ArgsErr::IgnoredCheckClippyDoc) 1557 ); 1558 assert_eq!( 1559 MetaCmd::from_args( 1560 [ 1561 OsString::new(), 1562 "doc-tests".to_owned().into(), 1563 "--ignored".to_owned().into() 1564 ] 1565 .into_iter() 1566 ), 1567 Err(ArgsErr::IgnoredCheckClippyDoc) 1568 ); 1569 assert_eq!( 1570 MetaCmd::from_args( 1571 [ 1572 OsString::new(), 1573 "check".to_owned().into(), 1574 "--ignored".to_owned().into() 1575 ] 1576 .into_iter() 1577 ), 1578 Err(ArgsErr::IgnoredCheckClippyDoc) 1579 ); 1580 assert_eq!( 1581 MetaCmd::from_args( 1582 [ 1583 OsString::new(), 1584 "clippy".to_owned().into(), 1585 "--include-ignored".to_owned().into() 1586 ] 1587 .into_iter() 1588 ), 1589 Err(ArgsErr::IgnoredCheckClippyDoc) 1590 ); 1591 assert_eq!( 1592 MetaCmd::from_args( 1593 [ 1594 OsString::new(), 1595 "doc-tests".to_owned().into(), 1596 "--include-ignored".to_owned().into() 1597 ] 1598 .into_iter() 1599 ), 1600 Err(ArgsErr::IgnoredCheckClippyDoc) 1601 ); 1602 assert_eq!( 1603 MetaCmd::from_args( 1604 [ 1605 OsString::new(), 1606 "check".to_owned().into(), 1607 "--include-ignored".to_owned().into() 1608 ] 1609 .into_iter() 1610 ), 1611 Err(ArgsErr::IgnoredCheckClippyDoc) 1612 ); 1613 assert_eq!( 1614 MetaCmd::from_args( 1615 [ 1616 OsString::new(), 1617 "tests".to_owned().into(), 1618 "--ignored".to_owned().into(), 1619 "--include-ignored".to_owned().into() 1620 ] 1621 .into_iter() 1622 ), 1623 Err(ArgsErr::IgnoredIncludeIgnored) 1624 ); 1625 assert_eq!( 1626 MetaCmd::from_args( 1627 [ 1628 OsString::new(), 1629 "--ignored".to_owned().into(), 1630 "--include-ignored".to_owned().into() 1631 ] 1632 .into_iter() 1633 ), 1634 Err(ArgsErr::IgnoredIncludeIgnored) 1635 ); 1636 assert_eq!( 1637 MetaCmd::from_args( 1638 [OsString::new(), "--ignore-features".to_owned().into(),].into_iter() 1639 ), 1640 Err(ArgsErr::MissingIgnoredFeatures) 1641 ); 1642 assert_eq!( 1643 MetaCmd::from_args( 1644 [ 1645 OsString::new(), 1646 "--ignore-features".to_owned().into(), 1647 ",".to_owned().into(), 1648 ] 1649 .into_iter() 1650 ), 1651 Err(ArgsErr::DuplicateIgnoredFeatures(",".to_owned().into())) 1652 ); 1653 assert_eq!( 1654 MetaCmd::from_args( 1655 [ 1656 OsString::new(), 1657 "--ignore-features".to_owned().into(), 1658 "a,,a".to_owned().into(), 1659 ] 1660 .into_iter() 1661 ), 1662 Err(ArgsErr::DuplicateIgnoredFeatures("a,,a".to_owned().into())) 1663 ); 1664 assert_eq!( 1665 MetaCmd::from_args( 1666 [ 1667 OsString::new(), 1668 "--ignore-features".to_owned().into(), 1669 ",a,b,".to_owned().into(), 1670 ] 1671 .into_iter() 1672 ), 1673 Err(ArgsErr::DuplicateIgnoredFeatures(",a,b,".to_owned().into())) 1674 ); 1675 assert_eq!( 1676 MetaCmd::from_args( 1677 [ 1678 OsString::new(), 1679 "--ignore-features".to_owned().into(), 1680 ",a,,b".to_owned().into(), 1681 ] 1682 .into_iter() 1683 ), 1684 Err(ArgsErr::DuplicateIgnoredFeatures(",a,,b".to_owned().into())) 1685 ); 1686 assert_eq!( 1687 MetaCmd::from_args( 1688 [ 1689 OsString::new(), 1690 "--ignore-features".to_owned().into(), 1691 "a,b,,".to_owned().into(), 1692 ] 1693 .into_iter() 1694 ), 1695 Err(ArgsErr::DuplicateIgnoredFeatures("a,b,,".to_owned().into())) 1696 ); 1697 // When paths are passed, no attempt is made to interpret them. `cargo` is unconditionally pushed 1698 // to the path. Similarly features to ignored are not handled special. 1699 assert_eq!( 1700 MetaCmd::from_args( 1701 [ 1702 OsString::new(), 1703 "--all-targets".to_owned().into(), 1704 "--allow-implied-features".to_owned().into(), 1705 "--cargo-home".to_owned().into(), 1706 "--ignored".to_owned().into(), 1707 "--cargo-path".to_owned().into(), 1708 "cargo".to_owned().into(), 1709 "--color".to_owned().into(), 1710 "--default-toolchain".to_owned().into(), 1711 "--deny-warnings".to_owned().into(), 1712 "--dir".to_owned().into(), 1713 OsString::new(), 1714 "--ignore-compile-errors".to_owned().into(), 1715 "--ignore-features".to_owned().into(), 1716 "--include-ignored".to_owned().into(), 1717 "--ignore-msrv".to_owned().into(), 1718 "--rustup-home".to_owned().into(), 1719 OsString::new(), 1720 "--progress".to_owned().into(), 1721 "--skip-msrv".to_owned().into(), 1722 "--summary".to_owned().into(), 1723 ] 1724 .into_iter() 1725 ), 1726 Ok(MetaCmd::Cargo( 1727 Cmd::All(true, true, Ignored::None), 1728 Opts { 1729 exec_dir: Some(PathBuf::new()), 1730 rustup_home: Some(PathBuf::new()), 1731 cargo_home: Some("--ignored".to_owned().into()), 1732 cargo_path: "cargo/cargo".to_owned().into(), 1733 color: true, 1734 default_toolchain: true, 1735 allow_implied_features: true, 1736 ignore_compile_errors: true, 1737 ignore_msrv: true, 1738 progress: true, 1739 skip_msrv: true, 1740 summary: true, 1741 ignore_features: vec!["--include-ignored".to_owned()], 1742 } 1743 )) 1744 ); 1745 assert_eq!( 1746 MetaCmd::from_args( 1747 [ 1748 OsString::new(), 1749 "clippy".to_owned().into(), 1750 "--all-targets".to_owned().into(), 1751 "--allow-implied-features".to_owned().into(), 1752 "--cargo-home".to_owned().into(), 1753 "--ignored".to_owned().into(), 1754 "--cargo-path".to_owned().into(), 1755 "cargo".to_owned().into(), 1756 "--color".to_owned().into(), 1757 "--default-toolchain".to_owned().into(), 1758 "--deny-warnings".to_owned().into(), 1759 "--dir".to_owned().into(), 1760 OsString::new(), 1761 "--ignore-compile-errors".to_owned().into(), 1762 "--ignore-features".to_owned().into(), 1763 ",a".to_owned().into(), 1764 "--ignore-msrv".to_owned().into(), 1765 "--rustup-home".to_owned().into(), 1766 "a".to_owned().into(), 1767 "--progress".to_owned().into(), 1768 "--skip-msrv".to_owned().into(), 1769 "--summary".to_owned().into(), 1770 ] 1771 .into_iter() 1772 ), 1773 Ok(MetaCmd::Cargo( 1774 Cmd::Clippy(true, true,), 1775 Opts { 1776 exec_dir: Some(PathBuf::new()), 1777 rustup_home: Some("a".to_owned().into()), 1778 cargo_home: Some("--ignored".to_owned().into()), 1779 cargo_path: "cargo/cargo".to_owned().into(), 1780 color: true, 1781 default_toolchain: true, 1782 allow_implied_features: true, 1783 ignore_compile_errors: true, 1784 ignore_msrv: true, 1785 progress: true, 1786 skip_msrv: true, 1787 summary: true, 1788 ignore_features: vec![String::new(), "a".to_owned()], 1789 } 1790 )) 1791 ); 1792 assert_eq!( 1793 MetaCmd::from_args( 1794 [ 1795 OsString::new(), 1796 "clippy".to_owned().into(), 1797 "--all-targets".to_owned().into(), 1798 "--deny-warnings".to_owned().into(), 1799 ] 1800 .into_iter() 1801 ), 1802 Ok(MetaCmd::Cargo( 1803 Cmd::Clippy(true, true,), 1804 Opts { 1805 exec_dir: None, 1806 rustup_home: None, 1807 cargo_home: None, 1808 cargo_path: "cargo".to_owned().into(), 1809 color: false, 1810 default_toolchain: false, 1811 allow_implied_features: false, 1812 ignore_compile_errors: false, 1813 ignore_msrv: false, 1814 progress: false, 1815 skip_msrv: false, 1816 summary: false, 1817 ignore_features: Vec::new(), 1818 } 1819 )) 1820 ); 1821 assert_eq!( 1822 MetaCmd::from_args( 1823 [ 1824 OsString::new(), 1825 "tests".to_owned().into(), 1826 "--allow-implied-features".to_owned().into(), 1827 "--cargo-home".to_owned().into(), 1828 "--ignored".to_owned().into(), 1829 "--cargo-path".to_owned().into(), 1830 "cargo".to_owned().into(), 1831 "--color".to_owned().into(), 1832 "--default-toolchain".to_owned().into(), 1833 "--dir".to_owned().into(), 1834 OsString::new(), 1835 "--ignore-compile-errors".to_owned().into(), 1836 "--ignore-features".to_owned().into(), 1837 OsString::new(), 1838 "--ignore-msrv".to_owned().into(), 1839 "--ignored".to_owned().into(), 1840 "--rustup-home".to_owned().into(), 1841 OsString::new(), 1842 "--progress".to_owned().into(), 1843 "--skip-msrv".to_owned().into(), 1844 "--summary".to_owned().into(), 1845 ] 1846 .into_iter() 1847 ), 1848 Ok(MetaCmd::Cargo( 1849 Cmd::Tests(Ignored::Only), 1850 Opts { 1851 exec_dir: Some(PathBuf::new()), 1852 rustup_home: Some(PathBuf::new()), 1853 cargo_home: Some("--ignored".to_owned().into()), 1854 cargo_path: "cargo/cargo".to_owned().into(), 1855 color: true, 1856 default_toolchain: true, 1857 allow_implied_features: true, 1858 ignore_compile_errors: true, 1859 ignore_msrv: true, 1860 progress: true, 1861 skip_msrv: true, 1862 summary: true, 1863 ignore_features: vec![String::new()], 1864 } 1865 )) 1866 ); 1867 assert_eq!( 1868 MetaCmd::from_args([OsString::new(), "tests".to_owned().into(),].into_iter()), 1869 Ok(MetaCmd::Cargo( 1870 Cmd::Tests(Ignored::None), 1871 Opts { 1872 exec_dir: None, 1873 rustup_home: None, 1874 cargo_home: None, 1875 cargo_path: "cargo".to_owned().into(), 1876 color: false, 1877 default_toolchain: false, 1878 allow_implied_features: false, 1879 ignore_compile_errors: false, 1880 ignore_msrv: false, 1881 progress: false, 1882 skip_msrv: false, 1883 summary: false, 1884 ignore_features: Vec::new(), 1885 } 1886 )) 1887 ); 1888 assert_eq!( 1889 MetaCmd::from_args( 1890 [ 1891 OsString::new(), 1892 "tests".to_owned().into(), 1893 "--include-ignored".to_owned().into() 1894 ] 1895 .into_iter() 1896 ), 1897 Ok(MetaCmd::Cargo( 1898 Cmd::Tests(Ignored::Include), 1899 Opts { 1900 exec_dir: None, 1901 rustup_home: None, 1902 cargo_home: None, 1903 cargo_path: "cargo".to_owned().into(), 1904 color: false, 1905 default_toolchain: false, 1906 allow_implied_features: false, 1907 ignore_compile_errors: false, 1908 ignore_msrv: false, 1909 progress: false, 1910 skip_msrv: false, 1911 summary: false, 1912 ignore_features: Vec::new(), 1913 } 1914 )) 1915 ); 1916 assert_eq!( 1917 MetaCmd::from_args( 1918 [ 1919 OsString::new(), 1920 "doc-tests".to_owned().into(), 1921 "--allow-implied-features".to_owned().into(), 1922 "--cargo-home".to_owned().into(), 1923 "--ignored".to_owned().into(), 1924 "--cargo-path".to_owned().into(), 1925 "cargo".to_owned().into(), 1926 "--color".to_owned().into(), 1927 "--default-toolchain".to_owned().into(), 1928 "--dir".to_owned().into(), 1929 OsString::new(), 1930 "--ignore-compile-errors".to_owned().into(), 1931 "--ignore-features".to_owned().into(), 1932 "a,".to_owned().into(), 1933 "--ignore-msrv".to_owned().into(), 1934 "--rustup-home".to_owned().into(), 1935 OsString::new(), 1936 "--progress".to_owned().into(), 1937 "--skip-msrv".to_owned().into(), 1938 "--summary".to_owned().into(), 1939 ] 1940 .into_iter() 1941 ), 1942 Ok(MetaCmd::Cargo( 1943 Cmd::DocTests, 1944 Opts { 1945 exec_dir: Some(PathBuf::new()), 1946 rustup_home: Some(PathBuf::new()), 1947 cargo_home: Some("--ignored".to_owned().into()), 1948 cargo_path: "cargo/cargo".to_owned().into(), 1949 color: true, 1950 default_toolchain: true, 1951 allow_implied_features: true, 1952 ignore_compile_errors: true, 1953 ignore_msrv: true, 1954 progress: true, 1955 skip_msrv: true, 1956 summary: true, 1957 ignore_features: vec!["a".to_owned(), String::new()], 1958 } 1959 )) 1960 ); 1961 assert_eq!( 1962 MetaCmd::from_args([OsString::new(), "doc-tests".to_owned().into(),].into_iter()), 1963 Ok(MetaCmd::Cargo( 1964 Cmd::DocTests, 1965 Opts { 1966 exec_dir: None, 1967 rustup_home: None, 1968 cargo_home: None, 1969 cargo_path: "cargo".to_owned().into(), 1970 color: false, 1971 default_toolchain: false, 1972 allow_implied_features: false, 1973 ignore_compile_errors: false, 1974 ignore_msrv: false, 1975 progress: false, 1976 skip_msrv: false, 1977 summary: false, 1978 ignore_features: Vec::new(), 1979 } 1980 )) 1981 ); 1982 assert_eq!( 1983 MetaCmd::from_args( 1984 [ 1985 OsString::new(), 1986 "check".to_owned().into(), 1987 "--all-targets".to_owned().into(), 1988 "--allow-implied-features".to_owned().into(), 1989 "--cargo-home".to_owned().into(), 1990 "--ignored".to_owned().into(), 1991 "--cargo-path".to_owned().into(), 1992 "cargo".to_owned().into(), 1993 "--color".to_owned().into(), 1994 "--default-toolchain".to_owned().into(), 1995 "--dir".to_owned().into(), 1996 OsString::new(), 1997 "--ignore-compile-errors".to_owned().into(), 1998 "--ignore-features".to_owned().into(), 1999 "a,".to_owned().into(), 2000 "--ignore-msrv".to_owned().into(), 2001 "--rustup-home".to_owned().into(), 2002 OsString::new(), 2003 "--progress".to_owned().into(), 2004 "--skip-msrv".to_owned().into(), 2005 "--summary".to_owned().into(), 2006 ] 2007 .into_iter() 2008 ), 2009 Ok(MetaCmd::Cargo( 2010 Cmd::Check(true), 2011 Opts { 2012 exec_dir: Some(PathBuf::new()), 2013 rustup_home: Some(PathBuf::new()), 2014 cargo_home: Some("--ignored".to_owned().into()), 2015 cargo_path: "cargo/cargo".to_owned().into(), 2016 color: true, 2017 default_toolchain: true, 2018 allow_implied_features: true, 2019 ignore_compile_errors: true, 2020 ignore_msrv: true, 2021 progress: true, 2022 skip_msrv: true, 2023 summary: true, 2024 ignore_features: vec!["a".to_owned(), String::new()], 2025 } 2026 )) 2027 ); 2028 assert_eq!( 2029 MetaCmd::from_args([OsString::new(), "check".to_owned().into(),].into_iter()), 2030 Ok(MetaCmd::Cargo( 2031 Cmd::Check(false), 2032 Opts { 2033 exec_dir: None, 2034 rustup_home: None, 2035 cargo_home: None, 2036 cargo_path: "cargo".to_owned().into(), 2037 color: false, 2038 default_toolchain: false, 2039 allow_implied_features: false, 2040 ignore_compile_errors: false, 2041 ignore_msrv: false, 2042 progress: false, 2043 skip_msrv: false, 2044 summary: false, 2045 ignore_features: Vec::new(), 2046 } 2047 )) 2048 ); 2049 assert_eq!( 2050 MetaCmd::from_args( 2051 [ 2052 OsString::new(), 2053 "--ignore-features".to_owned().into(), 2054 "a,,b".to_owned().into(), 2055 ] 2056 .into_iter() 2057 ), 2058 Ok(MetaCmd::Cargo( 2059 Cmd::All(false, false, Ignored::None), 2060 Opts { 2061 exec_dir: None, 2062 rustup_home: None, 2063 cargo_home: None, 2064 cargo_path: "cargo".to_owned().into(), 2065 color: false, 2066 default_toolchain: false, 2067 allow_implied_features: false, 2068 ignore_compile_errors: false, 2069 ignore_msrv: false, 2070 progress: false, 2071 skip_msrv: false, 2072 summary: false, 2073 ignore_features: vec!["a".to_owned(), String::new(), "b".to_owned()], 2074 } 2075 )) 2076 ); 2077 assert_eq!( 2078 MetaCmd::from_args( 2079 [ 2080 OsString::new(), 2081 "--ignore-features".to_owned().into(), 2082 "a,b,".to_owned().into(), 2083 ] 2084 .into_iter() 2085 ), 2086 Ok(MetaCmd::Cargo( 2087 Cmd::All(false, false, Ignored::None), 2088 Opts { 2089 exec_dir: None, 2090 rustup_home: None, 2091 cargo_home: None, 2092 cargo_path: "cargo".to_owned().into(), 2093 color: false, 2094 default_toolchain: false, 2095 allow_implied_features: false, 2096 ignore_compile_errors: false, 2097 ignore_msrv: false, 2098 progress: false, 2099 skip_msrv: false, 2100 summary: false, 2101 ignore_features: vec!["a".to_owned(), "b".to_owned(), String::new()], 2102 } 2103 )) 2104 ); 2105 assert_eq!( 2106 MetaCmd::from_args( 2107 [ 2108 OsString::new(), 2109 "--ignore-features".to_owned().into(), 2110 "a,b".to_owned().into(), 2111 ] 2112 .into_iter() 2113 ), 2114 Ok(MetaCmd::Cargo( 2115 Cmd::All(false, false, Ignored::None), 2116 Opts { 2117 exec_dir: None, 2118 rustup_home: None, 2119 cargo_home: None, 2120 cargo_path: "cargo".to_owned().into(), 2121 color: false, 2122 default_toolchain: false, 2123 allow_implied_features: false, 2124 ignore_compile_errors: false, 2125 ignore_msrv: false, 2126 progress: false, 2127 skip_msrv: false, 2128 summary: false, 2129 ignore_features: vec!["a".to_owned(), "b".to_owned()], 2130 } 2131 )) 2132 ); 2133 // No whitespace cleanup is done on the features. 2134 assert_eq!( 2135 MetaCmd::from_args( 2136 [ 2137 OsString::new(), 2138 "--ignore-features".to_owned().into(), 2139 "a , , b, ".to_owned().into(), 2140 ] 2141 .into_iter() 2142 ), 2143 Ok(MetaCmd::Cargo( 2144 Cmd::All(false, false, Ignored::None), 2145 Opts { 2146 exec_dir: None, 2147 rustup_home: None, 2148 cargo_home: None, 2149 cargo_path: "cargo".to_owned().into(), 2150 color: false, 2151 default_toolchain: false, 2152 allow_implied_features: false, 2153 ignore_compile_errors: false, 2154 ignore_msrv: false, 2155 progress: false, 2156 skip_msrv: false, 2157 summary: false, 2158 ignore_features: vec![ 2159 "a ".to_owned(), 2160 " ".to_owned(), 2161 " b".to_owned(), 2162 " ".to_owned() 2163 ], 2164 } 2165 )) 2166 ); 2167 assert_eq!( 2168 MetaCmd::from_args([OsString::new(), "help".to_owned().into(),].into_iter()), 2169 Ok(MetaCmd::Help) 2170 ); 2171 assert_eq!( 2172 MetaCmd::from_args([OsString::new(), "version".to_owned().into(),].into_iter()), 2173 Ok(MetaCmd::Version) 2174 ); 2175 } 2176 #[test] 2177 fn non_zero_usize_plus_1() { 2178 #[cfg(target_pointer_width = "64")] 2179 assert_eq!(NonZeroUsizePlus1(0).to_string(), "18446744073709551616"); 2180 #[cfg(target_pointer_width = "64")] 2181 assert_eq!( 2182 NonZeroUsizePlus1(usize::MAX).to_string(), 2183 "18446744073709551615" 2184 ); 2185 #[cfg(target_pointer_width = "32")] 2186 assert_eq!(NonZeroUsizePlus1(0).to_string(), "4294967296"); 2187 #[cfg(target_pointer_width = "32")] 2188 assert_eq!(NonZeroUsizePlus1(usize::MAX).to_string(), "4294967295"); 2189 #[cfg(target_pointer_width = "16")] 2190 assert_eq!(NonZeroUsizePlus1(0).to_string(), "65536"); 2191 #[cfg(target_pointer_width = "16")] 2192 assert_eq!(NonZeroUsizePlus1(usize::MAX).to_string(), "65535"); 2193 assert_eq!(NonZeroUsizePlus1(1).to_string(), "1"); 2194 assert_eq!(NonZeroUsizePlus1(2).to_string(), "2"); 2195 assert_eq!(NonZeroUsizePlus1(10).to_string(), "10"); 2196 } 2197 }