Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --input argument #563

Merged
merged 11 commits into from
Mar 15, 2023
12 changes: 9 additions & 3 deletions src/benchmark/executor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::process::{ExitStatus, Stdio};
use std::process::ExitStatus;

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;
Expand Down Expand Up @@ -36,11 +38,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()?;
sharkdp marked this conversation as resolved.
Show resolved Hide resolved
command.stdin(stdin).stdout(stdout).stderr(stderr);

command.env(
"HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET",
Expand Down Expand Up @@ -83,6 +87,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(),
)?;
Expand Down Expand Up @@ -136,6 +141,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(),
)?;
Expand Down
10 changes: 10 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,16 @@ fn build_command() -> Command<'static> {
.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("input")
.long("input")
.takes_value(true)
.number_of_values(1)
.value_name("FROM")
.help("Control where the input of the benchmark comes from. <FROM> can be: \
null: Read from /edv/null (the default).
<FILE>: Read the input from the given file"),
)
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
48 changes: 47 additions & 1 deletion src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ impl Default for RunBounds {
}
}

#[derive(Debug, Clone, PartialEq)]
pub enum CommandInputPolicy {
/// Read from the null device
Null,

/// Read 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 {
Expand Down Expand Up @@ -193,10 +223,13 @@ pub struct Options {
/// Determines how we run commands
pub executor_kind: ExecutorKind,

/// Where input to the benchmarked command comes from
pub command_input_policy: CommandInputPolicy,

/// 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>,
}

Expand All @@ -214,6 +247,7 @@ impl Default for Options {
executor_kind: ExecutorKind::default(),
command_output_policy: CommandOutputPolicy::Null,
time_unit: None,
command_input_policy: CommandInputPolicy::Null,
}
}
}
Expand Down Expand Up @@ -351,6 +385,18 @@ impl Options {
.map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?;
}

options.command_input_policy = if let Some(path_str) = matches.value_of("input") {
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)
}

Expand Down
1 change: 1 addition & 0 deletions tests/example_stdin_data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This data is passed to the command via stdin
sharkdp marked this conversation as resolved.
Show resolved Hide resolved
28 changes: 28 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("--input=example_stdin_data")
.arg("--show-output")
.arg("cat")
.assert()
.success()
.stdout(predicate::str::contains(
"This data is passed to the command via stdin",
));
}
sharkdp marked this conversation as resolved.
Show resolved Hide resolved

#[test]
fn fails_if_invalid_stdin_data_file_provided() {
hyperfine()
.arg("--runs=1")
.arg("--input=example_stdin_data_invalid")
sharkdp marked this conversation as resolved.
Show resolved Hide resolved
.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()
Expand Down