Makes --comand-name take arguments from --parameter-list.

This commit is contained in:
Steven Gu 2020-12-15 13:43:53 +08:00 committed by David Peter
parent a0445321f5
commit 538dd06c16
4 changed files with 185 additions and 37 deletions

View File

@ -117,7 +117,7 @@ pub fn mean_shell_spawning_time(
// Just run the shell without any command
let res = time_shell_command(
shell,
&Command::new(""),
&Command::new(None, ""),
show_output,
CmdFailureAction::RaiseError,
None,
@ -229,20 +229,13 @@ pub fn run_benchmark(
shell_spawning_time: TimingResult,
options: &HyperfineOptions,
) -> io::Result<BenchmarkResult> {
let shell_cmd = cmd.get_shell_command();
let command_name = if let Some(names) = &options.names {
names.get(num).unwrap_or(&shell_cmd)
} else {
&shell_cmd
};
let command_name = command_name.to_string();
let command_name = cmd.get_name();
if options.output_style != OutputStyleOption::Disabled {
println!(
"{}{}: {}",
"Benchmark ".bold(),
(num + 1).to_string().bold(),
&command_name
command_name,
);
}
@ -259,7 +252,7 @@ pub fn run_benchmark(
} else {
&values[num]
};
Command::new_parametrized(preparation_command, cmd.get_parameters().clone())
Command::new_parametrized(None, preparation_command, cmd.get_parameters().clone())
});
// Warmup phase
@ -450,7 +443,7 @@ pub fn run_benchmark(
// Run cleanup command
let cleanup_cmd = options.cleanup_command.as_ref().map(|cleanup_command| {
Command::new_parametrized(cleanup_command, cmd.get_parameters().clone())
Command::new_parametrized(None, cleanup_command, cmd.get_parameters().clone())
});
run_cleanup_command(&options.shell, &cleanup_cmd, options.show_output)?;

View File

@ -83,6 +83,7 @@ fn build_parameterized_commands<'a, T: Numeric>(
param_min: T,
param_max: T,
step: T,
command_names: Vec<&'a str>,
command_strings: Vec<&'a str>,
param_name: &'a str,
) -> Result<Vec<Command<'a>>, ParameterScanError> {
@ -90,9 +91,20 @@ fn build_parameterized_commands<'a, T: Numeric>(
let param_range = RangeStep::new(param_min, param_max, step);
let mut commands = vec![];
let mut i = 0;
let name_count = command_names.len();
for value in param_range {
for cmd in &command_strings {
// Sets the command name by index (remainder) if exists.
let name = if name_count > 0 {
Some(command_names[i % name_count])
} else {
None
};
i += 1;
commands.push(Command::new_parametrized(
name,
cmd,
vec![(param_name, ParameterValue::Numeric(value.into()))],
));
@ -102,10 +114,12 @@ fn build_parameterized_commands<'a, T: Numeric>(
}
pub fn get_parameterized_commands<'a>(
command_names: Option<Values<'a>>,
command_strings: Values<'a>,
mut vals: clap::Values<'a>,
step: Option<&str>,
) -> Result<Vec<Command<'a>>, ParameterScanError> {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
let command_strings = command_strings.collect::<Vec<&str>>();
let param_name = vals.next().unwrap();
let param_min = vals.next().unwrap();
@ -121,6 +135,7 @@ pub fn get_parameterized_commands<'a>(
param_min,
param_max,
step,
command_names,
command_strings,
param_name,
);
@ -135,7 +150,14 @@ pub fn get_parameterized_commands<'a>(
}
let step = Decimal::from_str(step.unwrap())?;
build_parameterized_commands(param_min, param_max, step, command_strings, param_name)
build_parameterized_commands(
param_min,
param_max,
step,
command_names,
command_strings,
param_name,
)
}
#[test]
@ -163,8 +185,9 @@ fn test_decimal_range() {
#[test]
fn test_get_parameterized_commands_int() {
let commands =
build_parameterized_commands(1i32, 7i32, 3i32, vec!["echo {val}"], "val").unwrap();
build_parameterized_commands(1i32, 7i32, 3i32, vec![], vec!["echo {val}"], "val").unwrap();
assert_eq!(commands.len(), 3);
assert_eq!(commands[2].get_name(), "echo 7");
assert_eq!(commands[2].get_shell_command(), "echo 7");
}
@ -174,9 +197,54 @@ fn test_get_parameterized_commands_decimal() {
let param_max = Decimal::from_str("1").unwrap();
let step = Decimal::from_str("0.33").unwrap();
let commands =
build_parameterized_commands(param_min, param_max, step, vec!["echo {val}"], "val")
.unwrap();
let commands = build_parameterized_commands(
param_min,
param_max,
step,
vec![],
vec!["echo {val}"],
"val",
)
.unwrap();
assert_eq!(commands.len(), 4);
assert_eq!(commands[3].get_name(), "echo 0.99");
assert_eq!(commands[3].get_shell_command(), "echo 0.99");
}
#[test]
fn test_get_parameterized_command_names() {
let commands = build_parameterized_commands(
1i32,
3i32,
1i32,
vec!["name-{val}"],
vec!["echo {val}"],
"val",
)
.unwrap();
assert_eq!(commands.len(), 3);
let command_names = commands
.iter()
.map(|c| c.get_name())
.collect::<Vec<String>>();
assert_eq!(command_names, vec!["name-1", "name-2", "name-3"]);
}
#[test]
fn test_get_specified_command_names() {
let commands = build_parameterized_commands(
1i32,
3i32,
1i32,
vec!["name-a", "name-b", "name-c"],
vec!["echo {val}"],
"val",
)
.unwrap();
assert_eq!(commands.len(), 3);
let command_names = commands
.iter()
.map(|c| c.get_name())
.collect::<Vec<String>>();
assert_eq!(command_names, vec!["name-a", "name-b", "name-c"]);
}

View File

@ -58,6 +58,9 @@ impl<'a> ToString for ParameterValue {
/// A command that should be benchmarked.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Command<'a> {
/// The command name (without parameter substitution)
name: Option<&'a str>,
/// The command that should be executed (without parameter substitution)
expression: &'a str,
@ -66,24 +69,42 @@ pub struct Command<'a> {
}
impl<'a> Command<'a> {
pub fn new(expression: &'a str) -> Command<'a> {
pub fn new(name: Option<&'a str>, expression: &'a str) -> Command<'a> {
Command {
name,
expression,
parameters: Vec::new(),
}
}
pub fn new_parametrized(
name: Option<&'a str>,
expression: &'a str,
parameters: Vec<(&'a str, ParameterValue)>,
) -> Command<'a> {
Command {
name,
expression,
parameters,
}
}
pub fn get_name(&self) -> String {
self.name.map_or_else(
|| self.get_shell_command(),
|name| self.replace_parameters_in(name),
)
}
pub fn get_shell_command(&self) -> String {
self.replace_parameters_in(self.expression)
}
pub fn get_parameters(&self) -> &Vec<(&'a str, ParameterValue)> {
&self.parameters
}
fn replace_parameters_in(&self, original: &str) -> String {
let mut result = String::new();
let mut replacements = BTreeMap::<String, String>::new();
for (param_name, param_value) in &self.parameters {
@ -92,7 +113,7 @@ impl<'a> Command<'a> {
param_value.to_string(),
);
}
let mut remaining = self.expression;
let mut remaining = original;
// Manually replace consecutive occurrences to avoid double-replacing: e.g.,
//
// hyperfine -L foo 'a,{bar}' -L bar 'baz,quux' 'echo {foo} {bar}'
@ -111,15 +132,12 @@ impl<'a> Command<'a> {
}
result
}
pub fn get_parameters(&self) -> &Vec<(&'a str, ParameterValue)> {
&self.parameters
}
}
#[test]
fn test_get_shell_command_nonoverlapping() {
let cmd = Command::new_parametrized(
None,
"echo {foo} {bar}",
vec![
("foo", ParameterValue::Text("{bar} baz".into())),
@ -129,6 +147,19 @@ fn test_get_shell_command_nonoverlapping() {
assert_eq!(cmd.get_shell_command(), "echo {bar} baz quux");
}
#[test]
fn test_get_parameterized_command_name() {
let cmd = Command::new_parametrized(
Some("name-{bar}-{foo}"),
"echo {foo} {bar}",
vec![
("foo", ParameterValue::Text("baz".into())),
("bar", ParameterValue::Text("quux".into())),
],
);
assert_eq!(cmd.get_name(), "name-quux-baz");
}
impl<'a> fmt::Display for Command<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get_shell_command())

View File

@ -74,7 +74,7 @@ fn main() {
let commands = build_commands(&matches);
let export_manager = match build_export_manager(&matches) {
Ok(export_manager) => export_manager,
Err(ref e) => error(&e.to_string())
Err(ref e) => error(&e.to_string()),
};
let res = match options {
@ -139,16 +139,6 @@ fn build_hyperfine_options<'a>(
(None, None) => {}
};
options.names = matches
.values_of("command-name")
.map(|values| values.map(String::from).collect::<Vec<String>>());
if let Some(ref names) = options.names {
let command_strings = matches.values_of("command").unwrap();
if names.len() > command_strings.len() {
return Err(OptionsError::TooManyCommandNames(command_strings.len()));
}
}
options.preparation_command = matches
.values_of("prepare")
.map(|values| values.map(String::from).collect::<Vec<String>>());
@ -224,15 +214,18 @@ fn build_export_manager(matches: &ArgMatches<'_>) -> io::Result<ExportManager> {
/// Build the commands to benchmark
fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
let command_names = matches.values_of("command-name");
let command_strings = matches.values_of("command").unwrap();
if let Some(args) = matches.values_of("parameter-scan") {
let step_size = matches.value_of("parameter-step-size");
match get_parameterized_commands(command_strings, args, step_size) {
match get_parameterized_commands(command_names, command_strings, args, step_size) {
Ok(commands) => commands,
Err(e) => error(&e.to_string()),
}
} else if let Some(args) = matches.values_of("parameter-list") {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
let args: Vec<_> = args.collect();
let param_names_and_values: Vec<(&str, Vec<String>)> = args
.chunks_exact(2)
@ -262,9 +255,19 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
return Vec::new();
}
let mut i = 0;
let name_count = command_names.len();
let mut commands = Vec::with_capacity(param_space_size);
let mut index = vec![0usize; dimensions.len()];
'outer: loop {
// Sets the command name by index (remainder) if exists.
let name = if name_count > 0 {
Some(command_names[i % name_count])
} else {
None
};
i += 1;
let (command_index, params_indices) = index.split_first().unwrap();
let parameters = param_names_and_values
.iter()
@ -272,6 +275,7 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
.map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone())))
.collect();
commands.push(Command::new_parametrized(
name,
command_list[*command_index],
parameters,
));
@ -290,7 +294,18 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
commands
} else {
command_strings.map(Command::new).collect()
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
if command_names.len() > command_strings.len() {
let err = OptionsError::TooManyCommandNames(command_strings.len());
error(&err.to_string());
}
let command_list = command_strings.collect::<Vec<&str>>();
let mut commands = Vec::with_capacity(command_list.len());
for (i, s) in command_list.iter().enumerate() {
commands.push(Command::new(command_names.get(i).copied(), &s));
}
commands
}
}
@ -328,7 +343,7 @@ fn test_build_commands_cross_product() {
let cmd = |cmd: usize, foo: &str, bar: &str| {
let expression = ["echo {foo} {bar}", "printf '%s\n' {foo} {bar}"][cmd];
let params = vec![("foo", pv(foo)), ("bar", pv(bar))];
Command::new_parametrized(expression, params)
Command::new_parametrized(None, expression, params)
};
let expected = vec![
cmd(0, "a", "z"),
@ -342,3 +357,44 @@ fn test_build_commands_cross_product() {
];
assert_eq!(result, expected);
}
#[test]
fn test_build_parameter_list_commands() {
let matches = get_arg_matches(vec![
"hyperfine",
"echo {foo}",
"--parameter-list",
"foo",
"1,2",
"--command-name",
"name-{foo}",
]);
let commands = build_commands(&matches);
assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2");
assert_eq!(commands[0].get_shell_command(), "echo 1");
assert_eq!(commands[1].get_shell_command(), "echo 2");
}
#[test]
fn test_build_parameter_range_commands() {
let matches = get_arg_matches(vec![
"hyperfine",
"echo {val}",
"--parameter-scan",
"val",
"1",
"2",
"--parameter-step-size",
"1",
"--command-name",
"name-{val}",
]);
let commands = build_commands(&matches);
assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2");
assert_eq!(commands[0].get_shell_command(), "echo 1");
assert_eq!(commands[1].get_shell_command(), "echo 2");
}