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 }