diff --git a/tests/ci.rs b/tests/ci.rs index 91623226..c7042b23 100644 --- a/tests/ci.rs +++ b/tests/ci.rs @@ -23,6 +23,8 @@ fn clippy() { "--deny=warnings", "--warn=clippy::pedantic", "--allow=clippy::format-collect", + "--allow=clippy::missing-errors-doc", + "--allow=clippy::missing-panics-doc", ]) .current_dir(dir) .assert() diff --git a/tests/rustsec_comparison.rs b/tests/rustsec_comparison.rs index a8a68a47..18e00551 100644 --- a/tests/rustsec_comparison.rs +++ b/tests/rustsec_comparison.rs @@ -1,5 +1,8 @@ -use snapbox::{assert_matches_path, cmd::Command}; -use std::env::{remove_var, var}; +use snapbox::assert_matches_path; +use std::{env::remove_var, process::Command}; + +mod util; +use util::{tee, token_modifier, Tee}; #[ctor::ctor] fn initialize() { @@ -8,24 +11,18 @@ fn initialize() { #[test] fn rustsec_comparison() { - let assert = Command::new("cargo") + let mut command = Command::new("cargo"); + command .arg("run") .current_dir("rustsec_comparison") - .env("RUST_BACKTRACE", "0") - .assert(); + .env("RUST_BACKTRACE", "0"); + + let output = tee(command, Tee::Stdout).unwrap(); - let stdout_actual = std::str::from_utf8(&assert.get_output().stdout).unwrap(); + let stdout_actual = std::str::from_utf8(&output.captured).unwrap(); assert_matches_path( format!("tests/rustsec_comparison.{}.stdout", token_modifier()), stdout_actual, ); } - -fn token_modifier() -> &'static str { - if var("GITHUB_TOKEN_PATH").is_ok() { - "with_token" - } else { - "without_token" - } -} diff --git a/tests/snapbox.rs b/tests/snapbox.rs index 13635b44..59a488f1 100644 --- a/tests/snapbox.rs +++ b/tests/snapbox.rs @@ -8,15 +8,18 @@ use regex::Regex; use serde::Deserialize; use snapbox::{ assert_matches_path, - cmd::{cargo_bin, Command}, + cmd::{cargo_bin, Command as SnapboxCommand}, }; use std::{ - env::var, ffi::OsStr, fs::{read_dir, read_to_string}, + process::Command, }; use tempfile::tempdir; +mod util; +use util::{enabled, tee, token_modifier, Tee}; + #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct Test { @@ -54,45 +57,55 @@ fn snapbox() -> Result<()> { let tempdir = tempdir()?; - let mut command = - Command::new("git").args(["clone", &test.url, &tempdir.path().to_string_lossy()]); + let mut command = SnapboxCommand::new("git").args([ + "clone", + &test.url, + &tempdir.path().to_string_lossy(), + ]); if test.rev.is_none() { command = command.arg("--depth=1"); } command.assert().success(); if let Some(rev) = &test.rev { - Command::new("git") + SnapboxCommand::new("git") .args(["checkout", rev]) .current_dir(&tempdir) .assert() .success(); } - let output = Command::new(cargo_bin("cargo-unmaintained")) + let mut command = Command::new(cargo_bin("cargo-unmaintained")); + command .args(["unmaintained", "--color=never", "--imprecise"]) - .current_dir(&tempdir) - .output()?; + .current_dir(&tempdir); + + if enabled("VERBOSE") { + // smoelius If `VERBOSE` is enabled, don't bother comparing stderr, because it won't + // match. + command.arg("--verbose"); + + let output = tee(command, Tee::Stdout)?; - let stderr_actual = String::from_utf8(output.stderr)?; - let stdout_actual = String::from_utf8(output.stdout)?; + let stdout_actual = String::from_utf8(output.captured)?; - // smoelius: Compare stderr before stdout so that you can see any errors that occurred. - assert_matches_path(stderr_path, stderr_actual); - assert_matches_path(stdout_path, stdout_actual); + assert_matches_path(stdout_path, stdout_actual); + } else { + let output = command.output()?; + + let stderr_actual = String::from_utf8(output.stderr)?; + let stdout_actual = String::from_utf8(output.stdout)?; + + // smoelius: Compare stderr before stdout so that you can see any errors that + // occurred. + assert_matches_path(stderr_path, stderr_actual); + assert_matches_path(stdout_path, stdout_actual); + } Ok(()) }) } -fn token_modifier() -> &'static str { - if var("GITHUB_TOKEN_PATH").is_ok() { - "with_token" - } else { - "without_token" - } -} - static RES: Lazy<[Regex; 2]> = Lazy::new(|| { [ Regex::new(r"([^ ]*) days").unwrap(), diff --git a/tests/util.rs b/tests/util.rs new file mode 100644 index 00000000..3fac2cad --- /dev/null +++ b/tests/util.rs @@ -0,0 +1,73 @@ +#![cfg_attr(dylint_lib = "general", allow(crate_wide_allow))] +#![allow(dead_code)] + +use anyhow::{Context, Result}; +use std::{ + env::var, + io::Read, + process::{Command, ExitStatus, Stdio}, +}; + +#[derive(Clone, Copy)] +pub enum Tee { + Stdout, + Stderr, +} + +pub struct Output { + pub status: ExitStatus, + pub captured: Vec, +} + +const BUF_SIZE: usize = 1024; + +pub fn tee(mut command: Command, which: Tee) -> Result { + match which { + Tee::Stdout => { + command.stdout(Stdio::piped()); + } + Tee::Stderr => { + command.stderr(Stdio::piped()); + } + } + + let mut child = command + .spawn() + .with_context(|| format!("command failed: {command:?}"))?; + + let stream: &mut dyn Read = match which { + Tee::Stdout => child.stdout.as_mut().unwrap(), + Tee::Stderr => child.stderr.as_mut().unwrap(), + }; + + let mut captured = Vec::new(); + + loop { + let mut buf = [0u8; BUF_SIZE]; + let size = stream.read(&mut buf).with_context(|| "`read` failed")?; + if size == 0 { + break; + } + let s = std::str::from_utf8(&buf)?; + print!("{s}"); + captured.extend_from_slice(&buf[..size]); + } + + let status = child.wait().with_context(|| "`wait` failed")?; + + Ok(Output { status, captured }) +} + +#[must_use] +pub fn token_modifier() -> &'static str { + if var("GITHUB_TOKEN_PATH").is_ok() { + "with_token" + } else { + "without_token" + } +} + +#[must_use] +pub fn enabled(key: &str) -> bool { + var(key).map_or(false, |value| value != "0") +}