args.rs (125243B)
1 use super::{ 2 cargo::{CargoErr, Check, Clippy, Options, Test, 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] [COMMAND] [OPTIONS] [COMMAND] [OPTIONS] 19 20 Commands: 21 check cargo check 22 clippy cargo clippy 23 help This message 24 test cargo test 25 version Prints version info 26 27 Global Options: 28 --allow-implied features Allow implied features from optional dependencies 29 --cargo-home <PATH> Set the storage directory used by cargo 30 --cargo-path <PATH> Set the path cargo is in. Defaults to cargo 31 --color --color always is passed to each command; otherwise --color never is 32 --default-toolchain cargo is invoked as is (i.e., cargo +stable is never used) 33 --dir <PATH> Set the working directory 34 --ignore-compile-errors compile_error!s are ignored 35 --ignore-features <feats> Ignore the provided comma-separated features 36 --ignore-msrv --ignore-rust-version is passed to each command for the default toolchain 37 --progress Writes the progress to stdout 38 --rustup-home <PATH> Set the storage directory used by rustup 39 --skip-msrv cargo +<MSRV> is not used 40 --summary Writes the toolchain(s) and combinations of features used to stdout on success 41 42 Check Options: 43 --all-targets --all-targets is passed 44 --benches --benches is passed 45 --bins --bins is passed 46 --examples --examples is passed 47 --lib --lib is passed 48 --tests --tests is passed 49 50 Clippy Options: 51 --all-targets --all-targets is passed 52 --benches --benches is passed 53 --bins --bins is passed 54 --deny-warnings -- -Dwarnings is passed 55 --examples --examples is passed 56 --lib --lib is passed 57 --tests --tests is passed 58 59 Test Options: 60 --all-targets cargo test --all-targets and cargo test --doc are run 61 --benches --benches is passed 62 --bins --bins is passed 63 --doc --doc is passed 64 --examples --examples is passed 65 --ignored -- --ignored is passed 66 --include-ignored -- --include-ignored is passed 67 --lib --lib is passed 68 --tests --tests is passed 69 70 The following conditions must be met: 71 72 * the help and version commands mustn't be combined with other commands or options 73 * any unique combination of check, clippy, and test can be used 74 * command-specific options must be unique for a given command 75 * global options are allowed after any command but must be unique 76 * command-specific options must follow the command 77 * the test options --ignored and --include-ignored are mutually exclusive 78 * --all-targets mustn't be combined with other targets (e.g., --lib) 79 * the test option --doc mustn't be combined with other targets 80 * --ignore-msrv is not allowed if the stable toolchain is used 81 82 cargo +stable will be used to run the command(s) if all of the following conditions are met: 83 84 * --default-toolchain was not passed 85 * rust-toolchain.toml does not exist in the package directory nor its ancestor directories 86 * --rustup-home was not passed for platforms that don't support rustup 87 88 If the above are not met, cargo will be used instead. cargo +<MSRV> will also be used 89 if all of the following conditions are met: 90 91 * --skip-msrv was not passed 92 * package has an MSRV defined via rust-version that is semantically less than the stable or not 93 equivalent to the default toolchain used 94 * --rustup-home was passed or the platform supports rustup 95 96 For the toolchain(s) used, the command(s) are run for each combination of features sans any provided 97 with --ignore-features. Features provided with --ignore-features must be unique and represent valid 98 features in the package. An empty value is interpreted as the empty set of features. 99 "; 100 /// `"help"`. 101 const HELP: &str = "help"; 102 /// `"version"`. 103 const VERSION: &str = "version"; 104 /// `"check"`. 105 const CHECK: &str = "check"; 106 /// `"clippy"`. 107 const CLIPPY: &str = "clippy"; 108 /// `"test"`. 109 const TEST: &str = "test"; 110 /// `"--all-targets"`. 111 const ALL_TARGETS: &str = "--all-targets"; 112 /// `"--allow-implied-features"`. 113 const ALLOW_IMPLIED_FEATURES: &str = "--allow-implied-features"; 114 /// `"--benches"`. 115 const BENCHES: &str = "--benches"; 116 /// `"--bins"`. 117 const BINS: &str = "--bins"; 118 /// `"--cargo-home"`. 119 const CARGO_HOME: &str = "--cargo-home"; 120 /// `"--cargo-path"`. 121 const CARGO_PATH: &str = "--cargo-path"; 122 /// `"--color"`. 123 const COLOR: &str = "--color"; 124 /// `"--default-toolchain"`. 125 const DEFAULT_TOOLCHAIN: &str = "--default-toolchain"; 126 /// `"--deny-warnings"`. 127 const DENY_WARNINGS: &str = "--deny-warnings"; 128 /// `"--dir"`. 129 const DIR: &str = "--dir"; 130 /// `"--doc"`. 131 const DOC: &str = "--doc"; 132 /// `"--examples"`. 133 const EXAMPLES: &str = "--examples"; 134 /// `"--ignore-compile-errors"`. 135 const IGNORE_COMPILE_ERRORS: &str = "--ignore-compile-errors"; 136 /// `"--ignore-features"`. 137 const IGNORE_FEATURES: &str = "--ignore-features"; 138 /// `"--ignore-msrv"`. 139 const IGNORE_MSRV: &str = "--ignore-msrv"; 140 /// `"--ignored"`. 141 const IGNORED: &str = "--ignored"; 142 /// `"--include-ignored"`. 143 const INCLUDE_IGNORED: &str = "--include-ignored"; 144 /// `"--lib"`. 145 const LIB: &str = "--lib"; 146 /// `"--progress"`. 147 const PROGRESS: &str = "--progress"; 148 /// `"--rustup-home"`. 149 const RUSTUP_HOME: &str = "--rustup-home"; 150 /// `"--skip-msrv"`. 151 const SKIP_MSRV: &str = "--skip-msrv"; 152 /// `"--summary"`. 153 const SUMMARY: &str = "--summary"; 154 /// `"--tests"`. 155 const TESTS: &str = "--tests"; 156 /// `"cargo"`. 157 const CARGO: &str = "cargo"; 158 /// `"test --doc"`. 159 const TEST_DOC: &str = "test --doc"; 160 /// `"test --all-targets"`. 161 const TEST_ALL_TARGETS: &str = "test --all-targets"; 162 /// `"1"`. 163 const ONE: &str = "1"; 164 /// `"2"`. 165 const TWO: &str = "2"; 166 /// `"3"`. 167 const THREE: &str = "3"; 168 /// `"4"`. 169 const FOUR: &str = "4"; 170 /// `"cargo "`. 171 const CARGO_SPACE: &str = "cargo "; 172 /// Error returned when parsing arguments passed to the application. 173 #[cfg_attr(test, derive(Debug, PartialEq))] 174 pub(crate) enum ArgsErr { 175 /// Error when no arguments exist. 176 NoArgs, 177 /// Error when no command was passed. 178 NoCommand, 179 /// Error when an unknown argument is passed. The contained [`OsString`] is the value of the unknown command 180 /// or option. 181 UnknownArg(OsString), 182 /// Error when an option is passed more than once. The contained [`OsString`] is the duplicate argument. 183 DuplicateOption(OsString), 184 /// Error when `help` is passed followed by a non-empty sequence of arguments. 185 HelpWithArgs, 186 /// Error when `version` is passed followed by a non-empty sequence of arguments. 187 VersionWithArgs, 188 /// Error when `--dir` is passed with no file path to the directory `ci-cargo` should run in. 189 MissingDirPath, 190 /// Error when `--cargo-path` is passed with no file path to the directory `cargo` should be located in. 191 MissingCargoPath, 192 /// Error when `--cargo-home` is passed with no file path to the storage directory `cargo` uses. 193 MissingCargoHome, 194 /// Error when `--rustup-home` is passed with no file path to the storage directory `rustup` uses. 195 MissingRustupHome, 196 /// Error when `--all-targets` is passed with other targets. 197 AllTargets, 198 /// Error when `--doc` is passed with other targets with the `test` command. 199 Doc, 200 /// Error when `--ignored` and `--include-ignored` are passed with the `test` command. 201 IgnoredIncludeIgnored, 202 /// Error when `--ignore-features` was not passed any features. 203 /// 204 /// Note to _only_ pass in the empty set in an interactive terminal, 205 /// you likely will need to use quotes. 206 MissingIgnoredFeatures, 207 /// Error when `--ignore-features` is passed duplicate features. 208 DuplicateIgnoredFeatures(OsString), 209 } 210 impl ArgsErr { 211 /// Writes `self` to `stderr`. 212 pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> { 213 const FINAL_SENTENCE: &str = " See ci-cargo help for more information."; 214 match self { 215 Self::NoArgs => writeln!( 216 stderr, 217 "No arguments exist including the name of the process itself.{FINAL_SENTENCE}" 218 ), 219 Self::NoCommand => writeln!( 220 stderr, 221 "A command was not passed as the first argument.{FINAL_SENTENCE}" 222 ), 223 Self::UnknownArg(arg) => { 224 writeln!( 225 stderr, 226 "{} is an unknown argument.{FINAL_SENTENCE}", 227 arg.display() 228 ) 229 } 230 Self::DuplicateOption(arg) => { 231 writeln!( 232 stderr, 233 "{} was passed more than once.{FINAL_SENTENCE}", 234 arg.display() 235 ) 236 } 237 Self::HelpWithArgs => { 238 writeln!( 239 stderr, 240 "{HELP} was passed with one or more arguments.{FINAL_SENTENCE}", 241 ) 242 } 243 Self::VersionWithArgs => { 244 writeln!( 245 stderr, 246 "{VERSION} was passed with one or more arguments.{FINAL_SENTENCE}", 247 ) 248 } 249 Self::MissingDirPath => { 250 writeln!( 251 stderr, 252 "{DIR} was passed without a path to the directory ci-cargo should run in.{FINAL_SENTENCE}" 253 ) 254 } 255 Self::MissingCargoPath => { 256 writeln!( 257 stderr, 258 "{CARGO_PATH} was passed without a path to the directory cargo is located in.{FINAL_SENTENCE}" 259 ) 260 } 261 Self::MissingCargoHome => { 262 writeln!( 263 stderr, 264 "{CARGO_HOME} was passed without a path to the cargo storage directory.{FINAL_SENTENCE}" 265 ) 266 } 267 Self::MissingRustupHome => { 268 writeln!( 269 stderr, 270 "{RUSTUP_HOME} was passed without a path to the rustup storage directory.{FINAL_SENTENCE}" 271 ) 272 } 273 Self::AllTargets => { 274 writeln!( 275 stderr, 276 "{ALL_TARGETS} was passed with other targets.{FINAL_SENTENCE}" 277 ) 278 } 279 Self::Doc => { 280 writeln!( 281 stderr, 282 "{DOC} was passed with other targets.{FINAL_SENTENCE}" 283 ) 284 } 285 Self::IgnoredIncludeIgnored => { 286 writeln!( 287 stderr, 288 "{IGNORED} and {INCLUDE_IGNORED} were both passed.{FINAL_SENTENCE}" 289 ) 290 } 291 Self::MissingIgnoredFeatures => { 292 writeln!( 293 stderr, 294 "{IGNORE_FEATURES} was passed without any features to ignore.{FINAL_SENTENCE}" 295 ) 296 } 297 Self::DuplicateIgnoredFeatures(feats) => { 298 writeln!( 299 stderr, 300 "{IGNORE_FEATURES} was passed {} which contains at least one duplicate feature.{FINAL_SENTENCE}", 301 feats.display() 302 ) 303 } 304 } 305 } 306 } 307 /// Options to use for `cargo`. 308 #[expect( 309 clippy::struct_excessive_bools, 310 reason = "not a problem. arguable false positive based on its use" 311 )] 312 #[cfg_attr(test, derive(Debug, PartialEq))] 313 pub(crate) struct Opts { 314 /// The directory to run `ci-cargo` in. 315 pub exec_dir: Option<PathBuf>, 316 /// Storage directory for `rustup`. 317 pub rustup_home: Option<PathBuf>, 318 /// Path to `cargo`. 319 pub cargo_path: PathBuf, 320 /// Storage directory for `cargo`. 321 pub cargo_home: Option<PathBuf>, 322 /// `true` iff color should be outputted. 323 pub color: bool, 324 /// `true` iff `cargo` should be used instead of `cargo +stable`. 325 pub default_toolchain: bool, 326 /// `true` iff implied features should be allowed an tested. 327 pub allow_implied_features: bool, 328 /// `true` iff `compile_error`s should be ignored. 329 pub ignore_compile_errors: bool, 330 /// `true` iff `--ignore-rust-version` should be passed. 331 pub ignore_msrv: bool, 332 /// `true` iff progress should be written to `stdout`. 333 pub progress: bool, 334 /// `true` iff the MSRV toolchain should not be used. 335 pub skip_msrv: bool, 336 /// `true` iff the toolchains used and combinations of features run on should be written 337 /// to `stdout` upon success. 338 pub summary: bool, 339 /// The features to ignore. 340 /// 341 /// Note this is empty iff there are no features to ignore. The contained features 342 /// are distinct. The empty `String` corresponds to the empty set of features to 343 /// ignore (i.e., --no-default-features). 344 pub ignore_features: Vec<String>, 345 } 346 impl Default for Opts { 347 fn default() -> Self { 348 Self { 349 exec_dir: None, 350 rustup_home: None, 351 cargo_path: cargo_path(), 352 cargo_home: None, 353 color: false, 354 default_toolchain: false, 355 allow_implied_features: false, 356 ignore_compile_errors: false, 357 ignore_msrv: false, 358 progress: false, 359 skip_msrv: false, 360 summary: false, 361 ignore_features: Vec::new(), 362 } 363 } 364 } 365 /// Controls if `cargo test -- --ignored` or `cargo test --include-ignored` should be run. 366 #[cfg_attr(test, derive(Debug, PartialEq))] 367 #[derive(Clone, Copy, Default)] 368 pub(crate) enum Ignored { 369 /// Don't run any `ignore` tests. 370 #[default] 371 None, 372 /// Only run `ignore` tests. 373 Only, 374 /// Run all tests. 375 Include, 376 } 377 /// Positive `usize` or `usize::MAX + 1`. 378 /// 379 /// Since we don't use 0, it's repurposed as `usize::MAX + 1`. 380 #[cfg_attr(test, derive(Debug, PartialEq))] 381 #[derive(Clone, Copy)] 382 pub(crate) struct NonZeroUsizePlus1(usize); 383 impl NonZeroUsizePlus1 { 384 /// Returns `Self` containing `val`. 385 /// 386 /// Note calling code must know that `0` is treated liked 387 /// `usize::MAX + 1`. 388 pub(crate) const fn new(val: usize) -> Self { 389 Self(val) 390 } 391 } 392 impl Display for NonZeroUsizePlus1 { 393 #[expect(unsafe_code, reason = "comment justifies correctness")] 394 #[expect( 395 clippy::arithmetic_side_effects, 396 reason = "comment justifies correctness" 397 )] 398 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 399 /// Helper for `unlikely`. 400 #[inline(always)] 401 #[cold] 402 const fn cold_path() {} 403 /// Hint that a branch is unlikely. 404 #[expect( 405 clippy::inline_always, 406 reason = "purpose is for the compiler to not optimize" 407 )] 408 #[inline(always)] 409 const fn unlikely(b: bool) -> bool { 410 if b { 411 cold_path(); 412 true 413 } else { 414 false 415 } 416 } 417 if unlikely(self.0 == 0) { 418 let mut val = usize::MAX.to_string(); 419 // This won't underflow since the length is at least 1. 420 let idx = val.len() - 1; 421 // SAFETY: 422 // 2^n is even for all n != 0. When n = 0, 2^n = 1. This means we can always increment the last 423 // digit without carrying. 424 // We only mutate the last digit which is guaranteed to be valid ASCII; thus we can increment 425 // the `u8` since digits are consecutive in ASCII. 426 *unsafe { val.as_bytes_mut() }.index_mut(idx) += 1; 427 write!(f, "{val}") 428 } else { 429 write!(f, "{}", self.0) 430 } 431 } 432 } 433 /// Progress tracker for when `--progress` was passed. 434 struct Progress<'package, 'toolchain> { 435 /// The name of the package. 436 package: &'package str, 437 /// The current toolchain counter. 438 toolchain_counter: &'static str, 439 /// The total toolchains that will be used. 440 toolchain_total: &'static str, 441 /// `"cargo"` or `"cargo "`. 442 /// 443 /// Exists for consistent formatting with [`Self::toolchain`]. 444 cargo_cmd: &'toolchain str, 445 /// The current toolchain. 446 toolchain: &'toolchain str, 447 /// The current command counter. 448 cmd_counter: &'static str, 449 /// The total commands that will be used. 450 cmd_total: &'static str, 451 /// The current command. 452 cmd: &'static str, 453 /// The total number of features in the power set. 454 features_total: String, 455 /// The time in which we started. 456 time_started: Instant, 457 /// `stdout` stream. 458 /// 459 /// None iff we encountered any error when writing 460 /// to it. 461 stdout: Option<StdoutLock<'static>>, 462 } 463 impl<'package> Progress<'package, '_> { 464 /// Returns `Self` based on running check. 465 fn check( 466 package: &'package str, 467 toolchain: Toolchain<'_>, 468 use_msrv: bool, 469 features_total: NonZeroUsizePlus1, 470 ) -> Self { 471 Self::inner_new(package, CHECK, ONE, toolchain, use_msrv, features_total) 472 } 473 /// Returns `Self` based on running clippy. 474 fn clippy( 475 package: &'package str, 476 toolchain: Toolchain<'_>, 477 use_msrv: bool, 478 features_total: NonZeroUsizePlus1, 479 ) -> Self { 480 Self::inner_new(package, CLIPPY, ONE, toolchain, use_msrv, features_total) 481 } 482 /// Returns `Self` based on running test. 483 fn test( 484 package: &'package str, 485 toolchain: Toolchain<'_>, 486 use_msrv: bool, 487 features_total: NonZeroUsizePlus1, 488 all_targets: bool, 489 ) -> Self { 490 if all_targets { 491 Self::inner_new( 492 package, 493 TEST_ALL_TARGETS, 494 TWO, 495 toolchain, 496 use_msrv, 497 features_total, 498 ) 499 } else { 500 Self::inner_new(package, TEST, ONE, toolchain, use_msrv, features_total) 501 } 502 } 503 /// Returns `Self` based on running both check and clippy. 504 fn check_clippy( 505 package: &'package str, 506 toolchain: Toolchain<'_>, 507 use_msrv: bool, 508 features_total: NonZeroUsizePlus1, 509 ) -> Self { 510 Self::inner_new(package, CHECK, TWO, toolchain, use_msrv, features_total) 511 } 512 /// Returns `Self` based on running both check and test. 513 fn check_test( 514 package: &'package str, 515 toolchain: Toolchain<'_>, 516 use_msrv: bool, 517 features_total: NonZeroUsizePlus1, 518 test_all_targets: bool, 519 ) -> Self { 520 if test_all_targets { 521 Self::inner_new(package, CHECK, THREE, toolchain, use_msrv, features_total) 522 } else { 523 Self::inner_new(package, CHECK, TWO, toolchain, use_msrv, features_total) 524 } 525 } 526 /// Returns `Self` based on running both clippy and test. 527 fn clippy_test( 528 package: &'package str, 529 toolchain: Toolchain<'_>, 530 use_msrv: bool, 531 features_total: NonZeroUsizePlus1, 532 test_all_targets: bool, 533 ) -> Self { 534 if test_all_targets { 535 Self::inner_new(package, CLIPPY, THREE, toolchain, use_msrv, features_total) 536 } else { 537 Self::inner_new(package, CLIPPY, TWO, toolchain, use_msrv, features_total) 538 } 539 } 540 /// Returns `Self` based on running check, clippy, and test. 541 fn check_clippy_test( 542 package: &'package str, 543 toolchain: Toolchain<'_>, 544 use_msrv: bool, 545 features_total: NonZeroUsizePlus1, 546 test_all_targets: bool, 547 ) -> Self { 548 if test_all_targets { 549 Self::inner_new(package, CHECK, FOUR, toolchain, use_msrv, features_total) 550 } else { 551 Self::inner_new(package, CHECK, THREE, toolchain, use_msrv, features_total) 552 } 553 } 554 /// Returns `Self` based on the passed arguments. 555 fn inner_new( 556 package: &'package str, 557 cmd: &'static str, 558 cmd_total: &'static str, 559 tool: Toolchain<'_>, 560 use_msrv: bool, 561 features_total: NonZeroUsizePlus1, 562 ) -> Self { 563 let (cargo_cmd, toolchain) = if matches!(tool, Toolchain::Stable) { 564 (CARGO_SPACE, "+stable") 565 } else { 566 (CARGO, "") 567 }; 568 Self { 569 package, 570 toolchain_counter: ONE, 571 toolchain_total: if use_msrv { TWO } else { ONE }, 572 cargo_cmd, 573 toolchain, 574 cmd_counter: ONE, 575 cmd_total, 576 cmd, 577 features_total: features_total.to_string(), 578 time_started: Instant::now(), 579 stdout: Some(io::stdout().lock()), 580 } 581 } 582 /// Writes the progress so far to `stdout`. 583 /// 584 /// If writing to `stdout` errors, then `stdout` will never be written to. 585 fn write_to_stdout( 586 &mut self, 587 features: &str, 588 features_counter: usize, 589 features_skipped: usize, 590 ) { 591 if let Some(ref mut std) = self.stdout { 592 // Example: 593 // "Package: foo. Toolchain (1/2): cargo +stable. Features (18/128, 3 skipped): foo,bar. Command (1/2): clippy. Time running: 49 s."); 594 // Note `features_skipped` maxes at `usize::MAX` since the empty set is never skipped. 595 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() { 596 drop(self.stdout.take()); 597 } 598 } 599 } 600 } 601 /// Target selection. 602 #[cfg_attr(test, derive(Debug, PartialEq))] 603 #[derive(Clone, Copy)] 604 pub(crate) enum Target { 605 /// `--benches`. 606 Benches, 607 /// `--bins`. 608 Bins, 609 /// `--examples`. 610 Examples, 611 /// `--lib`. 612 Lib, 613 /// `--tests`. 614 Tests, 615 } 616 impl Target { 617 /// Transfoms `self` into a `u8`. 618 const fn to_u8(self) -> u8 { 619 match self { 620 Self::Benches => 1, 621 Self::Bins => 2, 622 Self::Examples => 4, 623 Self::Lib => 8, 624 Self::Tests => 16, 625 } 626 } 627 /// Returns `self` as an `OsString`. 628 fn to_os_string(self) -> OsString { 629 match match self { 630 Self::Benches => BENCHES, 631 Self::Bins => BINS, 632 Self::Examples => EXAMPLES, 633 Self::Lib => LIB, 634 Self::Tests => TESTS, 635 } 636 .parse() 637 { 638 Ok(val) => val, 639 Err(e) => match e {}, 640 } 641 } 642 } 643 /// Combination of targets to select. 644 #[cfg_attr(test, derive(Debug, PartialEq))] 645 #[derive(Clone, Copy, Default)] 646 pub(crate) struct Targets(u8); 647 impl Targets { 648 /// Returns `Self` only containing `target`. 649 const fn new(target: Target) -> Self { 650 Self(target.to_u8()) 651 } 652 /// Adds `target` to `self` returning `true` iff `target` wasn't added before. 653 const fn add(&mut self, target: Target) -> bool { 654 let this = self.0 | target.to_u8(); 655 if this == self.0 { 656 false 657 } else { 658 self.0 = this; 659 true 660 } 661 } 662 /// Returns `true` iff `self` contains `target`. 663 pub(super) const fn contains(self, target: Target) -> bool { 664 let val = target.to_u8(); 665 self.0 & val == val 666 } 667 } 668 /// Test targets to select. 669 #[cfg_attr(test, derive(Debug, PartialEq))] 670 #[derive(Clone, Copy)] 671 pub(crate) enum CheckClippyTargets { 672 /// Use the default targets. 673 Default, 674 /// `--all-targets`. 675 All, 676 /// All targets. 677 Targets(Targets), 678 } 679 /// Test targets to select. 680 #[cfg_attr(test, derive(Debug, PartialEq))] 681 #[derive(Clone, Copy)] 682 pub(crate) enum TestTargets { 683 /// Use the default targets. 684 Default, 685 /// `--all-targets`. 686 /// 687 /// This causes all non-`doc` targets to be run and the `--doc` target/mode to be run as well. 688 All, 689 /// Non-`doc` targets. 690 Targets(Targets), 691 /// `--doc`. 692 Doc, 693 } 694 /// `cargo` command(s) we should run. 695 #[cfg_attr(test, derive(Debug, PartialEq))] 696 #[derive(Clone, Copy)] 697 pub(crate) enum Cmd { 698 /// `cargo check`. 699 Check(CheckClippyTargets), 700 /// `cargo clippy`. 701 /// 702 /// The contained `bool` is `true` iff `--deny-warnings` was passed. 703 Clippy(CheckClippyTargets, bool), 704 /// `cargo test`. 705 Test(TestTargets, Ignored), 706 /// [`Self::Check`] and [`Self::Clippy`]. 707 CheckClippy(CheckClippyTargets, CheckClippyTargets, bool), 708 /// [`Self::Check`] and [`Self::Test`]. 709 CheckTest(CheckClippyTargets, TestTargets, Ignored), 710 /// [`Self::Clippy`] and [`Self::Test`]. 711 ClippyTest(CheckClippyTargets, bool, TestTargets, Ignored), 712 /// [`Self::Check`], [`Self::Clippy`], and [`Self::Test`]. 713 CheckClippyTest( 714 CheckClippyTargets, 715 CheckClippyTargets, 716 bool, 717 TestTargets, 718 Ignored, 719 ), 720 } 721 impl Cmd { 722 /// Runs the appropriate `cargo` command(s) 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 #[expect(clippy::too_many_lines, reason = "long match expression")] 727 pub(crate) fn run<'a>( 728 self, 729 options: Options<'a, '_, '_>, 730 msrv: Option<&'a str>, 731 power_set: &mut PowerSet<'_>, 732 progress: bool, 733 ) -> Result<(), Box<CargoErr>> { 734 match self { 735 Self::Check(targets) => Self::run_check( 736 progress.then(|| { 737 Progress::check( 738 options.package_name, 739 options.toolchain, 740 msrv.is_some(), 741 power_set.len(), 742 ) 743 }), 744 msrv, 745 options, 746 targets, 747 power_set, 748 ), 749 Self::Clippy(targets, deny_warnings) => Self::run_clippy( 750 progress.then(|| { 751 Progress::clippy( 752 options.package_name, 753 options.toolchain, 754 msrv.is_some(), 755 power_set.len(), 756 ) 757 }), 758 msrv, 759 options, 760 targets, 761 deny_warnings, 762 power_set, 763 ), 764 Self::Test(targets, ignored) => { 765 if matches!(targets, TestTargets::All) { 766 Self::run_test_all_targets( 767 progress.then(|| { 768 Progress::test( 769 options.package_name, 770 options.toolchain, 771 msrv.is_some(), 772 power_set.len(), 773 true, 774 ) 775 }), 776 msrv, 777 options, 778 ignored, 779 power_set, 780 ) 781 } else { 782 Self::run_test( 783 progress.then(|| { 784 Progress::test( 785 options.package_name, 786 options.toolchain, 787 msrv.is_some(), 788 power_set.len(), 789 false, 790 ) 791 }), 792 msrv, 793 options, 794 targets, 795 ignored, 796 power_set, 797 ) 798 } 799 } 800 Self::CheckClippy(check_targets, clippy_targets, deny_warnings) => { 801 Self::run_check_clippy( 802 progress.then(|| { 803 Progress::check_clippy( 804 options.package_name, 805 options.toolchain, 806 msrv.is_some(), 807 power_set.len(), 808 ) 809 }), 810 msrv, 811 options, 812 check_targets, 813 (clippy_targets, deny_warnings), 814 power_set, 815 ) 816 } 817 Self::CheckTest(check_targets, test_targets, ignored) => { 818 if matches!(test_targets, TestTargets::All) { 819 Self::run_check_test_all_targets( 820 progress.then(|| { 821 Progress::check_test( 822 options.package_name, 823 options.toolchain, 824 msrv.is_some(), 825 power_set.len(), 826 true, 827 ) 828 }), 829 msrv, 830 options, 831 check_targets, 832 ignored, 833 power_set, 834 ) 835 } else { 836 Self::run_check_test( 837 progress.then(|| { 838 Progress::check_test( 839 options.package_name, 840 options.toolchain, 841 msrv.is_some(), 842 power_set.len(), 843 false, 844 ) 845 }), 846 msrv, 847 options, 848 check_targets, 849 (test_targets, ignored), 850 power_set, 851 ) 852 } 853 } 854 Self::ClippyTest(clippy_targets, deny_warnings, test_targets, ignored) => { 855 if matches!(test_targets, TestTargets::All) { 856 Self::run_clippy_test_all_targets( 857 progress.then(|| { 858 Progress::clippy_test( 859 options.package_name, 860 options.toolchain, 861 msrv.is_some(), 862 power_set.len(), 863 true, 864 ) 865 }), 866 msrv, 867 options, 868 (clippy_targets, deny_warnings), 869 ignored, 870 power_set, 871 ) 872 } else { 873 Self::run_clippy_test( 874 progress.then(|| { 875 Progress::clippy_test( 876 options.package_name, 877 options.toolchain, 878 msrv.is_some(), 879 power_set.len(), 880 false, 881 ) 882 }), 883 msrv, 884 options, 885 (clippy_targets, deny_warnings), 886 (test_targets, ignored), 887 power_set, 888 ) 889 } 890 } 891 Self::CheckClippyTest( 892 check_targets, 893 clippy_targets, 894 deny_warnings, 895 test_targets, 896 ignored, 897 ) => { 898 if matches!(test_targets, TestTargets::All) { 899 Self::run_check_clippy_test_all_targets( 900 progress.then(|| { 901 Progress::check_clippy_test( 902 options.package_name, 903 options.toolchain, 904 msrv.is_some(), 905 power_set.len(), 906 true, 907 ) 908 }), 909 msrv, 910 options, 911 check_targets, 912 (clippy_targets, deny_warnings), 913 ignored, 914 power_set, 915 ) 916 } else { 917 Self::run_check_clippy_test( 918 progress.then(|| { 919 Progress::check_clippy_test( 920 options.package_name, 921 options.toolchain, 922 msrv.is_some(), 923 power_set.len(), 924 false, 925 ) 926 }), 927 msrv, 928 options, 929 check_targets, 930 (clippy_targets, deny_warnings), 931 (test_targets, ignored), 932 power_set, 933 ) 934 } 935 } 936 } 937 } 938 /// Runs `cargo check` for all features in `power_set`. 939 /// 940 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 941 /// later used. 942 fn run_check<'a>( 943 mut progress: Option<Progress<'_, 'a>>, 944 msrv: Option<&'a str>, 945 mut options: Options<'a, '_, '_>, 946 targets: CheckClippyTargets, 947 power_set: &mut PowerSet<'_>, 948 ) -> Result<(), Box<CargoErr>> { 949 /// Runs the commands for each feature combination. 950 fn run_loop<'a>( 951 progress: &mut Option<Progress<'_, 'a>>, 952 options: &mut Options<'a, '_, '_>, 953 targets: CheckClippyTargets, 954 power_set: &mut PowerSet<'_>, 955 ) -> Result<(), Box<CargoErr>> { 956 let mut feat_counter = 1usize; 957 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 958 // cause some bloat though. 959 if let Some(ref mut prog) = *progress { 960 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 961 prog.cmd_counter = ONE; 962 prog.cmd = CHECK; 963 prog.write_to_stdout(set, feat_counter, skip_count); 964 Check::run(options, targets, set)?; 965 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 966 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 967 // [`NonZeroUsizePlus1::fmt`]. 968 feat_counter = feat_counter.wrapping_add(1); 969 } 970 } else { 971 while let Some(set) = power_set.next_set() { 972 Check::run(options, targets, set)?; 973 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 974 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 975 // [`NonZeroUsizePlus1::fmt`]. 976 feat_counter = feat_counter.wrapping_add(1); 977 } 978 } 979 Ok(()) 980 } 981 run_loop(&mut progress, &mut options, targets, power_set).and_then(|()| { 982 if let Some(msrv_val) = msrv { 983 if let Some(ref mut prog) = progress { 984 prog.toolchain_counter = TWO; 985 prog.cargo_cmd = CARGO_SPACE; 986 prog.toolchain = msrv_val; 987 } 988 options.toolchain = Toolchain::Msrv(msrv_val); 989 power_set.reset(); 990 run_loop(&mut progress, &mut options, targets, power_set) 991 } else { 992 Ok(()) 993 } 994 }) 995 } 996 /// Runs `cargo clippy` for all features in `power_set`. 997 /// 998 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 999 /// later used. 1000 fn run_clippy<'a>( 1001 mut progress: Option<Progress<'_, 'a>>, 1002 msrv: Option<&'a str>, 1003 mut options: Options<'a, '_, '_>, 1004 targets: CheckClippyTargets, 1005 deny_warnings: bool, 1006 power_set: &mut PowerSet<'_>, 1007 ) -> Result<(), Box<CargoErr>> { 1008 /// Runs the commands for each feature combination. 1009 fn run_loop<'a>( 1010 progress: &mut Option<Progress<'_, 'a>>, 1011 options: &mut Options<'a, '_, '_>, 1012 targets: CheckClippyTargets, 1013 deny_warnings: bool, 1014 power_set: &mut PowerSet<'_>, 1015 ) -> Result<(), Box<CargoErr>> { 1016 let mut feat_counter = 1usize; 1017 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1018 // cause some bloat though. 1019 if let Some(ref mut prog) = *progress { 1020 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1021 prog.cmd_counter = ONE; 1022 prog.cmd = CLIPPY; 1023 prog.write_to_stdout(set, feat_counter, skip_count); 1024 Clippy::run(options, targets, deny_warnings, set)?; 1025 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1026 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1027 // [`NonZeroUsizePlus1::fmt`]. 1028 feat_counter = feat_counter.wrapping_add(1); 1029 } 1030 } else { 1031 while let Some(set) = power_set.next_set() { 1032 Clippy::run(options, targets, deny_warnings, set)?; 1033 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1034 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1035 // [`NonZeroUsizePlus1::fmt`]. 1036 feat_counter = feat_counter.wrapping_add(1); 1037 } 1038 } 1039 Ok(()) 1040 } 1041 run_loop( 1042 &mut progress, 1043 &mut options, 1044 targets, 1045 deny_warnings, 1046 power_set, 1047 ) 1048 .and_then(|()| { 1049 if let Some(msrv_val) = msrv { 1050 if let Some(ref mut prog) = progress { 1051 prog.toolchain_counter = TWO; 1052 prog.cargo_cmd = CARGO_SPACE; 1053 prog.toolchain = msrv_val; 1054 } 1055 options.toolchain = Toolchain::Msrv(msrv_val); 1056 power_set.reset(); 1057 run_loop( 1058 &mut progress, 1059 &mut options, 1060 targets, 1061 deny_warnings, 1062 power_set, 1063 ) 1064 } else { 1065 Ok(()) 1066 } 1067 }) 1068 } 1069 /// Runs `cargo test` for all features in `power_set`. 1070 /// 1071 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1072 /// later used. 1073 fn run_test<'a>( 1074 mut progress: Option<Progress<'_, 'a>>, 1075 msrv: Option<&'a str>, 1076 mut options: Options<'a, '_, '_>, 1077 targets: TestTargets, 1078 ignored: Ignored, 1079 power_set: &mut PowerSet<'_>, 1080 ) -> Result<(), Box<CargoErr>> { 1081 /// Runs the commands for each feature combination. 1082 fn run_loop<'a>( 1083 progress: &mut Option<Progress<'_, 'a>>, 1084 options: &mut Options<'a, '_, '_>, 1085 targets: TestTargets, 1086 ignored: Ignored, 1087 power_set: &mut PowerSet<'_>, 1088 ) -> Result<(), Box<CargoErr>> { 1089 let mut feat_counter = 1usize; 1090 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1091 // cause some bloat though. 1092 if let Some(ref mut prog) = *progress { 1093 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1094 prog.cmd_counter = ONE; 1095 prog.cmd = TEST; 1096 prog.write_to_stdout(set, feat_counter, skip_count); 1097 Test::run(options, targets, ignored, set)?; 1098 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1099 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1100 // [`NonZeroUsizePlus1::fmt`]. 1101 feat_counter = feat_counter.wrapping_add(1); 1102 } 1103 } else { 1104 while let Some(set) = power_set.next_set() { 1105 Test::run(options, targets, ignored, set)?; 1106 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1107 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1108 // [`NonZeroUsizePlus1::fmt`]. 1109 feat_counter = feat_counter.wrapping_add(1); 1110 } 1111 } 1112 Ok(()) 1113 } 1114 run_loop(&mut progress, &mut options, targets, ignored, power_set).and_then(|()| { 1115 if let Some(msrv_val) = msrv { 1116 if let Some(ref mut prog) = progress { 1117 prog.toolchain_counter = TWO; 1118 prog.cargo_cmd = CARGO_SPACE; 1119 prog.toolchain = msrv_val; 1120 } 1121 options.toolchain = Toolchain::Msrv(msrv_val); 1122 power_set.reset(); 1123 run_loop(&mut progress, &mut options, targets, ignored, power_set) 1124 } else { 1125 Ok(()) 1126 } 1127 }) 1128 } 1129 /// Runs `cargo test --all-targets` and `cargo test --doc` for all features in `power_set`. 1130 /// 1131 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1132 /// later used. 1133 fn run_test_all_targets<'a>( 1134 mut progress: Option<Progress<'_, 'a>>, 1135 msrv: Option<&'a str>, 1136 mut options: Options<'a, '_, '_>, 1137 ignored: Ignored, 1138 power_set: &mut PowerSet<'_>, 1139 ) -> Result<(), Box<CargoErr>> { 1140 /// Runs the commands for each feature combination. 1141 fn run_loop<'a>( 1142 progress: &mut Option<Progress<'_, 'a>>, 1143 options: &mut Options<'a, '_, '_>, 1144 ignored: Ignored, 1145 power_set: &mut PowerSet<'_>, 1146 ) -> Result<(), Box<CargoErr>> { 1147 let mut feat_counter = 1usize; 1148 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1149 // cause some bloat though. 1150 if let Some(ref mut prog) = *progress { 1151 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1152 prog.cmd_counter = ONE; 1153 prog.cmd = TEST_ALL_TARGETS; 1154 prog.write_to_stdout(set, feat_counter, skip_count); 1155 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1156 prog.cmd_counter = TWO; 1157 prog.cmd = TEST_DOC; 1158 prog.write_to_stdout(set, feat_counter, skip_count); 1159 Test::run(options, TestTargets::Doc, ignored, set) 1160 })?; 1161 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1162 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1163 // [`NonZeroUsizePlus1::fmt`]. 1164 feat_counter = feat_counter.wrapping_add(1); 1165 } 1166 } else { 1167 while let Some(set) = power_set.next_set() { 1168 Test::run(options, TestTargets::All, ignored, set) 1169 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set))?; 1170 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1171 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1172 // [`NonZeroUsizePlus1::fmt`]. 1173 feat_counter = feat_counter.wrapping_add(1); 1174 } 1175 } 1176 Ok(()) 1177 } 1178 run_loop(&mut progress, &mut options, ignored, power_set).and_then(|()| { 1179 if let Some(msrv_val) = msrv { 1180 if let Some(ref mut prog) = progress { 1181 prog.toolchain_counter = TWO; 1182 prog.cargo_cmd = CARGO_SPACE; 1183 prog.toolchain = msrv_val; 1184 } 1185 options.toolchain = Toolchain::Msrv(msrv_val); 1186 power_set.reset(); 1187 run_loop(&mut progress, &mut options, ignored, power_set) 1188 } else { 1189 Ok(()) 1190 } 1191 }) 1192 } 1193 /// Runs `cargo check` and `cargo clippy` for all features in `power_set`. 1194 /// 1195 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1196 /// later used. 1197 fn run_check_clippy<'a>( 1198 mut progress: Option<Progress<'_, 'a>>, 1199 msrv: Option<&'a str>, 1200 mut options: Options<'a, '_, '_>, 1201 check_targets: CheckClippyTargets, 1202 clippy_info: (CheckClippyTargets, bool), 1203 power_set: &mut PowerSet<'_>, 1204 ) -> Result<(), Box<CargoErr>> { 1205 /// Runs the commands for each feature combination. 1206 fn run_loop<'a>( 1207 progress: &mut Option<Progress<'_, 'a>>, 1208 options: &mut Options<'a, '_, '_>, 1209 check_targets: CheckClippyTargets, 1210 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1211 power_set: &mut PowerSet<'_>, 1212 ) -> Result<(), Box<CargoErr>> { 1213 let mut feat_counter = 1usize; 1214 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1215 // cause some bloat though. 1216 if let Some(ref mut prog) = *progress { 1217 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1218 prog.cmd_counter = ONE; 1219 prog.cmd = CHECK; 1220 prog.write_to_stdout(set, feat_counter, skip_count); 1221 Check::run(options, check_targets, set).and_then(|()| { 1222 prog.cmd_counter = TWO; 1223 prog.cmd = CLIPPY; 1224 prog.write_to_stdout(set, feat_counter, skip_count); 1225 Clippy::run(options, clippy_targets, deny_warnings, set) 1226 })?; 1227 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1228 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1229 // [`NonZeroUsizePlus1::fmt`]. 1230 feat_counter = feat_counter.wrapping_add(1); 1231 } 1232 } else { 1233 while let Some(set) = power_set.next_set() { 1234 Check::run(options, check_targets, set) 1235 .and_then(|()| Clippy::run(options, clippy_targets, deny_warnings, set))?; 1236 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1237 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1238 // [`NonZeroUsizePlus1::fmt`]. 1239 feat_counter = feat_counter.wrapping_add(1); 1240 } 1241 } 1242 Ok(()) 1243 } 1244 run_loop( 1245 &mut progress, 1246 &mut options, 1247 check_targets, 1248 clippy_info, 1249 power_set, 1250 ) 1251 .and_then(|()| { 1252 if let Some(msrv_val) = msrv { 1253 if let Some(ref mut prog) = progress { 1254 prog.toolchain_counter = TWO; 1255 prog.cargo_cmd = CARGO_SPACE; 1256 prog.toolchain = msrv_val; 1257 } 1258 options.toolchain = Toolchain::Msrv(msrv_val); 1259 power_set.reset(); 1260 run_loop( 1261 &mut progress, 1262 &mut options, 1263 check_targets, 1264 clippy_info, 1265 power_set, 1266 ) 1267 } else { 1268 Ok(()) 1269 } 1270 }) 1271 } 1272 /// Runs `cargo check` and `cargo test` for all features in `power_set`. 1273 /// 1274 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1275 /// later used. 1276 fn run_check_test<'a>( 1277 mut progress: Option<Progress<'_, 'a>>, 1278 msrv: Option<&'a str>, 1279 mut options: Options<'a, '_, '_>, 1280 check_targets: CheckClippyTargets, 1281 test_info: (TestTargets, Ignored), 1282 power_set: &mut PowerSet<'_>, 1283 ) -> Result<(), Box<CargoErr>> { 1284 /// Runs the commands for each feature combination. 1285 fn run_loop<'a>( 1286 progress: &mut Option<Progress<'_, 'a>>, 1287 options: &mut Options<'a, '_, '_>, 1288 check_targets: CheckClippyTargets, 1289 (test_targets, ignored): (TestTargets, Ignored), 1290 power_set: &mut PowerSet<'_>, 1291 ) -> Result<(), Box<CargoErr>> { 1292 let mut feat_counter = 1usize; 1293 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1294 // cause some bloat though. 1295 if let Some(ref mut prog) = *progress { 1296 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1297 prog.cmd_counter = ONE; 1298 prog.cmd = CHECK; 1299 prog.write_to_stdout(set, feat_counter, skip_count); 1300 Check::run(options, check_targets, set).and_then(|()| { 1301 prog.cmd_counter = TWO; 1302 prog.cmd = TEST; 1303 prog.write_to_stdout(set, feat_counter, skip_count); 1304 Test::run(options, test_targets, ignored, set) 1305 })?; 1306 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1307 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1308 // [`NonZeroUsizePlus1::fmt`]. 1309 feat_counter = feat_counter.wrapping_add(1); 1310 } 1311 } else { 1312 while let Some(set) = power_set.next_set() { 1313 Check::run(options, check_targets, set) 1314 .and_then(|()| Test::run(options, test_targets, ignored, set))?; 1315 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1316 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1317 // [`NonZeroUsizePlus1::fmt`]. 1318 feat_counter = feat_counter.wrapping_add(1); 1319 } 1320 } 1321 Ok(()) 1322 } 1323 run_loop( 1324 &mut progress, 1325 &mut options, 1326 check_targets, 1327 test_info, 1328 power_set, 1329 ) 1330 .and_then(|()| { 1331 if let Some(msrv_val) = msrv { 1332 if let Some(ref mut prog) = progress { 1333 prog.toolchain_counter = TWO; 1334 prog.cargo_cmd = CARGO_SPACE; 1335 prog.toolchain = msrv_val; 1336 } 1337 options.toolchain = Toolchain::Msrv(msrv_val); 1338 power_set.reset(); 1339 run_loop( 1340 &mut progress, 1341 &mut options, 1342 check_targets, 1343 test_info, 1344 power_set, 1345 ) 1346 } else { 1347 Ok(()) 1348 } 1349 }) 1350 } 1351 /// Runs `cargo check`, `cargo test --all-targets`, and `cargo test --doc` for all features in `power_set`. 1352 /// 1353 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1354 /// later used. 1355 fn run_check_test_all_targets<'a>( 1356 mut progress: Option<Progress<'_, 'a>>, 1357 msrv: Option<&'a str>, 1358 mut options: Options<'a, '_, '_>, 1359 check_targets: CheckClippyTargets, 1360 ignored: Ignored, 1361 power_set: &mut PowerSet<'_>, 1362 ) -> Result<(), Box<CargoErr>> { 1363 /// Runs the commands for each feature combination. 1364 fn run_loop<'a>( 1365 progress: &mut Option<Progress<'_, 'a>>, 1366 options: &mut Options<'a, '_, '_>, 1367 check_targets: CheckClippyTargets, 1368 ignored: Ignored, 1369 power_set: &mut PowerSet<'_>, 1370 ) -> Result<(), Box<CargoErr>> { 1371 let mut feat_counter = 1usize; 1372 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1373 // cause some bloat though. 1374 if let Some(ref mut prog) = *progress { 1375 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1376 prog.cmd_counter = ONE; 1377 prog.cmd = CHECK; 1378 prog.write_to_stdout(set, feat_counter, skip_count); 1379 Check::run(options, check_targets, set).and_then(|()| { 1380 prog.cmd_counter = TWO; 1381 prog.cmd = TEST_ALL_TARGETS; 1382 prog.write_to_stdout(set, feat_counter, skip_count); 1383 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1384 prog.cmd_counter = THREE; 1385 prog.cmd = TEST_DOC; 1386 prog.write_to_stdout(set, feat_counter, skip_count); 1387 Test::run(options, TestTargets::Doc, ignored, set) 1388 }) 1389 })?; 1390 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1391 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1392 // [`NonZeroUsizePlus1::fmt`]. 1393 feat_counter = feat_counter.wrapping_add(1); 1394 } 1395 } else { 1396 while let Some(set) = power_set.next_set() { 1397 Check::run(options, check_targets, set).and_then(|()| { 1398 Test::run(options, TestTargets::All, ignored, set) 1399 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1400 })?; 1401 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1402 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1403 // [`NonZeroUsizePlus1::fmt`]. 1404 feat_counter = feat_counter.wrapping_add(1); 1405 } 1406 } 1407 Ok(()) 1408 } 1409 run_loop( 1410 &mut progress, 1411 &mut options, 1412 check_targets, 1413 ignored, 1414 power_set, 1415 ) 1416 .and_then(|()| { 1417 if let Some(msrv_val) = msrv { 1418 if let Some(ref mut prog) = progress { 1419 prog.toolchain_counter = TWO; 1420 prog.cargo_cmd = CARGO_SPACE; 1421 prog.toolchain = msrv_val; 1422 } 1423 options.toolchain = Toolchain::Msrv(msrv_val); 1424 power_set.reset(); 1425 run_loop( 1426 &mut progress, 1427 &mut options, 1428 check_targets, 1429 ignored, 1430 power_set, 1431 ) 1432 } else { 1433 Ok(()) 1434 } 1435 }) 1436 } 1437 /// Runs `cargo clippy` and `cargo test` for all features in `power_set`. 1438 /// 1439 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1440 /// later used. 1441 fn run_clippy_test<'a>( 1442 mut progress: Option<Progress<'_, 'a>>, 1443 msrv: Option<&'a str>, 1444 mut options: Options<'a, '_, '_>, 1445 clippy_info: (CheckClippyTargets, bool), 1446 test_info: (TestTargets, Ignored), 1447 power_set: &mut PowerSet<'_>, 1448 ) -> Result<(), Box<CargoErr>> { 1449 /// Runs the commands for each feature combination. 1450 fn run_loop<'a>( 1451 progress: &mut Option<Progress<'_, 'a>>, 1452 options: &mut Options<'a, '_, '_>, 1453 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1454 (test_targets, ignored): (TestTargets, Ignored), 1455 power_set: &mut PowerSet<'_>, 1456 ) -> Result<(), Box<CargoErr>> { 1457 let mut feat_counter = 1usize; 1458 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1459 // cause some bloat though. 1460 if let Some(ref mut prog) = *progress { 1461 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1462 prog.cmd_counter = ONE; 1463 prog.cmd = CLIPPY; 1464 prog.write_to_stdout(set, feat_counter, skip_count); 1465 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1466 prog.cmd_counter = TWO; 1467 prog.cmd = TEST; 1468 prog.write_to_stdout(set, feat_counter, skip_count); 1469 Test::run(options, test_targets, ignored, set) 1470 })?; 1471 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1472 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1473 // [`NonZeroUsizePlus1::fmt`]. 1474 feat_counter = feat_counter.wrapping_add(1); 1475 } 1476 } else { 1477 while let Some(set) = power_set.next_set() { 1478 Clippy::run(options, clippy_targets, deny_warnings, set) 1479 .and_then(|()| Test::run(options, test_targets, ignored, set))?; 1480 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1481 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1482 // [`NonZeroUsizePlus1::fmt`]. 1483 feat_counter = feat_counter.wrapping_add(1); 1484 } 1485 } 1486 Ok(()) 1487 } 1488 run_loop( 1489 &mut progress, 1490 &mut options, 1491 clippy_info, 1492 test_info, 1493 power_set, 1494 ) 1495 .and_then(|()| { 1496 if let Some(msrv_val) = msrv { 1497 if let Some(ref mut prog) = progress { 1498 prog.toolchain_counter = TWO; 1499 prog.cargo_cmd = CARGO_SPACE; 1500 prog.toolchain = msrv_val; 1501 } 1502 options.toolchain = Toolchain::Msrv(msrv_val); 1503 power_set.reset(); 1504 run_loop( 1505 &mut progress, 1506 &mut options, 1507 clippy_info, 1508 test_info, 1509 power_set, 1510 ) 1511 } else { 1512 Ok(()) 1513 } 1514 }) 1515 } 1516 /// Runs `cargo clippy`, `cargo test --all-targets`, and `cargo test --doc` for all features in `power_set`. 1517 /// 1518 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1519 /// later used. 1520 fn run_clippy_test_all_targets<'a>( 1521 mut progress: Option<Progress<'_, 'a>>, 1522 msrv: Option<&'a str>, 1523 mut options: Options<'a, '_, '_>, 1524 clippy_info: (CheckClippyTargets, bool), 1525 ignored: Ignored, 1526 power_set: &mut PowerSet<'_>, 1527 ) -> Result<(), Box<CargoErr>> { 1528 /// Runs the commands for each feature combination. 1529 fn run_loop<'a>( 1530 progress: &mut Option<Progress<'_, 'a>>, 1531 options: &mut Options<'a, '_, '_>, 1532 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1533 ignored: Ignored, 1534 power_set: &mut PowerSet<'_>, 1535 ) -> Result<(), Box<CargoErr>> { 1536 let mut feat_counter = 1usize; 1537 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1538 // cause some bloat though. 1539 if let Some(ref mut prog) = *progress { 1540 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1541 prog.cmd_counter = ONE; 1542 prog.cmd = CLIPPY; 1543 prog.write_to_stdout(set, feat_counter, skip_count); 1544 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1545 prog.cmd_counter = TWO; 1546 prog.cmd = TEST_ALL_TARGETS; 1547 prog.write_to_stdout(set, feat_counter, skip_count); 1548 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1549 prog.cmd_counter = THREE; 1550 prog.cmd = TEST_DOC; 1551 prog.write_to_stdout(set, feat_counter, skip_count); 1552 Test::run(options, TestTargets::Doc, ignored, set) 1553 }) 1554 })?; 1555 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1556 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1557 // [`NonZeroUsizePlus1::fmt`]. 1558 feat_counter = feat_counter.wrapping_add(1); 1559 } 1560 } else { 1561 while let Some(set) = power_set.next_set() { 1562 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1563 Test::run(options, TestTargets::All, ignored, set) 1564 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1565 })?; 1566 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1567 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1568 // [`NonZeroUsizePlus1::fmt`]. 1569 feat_counter = feat_counter.wrapping_add(1); 1570 } 1571 } 1572 Ok(()) 1573 } 1574 run_loop(&mut progress, &mut options, clippy_info, ignored, power_set).and_then(|()| { 1575 if let Some(msrv_val) = msrv { 1576 if let Some(ref mut prog) = progress { 1577 prog.toolchain_counter = TWO; 1578 prog.cargo_cmd = CARGO_SPACE; 1579 prog.toolchain = msrv_val; 1580 } 1581 options.toolchain = Toolchain::Msrv(msrv_val); 1582 power_set.reset(); 1583 run_loop(&mut progress, &mut options, clippy_info, ignored, power_set) 1584 } else { 1585 Ok(()) 1586 } 1587 }) 1588 } 1589 /// Runs `cargo check`, `cargo clippy`, and `cargo test` for all features in `power_set`. 1590 /// 1591 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1592 /// later used. 1593 fn run_check_clippy_test<'a>( 1594 mut progress: Option<Progress<'_, 'a>>, 1595 msrv: Option<&'a str>, 1596 mut options: Options<'a, '_, '_>, 1597 check_targets: CheckClippyTargets, 1598 clippy_info: (CheckClippyTargets, bool), 1599 test_info: (TestTargets, Ignored), 1600 power_set: &mut PowerSet<'_>, 1601 ) -> Result<(), Box<CargoErr>> { 1602 /// Runs the commands for each feature combination. 1603 fn run_loop<'a>( 1604 progress: &mut Option<Progress<'_, 'a>>, 1605 options: &mut Options<'a, '_, '_>, 1606 check_targets: CheckClippyTargets, 1607 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1608 (test_targets, ignored): (TestTargets, Ignored), 1609 power_set: &mut PowerSet<'_>, 1610 ) -> Result<(), Box<CargoErr>> { 1611 let mut feat_counter = 1usize; 1612 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1613 // cause some bloat though. 1614 if let Some(ref mut prog) = *progress { 1615 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1616 prog.cmd_counter = ONE; 1617 prog.cmd = CHECK; 1618 prog.write_to_stdout(set, feat_counter, skip_count); 1619 Check::run(options, check_targets, set).and_then(|()| { 1620 prog.cmd_counter = TWO; 1621 prog.cmd = CLIPPY; 1622 prog.write_to_stdout(set, feat_counter, skip_count); 1623 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1624 prog.cmd_counter = THREE; 1625 prog.cmd = TEST; 1626 prog.write_to_stdout(set, feat_counter, skip_count); 1627 Test::run(options, test_targets, ignored, set) 1628 }) 1629 })?; 1630 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1631 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1632 // [`NonZeroUsizePlus1::fmt`]. 1633 feat_counter = feat_counter.wrapping_add(1); 1634 } 1635 } else { 1636 while let Some(set) = power_set.next_set() { 1637 Check::run(options, check_targets, set).and_then(|()| { 1638 Clippy::run(options, clippy_targets, deny_warnings, set) 1639 .and_then(|()| Test::run(options, test_targets, ignored, set)) 1640 })?; 1641 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1642 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1643 // [`NonZeroUsizePlus1::fmt`]. 1644 feat_counter = feat_counter.wrapping_add(1); 1645 } 1646 } 1647 Ok(()) 1648 } 1649 run_loop( 1650 &mut progress, 1651 &mut options, 1652 check_targets, 1653 clippy_info, 1654 test_info, 1655 power_set, 1656 ) 1657 .and_then(|()| { 1658 if let Some(msrv_val) = msrv { 1659 if let Some(ref mut prog) = progress { 1660 prog.toolchain_counter = TWO; 1661 prog.cargo_cmd = CARGO_SPACE; 1662 prog.toolchain = msrv_val; 1663 } 1664 options.toolchain = Toolchain::Msrv(msrv_val); 1665 power_set.reset(); 1666 run_loop( 1667 &mut progress, 1668 &mut options, 1669 check_targets, 1670 clippy_info, 1671 test_info, 1672 power_set, 1673 ) 1674 } else { 1675 Ok(()) 1676 } 1677 }) 1678 } 1679 /// Runs `cargo check`, `cargo clippy`, `cargo test --all-targets`, and `cargo test --doc` for all features 1680 /// in `power_set`. 1681 /// 1682 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1683 /// later used. 1684 fn run_check_clippy_test_all_targets<'a>( 1685 mut progress: Option<Progress<'_, 'a>>, 1686 msrv: Option<&'a str>, 1687 mut options: Options<'a, '_, '_>, 1688 check_targets: CheckClippyTargets, 1689 clippy_info: (CheckClippyTargets, bool), 1690 ignored: Ignored, 1691 power_set: &mut PowerSet<'_>, 1692 ) -> Result<(), Box<CargoErr>> { 1693 /// Runs the commands for each feature combination. 1694 fn run_loop<'a>( 1695 progress: &mut Option<Progress<'_, 'a>>, 1696 options: &mut Options<'a, '_, '_>, 1697 check_targets: CheckClippyTargets, 1698 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1699 ignored: Ignored, 1700 power_set: &mut PowerSet<'_>, 1701 ) -> Result<(), Box<CargoErr>> { 1702 let mut feat_counter = 1usize; 1703 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1704 // cause some bloat though. 1705 if let Some(ref mut prog) = *progress { 1706 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1707 prog.cmd_counter = ONE; 1708 prog.cmd = CHECK; 1709 prog.write_to_stdout(set, feat_counter, skip_count); 1710 Check::run(options, check_targets, set).and_then(|()| { 1711 prog.cmd_counter = TWO; 1712 prog.cmd = CLIPPY; 1713 prog.write_to_stdout(set, feat_counter, skip_count); 1714 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1715 prog.cmd_counter = THREE; 1716 prog.cmd = TEST_ALL_TARGETS; 1717 prog.write_to_stdout(set, feat_counter, skip_count); 1718 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1719 prog.cmd_counter = FOUR; 1720 prog.cmd = TEST_DOC; 1721 prog.write_to_stdout(set, feat_counter, skip_count); 1722 Test::run(options, TestTargets::Doc, ignored, set) 1723 }) 1724 }) 1725 })?; 1726 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1727 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1728 // [`NonZeroUsizePlus1::fmt`]. 1729 feat_counter = feat_counter.wrapping_add(1); 1730 } 1731 } else { 1732 while let Some(set) = power_set.next_set() { 1733 Check::run(options, check_targets, set).and_then(|()| { 1734 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1735 Test::run(options, TestTargets::All, ignored, set) 1736 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1737 }) 1738 })?; 1739 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1740 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1741 // [`NonZeroUsizePlus1::fmt`]. 1742 feat_counter = feat_counter.wrapping_add(1); 1743 } 1744 } 1745 Ok(()) 1746 } 1747 run_loop( 1748 &mut progress, 1749 &mut options, 1750 check_targets, 1751 clippy_info, 1752 ignored, 1753 power_set, 1754 ) 1755 .and_then(|()| { 1756 if let Some(msrv_val) = msrv { 1757 if let Some(ref mut prog) = progress { 1758 prog.toolchain_counter = TWO; 1759 prog.cargo_cmd = CARGO_SPACE; 1760 prog.toolchain = msrv_val; 1761 } 1762 options.toolchain = Toolchain::Msrv(msrv_val); 1763 power_set.reset(); 1764 run_loop( 1765 &mut progress, 1766 &mut options, 1767 check_targets, 1768 clippy_info, 1769 ignored, 1770 power_set, 1771 ) 1772 } else { 1773 Ok(()) 1774 } 1775 }) 1776 } 1777 } 1778 /// `cargo` command passed. 1779 enum CargoCmd<'a> { 1780 /// check. 1781 Check(&'a mut CheckClippyTargets), 1782 /// clippy. 1783 Clippy(&'a mut (CheckClippyTargets, bool)), 1784 /// test. 1785 Test(&'a mut (TestTargets, Ignored)), 1786 } 1787 impl CargoCmd<'_> { 1788 /// Adds the `--all-targets` target to `self`. 1789 fn add_all_targets(&mut self) -> Result<(), ArgsErr> { 1790 match *self { 1791 Self::Check(ref mut check_targets) => match **check_targets { 1792 CheckClippyTargets::Default => { 1793 **check_targets = CheckClippyTargets::All; 1794 Ok(()) 1795 } 1796 CheckClippyTargets::All => { 1797 Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1798 Ok(arg) => arg, 1799 Err(e) => match e {}, 1800 })) 1801 } 1802 CheckClippyTargets::Targets(_) => Err(ArgsErr::AllTargets), 1803 }, 1804 Self::Clippy(&mut (ref mut clippy_targets, _)) => match *clippy_targets { 1805 CheckClippyTargets::Default => { 1806 *clippy_targets = CheckClippyTargets::All; 1807 Ok(()) 1808 } 1809 CheckClippyTargets::All => { 1810 Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1811 Ok(arg) => arg, 1812 Err(e) => match e {}, 1813 })) 1814 } 1815 CheckClippyTargets::Targets(_) => Err(ArgsErr::AllTargets), 1816 }, 1817 Self::Test(&mut (ref mut test_targets, _)) => match *test_targets { 1818 TestTargets::Default => { 1819 *test_targets = TestTargets::All; 1820 Ok(()) 1821 } 1822 TestTargets::All => Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1823 Ok(arg) => arg, 1824 Err(e) => match e {}, 1825 })), 1826 TestTargets::Targets(_) | TestTargets::Doc => Err(ArgsErr::AllTargets), 1827 }, 1828 } 1829 } 1830 /// Adds the `target` to `self`. 1831 fn add_target(&mut self, target: Target) -> Result<(), ArgsErr> { 1832 match *self { 1833 Self::Check(ref mut check_targets) => match **check_targets { 1834 CheckClippyTargets::Default => { 1835 **check_targets = CheckClippyTargets::Targets(Targets::new(target)); 1836 Ok(()) 1837 } 1838 CheckClippyTargets::All => Err(ArgsErr::AllTargets), 1839 CheckClippyTargets::Targets(ref mut targets) => { 1840 if targets.add(target) { 1841 Ok(()) 1842 } else { 1843 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1844 } 1845 } 1846 }, 1847 Self::Clippy(&mut (ref mut clippy_targets, _)) => match *clippy_targets { 1848 CheckClippyTargets::Default => { 1849 *clippy_targets = CheckClippyTargets::Targets(Targets::new(target)); 1850 Ok(()) 1851 } 1852 CheckClippyTargets::All => Err(ArgsErr::AllTargets), 1853 CheckClippyTargets::Targets(ref mut targets) => { 1854 if targets.add(target) { 1855 Ok(()) 1856 } else { 1857 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1858 } 1859 } 1860 }, 1861 Self::Test(&mut (ref mut test_targets, _)) => match *test_targets { 1862 TestTargets::Default => { 1863 *test_targets = TestTargets::Targets(Targets::new(target)); 1864 Ok(()) 1865 } 1866 TestTargets::All => Err(ArgsErr::AllTargets), 1867 TestTargets::Targets(ref mut targets) => { 1868 if targets.add(target) { 1869 Ok(()) 1870 } else { 1871 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1872 } 1873 } 1874 TestTargets::Doc => Err(ArgsErr::Doc), 1875 }, 1876 } 1877 } 1878 } 1879 /// Helper to store options from the command line. 1880 #[expect( 1881 clippy::struct_excessive_bools, 1882 reason = "used exclusively in the recursive function MetaCmd::from_args::extract_options" 1883 )] 1884 #[derive(Default)] 1885 struct ArgOpts { 1886 /// `--allow-implied-features`. 1887 allow_implied_features: bool, 1888 /// `--cargo-home` along with the path. 1889 cargo_home: Option<PathBuf>, 1890 /// `--cargo-path` along with the path. 1891 cargo_path: Option<PathBuf>, 1892 /// `--color`. 1893 color: bool, 1894 /// `--default-toolchain`. 1895 default_toolchain: bool, 1896 /// `--dir` along with the path. 1897 dir: Option<PathBuf>, 1898 /// `--ignore-compile-errors`. 1899 ignore_compile_errors: bool, 1900 /// `--ignore-msrv`. 1901 ignore_msrv: bool, 1902 /// `--ignore-features` along with the features to ignore. 1903 ignore_features: Vec<String>, 1904 /// `--progress`. 1905 progress: bool, 1906 /// `--rustup-home` along with the path. 1907 rustup_home: Option<PathBuf>, 1908 /// `--skip-msrv`. 1909 skip_msrv: bool, 1910 /// `--summary`. 1911 summary: bool, 1912 } 1913 /// Returns `"cargo"`. 1914 fn cargo_path() -> PathBuf { 1915 CARGO.to_owned().into() 1916 } 1917 impl From<ArgOpts> for Opts { 1918 fn from(value: ArgOpts) -> Self { 1919 Self { 1920 exec_dir: value.dir, 1921 rustup_home: value.rustup_home, 1922 cargo_path: value.cargo_path.unwrap_or_else(cargo_path), 1923 cargo_home: value.cargo_home, 1924 color: value.color, 1925 default_toolchain: value.default_toolchain, 1926 allow_implied_features: value.allow_implied_features, 1927 ignore_compile_errors: value.ignore_compile_errors, 1928 ignore_msrv: value.ignore_msrv, 1929 progress: value.progress, 1930 skip_msrv: value.skip_msrv, 1931 summary: value.summary, 1932 ignore_features: value.ignore_features, 1933 } 1934 } 1935 } 1936 /// `ci-cargo` command to run. 1937 #[cfg_attr(test, derive(Debug, PartialEq))] 1938 pub(crate) enum MetaCmd { 1939 /// Run the `cargo` command(s). 1940 Cargo(Cmd, Opts), 1941 /// Write help message to stdout. 1942 Help, 1943 /// Write version info to stdout. 1944 Version, 1945 } 1946 impl MetaCmd { 1947 /// Extracts options from `args`. 1948 /// 1949 /// This must only be called from [`Self::from_args`]. `cmd` is the current command command-specific 1950 /// options apply to. 1951 /// 1952 /// Returns the next `CargoCmd`. 1953 #[expect(unsafe_code, reason = "comment justifies correctness")] 1954 #[expect( 1955 clippy::arithmetic_side_effects, 1956 reason = "comment justifies correctness" 1957 )] 1958 #[expect( 1959 clippy::too_many_lines, 1960 reason = "expected since we need to extract all the passed options" 1961 )] 1962 fn extract_options<T: Iterator<Item = OsString>>( 1963 cmd: &mut CargoCmd<'_>, 1964 opts: &mut ArgOpts, 1965 mut args: T, 1966 ) -> Result<Option<OsString>, ArgsErr> { 1967 while let Some(arg) = args.next() { 1968 if let Some(arg_str) = arg.to_str() { 1969 match arg_str { 1970 ALL_TARGETS => cmd.add_all_targets(), 1971 ALLOW_IMPLIED_FEATURES => { 1972 if opts.allow_implied_features { 1973 Err(ArgsErr::DuplicateOption(arg)) 1974 } else { 1975 opts.allow_implied_features = true; 1976 Ok(()) 1977 } 1978 } 1979 BENCHES => cmd.add_target(Target::Benches), 1980 BINS => cmd.add_target(Target::Bins), 1981 CARGO_HOME => args.next().map_or(Err(ArgsErr::MissingCargoHome), |path| { 1982 opts.cargo_home 1983 .replace(path.into()) 1984 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 1985 }), 1986 CARGO_PATH => args.next().map_or(Err(ArgsErr::MissingCargoPath), |p| { 1987 // This won't overflow since `p.len() + 6 < usize::MAX` since 1988 // `p.len() <= isize::MAX`, `usize::MAX >= u16::MAX`, and 1989 // `i16::MAX + 6 < u16::MAX`. 1990 let mut path = PathBuf::with_capacity(CARGO.len() + 1 + p.len()); 1991 path.push(p); 1992 path.push(CARGO); 1993 opts.cargo_path 1994 .replace(path) 1995 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 1996 }), 1997 COLOR => { 1998 if opts.color { 1999 Err(ArgsErr::DuplicateOption(arg)) 2000 } else { 2001 opts.color = true; 2002 Ok(()) 2003 } 2004 } 2005 DEFAULT_TOOLCHAIN => { 2006 if opts.default_toolchain { 2007 Err(ArgsErr::DuplicateOption(arg)) 2008 } else { 2009 opts.default_toolchain = true; 2010 Ok(()) 2011 } 2012 } 2013 DENY_WARNINGS => match *cmd { 2014 CargoCmd::Clippy(&mut (_, ref mut deny_warnings)) => { 2015 if *deny_warnings { 2016 Err(ArgsErr::DuplicateOption(arg)) 2017 } else { 2018 *deny_warnings = true; 2019 Ok(()) 2020 } 2021 } 2022 CargoCmd::Check(_) | CargoCmd::Test(_) => Err(ArgsErr::UnknownArg(arg)), 2023 }, 2024 DIR => args.next().map_or(Err(ArgsErr::MissingDirPath), |path| { 2025 opts.dir 2026 .replace(path.into()) 2027 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 2028 }), 2029 DOC => match *cmd { 2030 CargoCmd::Test(&mut (ref mut test_targets, _)) => match *test_targets { 2031 TestTargets::Default => { 2032 *test_targets = TestTargets::Doc; 2033 Ok(()) 2034 } 2035 TestTargets::All | TestTargets::Targets(_) => Err(ArgsErr::Doc), 2036 TestTargets::Doc => Err(ArgsErr::DuplicateOption(arg)), 2037 }, 2038 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2039 }, 2040 EXAMPLES => cmd.add_target(Target::Examples), 2041 IGNORE_COMPILE_ERRORS => { 2042 if opts.ignore_compile_errors { 2043 Err(ArgsErr::DuplicateOption(arg)) 2044 } else { 2045 opts.ignore_compile_errors = true; 2046 Ok(()) 2047 } 2048 } 2049 IGNORE_FEATURES => { 2050 if opts.ignore_features.is_empty() { 2051 args.next() 2052 .map_or(Err(ArgsErr::MissingIgnoredFeatures), |feats_os| { 2053 if let Some(feats) = feats_os.to_str() { 2054 feats 2055 .as_bytes() 2056 .split(|b| *b == b',') 2057 .try_fold((), |(), feat| { 2058 if opts 2059 .ignore_features 2060 .iter() 2061 .any(|f| f.as_bytes() == feat) 2062 { 2063 Err(()) 2064 } else { 2065 let utf8 = feat.to_owned(); 2066 // SAFETY: 2067 // `feats` is a valid `str` and was split by 2068 // a single UTF-8 code unit; thus `utf8` is also 2069 // valid UTF-8. 2070 opts.ignore_features.push(unsafe { 2071 String::from_utf8_unchecked(utf8) 2072 }); 2073 Ok(()) 2074 } 2075 }) 2076 .map_err(|()| { 2077 ArgsErr::DuplicateIgnoredFeatures(feats_os) 2078 }) 2079 } else { 2080 Err(ArgsErr::UnknownArg(feats_os)) 2081 } 2082 }) 2083 } else { 2084 Err(ArgsErr::DuplicateOption(arg)) 2085 } 2086 } 2087 IGNORE_MSRV => { 2088 if opts.ignore_msrv { 2089 Err(ArgsErr::DuplicateOption(arg)) 2090 } else { 2091 opts.ignore_msrv = true; 2092 Ok(()) 2093 } 2094 } 2095 IGNORED => match *cmd { 2096 CargoCmd::Test(&mut (_, ref mut ignored)) => match *ignored { 2097 Ignored::None => { 2098 *ignored = Ignored::Only; 2099 Ok(()) 2100 } 2101 Ignored::Only => Err(ArgsErr::DuplicateOption(arg)), 2102 Ignored::Include => Err(ArgsErr::IgnoredIncludeIgnored), 2103 }, 2104 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2105 }, 2106 INCLUDE_IGNORED => match *cmd { 2107 CargoCmd::Test(&mut (_, ref mut ignored)) => match *ignored { 2108 Ignored::None => { 2109 *ignored = Ignored::Include; 2110 Ok(()) 2111 } 2112 Ignored::Only => Err(ArgsErr::IgnoredIncludeIgnored), 2113 Ignored::Include => Err(ArgsErr::DuplicateOption(arg)), 2114 }, 2115 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2116 }, 2117 LIB => cmd.add_target(Target::Lib), 2118 PROGRESS => { 2119 if opts.progress { 2120 Err(ArgsErr::DuplicateOption(arg)) 2121 } else { 2122 opts.progress = true; 2123 Ok(()) 2124 } 2125 } 2126 RUSTUP_HOME => args.next().map_or(Err(ArgsErr::MissingRustupHome), |path| { 2127 opts.rustup_home 2128 .replace(path.into()) 2129 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 2130 }), 2131 SKIP_MSRV => { 2132 if opts.skip_msrv { 2133 Err(ArgsErr::DuplicateOption(arg)) 2134 } else { 2135 opts.skip_msrv = true; 2136 Ok(()) 2137 } 2138 } 2139 SUMMARY => { 2140 if opts.summary { 2141 Err(ArgsErr::DuplicateOption(arg)) 2142 } else { 2143 opts.summary = true; 2144 Ok(()) 2145 } 2146 } 2147 TESTS => cmd.add_target(Target::Tests), 2148 _ => return Ok(Some(arg)), 2149 } 2150 } else { 2151 Err(ArgsErr::UnknownArg(arg)) 2152 }?; 2153 } 2154 Ok(None) 2155 } 2156 /// Returns data we need by reading the supplied CLI arguments. 2157 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 2158 pub(crate) fn from_args<T: Iterator<Item = OsString>>(mut args: T) -> Result<Self, ArgsErr> { 2159 args.next().ok_or(ArgsErr::NoArgs).and_then(|_| { 2160 args.next().ok_or(ArgsErr::NoCommand).and_then(|fst_cmd| { 2161 if let Some(fst_cmd_str) = fst_cmd.to_str() { 2162 let mut opts = ArgOpts::default(); 2163 let mut check_targets = None; 2164 let mut clippy_info: Option<(CheckClippyTargets, bool)> = None; 2165 let mut test_info: Option<(TestTargets, Ignored)> = None; 2166 let mut cargo_cmd = match fst_cmd_str { 2167 CHECK => CargoCmd::Check(check_targets.insert(CheckClippyTargets::Default)), 2168 CLIPPY => CargoCmd::Clippy( 2169 clippy_info.insert((CheckClippyTargets::Default, false)), 2170 ), 2171 HELP => { 2172 return args 2173 .next() 2174 .map_or_else(|| Ok(Self::Help), |_| Err(ArgsErr::HelpWithArgs)); 2175 } 2176 TEST => { 2177 CargoCmd::Test(test_info.insert((TestTargets::Default, Ignored::None))) 2178 } 2179 VERSION => { 2180 return args.next().map_or_else( 2181 || Ok(Self::Version), 2182 |_| Err(ArgsErr::VersionWithArgs), 2183 ); 2184 } 2185 _ => return Err(ArgsErr::NoCommand), 2186 }; 2187 while let Some(arg) = 2188 Self::extract_options(&mut cargo_cmd, &mut opts, &mut args)? 2189 { 2190 match arg.to_str().unwrap_or_else(|| { 2191 unreachable!("there is a bug in args::MetaCmd::extract_options") 2192 }) { 2193 CHECK => { 2194 if check_targets.is_some() { 2195 return Err(ArgsErr::DuplicateOption(arg)); 2196 } 2197 cargo_cmd = CargoCmd::Check( 2198 check_targets.insert(CheckClippyTargets::Default), 2199 ); 2200 } 2201 CLIPPY => { 2202 if clippy_info.is_some() { 2203 return Err(ArgsErr::DuplicateOption(arg)); 2204 } 2205 cargo_cmd = CargoCmd::Clippy( 2206 clippy_info.insert((CheckClippyTargets::Default, false)), 2207 ); 2208 } 2209 TEST => { 2210 if test_info.is_some() { 2211 return Err(ArgsErr::DuplicateOption(arg)); 2212 } 2213 cargo_cmd = CargoCmd::Test( 2214 test_info.insert((TestTargets::Default, Ignored::None)), 2215 ); 2216 } 2217 _ => return Err(ArgsErr::UnknownArg(arg)), 2218 } 2219 } 2220 if let Some(check) = check_targets { 2221 Ok(Self::Cargo( 2222 clippy_info.map_or_else( 2223 || { 2224 test_info.map_or(Cmd::Check(check), |test| { 2225 Cmd::CheckTest(check, test.0, test.1) 2226 }) 2227 }, 2228 |clippy| { 2229 test_info.map_or( 2230 Cmd::CheckClippy(check, clippy.0, clippy.1), 2231 |test| { 2232 Cmd::CheckClippyTest( 2233 check, clippy.0, clippy.1, test.0, test.1, 2234 ) 2235 }, 2236 ) 2237 }, 2238 ), 2239 opts.into(), 2240 )) 2241 } else if let Some(clippy) = clippy_info { 2242 Ok(Self::Cargo( 2243 test_info.map_or(Cmd::Clippy(clippy.0, clippy.1), |test| { 2244 Cmd::ClippyTest(clippy.0, clippy.1, test.0, test.1) 2245 }), 2246 opts.into(), 2247 )) 2248 } else if let Some(test) = test_info { 2249 Ok(Self::Cargo(Cmd::Test(test.0, test.1), opts.into())) 2250 } else { 2251 Err(ArgsErr::NoCommand) 2252 } 2253 } else { 2254 Err(ArgsErr::UnknownArg(fst_cmd)) 2255 } 2256 }) 2257 }) 2258 } 2259 } 2260 #[cfg(test)] 2261 mod tests { 2262 use super::{ 2263 ArgsErr, CheckClippyTargets, Cmd, Ignored, MetaCmd, NonZeroUsizePlus1, Opts, OsString, 2264 PathBuf, Target, Targets, TestTargets, 2265 }; 2266 use core::iter; 2267 #[cfg(unix)] 2268 use std::os::unix::ffi::OsStringExt as _; 2269 #[expect( 2270 clippy::cognitive_complexity, 2271 clippy::too_many_lines, 2272 reason = "want to test for a lot of things" 2273 )] 2274 #[test] 2275 fn arg_parsing() { 2276 assert_eq!(MetaCmd::from_args(iter::empty()), Err(ArgsErr::NoArgs)); 2277 assert_eq!( 2278 MetaCmd::from_args(iter::once(OsString::new())), 2279 Err(ArgsErr::NoCommand) 2280 ); 2281 assert_eq!( 2282 MetaCmd::from_args([OsString::new(), OsString::new()].into_iter()), 2283 Err(ArgsErr::NoCommand) 2284 ); 2285 // Invalid UTF-8 errors gracefully. 2286 #[cfg(unix)] 2287 assert_eq!( 2288 MetaCmd::from_args([OsString::new(), OsString::from_vec(vec![255])].into_iter()), 2289 Err(ArgsErr::UnknownArg(OsString::from_vec(vec![255]))) 2290 ); 2291 // Whitespace is not ignored. 2292 assert_eq!( 2293 MetaCmd::from_args([OsString::new(), " clippy".to_owned().into()].into_iter()), 2294 Err(ArgsErr::NoCommand) 2295 ); 2296 // We parse in a case-sensitive way. 2297 assert_eq!( 2298 MetaCmd::from_args([OsString::new(), "Clippy".to_owned().into()].into_iter()), 2299 Err(ArgsErr::NoCommand) 2300 ); 2301 // We require options to be after the command (if one was passed). 2302 assert_eq!( 2303 MetaCmd::from_args( 2304 [ 2305 OsString::new(), 2306 "--summary".to_owned().into(), 2307 "clippy".to_owned().into() 2308 ] 2309 .into_iter() 2310 ), 2311 Err(ArgsErr::NoCommand) 2312 ); 2313 assert_eq!( 2314 MetaCmd::from_args( 2315 [ 2316 OsString::new(), 2317 "help".to_owned().into(), 2318 "--summary".to_owned().into() 2319 ] 2320 .into_iter() 2321 ), 2322 Err(ArgsErr::HelpWithArgs) 2323 ); 2324 assert_eq!( 2325 MetaCmd::from_args( 2326 [ 2327 OsString::new(), 2328 "version".to_owned().into(), 2329 "foo".to_owned().into() 2330 ] 2331 .into_iter() 2332 ), 2333 Err(ArgsErr::VersionWithArgs) 2334 ); 2335 assert_eq!( 2336 MetaCmd::from_args( 2337 [ 2338 OsString::new(), 2339 "check".to_owned().into(), 2340 "--cargo-path".to_owned().into() 2341 ] 2342 .into_iter() 2343 ), 2344 Err(ArgsErr::MissingCargoPath) 2345 ); 2346 assert_eq!( 2347 MetaCmd::from_args( 2348 [ 2349 OsString::new(), 2350 "check".to_owned().into(), 2351 "--cargo-home".to_owned().into() 2352 ] 2353 .into_iter() 2354 ), 2355 Err(ArgsErr::MissingCargoHome) 2356 ); 2357 assert_eq!( 2358 MetaCmd::from_args( 2359 [ 2360 OsString::new(), 2361 "check".to_owned().into(), 2362 "--rustup-home".to_owned().into() 2363 ] 2364 .into_iter() 2365 ), 2366 Err(ArgsErr::MissingRustupHome) 2367 ); 2368 assert_eq!( 2369 MetaCmd::from_args( 2370 [ 2371 OsString::new(), 2372 "test".to_owned().into(), 2373 "--deny-warnings".to_owned().into() 2374 ] 2375 .into_iter() 2376 ), 2377 Err(ArgsErr::UnknownArg("--deny-warnings".to_owned().into())) 2378 ); 2379 assert_eq!( 2380 MetaCmd::from_args( 2381 [ 2382 OsString::new(), 2383 "check".to_owned().into(), 2384 "--deny-warnings".to_owned().into() 2385 ] 2386 .into_iter() 2387 ), 2388 Err(ArgsErr::UnknownArg("--deny-warnings".to_owned().into())) 2389 ); 2390 assert_eq!( 2391 MetaCmd::from_args( 2392 [ 2393 OsString::new(), 2394 "clippy".to_owned().into(), 2395 "--ignored".to_owned().into() 2396 ] 2397 .into_iter() 2398 ), 2399 Err(ArgsErr::UnknownArg("--ignored".to_owned().into())) 2400 ); 2401 assert_eq!( 2402 MetaCmd::from_args( 2403 [ 2404 OsString::new(), 2405 "check".to_owned().into(), 2406 "--ignored".to_owned().into() 2407 ] 2408 .into_iter() 2409 ), 2410 Err(ArgsErr::UnknownArg("--ignored".to_owned().into())) 2411 ); 2412 assert_eq!( 2413 MetaCmd::from_args( 2414 [ 2415 OsString::new(), 2416 "clippy".to_owned().into(), 2417 "--include-ignored".to_owned().into() 2418 ] 2419 .into_iter() 2420 ), 2421 Err(ArgsErr::UnknownArg("--include-ignored".to_owned().into())) 2422 ); 2423 assert_eq!( 2424 MetaCmd::from_args( 2425 [ 2426 OsString::new(), 2427 "check".to_owned().into(), 2428 "--include-ignored".to_owned().into() 2429 ] 2430 .into_iter() 2431 ), 2432 Err(ArgsErr::UnknownArg("--include-ignored".to_owned().into())) 2433 ); 2434 assert_eq!( 2435 MetaCmd::from_args( 2436 [ 2437 OsString::new(), 2438 "test".to_owned().into(), 2439 "--ignored".to_owned().into(), 2440 "--include-ignored".to_owned().into() 2441 ] 2442 .into_iter() 2443 ), 2444 Err(ArgsErr::IgnoredIncludeIgnored) 2445 ); 2446 assert_eq!( 2447 MetaCmd::from_args( 2448 [ 2449 OsString::new(), 2450 "test".to_owned().into(), 2451 "--ignore-features".to_owned().into(), 2452 ] 2453 .into_iter() 2454 ), 2455 Err(ArgsErr::MissingIgnoredFeatures) 2456 ); 2457 assert_eq!( 2458 MetaCmd::from_args( 2459 [ 2460 OsString::new(), 2461 "clippy".to_owned().into(), 2462 "--ignore-features".to_owned().into(), 2463 ",".to_owned().into(), 2464 ] 2465 .into_iter() 2466 ), 2467 Err(ArgsErr::DuplicateIgnoredFeatures(",".to_owned().into())) 2468 ); 2469 assert_eq!( 2470 MetaCmd::from_args( 2471 [ 2472 OsString::new(), 2473 "check".to_owned().into(), 2474 "--ignore-features".to_owned().into(), 2475 "a,,a".to_owned().into(), 2476 ] 2477 .into_iter() 2478 ), 2479 Err(ArgsErr::DuplicateIgnoredFeatures("a,,a".to_owned().into())) 2480 ); 2481 assert_eq!( 2482 MetaCmd::from_args( 2483 [ 2484 OsString::new(), 2485 "check".to_owned().into(), 2486 "--ignore-features".to_owned().into(), 2487 ",a,b,".to_owned().into(), 2488 ] 2489 .into_iter() 2490 ), 2491 Err(ArgsErr::DuplicateIgnoredFeatures(",a,b,".to_owned().into())) 2492 ); 2493 assert_eq!( 2494 MetaCmd::from_args( 2495 [ 2496 OsString::new(), 2497 "clippy".to_owned().into(), 2498 "--ignore-features".to_owned().into(), 2499 ",a,,b".to_owned().into(), 2500 ] 2501 .into_iter() 2502 ), 2503 Err(ArgsErr::DuplicateIgnoredFeatures(",a,,b".to_owned().into())) 2504 ); 2505 assert_eq!( 2506 MetaCmd::from_args( 2507 [ 2508 OsString::new(), 2509 "clippy".to_owned().into(), 2510 "--ignore-features".to_owned().into(), 2511 "a,b,,".to_owned().into(), 2512 ] 2513 .into_iter() 2514 ), 2515 Err(ArgsErr::DuplicateIgnoredFeatures("a,b,,".to_owned().into())) 2516 ); 2517 // `--all-targets` can't be combined with other targets. 2518 assert_eq!( 2519 MetaCmd::from_args( 2520 [ 2521 OsString::new(), 2522 "clippy".to_owned().into(), 2523 "--lib".to_owned().into(), 2524 "--all-targets".to_owned().into(), 2525 ] 2526 .into_iter() 2527 ), 2528 Err(ArgsErr::AllTargets) 2529 ); 2530 assert_eq!( 2531 MetaCmd::from_args( 2532 [ 2533 OsString::new(), 2534 "check".to_owned().into(), 2535 "--lib".to_owned().into(), 2536 "--all-targets".to_owned().into(), 2537 ] 2538 .into_iter() 2539 ), 2540 Err(ArgsErr::AllTargets) 2541 ); 2542 assert_eq!( 2543 MetaCmd::from_args( 2544 [ 2545 OsString::new(), 2546 "check".to_owned().into(), 2547 "--all-targets".to_owned().into(), 2548 "test".to_owned().into(), 2549 "--doc".to_owned().into(), 2550 "--all-targets".to_owned().into(), 2551 ] 2552 .into_iter() 2553 ), 2554 Err(ArgsErr::AllTargets) 2555 ); 2556 // `--doc` can't be combined with other targets. 2557 assert_eq!( 2558 MetaCmd::from_args( 2559 [ 2560 OsString::new(), 2561 "test".to_owned().into(), 2562 "--all-targets".to_owned().into(), 2563 "--doc".to_owned().into(), 2564 ] 2565 .into_iter() 2566 ), 2567 Err(ArgsErr::Doc) 2568 ); 2569 assert_eq!( 2570 MetaCmd::from_args( 2571 [ 2572 OsString::new(), 2573 "test".to_owned().into(), 2574 "--doc".to_owned().into(), 2575 "--lib".to_owned().into(), 2576 ] 2577 .into_iter() 2578 ), 2579 Err(ArgsErr::Doc) 2580 ); 2581 assert_eq!( 2582 MetaCmd::from_args( 2583 [ 2584 OsString::new(), 2585 "clippy".to_owned().into(), 2586 "--doc".to_owned().into(), 2587 ] 2588 .into_iter() 2589 ), 2590 Err(ArgsErr::UnknownArg("--doc".to_owned().into())) 2591 ); 2592 assert_eq!( 2593 MetaCmd::from_args( 2594 [ 2595 OsString::new(), 2596 "check".to_owned().into(), 2597 "--color".to_owned().into(), 2598 "clippy".to_owned().into(), 2599 "--color".to_owned().into(), 2600 ] 2601 .into_iter() 2602 ), 2603 Err(ArgsErr::DuplicateOption("--color".to_owned().into())) 2604 ); 2605 assert_eq!( 2606 MetaCmd::from_args( 2607 [ 2608 OsString::new(), 2609 "test".to_owned().into(), 2610 "--ignored".to_owned().into(), 2611 "--ignored".to_owned().into(), 2612 ] 2613 .into_iter() 2614 ), 2615 Err(ArgsErr::DuplicateOption("--ignored".to_owned().into())) 2616 ); 2617 assert_eq!( 2618 MetaCmd::from_args( 2619 [ 2620 OsString::new(), 2621 "clippy".to_owned().into(), 2622 "--all-targets".to_owned().into(), 2623 "--allow-implied-features".to_owned().into(), 2624 "--cargo-home".to_owned().into(), 2625 "--ignored".to_owned().into(), 2626 "--cargo-path".to_owned().into(), 2627 "cargo".to_owned().into(), 2628 "--color".to_owned().into(), 2629 "--default-toolchain".to_owned().into(), 2630 "--deny-warnings".to_owned().into(), 2631 "--dir".to_owned().into(), 2632 OsString::new(), 2633 "--ignore-compile-errors".to_owned().into(), 2634 "--ignore-features".to_owned().into(), 2635 ",a".to_owned().into(), 2636 "--ignore-msrv".to_owned().into(), 2637 "--rustup-home".to_owned().into(), 2638 "a".to_owned().into(), 2639 "--progress".to_owned().into(), 2640 "--skip-msrv".to_owned().into(), 2641 "--summary".to_owned().into(), 2642 ] 2643 .into_iter() 2644 ), 2645 Ok(MetaCmd::Cargo( 2646 Cmd::Clippy(CheckClippyTargets::All, true,), 2647 Opts { 2648 exec_dir: Some(PathBuf::new()), 2649 rustup_home: Some("a".to_owned().into()), 2650 cargo_home: Some("--ignored".to_owned().into()), 2651 cargo_path: "cargo/cargo".to_owned().into(), 2652 color: true, 2653 default_toolchain: true, 2654 allow_implied_features: true, 2655 ignore_compile_errors: true, 2656 ignore_msrv: true, 2657 progress: true, 2658 skip_msrv: true, 2659 summary: true, 2660 ignore_features: vec![String::new(), "a".to_owned()], 2661 } 2662 )) 2663 ); 2664 assert_eq!( 2665 MetaCmd::from_args( 2666 [ 2667 OsString::new(), 2668 "clippy".to_owned().into(), 2669 "--all-targets".to_owned().into(), 2670 "--deny-warnings".to_owned().into(), 2671 ] 2672 .into_iter() 2673 ), 2674 Ok(MetaCmd::Cargo( 2675 Cmd::Clippy(CheckClippyTargets::All, true,), 2676 Opts { 2677 exec_dir: None, 2678 rustup_home: None, 2679 cargo_home: None, 2680 cargo_path: "cargo".to_owned().into(), 2681 color: false, 2682 default_toolchain: false, 2683 allow_implied_features: false, 2684 ignore_compile_errors: false, 2685 ignore_msrv: false, 2686 progress: false, 2687 skip_msrv: false, 2688 summary: false, 2689 ignore_features: Vec::new(), 2690 } 2691 )) 2692 ); 2693 assert_eq!( 2694 MetaCmd::from_args( 2695 [ 2696 OsString::new(), 2697 "test".to_owned().into(), 2698 "--allow-implied-features".to_owned().into(), 2699 "--cargo-home".to_owned().into(), 2700 "--ignored".to_owned().into(), 2701 "--cargo-path".to_owned().into(), 2702 "cargo".to_owned().into(), 2703 "--color".to_owned().into(), 2704 "--default-toolchain".to_owned().into(), 2705 "--dir".to_owned().into(), 2706 OsString::new(), 2707 "--ignore-compile-errors".to_owned().into(), 2708 "--ignore-features".to_owned().into(), 2709 OsString::new(), 2710 "--ignore-msrv".to_owned().into(), 2711 "--ignored".to_owned().into(), 2712 "--rustup-home".to_owned().into(), 2713 OsString::new(), 2714 "--progress".to_owned().into(), 2715 "--skip-msrv".to_owned().into(), 2716 "--summary".to_owned().into(), 2717 ] 2718 .into_iter() 2719 ), 2720 Ok(MetaCmd::Cargo( 2721 Cmd::Test(TestTargets::Default, Ignored::Only), 2722 Opts { 2723 exec_dir: Some(PathBuf::new()), 2724 rustup_home: Some(PathBuf::new()), 2725 cargo_home: Some("--ignored".to_owned().into()), 2726 cargo_path: "cargo/cargo".to_owned().into(), 2727 color: true, 2728 default_toolchain: true, 2729 allow_implied_features: true, 2730 ignore_compile_errors: true, 2731 ignore_msrv: true, 2732 progress: true, 2733 skip_msrv: true, 2734 summary: true, 2735 ignore_features: vec![String::new()], 2736 } 2737 )) 2738 ); 2739 assert_eq!( 2740 MetaCmd::from_args([OsString::new(), "test".to_owned().into(),].into_iter()), 2741 Ok(MetaCmd::Cargo( 2742 Cmd::Test(TestTargets::Default, Ignored::None), 2743 Opts { 2744 exec_dir: None, 2745 rustup_home: None, 2746 cargo_home: None, 2747 cargo_path: "cargo".to_owned().into(), 2748 color: false, 2749 default_toolchain: false, 2750 allow_implied_features: false, 2751 ignore_compile_errors: false, 2752 ignore_msrv: false, 2753 progress: false, 2754 skip_msrv: false, 2755 summary: false, 2756 ignore_features: Vec::new(), 2757 } 2758 )) 2759 ); 2760 assert_eq!( 2761 MetaCmd::from_args( 2762 [ 2763 OsString::new(), 2764 "test".to_owned().into(), 2765 "--include-ignored".to_owned().into() 2766 ] 2767 .into_iter() 2768 ), 2769 Ok(MetaCmd::Cargo( 2770 Cmd::Test(TestTargets::Default, Ignored::Include), 2771 Opts { 2772 exec_dir: None, 2773 rustup_home: None, 2774 cargo_home: None, 2775 cargo_path: "cargo".to_owned().into(), 2776 color: false, 2777 default_toolchain: false, 2778 allow_implied_features: false, 2779 ignore_compile_errors: false, 2780 ignore_msrv: false, 2781 progress: false, 2782 skip_msrv: false, 2783 summary: false, 2784 ignore_features: Vec::new(), 2785 } 2786 )) 2787 ); 2788 assert_eq!( 2789 MetaCmd::from_args( 2790 [ 2791 OsString::new(), 2792 "check".to_owned().into(), 2793 "--all-targets".to_owned().into(), 2794 "--allow-implied-features".to_owned().into(), 2795 "--cargo-home".to_owned().into(), 2796 "--ignored".to_owned().into(), 2797 "--cargo-path".to_owned().into(), 2798 "cargo".to_owned().into(), 2799 "--color".to_owned().into(), 2800 "--default-toolchain".to_owned().into(), 2801 "--dir".to_owned().into(), 2802 OsString::new(), 2803 "--ignore-compile-errors".to_owned().into(), 2804 "--ignore-features".to_owned().into(), 2805 "a,".to_owned().into(), 2806 "--ignore-msrv".to_owned().into(), 2807 "--rustup-home".to_owned().into(), 2808 OsString::new(), 2809 "--progress".to_owned().into(), 2810 "--skip-msrv".to_owned().into(), 2811 "--summary".to_owned().into(), 2812 ] 2813 .into_iter() 2814 ), 2815 Ok(MetaCmd::Cargo( 2816 Cmd::Check(CheckClippyTargets::All), 2817 Opts { 2818 exec_dir: Some(PathBuf::new()), 2819 rustup_home: Some(PathBuf::new()), 2820 cargo_home: Some("--ignored".to_owned().into()), 2821 cargo_path: "cargo/cargo".to_owned().into(), 2822 color: true, 2823 default_toolchain: true, 2824 allow_implied_features: true, 2825 ignore_compile_errors: true, 2826 ignore_msrv: true, 2827 progress: true, 2828 skip_msrv: true, 2829 summary: true, 2830 ignore_features: vec!["a".to_owned(), String::new()], 2831 } 2832 )) 2833 ); 2834 assert_eq!( 2835 MetaCmd::from_args([OsString::new(), "check".to_owned().into(),].into_iter()), 2836 Ok(MetaCmd::Cargo( 2837 Cmd::Check(CheckClippyTargets::Default), 2838 Opts { 2839 exec_dir: None, 2840 rustup_home: None, 2841 cargo_home: None, 2842 cargo_path: "cargo".to_owned().into(), 2843 color: false, 2844 default_toolchain: false, 2845 allow_implied_features: false, 2846 ignore_compile_errors: false, 2847 ignore_msrv: false, 2848 progress: false, 2849 skip_msrv: false, 2850 summary: false, 2851 ignore_features: Vec::new(), 2852 } 2853 )) 2854 ); 2855 assert_eq!( 2856 MetaCmd::from_args( 2857 [ 2858 OsString::new(), 2859 "check".to_owned().into(), 2860 "--ignore-features".to_owned().into(), 2861 "a,,b".to_owned().into(), 2862 ] 2863 .into_iter() 2864 ), 2865 Ok(MetaCmd::Cargo( 2866 Cmd::Check(CheckClippyTargets::Default), 2867 Opts { 2868 exec_dir: None, 2869 rustup_home: None, 2870 cargo_home: None, 2871 cargo_path: "cargo".to_owned().into(), 2872 color: false, 2873 default_toolchain: false, 2874 allow_implied_features: false, 2875 ignore_compile_errors: false, 2876 ignore_msrv: false, 2877 progress: false, 2878 skip_msrv: false, 2879 summary: false, 2880 ignore_features: vec!["a".to_owned(), String::new(), "b".to_owned()], 2881 } 2882 )) 2883 ); 2884 assert_eq!( 2885 MetaCmd::from_args( 2886 [ 2887 OsString::new(), 2888 "check".to_owned().into(), 2889 "--ignore-features".to_owned().into(), 2890 "a,b,".to_owned().into(), 2891 ] 2892 .into_iter() 2893 ), 2894 Ok(MetaCmd::Cargo( 2895 Cmd::Check(CheckClippyTargets::Default), 2896 Opts { 2897 exec_dir: None, 2898 rustup_home: None, 2899 cargo_home: None, 2900 cargo_path: "cargo".to_owned().into(), 2901 color: false, 2902 default_toolchain: false, 2903 allow_implied_features: false, 2904 ignore_compile_errors: false, 2905 ignore_msrv: false, 2906 progress: false, 2907 skip_msrv: false, 2908 summary: false, 2909 ignore_features: vec!["a".to_owned(), "b".to_owned(), String::new()], 2910 } 2911 )) 2912 ); 2913 assert_eq!( 2914 MetaCmd::from_args( 2915 [ 2916 OsString::new(), 2917 "check".to_owned().into(), 2918 "--ignore-features".to_owned().into(), 2919 "a,b".to_owned().into(), 2920 ] 2921 .into_iter() 2922 ), 2923 Ok(MetaCmd::Cargo( 2924 Cmd::Check(CheckClippyTargets::Default), 2925 Opts { 2926 exec_dir: None, 2927 rustup_home: None, 2928 cargo_home: None, 2929 cargo_path: "cargo".to_owned().into(), 2930 color: false, 2931 default_toolchain: false, 2932 allow_implied_features: false, 2933 ignore_compile_errors: false, 2934 ignore_msrv: false, 2935 progress: false, 2936 skip_msrv: false, 2937 summary: false, 2938 ignore_features: vec!["a".to_owned(), "b".to_owned()], 2939 } 2940 )) 2941 ); 2942 // No whitespace cleanup is done on the features. 2943 assert_eq!( 2944 MetaCmd::from_args( 2945 [ 2946 OsString::new(), 2947 "check".to_owned().into(), 2948 "--ignore-features".to_owned().into(), 2949 "a , , b, ".to_owned().into(), 2950 ] 2951 .into_iter() 2952 ), 2953 Ok(MetaCmd::Cargo( 2954 Cmd::Check(CheckClippyTargets::Default), 2955 Opts { 2956 exec_dir: None, 2957 rustup_home: None, 2958 cargo_home: None, 2959 cargo_path: "cargo".to_owned().into(), 2960 color: false, 2961 default_toolchain: false, 2962 allow_implied_features: false, 2963 ignore_compile_errors: false, 2964 ignore_msrv: false, 2965 progress: false, 2966 skip_msrv: false, 2967 summary: false, 2968 ignore_features: vec![ 2969 "a ".to_owned(), 2970 " ".to_owned(), 2971 " b".to_owned(), 2972 " ".to_owned() 2973 ], 2974 } 2975 )) 2976 ); 2977 assert_eq!( 2978 MetaCmd::from_args([OsString::new(), "help".to_owned().into(),].into_iter()), 2979 Ok(MetaCmd::Help) 2980 ); 2981 assert_eq!( 2982 MetaCmd::from_args([OsString::new(), "version".to_owned().into(),].into_iter()), 2983 Ok(MetaCmd::Version) 2984 ); 2985 let mut check_targets = Targets::new(Target::Examples); 2986 assert!(check_targets.add(Target::Tests)); 2987 assert!(!check_targets.add(Target::Examples)); 2988 let mut test_targets = Targets::new(Target::Benches); 2989 assert!(test_targets.add(Target::Bins)); 2990 assert!(!test_targets.add(Target::Bins)); 2991 assert!(test_targets.add(Target::Examples)); 2992 assert!(test_targets.add(Target::Lib)); 2993 assert!(test_targets.add(Target::Tests)); 2994 assert_eq!( 2995 MetaCmd::from_args( 2996 [ 2997 OsString::new(), 2998 "clippy".to_owned().into(), 2999 "--all-targets".to_owned().into(), 3000 "--allow-implied-features".to_owned().into(), 3001 "--cargo-home".to_owned().into(), 3002 "--ignored".to_owned().into(), 3003 "--cargo-path".to_owned().into(), 3004 "cargo".to_owned().into(), 3005 "--color".to_owned().into(), 3006 "--default-toolchain".to_owned().into(), 3007 "--deny-warnings".to_owned().into(), 3008 "--dir".to_owned().into(), 3009 OsString::new(), 3010 "--ignore-compile-errors".to_owned().into(), 3011 "--ignore-features".to_owned().into(), 3012 ",a".to_owned().into(), 3013 "--ignore-msrv".to_owned().into(), 3014 "--rustup-home".to_owned().into(), 3015 "a".to_owned().into(), 3016 "test".to_owned().into(), 3017 "--benches".to_owned().into(), 3018 "--bins".to_owned().into(), 3019 "--examples".to_owned().into(), 3020 "--include-ignored".to_owned().into(), 3021 "--lib".to_owned().into(), 3022 "--tests".to_owned().into(), 3023 "--progress".to_owned().into(), 3024 "--skip-msrv".to_owned().into(), 3025 "check".to_owned().into(), 3026 "--tests".to_owned().into(), 3027 "--examples".to_owned().into(), 3028 "--summary".to_owned().into(), 3029 ] 3030 .into_iter() 3031 ), 3032 Ok(MetaCmd::Cargo( 3033 Cmd::CheckClippyTest( 3034 CheckClippyTargets::Targets(check_targets), 3035 CheckClippyTargets::All, 3036 true, 3037 TestTargets::Targets(test_targets), 3038 Ignored::Include, 3039 ), 3040 Opts { 3041 exec_dir: Some(PathBuf::new()), 3042 rustup_home: Some("a".to_owned().into()), 3043 cargo_home: Some("--ignored".to_owned().into()), 3044 cargo_path: "cargo/cargo".to_owned().into(), 3045 color: true, 3046 default_toolchain: true, 3047 allow_implied_features: true, 3048 ignore_compile_errors: true, 3049 ignore_msrv: true, 3050 progress: true, 3051 skip_msrv: true, 3052 summary: true, 3053 ignore_features: vec![String::new(), "a".to_owned()], 3054 } 3055 )) 3056 ); 3057 } 3058 #[test] 3059 fn non_zero_usize_plus_1() { 3060 #[cfg(target_pointer_width = "64")] 3061 assert_eq!(NonZeroUsizePlus1(0).to_string(), "18446744073709551616"); 3062 #[cfg(target_pointer_width = "64")] 3063 assert_eq!( 3064 NonZeroUsizePlus1(usize::MAX).to_string(), 3065 "18446744073709551615" 3066 ); 3067 #[cfg(target_pointer_width = "32")] 3068 assert_eq!(NonZeroUsizePlus1(0).to_string(), "4294967296"); 3069 #[cfg(target_pointer_width = "32")] 3070 assert_eq!(NonZeroUsizePlus1(usize::MAX).to_string(), "4294967295"); 3071 #[cfg(target_pointer_width = "16")] 3072 assert_eq!(NonZeroUsizePlus1(0).to_string(), "65536"); 3073 #[cfg(target_pointer_width = "16")] 3074 assert_eq!(NonZeroUsizePlus1(usize::MAX).to_string(), "65535"); 3075 assert_eq!(NonZeroUsizePlus1(1).to_string(), "1"); 3076 assert_eq!(NonZeroUsizePlus1(2).to_string(), "2"); 3077 assert_eq!(NonZeroUsizePlus1(10).to_string(), "10"); 3078 } 3079 }