diff options
Diffstat (limited to 'xtask/src/run')
| -rw-r--r-- | xtask/src/run/data.rs | 87 | ||||
| -rw-r--r-- | xtask/src/run/iter.rs | 48 | ||||
| -rw-r--r-- | xtask/src/run/results.rs | 100 |
3 files changed, 235 insertions, 0 deletions
diff --git a/xtask/src/run/data.rs b/xtask/src/run/data.rs new file mode 100644 index 0000000..eacd72c --- /dev/null +++ b/xtask/src/run/data.rs @@ -0,0 +1,87 @@ +use std::{ + ffi::OsString, + process::{ExitStatus, Stdio}, +}; + +use diffy::{create_patch, PatchFormatter}; + +use crate::cargo_command::CargoCommand; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OutputMode { + PipedAndCollected, + Inherited, +} + +impl From<OutputMode> for Stdio { + fn from(value: OutputMode) -> Self { + match value { + OutputMode::PipedAndCollected => Stdio::piped(), + OutputMode::Inherited => Stdio::inherit(), + } + } +} + +#[derive(Debug, Clone)] +pub struct RunResult { + pub exit_status: ExitStatus, + pub stdout: String, + pub stderr: String, +} + +#[derive(Debug)] +pub enum FinalRunResult<'c> { + Success(CargoCommand<'c>, RunResult), + Failed(CargoCommand<'c>, RunResult), + CommandError(CargoCommand<'c>, anyhow::Error), +} + +#[derive(Debug)] +pub enum TestRunError { + FileCmpError { + expected: String, + got: String, + }, + FileError { + file: String, + }, + PathConversionError(OsString), + CommandError(RunResult), + #[allow(dead_code)] + IncompatibleCommand, +} + +impl core::fmt::Display for TestRunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestRunError::FileCmpError { expected, got } => { + let patch = create_patch(expected, got); + writeln!(f, "Differing output in files.\n")?; + let pf = PatchFormatter::new().with_color(); + writeln!(f, "{}", pf.fmt_patch(&patch))?; + write!( + f, + "See flag --overwrite-expected to create/update expected output." + ) + } + TestRunError::FileError { file } => { + write!(f, "File error on: {file}\nSee flag --overwrite-expected to create/update expected output.") + } + TestRunError::CommandError(e) => { + write!( + f, + "Command failed with exit status {}: {} {}", + e.exit_status, e.stdout, e.stderr + ) + } + TestRunError::PathConversionError(p) => { + write!(f, "Can't convert path from `OsString` to `String`: {p:?}") + } + TestRunError::IncompatibleCommand => { + write!(f, "Can't run that command in this context") + } + } + } +} + +impl std::error::Error for TestRunError {} diff --git a/xtask/src/run/iter.rs b/xtask/src/run/iter.rs new file mode 100644 index 0000000..d18ad49 --- /dev/null +++ b/xtask/src/run/iter.rs @@ -0,0 +1,48 @@ +use super::FinalRunResult; + +pub use iter::*; + +pub trait CoalescingRunner<'c> { + /// Run all the commands in this iterator, and coalesce the results into + /// one error (if any individual commands failed) + fn run_and_coalesce(self) -> Vec<FinalRunResult<'c>>; +} + +#[cfg(not(feature = "rayon"))] +mod iter { + use super::*; + use crate::{argument_parsing::Globals, cargo_command::*, run::run_and_convert}; + + pub fn into_iter<T: IntoIterator>(var: T) -> impl Iterator<Item = T::Item> { + var.into_iter() + } + + impl<'g, 'c, I> CoalescingRunner<'c> for I + where + I: Iterator<Item = (&'g Globals, CargoCommand<'c>, bool)>, + { + fn run_and_coalesce(self) -> Vec<FinalRunResult<'c>> { + self.map(run_and_convert).collect() + } + } +} + +#[cfg(feature = "rayon")] +mod iter { + use super::*; + use crate::{argument_parsing::Globals, cargo_command::*, run::run_and_convert}; + use rayon::prelude::*; + + pub fn into_iter<T: IntoParallelIterator>(var: T) -> impl ParallelIterator<Item = T::Item> { + var.into_par_iter() + } + + impl<'g, 'c, I> CoalescingRunner<'c> for I + where + I: ParallelIterator<Item = (&'g Globals, CargoCommand<'c>, bool)>, + { + fn run_and_coalesce(self) -> Vec<FinalRunResult<'c>> { + self.map(run_and_convert).collect() + } + } +} diff --git a/xtask/src/run/results.rs b/xtask/src/run/results.rs new file mode 100644 index 0000000..b64e7b1 --- /dev/null +++ b/xtask/src/run/results.rs @@ -0,0 +1,100 @@ +use log::{error, info, log, Level}; + +use crate::{argument_parsing::Globals, cargo_command::CargoCommand}; + +use super::data::FinalRunResult; + +const TARGET: &str = "xtask::results"; + +pub fn handle_results(globals: &Globals, results: Vec<FinalRunResult>) -> Result<(), ()> { + let errors = results.iter().filter_map(|r| { + if let FinalRunResult::Failed(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let successes = results.iter().filter_map(|r| { + if let FinalRunResult::Success(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let command_errors = results.iter().filter_map(|r| { + if let FinalRunResult::CommandError(c, e) = r { + Some((c, e)) + } else { + None + } + }); + + let log_stdout_stderr = |level: Level| { + move |(cmd, stdout, stderr): (&CargoCommand, &String, &String)| { + let cmd = cmd.as_cmd_string(); + if !stdout.is_empty() && !stderr.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStdout:\n{stdout}\nStderr:\n{stderr}" + ); + } else if !stdout.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStdout:\n{}", + stdout.trim_end() + ); + } else if !stderr.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStderr:\n{}", + stderr.trim_end() + ); + } + } + }; + + successes.for_each(|(cmd, stdout, stderr)| { + if globals.verbose > 0 { + info!( + target: TARGET, + "✅ Success: {cmd}\n {}", + cmd.as_cmd_string() + ); + } else { + info!(target: TARGET, "✅ Success: {cmd}"); + } + + log_stdout_stderr(Level::Debug)((cmd, stdout, stderr)); + }); + + errors.clone().for_each(|(cmd, stdout, stderr)| { + error!( + target: TARGET, + "❌ Failed: {cmd}\n {}", + cmd.as_cmd_string() + ); + log_stdout_stderr(Level::Error)((cmd, stdout, stderr)); + }); + + command_errors.clone().for_each(|(cmd, error)| { + error!( + target: TARGET, + "❌ Failed: {cmd}\n {}\n{error}", + cmd.as_cmd_string() + ) + }); + + let ecount = errors.count() + command_errors.count(); + if ecount != 0 { + error!(target: TARGET, "{ecount} commands failed."); + Err(()) + } else { + info!(target: TARGET, "🚀🚀🚀 All tasks succeeded 🚀🚀🚀"); + Ok(()) + } +} |
