aboutsummaryrefslogtreecommitdiff
path: root/xtask/src/run
diff options
context:
space:
mode:
Diffstat (limited to 'xtask/src/run')
-rw-r--r--xtask/src/run/data.rs87
-rw-r--r--xtask/src/run/iter.rs48
-rw-r--r--xtask/src/run/results.rs100
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(())
+ }
+}