Added --stdin-data argument

This argument accepts a path to a file. The data contained in the file will be passed to the command via stdin.

Closes sharkdp/hyperfine#541
This commit is contained in:
Steve Nease 2022-09-11 16:20:22 -05:00 committed by David Peter
parent 531fee3188
commit e38acdb683
6 changed files with 91 additions and 3 deletions

View File

@ -1,7 +1,7 @@
use std::process::{ExitStatus, Stdio};
use crate::command::Command;
use crate::options::{CmdFailureAction, CommandOutputPolicy, Options, OutputStyleOption, Shell};
use crate::options::{CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell};
use crate::output::progress_bar::get_progress_bar;
use crate::timer::{execute_and_measure, TimerResult};
use crate::util::randomized_environment_offset;
@ -36,11 +36,13 @@ pub trait Executor {
fn run_command_and_measure_common(
mut command: std::process::Command,
command_failure_action: CmdFailureAction,
command_input_policy: &CommandInputPolicy,
command_output_policy: &CommandOutputPolicy,
command_name: &str,
) -> Result<TimerResult> {
let (stdout, stderr) = command_output_policy.get_stdout_stderr()?;
command.stdin(Stdio::null()).stdout(stdout).stderr(stderr);
let stdin = command_input_policy.get_stdin()?;
command.stdin(stdin).stdout(stdout).stderr(stderr);
command.env(
"HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET",
@ -83,6 +85,7 @@ impl<'a> Executor for RawExecutor<'a> {
let result = run_command_and_measure_common(
command.get_command()?,
command_failure_action.unwrap_or(self.options.command_failure_action),
&self.options.command_input_policy,
&self.options.command_output_policy,
&command.get_command_line(),
)?;
@ -142,6 +145,7 @@ impl<'a> Executor for ShellExecutor<'a> {
let mut result = run_command_and_measure_common(
command_builder,
command_failure_action.unwrap_or(self.options.command_failure_action),
&self.options.command_input_policy,
&self.options.command_output_policy,
&command.get_command_line(),
)?;

View File

@ -312,6 +312,14 @@ fn build_command() -> Command {
.hide(true)
.help("Enable debug mode which does not actually run commands, but returns fake times when the command is 'sleep <time>'.")
)
.arg(
Arg::new("stdin-data")
.long("stdin-data")
.takes_value(true)
.number_of_values(1)
.value_name("FILE")
.help("Path to file containing stdin data to provide to the command"),
)
}
#[test]

View File

@ -53,4 +53,6 @@ pub enum OptionsError<'a> {
ShellParseError(shell_words::ParseError),
#[error("Unknown output policy '{0}'. Use './{0}' to output to a file named '{0}'.")]
UnknownOutputPolicy(String),
#[error("File containing stdin data '{0}' does not exist")]
StdinDataFileDoesNotExist(String),
}

View File

@ -110,6 +110,36 @@ impl Default for RunBounds {
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CommandInputPolicy {
/// Redirect from the null device
Null,
/// Redirect input from a file
File(PathBuf),
}
impl Default for CommandInputPolicy {
fn default() -> Self {
CommandInputPolicy::Null
}
}
impl CommandInputPolicy {
pub fn get_stdin(&self) -> io::Result<Stdio> {
let stream: Stdio = match self {
CommandInputPolicy::Null => Stdio::null(),
CommandInputPolicy::File(path) => {
let file: File = File::open(&path)?;
Stdio::from(file)
}
};
Ok(stream)
}
}
/// How to handle the output of benchmarked commands
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandOutputPolicy {
@ -196,8 +226,12 @@ pub struct Options {
/// What to do with the output of the benchmarked command
pub command_output_policy: CommandOutputPolicy,
/// Which time unit to use when displaying resuls
/// Which time unit to use when displaying results
pub time_unit: Option<Unit>,
/// Where input to the benchmarked command comes from
pub command_input_policy: CommandInputPolicy,
}
impl Default for Options {
@ -214,6 +248,7 @@ impl Default for Options {
executor_kind: ExecutorKind::default(),
command_output_policy: CommandOutputPolicy::Null,
time_unit: None,
command_input_policy: CommandInputPolicy::Null,
}
}
}
@ -354,6 +389,16 @@ impl Options {
.map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?;
}
options.command_input_policy = if let Some(path_str) = matches.value_of("stdin-data") {
let path = PathBuf::from(path_str);
if !path.exists() {
return Err(OptionsError::StdinDataFileDoesNotExist(path_str.to_string()))
}
CommandInputPolicy::File(path)
} else {
CommandInputPolicy::Null
};
Ok(options)
}

1
tests/example_stdin_data Normal file
View File

@ -0,0 +1 @@
This data is passed to the command via stdin

View File

@ -215,6 +215,34 @@ fn runs_commands_using_user_defined_shell() {
);
}
#[test]
fn can_pass_file_data_to_command_via_stdin(){
hyperfine()
.arg("--runs=1")
.arg("--stdin-data=example_stdin_data")
.arg("--show-output")
.arg("cat")
.assert()
.success()
.stdout(
predicate::str::contains("This data is passed to the command via stdin")
);
}
#[test]
fn fails_if_invalid_stdin_data_file_provided(){
hyperfine()
.arg("--runs=1")
.arg("--stdin-data=example_stdin_data_invalid")
.arg("--show-output")
.arg("cat")
.assert()
.failure()
.stderr(
predicate::str::contains("File containing stdin data 'example_stdin_data_invalid' does not exist")
);
}
#[test]
fn returns_mean_time_in_correct_unit() {
hyperfine_debug()