Unified error handling

This commit is contained in:
David Peter 2022-02-06 17:44:28 +01:00 committed by David Peter
parent 34dabcbed7
commit 2040810ce3
10 changed files with 95 additions and 101 deletions

7
Cargo.lock generated
View File

@ -11,6 +11,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anyhow"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]] [[package]]
name = "approx" name = "approx"
version = "0.5.1" version = "0.5.1"
@ -246,6 +252,7 @@ dependencies = [
name = "hyperfine" name = "hyperfine"
version = "1.12.0" version = "1.12.0"
dependencies = [ dependencies = [
"anyhow",
"approx", "approx",
"assert_cmd", "assert_cmd",
"atty", "atty",

View File

@ -23,6 +23,7 @@ rust_decimal = "1.19"
rand = "0.8" rand = "0.8"
shell-words = "1.0" shell-words = "1.0"
thiserror = "1.0" thiserror = "1.0"
anyhow = "1.0"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
libc = "0.2" libc = "0.2"

View File

@ -1,5 +1,4 @@
use std::cmp; use std::cmp;
use std::io;
use std::process::{ExitStatus, Stdio}; use std::process::{ExitStatus, Stdio};
use colored::*; use colored::*;
@ -18,6 +17,8 @@ use crate::timer::{TimerStart, TimerStop};
use crate::units::Second; use crate::units::Second;
use crate::warnings::Warnings; use crate::warnings::Warnings;
use anyhow::{bail, Result};
/// Threshold for warning about fast execution time /// Threshold for warning about fast execution time
pub const MIN_EXECUTION_TIME: Second = 5e-3; pub const MIN_EXECUTION_TIME: Second = 5e-3;
@ -50,7 +51,7 @@ pub fn time_shell_command(
show_output: bool, show_output: bool,
failure_action: CmdFailureAction, failure_action: CmdFailureAction,
shell_spawning_time: Option<TimingResult>, shell_spawning_time: Option<TimingResult>,
) -> io::Result<(TimingResult, ExitStatus)> { ) -> Result<(TimingResult, ExitStatus)> {
let (stdout, stderr) = if show_output { let (stdout, stderr) = if show_output {
(Stdio::inherit(), Stdio::inherit()) (Stdio::inherit(), Stdio::inherit())
} else { } else {
@ -65,18 +66,14 @@ pub fn time_shell_command(
let mut time_system = result.system_time; let mut time_system = result.system_time;
if failure_action == CmdFailureAction::RaiseError && !result.status.success() { if failure_action == CmdFailureAction::RaiseError && !result.status.success() {
return Err(io::Error::new( bail!(
io::ErrorKind::Other, "{}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \
format!(
"{}. \
Use the '-i'/'--ignore-failure' option if you want to ignore this. \
Alternatively, use the '--show-output' option to debug what went wrong.", Alternatively, use the '--show-output' option to debug what went wrong.",
result.status.code().map_or( result.status.code().map_or(
"The process has been terminated by a signal".into(), "The process has been terminated by a signal".into(),
|c| format!("Command terminated with non-zero exit code: {}", c) |c| format!("Command terminated with non-zero exit code: {}", c)
) )
), );
));
} }
// Correct for shell spawning time // Correct for shell spawning time
@ -101,7 +98,7 @@ pub fn mean_shell_spawning_time(
shell: &Shell, shell: &Shell,
style: OutputStyleOption, style: OutputStyleOption,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> Result<TimingResult> {
const COUNT: u64 = 50; const COUNT: u64 = 50;
let progress_bar = if style != OutputStyleOption::Disabled { let progress_bar = if style != OutputStyleOption::Disabled {
Some(get_progress_bar( Some(get_progress_bar(
@ -135,14 +132,10 @@ pub fn mean_shell_spawning_time(
format!("{} -c \"\"", shell) format!("{} -c \"\"", shell)
}; };
return Err(io::Error::new( bail!(
io::ErrorKind::Other, "Could not measure shell execution time. Make sure you can run '{}'.",
format!( shell_cmd
"Could not measure shell execution time. \ );
Make sure you can run '{}'.",
shell_cmd
),
));
} }
Ok((r, _)) => { Ok((r, _)) => {
times_real.push(r.time_real); times_real.push(r.time_real);
@ -172,11 +165,11 @@ fn run_intermediate_command(
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
error_output: &'static str, error_output: &'static str,
) -> io::Result<TimingResult> { ) -> Result<TimingResult> {
if let Some(ref cmd) = command { if let Some(ref cmd) = command {
let res = time_shell_command(shell, cmd, show_output, CmdFailureAction::RaiseError, None); let res = time_shell_command(shell, cmd, show_output, CmdFailureAction::RaiseError, None);
if res.is_err() { if res.is_err() {
return Err(io::Error::new(io::ErrorKind::Other, error_output)); bail!(error_output);
} }
return res.map(|r| r.0); return res.map(|r| r.0);
} }
@ -190,7 +183,7 @@ fn run_setup_command(
shell: &Shell, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> Result<TimingResult> {
let error_output = "The setup command terminated with a non-zero exit code. \ let error_output = "The setup command terminated with a non-zero exit code. \
Append ' || true' to the command if you are sure that this can be ignored."; Append ' || true' to the command if you are sure that this can be ignored.";
@ -202,7 +195,7 @@ fn run_preparation_command(
shell: &Shell, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> Result<TimingResult> {
let error_output = "The preparation command terminated with a non-zero exit code. \ let error_output = "The preparation command terminated with a non-zero exit code. \
Append ' || true' to the command if you are sure that this can be ignored."; Append ' || true' to the command if you are sure that this can be ignored.";
@ -214,7 +207,7 @@ fn run_cleanup_command(
shell: &Shell, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> Result<TimingResult> {
let error_output = "The cleanup command terminated with a non-zero exit code. \ let error_output = "The cleanup command terminated with a non-zero exit code. \
Append ' || true' to the command if you are sure that this can be ignored."; Append ' || true' to the command if you are sure that this can be ignored.";
@ -248,7 +241,7 @@ pub fn run_benchmark(
cmd: &Command<'_>, cmd: &Command<'_>,
shell_spawning_time: TimingResult, shell_spawning_time: TimingResult,
options: &HyperfineOptions, options: &HyperfineOptions,
) -> io::Result<BenchmarkResult> { ) -> Result<BenchmarkResult> {
let command_name = cmd.get_name(); let command_name = cmd.get_name();
if options.output_style != OutputStyleOption::Disabled { if options.output_style != OutputStyleOption::Disabled {
println!( println!(

View File

@ -1,5 +1,3 @@
use std::io::{Error, ErrorKind, Result};
use crate::benchmark_result::BenchmarkResult; use crate::benchmark_result::BenchmarkResult;
use crate::format::format_duration_value; use crate::format::format_duration_value;
use crate::relative_speed::{self, BenchmarkResultWithRelativeSpeed}; use crate::relative_speed::{self, BenchmarkResultWithRelativeSpeed};
@ -7,6 +5,8 @@ use crate::units::Unit;
use super::Exporter; use super::Exporter;
use anyhow::{anyhow, Result};
#[derive(Default)] #[derive(Default)]
pub struct AsciidocExporter {} pub struct AsciidocExporter {}
@ -36,9 +36,8 @@ impl Exporter for AsciidocExporter {
Ok(res) Ok(res)
} else { } else {
Err(Error::new( Err(anyhow!(
ErrorKind::Other, "Relative speed comparison is not available for Asciidoctor export."
"Relative speed comparison is not available for Asciidoctor export.",
)) ))
} }
} }

View File

@ -1,5 +1,4 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{Error, ErrorKind, Result};
use csv::WriterBuilder; use csv::WriterBuilder;
@ -7,6 +6,8 @@ use super::Exporter;
use crate::benchmark_result::BenchmarkResult; use crate::benchmark_result::BenchmarkResult;
use crate::units::Unit; use crate::units::Unit;
use anyhow::Result;
#[derive(Default)] #[derive(Default)]
pub struct CsvExporter {} pub struct CsvExporter {}
@ -49,9 +50,7 @@ impl Exporter for CsvExporter {
writer.write_record(fields)?; writer.write_record(fields)?;
} }
writer Ok(writer.into_inner()?)
.into_inner()
.map_err(|e| Error::new(ErrorKind::Other, e))
} }
} }

View File

@ -1,5 +1,3 @@
use std::io::{Error, ErrorKind, Result};
use serde::*; use serde::*;
use serde_json::to_vec_pretty; use serde_json::to_vec_pretty;
@ -7,6 +5,8 @@ use super::Exporter;
use crate::benchmark_result::BenchmarkResult; use crate::benchmark_result::BenchmarkResult;
use crate::units::Unit; use crate::units::Unit;
use anyhow::Result;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct HyperfineSummary<'a> { struct HyperfineSummary<'a> {
results: &'a [BenchmarkResult], results: &'a [BenchmarkResult],
@ -22,6 +22,6 @@ impl Exporter for JsonExporter {
content.push(b'\n'); content.push(b'\n');
} }
output.map_err(|e| Error::new(ErrorKind::Other, e)) Ok(output?)
} }
} }

View File

@ -1,11 +1,11 @@
use std::io::{Error, ErrorKind, Result};
use super::Exporter; use super::Exporter;
use crate::benchmark_result::BenchmarkResult; use crate::benchmark_result::BenchmarkResult;
use crate::format::format_duration_value; use crate::format::format_duration_value;
use crate::relative_speed::{self, BenchmarkResultWithRelativeSpeed}; use crate::relative_speed::{self, BenchmarkResultWithRelativeSpeed};
use crate::units::Unit; use crate::units::Unit;
use anyhow::{anyhow, Result};
#[derive(Default)] #[derive(Default)]
pub struct MarkdownExporter {} pub struct MarkdownExporter {}
@ -31,9 +31,8 @@ impl Exporter for MarkdownExporter {
Ok(destination) Ok(destination)
} else { } else {
Err(Error::new( Err(anyhow!(
ErrorKind::Other, "Relative speed comparison is not available for Markdown export."
"Relative speed comparison is not available for Markdown export.",
)) ))
} }
} }

View File

@ -1,5 +1,5 @@
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{Result, Write}; use std::io::Write;
mod asciidoc; mod asciidoc;
mod csv; mod csv;
@ -14,6 +14,8 @@ use self::markdown::MarkdownExporter;
use crate::benchmark_result::BenchmarkResult; use crate::benchmark_result::BenchmarkResult;
use crate::units::Unit; use crate::units::Unit;
use anyhow::{Context, Result};
/// The desired form of exporter to use for a given file. /// The desired form of exporter to use for a given file.
#[derive(Clone)] #[derive(Clone)]
pub enum ExportType { pub enum ExportType {
@ -50,7 +52,8 @@ pub struct ExportManager {
impl ExportManager { impl ExportManager {
/// Add an additional exporter to the ExportManager /// Add an additional exporter to the ExportManager
pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) -> Result<()> { pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) -> Result<()> {
let _ = File::create(filename)?; let _ = File::create(filename)
.with_context(|| format!("Could not create export file '{filename}'"))?;
let exporter: Box<dyn Exporter> = match export_type { let exporter: Box<dyn Exporter> = match export_type {
ExportType::Asciidoc => Box::new(AsciidocExporter::default()), ExportType::Asciidoc => Box::new(AsciidocExporter::default()),
@ -80,4 +83,5 @@ impl ExportManager {
fn write_to_file(filename: &str, content: &[u8]) -> Result<()> { fn write_to_file(filename: &str, content: &[u8]) -> Result<()> {
let mut file = OpenOptions::new().write(true).open(filename)?; let mut file = OpenOptions::new().write(true).open(filename)?;
file.write_all(content) file.write_all(content)
.with_context(|| format!("Failed to export results to '{}'", filename))
} }

View File

@ -1,7 +1,6 @@
use std::cmp; use std::cmp;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::env; use std::env;
use std::io;
use atty::Stream; use atty::Stream;
use clap::ArgMatches; use clap::ArgMatches;
@ -39,11 +38,7 @@ use tokenize::tokenize;
use types::ParameterValue; use types::ParameterValue;
use units::Unit; use units::Unit;
/// Print error message to stderr and terminate use anyhow::{bail, Result};
pub fn error(message: &str) -> ! {
eprintln!("{} {}", "Error:".red(), message);
std::process::exit(1);
}
pub fn write_benchmark_comparison(results: &[BenchmarkResult]) { pub fn write_benchmark_comparison(results: &[BenchmarkResult]) {
if results.len() < 2 { if results.len() < 2 {
@ -83,12 +78,11 @@ pub fn write_benchmark_comparison(results: &[BenchmarkResult]) {
} }
} }
/// Runs the benchmark for the given commands fn run_benchmarks_and_print_comparison(
fn run(
commands: &[Command<'_>], commands: &[Command<'_>],
options: &HyperfineOptions, options: &HyperfineOptions,
export_manager: &ExportManager, export_manager: &ExportManager,
) -> io::Result<()> { ) -> Result<()> {
let shell_spawning_time = let shell_spawning_time =
mean_shell_spawning_time(&options.shell, options.output_style, options.show_output)?; mean_shell_spawning_time(&options.shell, options.output_style, options.show_output)?;
@ -96,9 +90,9 @@ fn run(
if let Some(preparation_command) = &options.preparation_command { if let Some(preparation_command) = &options.preparation_command {
if preparation_command.len() > 1 && commands.len() != preparation_command.len() { if preparation_command.len() > 1 && commands.len() != preparation_command.len() {
error( bail!(
"The '--prepare' option has to be provided just once or N times, where N is the \ "The '--prepare' option has to be provided just once or N times, where N is the \
number of benchmark commands.", number of benchmark commands."
); );
} }
} }
@ -108,13 +102,7 @@ fn run(
timing_results.push(run_benchmark(num, cmd, shell_spawning_time, options)?); timing_results.push(run_benchmark(num, cmd, shell_spawning_time, options)?);
// Export (intermediate) results // Export (intermediate) results
let ans = export_manager.write_results(&timing_results, options.time_unit); export_manager.write_results(&timing_results, options.time_unit)?;
if let Err(e) = ans {
error(&format!(
"The following error occurred while exporting: {}",
e
));
}
} }
// Print relative speed comparison // Print relative speed comparison
@ -125,23 +113,22 @@ fn run(
Ok(()) Ok(())
} }
fn main() { fn run() -> Result<()> {
let matches = get_arg_matches(env::args_os()); let matches = get_arg_matches(env::args_os());
let options = build_hyperfine_options(&matches); let options = build_hyperfine_options(&matches)?;
let commands = build_commands(&matches); let commands = build_commands(&matches)?;
let export_manager = match build_export_manager(&matches) { let export_manager = build_export_manager(&matches)?;
Ok(export_manager) => export_manager,
Err(ref e) => error(&e.to_string()),
};
let res = match options { run_benchmarks_and_print_comparison(&commands, &options, &export_manager)
Ok(ref opts) => run(&commands, opts, &export_manager), }
Err(ref e) => error(&e.to_string()),
};
match res { fn main() {
match run() {
Ok(_) => {} Ok(_) => {}
Err(e) => error(&e.to_string()), Err(e) => {
eprintln!("{} {:#}", "Error:".red(), e);
std::process::exit(1);
}
} }
} }
@ -243,10 +230,10 @@ fn build_hyperfine_options<'a>(matches: &ArgMatches) -> Result<HyperfineOptions,
/// Build the ExportManager that will export the results specified /// Build the ExportManager that will export the results specified
/// in the given ArgMatches /// in the given ArgMatches
fn build_export_manager(matches: &ArgMatches) -> io::Result<ExportManager> { fn build_export_manager(matches: &ArgMatches) -> Result<ExportManager> {
let mut export_manager = ExportManager::default(); let mut export_manager = ExportManager::default();
{ {
let mut add_exporter = |flag, exporttype| -> io::Result<()> { let mut add_exporter = |flag, exporttype| -> Result<()> {
if let Some(filename) = matches.value_of(flag) { if let Some(filename) = matches.value_of(flag) {
export_manager.add_exporter(exporttype, filename)?; export_manager.add_exporter(exporttype, filename)?;
} }
@ -267,10 +254,12 @@ fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
if let Some(args) = matches.values_of("parameter-scan") { if let Some(args) = matches.values_of("parameter-scan") {
let step_size = matches.value_of("parameter-step-size"); let step_size = matches.value_of("parameter-step-size");
match get_parameterized_commands(command_names, command_strings, args, step_size) { Ok(get_parameterized_commands(
Ok(commands) => commands, command_names,
Err(e) => error(&e.to_string()), command_strings,
} args,
step_size,
)?)
} else if let Some(args) = matches.values_of("parameter-list") { } else if let Some(args) = matches.values_of("parameter-list") {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>()); let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
@ -286,7 +275,7 @@ fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
{ {
let dupes = find_dupes(param_names_and_values.iter().map(|(name, _)| *name)); let dupes = find_dupes(param_names_and_values.iter().map(|(name, _)| *name));
if !dupes.is_empty() { if !dupes.is_empty() {
error(&format!("duplicate parameter names: {}", &dupes.join(", "))) bail!("Duplicate parameter names: {}", &dupes.join(", "));
} }
} }
let command_list = command_strings.collect::<Vec<&str>>(); let command_list = command_strings.collect::<Vec<&str>>();
@ -300,16 +289,18 @@ fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
.collect(); .collect();
let param_space_size = dimensions.iter().product(); let param_space_size = dimensions.iter().product();
if param_space_size == 0 { if param_space_size == 0 {
return Vec::new(); return Ok(Vec::new());
} }
// `--command-name` should appear exactly once or exactly B times, // `--command-name` should appear exactly once or exactly B times,
// where B is the total number of benchmarks. // where B is the total number of benchmarks.
let command_name_count = command_names.len(); let command_name_count = command_names.len();
if command_name_count > 1 && command_name_count != param_space_size { if command_name_count > 1 && command_name_count != param_space_size {
let err = return Err(OptionsError::UnexpectedCommandNameCount(
OptionsError::UnexpectedCommandNameCount(command_name_count, param_space_size); command_name_count,
error(&err.to_string()); param_space_size,
)
.into());
} }
let mut i = 0; let mut i = 0;
@ -346,12 +337,11 @@ fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
break 'outer; break 'outer;
} }
commands Ok(commands)
} else { } else {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>()); let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
if command_names.len() > command_strings.len() { if command_names.len() > command_strings.len() {
let err = OptionsError::TooManyCommandNames(command_strings.len()); return Err(OptionsError::TooManyCommandNames(command_strings.len()).into());
error(&err.to_string());
} }
let command_list = command_strings.collect::<Vec<&str>>(); let command_list = command_strings.collect::<Vec<&str>>();
@ -359,7 +349,7 @@ fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
for (i, s) in command_list.iter().enumerate() { for (i, s) in command_list.iter().enumerate() {
commands.push(Command::new(command_names.get(i).copied(), s)); commands.push(Command::new(command_names.get(i).copied(), s));
} }
commands Ok(commands)
} }
} }
@ -389,7 +379,7 @@ fn test_build_commands_cross_product() {
"echo {par1} {par2}", "echo {par1} {par2}",
"printf '%s\n' {par1} {par2}", "printf '%s\n' {par1} {par2}",
]); ]);
let result = build_commands(&matches); let result = build_commands(&matches).unwrap();
// Iteration order: command list first, then parameters in listed order (here, "par1" before // Iteration order: command list first, then parameters in listed order (here, "par1" before
// "par2", which is distinct from their sorted order), with parameter values in listed order. // "par2", which is distinct from their sorted order), with parameter values in listed order.
@ -423,7 +413,7 @@ fn test_build_parameter_list_commands() {
"--command-name", "--command-name",
"name-{foo}", "name-{foo}",
]); ]);
let commands = build_commands(&matches); let commands = build_commands(&matches).unwrap();
assert_eq!(commands.len(), 2); assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2"); assert_eq!(commands[1].get_name(), "name-2");
@ -445,7 +435,7 @@ fn test_build_parameter_range_commands() {
"--command-name", "--command-name",
"name-{val}", "name-{val}",
]); ]);
let commands = build_commands(&matches); let commands = build_commands(&matches).unwrap();
assert_eq!(commands.len(), 2); assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2"); assert_eq!(commands[1].get_name(), "name-2");

View File

@ -1,9 +1,10 @@
use std::io;
use std::process::{ExitStatus, Stdio}; use std::process::{ExitStatus, Stdio};
use crate::options::Shell; use crate::options::Shell;
use crate::timer::get_cpu_timer; use crate::timer::get_cpu_timer;
use anyhow::{Context, Result};
/// Used to indicate the result of running a command /// Used to indicate the result of running a command
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ExecuteResult { pub struct ExecuteResult {
@ -24,7 +25,7 @@ pub fn execute_and_time(
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &Shell, shell: &Shell,
) -> io::Result<ExecuteResult> { ) -> Result<ExecuteResult> {
let mut child = run_shell_command(stdout, stderr, command, shell)?; let mut child = run_shell_command(stdout, stderr, command, shell)?;
let cpu_timer = get_cpu_timer(&child); let cpu_timer = get_cpu_timer(&child);
let status = child.wait()?; let status = child.wait()?;
@ -44,7 +45,7 @@ pub fn execute_and_time(
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &Shell, shell: &Shell,
) -> io::Result<ExecuteResult> { ) -> Result<ExecuteResult> {
let cpu_timer = get_cpu_timer(); let cpu_timer = get_cpu_timer();
let status = run_shell_command(stdout, stderr, command, shell)?; let status = run_shell_command(stdout, stderr, command, shell)?;
@ -65,7 +66,7 @@ fn run_shell_command(
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &Shell, shell: &Shell,
) -> io::Result<std::process::ExitStatus> { ) -> Result<std::process::ExitStatus> {
shell shell
.command() .command()
.arg("-c") .arg("-c")
@ -78,6 +79,7 @@ fn run_shell_command(
.stdout(stdout) .stdout(stdout)
.stderr(stderr) .stderr(stderr)
.status() .status()
.with_context(|| format!("Failed to run command '{}'", command))
} }
/// Run a Windows shell command using `cmd.exe /C` /// Run a Windows shell command using `cmd.exe /C`
@ -87,7 +89,7 @@ fn run_shell_command(
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &Shell, shell: &Shell,
) -> io::Result<std::process::Child> { ) -> Result<std::process::Child> {
shell shell
.command() .command()
.arg("/C") .arg("/C")