mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-02 09:01:25 +00:00
Move build_commands
This commit is contained in:
1 parent
33f8d03d6b
commit
cd38e9c63b
2 files changed
+209
-203
No files matched your search
+208
@@ -1,8 +1,15 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::{error::OptionsError, parameter_range::get_parameterized_commands};
|
||||||
|
|
||||||
|
use clap::ArgMatches;
|
||||||
|
|
||||||
|
use crate::tokenize::tokenize;
|
||||||
use crate::types::ParameterValue;
|
use crate::types::ParameterValue;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
/// A command that should be benchmarked.
|
/// A command that should be benchmarked.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Command<'a> {
|
pub struct Command<'a> {
|
||||||
@@ -82,6 +89,125 @@ 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> {
|
||||||
|
let mut counts = BTreeMap::<&'a str, usize>::new();
|
||||||
|
for s in i {
|
||||||
|
*counts.entry(s).or_default() += 1;
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, n)| if n > 1 { Some(k) } else { None })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_shell_command_nonoverlapping() {
|
fn test_get_shell_command_nonoverlapping() {
|
||||||
let cmd = Command::new_parametrized(
|
let cmd = Command::new_parametrized(
|
||||||
@@ -113,3 +239,85 @@ impl<'a> fmt::Display for Command<'a> {
|
|||||||
write!(f, "{}", self.get_shell_command())
|
write!(f, "{}", self.get_shell_command())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_commands_cross_product() {
|
||||||
|
use crate::app::get_arg_matches;
|
||||||
|
|
||||||
|
let matches = get_arg_matches(vec![
|
||||||
|
"hyperfine",
|
||||||
|
"-L",
|
||||||
|
"par1",
|
||||||
|
"a,b",
|
||||||
|
"-L",
|
||||||
|
"par2",
|
||||||
|
"z,y",
|
||||||
|
"echo {par1} {par2}",
|
||||||
|
"printf '%s\n' {par1} {par2}",
|
||||||
|
]);
|
||||||
|
let result = build_commands(&matches).unwrap();
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
let pv = |s: &str| ParameterValue::Text(s.to_string());
|
||||||
|
let cmd = |cmd: usize, par1: &str, par2: &str| {
|
||||||
|
let expression = ["echo {par1} {par2}", "printf '%s\n' {par1} {par2}"][cmd];
|
||||||
|
let params = vec![("par1", pv(par1)), ("par2", pv(par2))];
|
||||||
|
Command::new_parametrized(None, expression, params)
|
||||||
|
};
|
||||||
|
let expected = vec![
|
||||||
|
cmd(0, "a", "z"),
|
||||||
|
cmd(1, "a", "z"),
|
||||||
|
cmd(0, "b", "z"),
|
||||||
|
cmd(1, "b", "z"),
|
||||||
|
cmd(0, "a", "y"),
|
||||||
|
cmd(1, "a", "y"),
|
||||||
|
cmd(0, "b", "y"),
|
||||||
|
cmd(1, "b", "y"),
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_parameter_list_commands() {
|
||||||
|
use crate::app::get_arg_matches;
|
||||||
|
|
||||||
|
let matches = get_arg_matches(vec![
|
||||||
|
"hyperfine",
|
||||||
|
"echo {foo}",
|
||||||
|
"--parameter-list",
|
||||||
|
"foo",
|
||||||
|
"1,2",
|
||||||
|
"--command-name",
|
||||||
|
"name-{foo}",
|
||||||
|
]);
|
||||||
|
let commands = build_commands(&matches).unwrap();
|
||||||
|
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() {
|
||||||
|
use crate::app::get_arg_matches;
|
||||||
|
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).unwrap();
|
||||||
|
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");
|
||||||
|
}
|
||||||
+1
-203
@@ -1,7 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
@@ -27,13 +25,9 @@ pub mod warnings;
|
|||||||
use app::get_arg_matches;
|
use app::get_arg_matches;
|
||||||
use benchmark::{mean_shell_spawning_time, run_benchmark};
|
use benchmark::{mean_shell_spawning_time, run_benchmark};
|
||||||
use benchmark_result::BenchmarkResult;
|
use benchmark_result::BenchmarkResult;
|
||||||
use command::Command;
|
use command::{build_commands, Command};
|
||||||
use error::OptionsError;
|
|
||||||
use export::ExportManager;
|
use export::ExportManager;
|
||||||
use options::{Options, OutputStyleOption};
|
use options::{Options, OutputStyleOption};
|
||||||
use parameter_range::get_parameterized_commands;
|
|
||||||
use tokenize::tokenize;
|
|
||||||
use types::ParameterValue;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
@@ -132,199 +126,3 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the commands to benchmark
|
|
||||||
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> {
|
|
||||||
let mut counts = BTreeMap::<&'a str, usize>::new();
|
|
||||||
for s in i {
|
|
||||||
*counts.entry(s).or_default() += 1;
|
|
||||||
}
|
|
||||||
counts
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(k, n)| if n > 1 { Some(k) } else { None })
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_build_commands_cross_product() {
|
|
||||||
let matches = get_arg_matches(vec![
|
|
||||||
"hyperfine",
|
|
||||||
"-L",
|
|
||||||
"par1",
|
|
||||||
"a,b",
|
|
||||||
"-L",
|
|
||||||
"par2",
|
|
||||||
"z,y",
|
|
||||||
"echo {par1} {par2}",
|
|
||||||
"printf '%s\n' {par1} {par2}",
|
|
||||||
]);
|
|
||||||
let result = build_commands(&matches).unwrap();
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
let pv = |s: &str| ParameterValue::Text(s.to_string());
|
|
||||||
let cmd = |cmd: usize, par1: &str, par2: &str| {
|
|
||||||
let expression = ["echo {par1} {par2}", "printf '%s\n' {par1} {par2}"][cmd];
|
|
||||||
let params = vec![("par1", pv(par1)), ("par2", pv(par2))];
|
|
||||||
Command::new_parametrized(None, expression, params)
|
|
||||||
};
|
|
||||||
let expected = vec![
|
|
||||||
cmd(0, "a", "z"),
|
|
||||||
cmd(1, "a", "z"),
|
|
||||||
cmd(0, "b", "z"),
|
|
||||||
cmd(1, "b", "z"),
|
|
||||||
cmd(0, "a", "y"),
|
|
||||||
cmd(1, "a", "y"),
|
|
||||||
cmd(0, "b", "y"),
|
|
||||||
cmd(1, "b", "y"),
|
|
||||||
];
|
|
||||||
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).unwrap();
|
|
||||||
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).unwrap();
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user