Introduce Commands struct

This commit is contained in:
David Peter authored and David Peter committed 2022-02-06 22:49:04 +01:00
1 parent c5a2266199
commit a6baf49b72
4 files changed
+139 -125

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ use std::ffi::OsString;
use clap::{crate_version, App, AppSettings, Arg, ArgMatches};
pub fn get_arg_matches<'a, I, T>(args: I) -> ArgMatches
pub fn get_cli_arguments<'a, I, T>(args: I) -> ArgMatches
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone + 'a,
+3 -2
View File
@@ -1,5 +1,6 @@
use crate::command::Commands;
use crate::export::ExportManager;
use crate::options::{Options, OutputStyleOption};
use crate::{command::Command, export::ExportManager};
use super::{mean_shell_spawning_time, relative_speed, result::BenchmarkResult, run_benchmark};
@@ -45,7 +46,7 @@ pub fn write_benchmark_comparison(results: &[BenchmarkResult]) {
}
pub fn run_benchmarks_and_print_comparison(
commands: &[Command<'_>],
commands: &Commands,
options: &Options,
export_manager: &ExportManager,
) -> Result<()> {
+129 -116
View File
@@ -90,115 +90,9 @@ impl<'a> Command<'a> {
}
}
/// Build the commands to benchmark
pub fn build_commands(matches: &ArgMatches) -> Result<Vec<Command>> {
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");
Ok(get_parameterized_commands(
command_names,
command_strings,
args,
step_size,
)?)
} 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)
.map(|pair| {
let name = pair[0];
let list_str = pair[1];
(name, tokenize(list_str))
})
.collect();
{
let dupes = find_dupes(param_names_and_values.iter().map(|(name, _)| *name));
if !dupes.is_empty() {
bail!("Duplicate parameter names: {}", &dupes.join(", "));
}
}
let command_list = command_strings.collect::<Vec<&str>>();
let dimensions: Vec<usize> = std::iter::once(command_list.len())
.chain(
param_names_and_values
.iter()
.map(|(_, values)| values.len()),
)
.collect();
let param_space_size = dimensions.iter().product();
if param_space_size == 0 {
return Ok(Vec::new());
}
// `--command-name` should appear exactly once or exactly B times,
// where B is the total number of benchmarks.
let command_name_count = command_names.len();
if command_name_count > 1 && command_name_count != param_space_size {
return Err(OptionsError::UnexpectedCommandNameCount(
command_name_count,
param_space_size,
)
.into());
}
let mut i = 0;
let mut commands = Vec::with_capacity(param_space_size);
let mut index = vec![0usize; dimensions.len()];
'outer: loop {
let name = command_names
.get(i)
.or_else(|| command_names.get(0))
.copied();
i += 1;
let (command_index, params_indices) = index.split_first().unwrap();
let parameters = param_names_and_values
.iter()
.zip(params_indices)
.map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone())))
.collect();
commands.push(Command::new_parametrized(
name,
command_list[*command_index],
parameters,
));
// Increment index, exiting loop on overflow.
for (i, n) in index.iter_mut().zip(dimensions.iter()) {
*i += 1;
if *i < *n {
continue 'outer;
} else {
*i = 0;
}
}
break 'outer;
}
Ok(commands)
} else {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
if command_names.len() > command_strings.len() {
return Err(OptionsError::TooManyCommandNames(command_strings.len()).into());
}
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));
}
Ok(commands)
}
}
/// Finds all the strings that appear multiple times in the input iterator, returning them in
/// sorted order. If no string appears more than once, the result is an empty vector.
fn find_dupes<'a, I: IntoIterator<Item = &'a str>>(i: I) -> Vec<&'a str> {
fn find_duplicates<'a, I: IntoIterator<Item = &'a str>>(i: I) -> Vec<&'a str> {
let mut counts = BTreeMap::<&'a str, usize>::new();
for s in i {
*counts.entry(s).or_default() += 1;
@@ -209,6 +103,125 @@ fn find_dupes<'a, I: IntoIterator<Item = &'a str>>(i: I) -> Vec<&'a str> {
.collect()
}
pub struct Commands<'a>(Vec<Command<'a>>);
impl<'a> Commands<'a> {
/// Build the commands to benchmark
pub fn from_cli_arguments(matches: &'a ArgMatches) -> Result<Commands> {
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");
Ok(Self(get_parameterized_commands(
command_names,
command_strings,
args,
step_size,
)?))
} 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)
.map(|pair| {
let name = pair[0];
let list_str = pair[1];
(name, tokenize(list_str))
})
.collect();
{
let duplicates =
find_duplicates(param_names_and_values.iter().map(|(name, _)| *name));
if !duplicates.is_empty() {
bail!("Duplicate parameter names: {}", &duplicates.join(", "));
}
}
let command_list = command_strings.collect::<Vec<&str>>();
let dimensions: Vec<usize> = std::iter::once(command_list.len())
.chain(
param_names_and_values
.iter()
.map(|(_, values)| values.len()),
)
.collect();
let param_space_size = dimensions.iter().product();
if param_space_size == 0 {
return Ok(Self(Vec::new()));
}
// `--command-name` should appear exactly once or exactly B times,
// where B is the total number of benchmarks.
let command_name_count = command_names.len();
if command_name_count > 1 && command_name_count != param_space_size {
return Err(OptionsError::UnexpectedCommandNameCount(
command_name_count,
param_space_size,
)
.into());
}
let mut i = 0;
let mut commands = Vec::with_capacity(param_space_size);
let mut index = vec![0usize; dimensions.len()];
'outer: loop {
let name = command_names
.get(i)
.or_else(|| command_names.get(0))
.copied();
i += 1;
let (command_index, params_indices) = index.split_first().unwrap();
let parameters = param_names_and_values
.iter()
.zip(params_indices)
.map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone())))
.collect();
commands.push(Command::new_parametrized(
name,
command_list[*command_index],
parameters,
));
// Increment index, exiting loop on overflow.
for (i, n) in index.iter_mut().zip(dimensions.iter()) {
*i += 1;
if *i < *n {
continue 'outer;
} else {
*i = 0;
}
}
break 'outer;
}
Ok(Self(commands))
} else {
let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>());
if command_names.len() > command_strings.len() {
return Err(OptionsError::TooManyCommandNames(command_strings.len()).into());
}
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));
}
Ok(Self(commands))
}
}
pub fn iter(&self) -> impl Iterator<Item = &Command<'a>> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
#[test]
fn test_get_shell_command_nonoverlapping() {
let cmd = Command::new_parametrized(
@@ -243,9 +256,9 @@ impl<'a> fmt::Display for Command<'a> {
#[test]
fn test_build_commands_cross_product() {
use crate::app::get_arg_matches;
use crate::app::get_cli_arguments;
let matches = get_arg_matches(vec![
let matches = get_cli_arguments(vec![
"hyperfine",
"-L",
"par1",
@@ -256,7 +269,7 @@ fn test_build_commands_cross_product() {
"echo {par1} {par2}",
"printf '%s\n' {par1} {par2}",
]);
let result = build_commands(&matches).unwrap();
let result = Commands::from_cli_arguments(&matches).unwrap().0;
// 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.
@@ -281,9 +294,9 @@ fn test_build_commands_cross_product() {
#[test]
fn test_build_parameter_list_commands() {
use crate::app::get_arg_matches;
use crate::app::get_cli_arguments;
let matches = get_arg_matches(vec![
let matches = get_cli_arguments(vec![
"hyperfine",
"echo {foo}",
"--parameter-list",
@@ -292,7 +305,7 @@ fn test_build_parameter_list_commands() {
"--command-name",
"name-{foo}",
]);
let commands = build_commands(&matches).unwrap();
let commands = Commands::from_cli_arguments(&matches).unwrap().0;
assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2");
@@ -302,8 +315,8 @@ fn test_build_parameter_list_commands() {
#[test]
fn test_build_parameter_range_commands() {
use crate::app::get_arg_matches;
let matches = get_arg_matches(vec![
use crate::app::get_cli_arguments;
let matches = get_cli_arguments(vec![
"hyperfine",
"echo {val}",
"--parameter-scan",
@@ -315,7 +328,7 @@ fn test_build_parameter_range_commands() {
"--command-name",
"name-{val}",
]);
let commands = build_commands(&matches).unwrap();
let commands = Commands::from_cli_arguments(&matches).unwrap().0;
assert_eq!(commands.len(), 2);
assert_eq!(commands[0].get_name(), "name-1");
assert_eq!(commands[1].get_name(), "name-2");
+6 -6
View File
@@ -1,8 +1,8 @@
use std::env;
use app::get_arg_matches;
use app::get_cli_arguments;
use benchmark::schedule::run_benchmarks_and_print_comparison;
use command::build_commands;
use command::Commands;
use export::ExportManager;
use options::Options;
@@ -27,10 +27,10 @@ fn run() -> Result<()> {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).unwrap();
let matches = get_arg_matches(env::args_os());
let options = Options::from_cli_arguments(&matches)?;
let commands = build_commands(&matches)?;
let export_manager = ExportManager::from_cli_arguments(&matches)?;
let cli_arguments = get_cli_arguments(env::args_os());
let options = Options::from_cli_arguments(&cli_arguments)?;
let commands = Commands::from_cli_arguments(&cli_arguments)?;
let export_manager = ExportManager::from_cli_arguments(&cli_arguments)?;
run_benchmarks_and_print_comparison(&commands, &options, &export_manager)
}