ci-cargo

CI for Rust code.
git clone https://git.philomathiclife.com/repos/ci-cargo
Log | Files | Refs | README

cargo.rs (28831B)


      1 use super::{
      2     args::{CheckClippyTargets, Ignored, Target, TestTargets},
      3     manifest,
      4 };
      5 use std::{
      6     collections::HashSet,
      7     io::{Error, StderrLock, Write as _},
      8     path::{Path, PathBuf},
      9     process::{Command, Stdio},
     10 };
     11 /// Unit tests.
     12 #[cfg(test)]
     13 mod tests;
     14 /// Error returned from [`Toolchain::get_version`].
     15 pub(crate) enum ToolchainErr {
     16     /// [`Command::output`] erred with the contained `Error` for the contained `Command`.
     17     CommandFail(Command, Error),
     18     /// [`Command::output`] was successful for the contained `Command`, but it didn't return a status code.
     19     ///
     20     /// The contained `String` is the potentially empty content written to `stderr`.
     21     CommandNoStatus(Command, String),
     22     /// [`Command::output`] was successful for the contained `Command`, but it returned an error status code
     23     /// represented by the contained `i32`.
     24     ///
     25     /// The contained `String` is the potentially empty content written to `stderr`.
     26     CommandErr(Command, String, i32),
     27     /// [`Command::output`] was successful for the contained `Command`, but the data it wrote to `stdout` was not
     28     /// valid UTF-8.
     29     StdoutNotUtf8(Command),
     30     /// [`Command::output`] was successful for the contained `Command`, but the data it wrote to `stderr` was not
     31     /// valid UTF-8.
     32     StderrNotUtf8(Command),
     33     /// [`Command::output`] was successful for the contained `Command`, but the non-empty data it wrote to
     34     /// `stdout` was unexpected.
     35     ///
     36     /// The contained `String` is the unexpected data.
     37     UnexpectedOutput(Command, String),
     38     /// The parsed MSRV value is newer than `stable` or the default toolchain.
     39     MsrvTooHigh,
     40     /// Error when `cargo +<MSRV> -V` returns a version not compatible with the defined MSRV.
     41     ///
     42     /// The contained `Version` is the installed MSRV.
     43     MsrvNotCompatibleWithInstalledMsrv(Version),
     44 }
     45 impl ToolchainErr {
     46     /// Writes `self` to `stderr`.
     47     pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> {
     48         match self {
     49             Self::CommandFail(cmd, err) => stderr
     50                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
     51                 .and_then(|()| {
     52                     cmd.get_args().try_fold((), |(), arg| {
     53                         stderr.write_all(b" ").and_then(|()| {
     54                             stderr
     55                                 .write_all(arg.to_string_lossy().as_bytes())
     56                         })
     57                     }).and_then(|()| writeln!(stderr, " erred: {err}"))
     58                 }),
     59             Self::CommandNoStatus(cmd, err) => stderr
     60                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
     61                 .and_then(|()| {
     62                     cmd.get_args().try_fold((), |(), arg| {
     63                         stderr.write_all(b" ").and_then(|()| {
     64                             stderr
     65                                 .write_all(arg.to_string_lossy().as_bytes())
     66                         })
     67                     }).and_then(|()| {
     68                         if err.is_empty() {
     69                             writeln!(stderr, " did not return a status code and didn't write anything to stderr.")
     70                         } else {
     71                             writeln!(stderr, " did not return a status code but wrote the following to stderr: {err}")
     72                         }
     73                     })
     74                 }),
     75             Self::CommandErr(cmd, err, status) => stderr
     76                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
     77                 .and_then(|()| {
     78                     cmd.get_args().try_fold((), |(), arg| {
     79                         stderr.write_all(b" ").and_then(|()| {
     80                             stderr
     81                                 .write_all(arg.to_string_lossy().as_bytes())
     82                         })
     83                     }).and_then(|()| {
     84                         if err.is_empty() {
     85                             writeln!(stderr, " returned status code {status} but didn't write anything to stderr.")
     86                         } else {
     87                             writeln!(stderr, " returned status code {status} and wrote the following to stderr: {err}")
     88                         }
     89                     })
     90                 }),
     91             Self::StdoutNotUtf8(cmd) => stderr
     92                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
     93                 .and_then(|()| {
     94                     cmd.get_args().try_fold((), |(), arg| {
     95                         stderr.write_all(b" ").and_then(|()| {
     96                             stderr
     97                                 .write_all(arg.to_string_lossy().as_bytes())
     98                         })
     99                     }).and_then(|()| writeln!(stderr, " wrote invalid UTF-8 to stdout."))
    100                 }),
    101             Self::StderrNotUtf8(cmd) => stderr
    102                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
    103                 .and_then(|()| {
    104                     cmd.get_args().try_fold((), |(), arg| {
    105                         stderr.write_all(b" ").and_then(|()| {
    106                             stderr
    107                                 .write_all(arg.to_string_lossy().as_bytes())
    108                         })
    109                     }).and_then(|()| writeln!(stderr, " wrote invalid UTF-8 to stderr."))
    110                 }),
    111             Self::UnexpectedOutput(cmd, output) => stderr
    112                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
    113                 .and_then(|()| {
    114                     cmd.get_args().try_fold((), |(), arg| {
    115                         stderr.write_all(b" ").and_then(|()| {
    116                             stderr
    117                                 .write_all(arg.to_string_lossy().as_bytes())
    118                         })
    119                     }).and_then(|()| writeln!(stderr, " wrote the following unexpected data to stdout: {output}"))
    120                 }),
    121             Self::MsrvTooHigh => writeln!(stderr, "MSRV is higher than cargo stable."),
    122             Self::MsrvNotCompatibleWithInstalledMsrv(version)=> writeln!(stderr, "cargo +<MSRV> -V returned '{}.{}.{}' which is inconsistent with the defined MSRV.", version.major, version.minor, version.patch),
    123         }
    124     }
    125 }
    126 /// Error returned when running any `cargo` command.
    127 pub(crate) enum CargoErr {
    128     /// [`Command::output`] erred with the contained `Error` for the contained `Command`.
    129     CommandFail(Command, Error),
    130     /// [`Command::output`] was successful for the contained `Command`, but it didn't return a status code.
    131     ///
    132     /// The first contained `String` is the potentially empty content written to `stderr`, and the second
    133     /// `String` is the potentially empty content written to `stdout`.
    134     CommandNoStatus(Command, String, String),
    135     /// [`Command::output`] was successful for the contained `Command`, but it returned an error status code.
    136     ///
    137     /// The first contained `String` is the potentially empty content written to `stderr`, and the second
    138     /// `String` is the potentially empty content written to `stdout`.
    139     CommandErr(Command, String, String),
    140     /// [`Command::output`] was successful for the contained `Command`, but the data it wrote to `stdout` was not
    141     /// valid UTF-8.
    142     StdoutNotUtf8(Command),
    143     /// [`Command::output`] was successful for the contained `Command`, but the data it wrote to `stderr` was not
    144     /// valid UTF-8.
    145     StderrNotUtf8(Command),
    146     /// [`Command::output`] was successful for the contained `Command`, but a `compile_error` occurred for the
    147     /// `"default"` feature.
    148     CompileErrDefault(Command),
    149     /// [`Command::output`] was successful for the contained `Command`, but a `compile_error` occurred when
    150     /// no features were enabled and when there isn't a `"default"` feature defined.
    151     CompileErrNoFeatures(Command),
    152 }
    153 impl CargoErr {
    154     /// Writes `self` to `stderr`.
    155     pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> {
    156         match self {
    157             Self::CommandFail(cmd, err) => writeln!(stderr, "{err}").map(|()| cmd),
    158             Self::CommandNoStatus(cmd, err, out) => if out.is_empty() {
    159                 Ok(())
    160             } else {
    161                 writeln!(stderr, "{out}")
    162             }
    163             .and_then(|()| {
    164                 if err.is_empty() {
    165                     writeln!(stderr, "Missing status code and nothing written to stderr.")
    166                 } else {
    167                     writeln!(
    168                         stderr,
    169                         "Missing status code, but the following was written to stderr: {err}"
    170                     )
    171                 }
    172                 .map(|()| cmd)
    173             }),
    174             Self::CommandErr(cmd, err, out) => if out.is_empty() {
    175                 Ok(())
    176             } else {
    177                 writeln!(stderr, "{out}")
    178             }
    179             .and_then(|()| {
    180                 if err.is_empty() {
    181                     Ok(cmd)
    182                 } else {
    183                     writeln!(stderr, "{err}").map(|()| cmd)
    184                 }
    185             }),
    186             Self::StdoutNotUtf8(cmd) => {
    187                 writeln!(stderr, "Invalid UTF-8 written to stdout.").map(|()| cmd)
    188             }
    189             Self::StderrNotUtf8(cmd) => {
    190                 writeln!(stderr, "Invalid UTF-8 written to stderr.").map(|()| cmd)
    191             }
    192             Self::CompileErrDefault(cmd) => {
    193                 writeln!(stderr, "compile_error! raised on default feature.").map(|()| cmd)
    194             }
    195             Self::CompileErrNoFeatures(cmd) => writeln!(
    196                 stderr,
    197                 "compile_error! raised with no features when a default feature does not exist."
    198             )
    199             .map(|()| cmd),
    200         }
    201         .and_then(|cmd| {
    202             stderr
    203                 .write_all(cmd.get_program().to_string_lossy().as_bytes())
    204                 .and_then(|()| {
    205                     cmd.get_args()
    206                         .try_fold((), |(), arg| {
    207                             stderr
    208                                 .write_all(b" ")
    209                                 .and_then(|()| stderr.write_all(arg.to_string_lossy().as_bytes()))
    210                         })
    211                         .and_then(|()| writeln!(stderr))
    212                 })
    213         })
    214     }
    215 }
    216 /// Compiler version.
    217 #[cfg_attr(test, derive(PartialEq))]
    218 pub(crate) struct Version {
    219     /// Major version.
    220     pub major: u64,
    221     /// Minor version.
    222     pub minor: u64,
    223     /// Patch version.
    224     pub patch: u64,
    225 }
    226 /// `"+stable"`.
    227 const PLUS_STABLE: &str = "+stable";
    228 /// `"RUSTUP_HOME"`.
    229 const RUSTUP_HOME: &str = "RUSTUP_HOME";
    230 /// `"CARGO_HOME"`.
    231 const CARGO_HOME: &str = "CARGO_HOME";
    232 /// Toolchain to use.
    233 #[expect(
    234     variant_size_differences,
    235     reason = "fine. This doesn't get triggered if the other variants were unit variants despite Toolchain being the same size."
    236 )]
    237 #[derive(Clone, Copy)]
    238 pub(crate) enum Toolchain<'a> {
    239     /// `cargo +stable`.
    240     Stable,
    241     /// `cargo`.
    242     ///
    243     /// Contained `bool` is `true` iff `--ignore-rust-version` should be passed.
    244     Default(bool),
    245     /// `cargo +<MSRV>`.
    246     Msrv(&'a str),
    247 }
    248 impl Toolchain<'_> {
    249     /// Extracts the compiler version from `stdout`.
    250     ///
    251     /// This must only be called by [`Self::get_version`].
    252     #[expect(unsafe_code, reason = "comments justify correctness")]
    253     fn parse_stdout(cmd: Command, stdout: Vec<u8>) -> Result<Version, Box<ToolchainErr>> {
    254         /// `"cargo "`.
    255         const CARGO: &[u8; 6] = b"cargo ";
    256         if let Ok(utf8) = String::from_utf8(stdout) {
    257             utf8.as_bytes()
    258                 .split_at_checked(CARGO.len())
    259                 .and_then(|(pref, rem)| {
    260                     if pref == CARGO {
    261                         let mut iter = rem.split(|b| *b == b'.');
    262                         if let Some(fst) = iter.next()
    263                             // SAFETY:
    264                             // Original input was a `str`, and we split on a single-byte
    265                             // UTF-8 code unit.
    266                             && let Ok(major) = manifest::parse_int(unsafe {
    267                                 str::from_utf8_unchecked(fst)
    268                             })
    269                             && let Some(snd) = iter.next()
    270                             // SAFETY:
    271                             // Original input was a `str`, and we split on a single-byte
    272                             // UTF-8 code unit.
    273                             && let Ok(minor) = manifest::parse_int(unsafe {
    274                                 str::from_utf8_unchecked(snd)
    275                             })
    276                             && let Some(lst) = iter.next()
    277                             && iter.next().is_none()
    278                             && let Some(lst_fst) = lst.split(|b| *b == b' ').next()
    279                             // SAFETY:
    280                             // Original input was a `str`, and we split on a single-byte
    281                             // UTF-8 code unit.
    282                             && let Ok(patch) = manifest::parse_int(unsafe {
    283                                 str::from_utf8_unchecked(lst_fst)
    284                             })
    285                         {
    286                             Some(Version {
    287                                 major,
    288                                 minor,
    289                                 patch,
    290                             })
    291                         } else {
    292                             None
    293                         }
    294                     } else {
    295                         None
    296                     }
    297                 })
    298                 .ok_or_else(|| Box::new(ToolchainErr::UnexpectedOutput(cmd, utf8)))
    299         } else {
    300             Err(Box::new(ToolchainErr::StdoutNotUtf8(cmd)))
    301         }
    302     }
    303     /// Returns the version.
    304     pub(crate) fn get_version(
    305         self,
    306         rustup_home: Option<&Path>,
    307         cargo_path: &Path,
    308         cargo_home: Option<&Path>,
    309     ) -> Result<Version, Box<ToolchainErr>> {
    310         let mut cmd = Command::new(cargo_path);
    311         if let Some(env) = rustup_home {
    312             _ = cmd.env(RUSTUP_HOME, env);
    313         }
    314         if let Some(env) = cargo_home {
    315             _ = cmd.env(CARGO_HOME, env);
    316         }
    317         match self {
    318             Self::Stable => {
    319                 _ = cmd.arg(PLUS_STABLE);
    320             }
    321             Self::Default(_) => {}
    322             Self::Msrv(val) => {
    323                 _ = cmd.arg(val);
    324             }
    325         }
    326         match cmd
    327             .arg("-V")
    328             .stderr(Stdio::piped())
    329             .stdin(Stdio::null())
    330             .stdout(Stdio::piped())
    331             .output()
    332         {
    333             Ok(output) => {
    334                 if let Some(status_code) = output.status.code() {
    335                     if status_code == 0i32 {
    336                         Self::parse_stdout(cmd, output.stdout)
    337                     } else if let Ok(err) = String::from_utf8(output.stderr) {
    338                         Err(Box::new(ToolchainErr::CommandErr(cmd, err, status_code)))
    339                     } else {
    340                         Err(Box::new(ToolchainErr::StderrNotUtf8(cmd)))
    341                     }
    342                 } else if let Ok(err) = String::from_utf8(output.stderr) {
    343                     Err(Box::new(ToolchainErr::CommandNoStatus(cmd, err)))
    344                 } else {
    345                     Err(Box::new(ToolchainErr::StderrNotUtf8(cmd)))
    346                 }
    347             }
    348             Err(e) => Err(Box::new(ToolchainErr::CommandFail(cmd, e))),
    349         }
    350     }
    351 }
    352 /// `"--all-targets"`.
    353 const DASH_DASH_ALL_TARGETS: &str = "--all-targets";
    354 /// `"--benches"`.
    355 const DASH_DASH_BENCHES: &str = "--benches";
    356 /// `"--bins"`.
    357 const DASH_DASH_BINS: &str = "--bins";
    358 /// `"--examples"`.
    359 const DASH_DASH_EXAMPLES: &str = "--examples";
    360 /// `"--lib"`.
    361 const DASH_DASH_LIB: &str = "--lib";
    362 /// `"--tests"`.
    363 const DASH_DASH_TESTS: &str = "--tests";
    364 /// `"-p"`.
    365 const DASH_P: &str = "-p";
    366 /// `"-q"`.
    367 const DASH_Q: &str = "-q";
    368 /// `"--color"`.
    369 const DASH_DASH_COLOR: &str = "--color";
    370 /// `"always"`.
    371 const ALWAYS: &str = "always";
    372 /// `"never"`.
    373 const NEVER: &str = "never";
    374 /// `"--no-default-features"`.
    375 const DASH_DASH_NO_DEFAULT_FEATURES: &str = "--no-default-features";
    376 /// `"--features"`.
    377 const DASH_DASH_FEATURES: &str = "--features";
    378 /// `"--"`.
    379 const DASH_DASH: &str = "--";
    380 /// `"default"`.
    381 const DEFAULT: &str = "default";
    382 /// `"--ignore-rust-version"`.
    383 const DASH_DASH_IGNORE_RUST_VERSION: &str = "--ignore-rust-version";
    384 /// Common options to pass to [`Clippy::run`] and [`Test::run`].
    385 pub(crate) struct Options<'toolchain, 'package, 'errs> {
    386     /// The `cargo` toolchain to use.
    387     pub toolchain: Toolchain<'toolchain>,
    388     /// The path to the `rustup` storage directory.
    389     pub rustup_home: Option<PathBuf>,
    390     /// The path to `cargo`.
    391     pub cargo_path: PathBuf,
    392     /// The path to the `cargo` storage directory.
    393     pub cargo_home: Option<PathBuf>,
    394     /// Name of the package.
    395     pub package_name: &'package str,
    396     /// `true` iff color should be written to `stdout` and `stderr`.
    397     pub color: bool,
    398     /// `true` iff `compile_error`s should be ignored.
    399     pub ignore_compile_errors: bool,
    400     /// `true` iff a feature named `"default"` exists.
    401     pub default_feature_does_not_exist: bool,
    402     /// Hash set of non-terminating errors to be written at the very end.
    403     pub non_terminating_errors: &'errs mut HashSet<String>,
    404 }
    405 /// Executes `cmd`.
    406 fn execute_command(
    407     mut cmd: Command,
    408     options: &mut Options<'_, '_, '_>,
    409     features: &str,
    410 ) -> Result<(), Box<CargoErr>> {
    411     match cmd.stdout(Stdio::piped()).output() {
    412         Ok(output) => {
    413             if let Some(code) = output.status.code() {
    414                 match code {
    415                     0i32 => {
    416                         if !output.stderr.is_empty() {
    417                             _ = options.non_terminating_errors.insert(
    418                                 match String::from_utf8(output.stderr) {
    419                                     Ok(err) => err,
    420                                     Err(e) => e.to_string(),
    421                                 },
    422                             );
    423                         }
    424                         Ok(())
    425                     }
    426                     101i32 => {
    427                         /// `"compile_error!"` as a byte string.
    428                         const COMPILE_ERROR: &[u8; 14] = b"compile_error!";
    429                         if output
    430                             .stderr
    431                             .windows(COMPILE_ERROR.len())
    432                             .any(|window| window == COMPILE_ERROR)
    433                         {
    434                             if options.ignore_compile_errors {
    435                                 if features == DEFAULT {
    436                                     Err(Box::new(CargoErr::CompileErrDefault(cmd)))
    437                                 } else if options.default_feature_does_not_exist
    438                                     && features.is_empty()
    439                                 {
    440                                     Err(Box::new(CargoErr::CompileErrNoFeatures(cmd)))
    441                                 } else {
    442                                     Ok(())
    443                                 }
    444                             } else if let Ok(err) = String::from_utf8(output.stderr) {
    445                                 if let Ok(stdout) = String::from_utf8(output.stdout) {
    446                                     Err(Box::new(CargoErr::CommandErr(cmd, err, stdout)))
    447                                 } else {
    448                                     Err(Box::new(CargoErr::StdoutNotUtf8(cmd)))
    449                                 }
    450                             } else {
    451                                 Err(Box::new(CargoErr::StderrNotUtf8(cmd)))
    452                             }
    453                         } else if let Ok(err) = String::from_utf8(output.stderr) {
    454                             if let Ok(stdout) = String::from_utf8(output.stdout) {
    455                                 Err(Box::new(CargoErr::CommandErr(cmd, err, stdout)))
    456                             } else {
    457                                 Err(Box::new(CargoErr::StdoutNotUtf8(cmd)))
    458                             }
    459                         } else {
    460                             Err(Box::new(CargoErr::StderrNotUtf8(cmd)))
    461                         }
    462                     }
    463                     _ => {
    464                         if let Ok(err) = String::from_utf8(output.stderr) {
    465                             if let Ok(stdout) = String::from_utf8(output.stdout) {
    466                                 Err(Box::new(CargoErr::CommandErr(cmd, err, stdout)))
    467                             } else {
    468                                 Err(Box::new(CargoErr::StdoutNotUtf8(cmd)))
    469                             }
    470                         } else {
    471                             Err(Box::new(CargoErr::StderrNotUtf8(cmd)))
    472                         }
    473                     }
    474                 }
    475             } else if let Ok(err) = String::from_utf8(output.stderr) {
    476                 if let Ok(stdout) = String::from_utf8(output.stdout) {
    477                     Err(Box::new(CargoErr::CommandNoStatus(cmd, err, stdout)))
    478                 } else {
    479                     Err(Box::new(CargoErr::StdoutNotUtf8(cmd)))
    480                 }
    481             } else {
    482                 Err(Box::new(CargoErr::StderrNotUtf8(cmd)))
    483             }
    484         }
    485         Err(e) => Err(Box::new(CargoErr::CommandFail(cmd, e))),
    486     }
    487 }
    488 /// `cargo check`.
    489 pub(crate) struct Check;
    490 impl Check {
    491     /// Execute `cargo check`.
    492     pub(crate) fn run(
    493         options: &mut Options<'_, '_, '_>,
    494         targets: CheckClippyTargets,
    495         features: &str,
    496     ) -> Result<(), Box<CargoErr>> {
    497         let mut c = Command::new(options.cargo_path.as_path());
    498         _ = c.stderr(Stdio::piped()).stdin(Stdio::null());
    499         if let Some(ref env) = options.rustup_home {
    500             _ = c.env(RUSTUP_HOME, env);
    501         }
    502         if let Some(ref env) = options.cargo_home {
    503             _ = c.env(CARGO_HOME, env);
    504         }
    505         let ignore_msrv = match options.toolchain {
    506             Toolchain::Stable => {
    507                 _ = c.arg(PLUS_STABLE);
    508                 false
    509             }
    510             Toolchain::Default(ignore_msrv) => ignore_msrv,
    511             Toolchain::Msrv(ref msrv) => {
    512                 _ = c.arg(msrv);
    513                 false
    514             }
    515         };
    516         _ = c
    517             .arg("check")
    518             .arg(DASH_P)
    519             .arg(options.package_name)
    520             .arg(DASH_Q);
    521         match targets {
    522             CheckClippyTargets::Default => {}
    523             CheckClippyTargets::All => {
    524                 _ = c.arg(DASH_DASH_ALL_TARGETS);
    525             }
    526             CheckClippyTargets::Targets(tar) => {
    527                 if tar.contains(Target::Benches) {
    528                     _ = c.arg(DASH_DASH_BENCHES);
    529                 }
    530                 if tar.contains(Target::Bins) {
    531                     _ = c.arg(DASH_DASH_BINS);
    532                 }
    533                 if tar.contains(Target::Examples) {
    534                     _ = c.arg(DASH_DASH_EXAMPLES);
    535                 }
    536                 if tar.contains(Target::Lib) {
    537                     _ = c.arg(DASH_DASH_LIB);
    538                 }
    539                 if tar.contains(Target::Tests) {
    540                     _ = c.arg(DASH_DASH_TESTS);
    541                 }
    542             }
    543         }
    544         if ignore_msrv {
    545             _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION);
    546         }
    547         _ = c
    548             .arg(DASH_DASH_COLOR)
    549             .arg(if options.color { ALWAYS } else { NEVER })
    550             .arg(DASH_DASH_NO_DEFAULT_FEATURES);
    551         if !features.is_empty() {
    552             _ = c.arg(DASH_DASH_FEATURES).arg(features);
    553         }
    554         execute_command(c, options, features)
    555     }
    556 }
    557 /// `cargo clippy`.
    558 pub(crate) struct Clippy;
    559 impl Clippy {
    560     /// Execute `cargo clippy`.
    561     pub(crate) fn run(
    562         options: &mut Options<'_, '_, '_>,
    563         targets: CheckClippyTargets,
    564         deny_warnings: bool,
    565         features: &str,
    566     ) -> Result<(), Box<CargoErr>> {
    567         let mut c = Command::new(options.cargo_path.as_path());
    568         _ = c.stderr(Stdio::piped()).stdin(Stdio::null());
    569         if let Some(ref env) = options.rustup_home {
    570             _ = c.env(RUSTUP_HOME, env);
    571         }
    572         if let Some(ref env) = options.cargo_home {
    573             _ = c.env(CARGO_HOME, env);
    574         }
    575         let ignore_msrv = match options.toolchain {
    576             Toolchain::Stable => {
    577                 _ = c.arg(PLUS_STABLE);
    578                 false
    579             }
    580             Toolchain::Default(ignore_msrv) => ignore_msrv,
    581             Toolchain::Msrv(ref msrv) => {
    582                 _ = c.arg(msrv);
    583                 false
    584             }
    585         };
    586         _ = c
    587             .arg("clippy")
    588             .arg(DASH_P)
    589             .arg(options.package_name)
    590             .arg(DASH_Q);
    591         match targets {
    592             CheckClippyTargets::Default => {}
    593             CheckClippyTargets::All => {
    594                 _ = c.arg(DASH_DASH_ALL_TARGETS);
    595             }
    596             CheckClippyTargets::Targets(tar) => {
    597                 if tar.contains(Target::Benches) {
    598                     _ = c.arg(DASH_DASH_BENCHES);
    599                 }
    600                 if tar.contains(Target::Bins) {
    601                     _ = c.arg(DASH_DASH_BINS);
    602                 }
    603                 if tar.contains(Target::Examples) {
    604                     _ = c.arg(DASH_DASH_EXAMPLES);
    605                 }
    606                 if tar.contains(Target::Lib) {
    607                     _ = c.arg(DASH_DASH_LIB);
    608                 }
    609                 if tar.contains(Target::Tests) {
    610                     _ = c.arg(DASH_DASH_TESTS);
    611                 }
    612             }
    613         }
    614         if ignore_msrv {
    615             _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION);
    616         }
    617         _ = c
    618             .arg(DASH_DASH_COLOR)
    619             .arg(if options.color { ALWAYS } else { NEVER })
    620             .arg(DASH_DASH_NO_DEFAULT_FEATURES);
    621         if !features.is_empty() {
    622             _ = c.arg(DASH_DASH_FEATURES).arg(features);
    623         }
    624         if deny_warnings {
    625             _ = c.arg(DASH_DASH).arg("-Dwarnings");
    626         }
    627         execute_command(c, options, features)
    628     }
    629 }
    630 /// `cargo test`.
    631 pub(crate) struct Test;
    632 impl Test {
    633     /// Execute `cargo test`.
    634     pub(crate) fn run(
    635         options: &mut Options<'_, '_, '_>,
    636         targets: TestTargets,
    637         ignored: Ignored,
    638         features: &str,
    639     ) -> Result<(), Box<CargoErr>> {
    640         /// `"--ignored"`.
    641         const DASH_DASH_IGNORED: &str = "--ignored";
    642         /// `"--include-ignored"`.
    643         const DASH_DASH_INCLUDE_IGNORED: &str = "--include-ignored";
    644         let mut c = Command::new(options.cargo_path.as_path());
    645         _ = c.stderr(Stdio::piped()).stdin(Stdio::null());
    646         if let Some(ref env) = options.rustup_home {
    647             _ = c.env(RUSTUP_HOME, env);
    648         }
    649         if let Some(ref env) = options.cargo_home {
    650             _ = c.env(CARGO_HOME, env);
    651         }
    652         let ignore_msrv = match options.toolchain {
    653             Toolchain::Stable => {
    654                 _ = c.arg(PLUS_STABLE);
    655                 false
    656             }
    657             Toolchain::Default(ignore_msrv) => ignore_msrv,
    658             Toolchain::Msrv(ref msrv) => {
    659                 _ = c.arg(msrv);
    660                 false
    661             }
    662         };
    663         _ = c
    664             .arg("test")
    665             .arg(DASH_P)
    666             .arg(options.package_name)
    667             .arg(DASH_Q);
    668         match targets {
    669             TestTargets::Default => {}
    670             TestTargets::All => {
    671                 _ = c.arg(DASH_DASH_ALL_TARGETS);
    672             }
    673             TestTargets::Targets(tar) => {
    674                 if tar.contains(Target::Benches) {
    675                     _ = c.arg(DASH_DASH_BENCHES);
    676                 }
    677                 if tar.contains(Target::Bins) {
    678                     _ = c.arg(DASH_DASH_BINS);
    679                 }
    680                 if tar.contains(Target::Examples) {
    681                     _ = c.arg(DASH_DASH_EXAMPLES);
    682                 }
    683                 if tar.contains(Target::Lib) {
    684                     _ = c.arg(DASH_DASH_LIB);
    685                 }
    686                 if tar.contains(Target::Tests) {
    687                     _ = c.arg(DASH_DASH_TESTS);
    688                 }
    689             }
    690             TestTargets::Doc => {
    691                 _ = c.arg("--doc");
    692             }
    693         }
    694         if ignore_msrv {
    695             _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION);
    696         }
    697         _ = c
    698             .arg(DASH_DASH_COLOR)
    699             .arg(if options.color { ALWAYS } else { NEVER })
    700             .arg(DASH_DASH_NO_DEFAULT_FEATURES);
    701         if !features.is_empty() {
    702             _ = c.arg(DASH_DASH_FEATURES).arg(features);
    703         }
    704         _ = c
    705             .arg(DASH_DASH)
    706             .arg(DASH_DASH_COLOR)
    707             .arg(if options.color { ALWAYS } else { NEVER });
    708         match ignored {
    709             Ignored::None => {}
    710             Ignored::Only => {
    711                 _ = c.arg(DASH_DASH_IGNORED);
    712             }
    713             Ignored::Include => {
    714                 _ = c.arg(DASH_DASH_INCLUDE_IGNORED);
    715             }
    716         }
    717         execute_command(c, options, features)
    718     }
    719 }