summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Peter <mail@david-peter.de>2022-02-06 21:15:36 +0100
committerDavid Peter <sharkdp@users.noreply.github.com>2022-02-06 22:49:04 +0100
commit6db82cdb2a28caf763d3ba09b7ccefc49300ba44 (patch)
tree58947cc1725f907b68290cfab6b3fbc8ce97705f
parenta6baf49b726fa0883048c79b3b2cecb7e1a87110 (diff)
Split range into range_step and commands
-rw-r--r--src/command.rs180
-rw-r--r--src/parameter/mod.rs2
-rw-r--r--src/parameter/range.rs318
-rw-r--r--src/parameter/range_step.rs150
4 files changed, 328 insertions, 322 deletions
diff --git a/src/command.rs b/src/command.rs
index 8b079b5..b64e19c 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -1,8 +1,11 @@
use std::collections::BTreeMap;
use std::fmt;
+use std::str::FromStr;
-use crate::error::OptionsError;
-use crate::parameter::range::get_parameterized_commands;
+use crate::{
+ error::{OptionsError, ParameterScanError},
+ parameter::range_step::{Numeric, RangeStep},
+};
use clap::ArgMatches;
@@ -10,6 +13,8 @@ use crate::parameter::tokenize::tokenize;
use crate::parameter::ParameterValue;
use anyhow::{bail, Result};
+use clap::Values;
+use rust_decimal::Decimal;
/// A command that should be benchmarked.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -103,10 +108,96 @@ fn find_duplicates<'a, I: IntoIterator<Item = &'a str>>(i: I) -> Vec<&'a str> {
.collect()
}
+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> {
+ let param_range = RangeStep::new(param_min, param_max, step)?;
+ let param_count = param_range.size_hint().1.unwrap();
+ let command_name_count = command_names.len();
+
+ // `--command-name` should appear exactly once or exactly B times,
+ // where B is the total number of benchmarks.
+ if command_name_count > 1 && command_name_count != param_count {
+ return Err(ParameterScanError::UnexpectedCommandNameCount(
+ command_name_count,
+ param_count,
+ ));
+ }
+
+ let mut i = 0;
+ let mut commands = vec![];
+ for value in param_range {
+ for cmd in &command_strings {
+ let name = command_names
+ .get(i)
+ .or_else(|| command_names.get(0))
+ .copied();
+ commands.push(Command::new_parametrized(
+ name,
+ cmd,
+ vec![(param_name, ParameterValue::Numeric(value.into()))],
+ ));
+ i += 1;
+ }
+ }
+ Ok(commands)
+}
+
+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();
+ let param_max = vals.next().unwrap();
+
+ // attempt to parse as integers
+ if let (Ok(param_min), Ok(param_max), Ok(step)) = (
+ param_min.parse::<i32>(),
+ param_max.parse::<i32>(),
+ step.unwrap_or("1").parse::<i32>(),
+ ) {
+ return build_parameterized_commands(
+ param_min,
+ param_max,
+ step,
+ command_names,
+ command_strings,
+ param_name,
+ );
+ }
+
+ // try parsing them as decimals
+ let param_min = Decimal::from_str(param_min)?;
+ let param_max = Decimal::from_str(param_max)?;
+
+ if step.is_none() {
+ return Err(ParameterScanError::StepRequired);
+ }
+
+ let step = Decimal::from_str(step.unwrap())?;
+ build_parameterized_commands(
+ param_min,
+ param_max,
+ step,
+ command_names,
+ command_strings,
+ param_name,
+ )
+}
+
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();
@@ -335,3 +426,86 @@ fn test_build_parameter_range_commands() {
assert_eq!(commands[0].get_shell_command(), "echo 1");
assert_eq!(commands[1].get_shell_command(), "echo 2");
}
+
+#[test]
+fn test_get_parameterized_commands_int() {
+ let commands =
+ 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");
+}
+
+#[test]
+fn test_get_parameterized_commands_decimal() {
+ let param_min = Decimal::from_str("0").unwrap();
+ 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![],
+ 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"]);
+}
+
+#[test]
+fn test_different_command_name_count_with_parameters() {
+ let result = build_parameterized_commands(
+ 1i32,
+ 3i32,
+ 1i32,
+ vec!["name-1", "name-2"],
+ vec!["echo {val}"],
+ "val",
+ );
+ assert_eq!(
+ format!("{}", result.unwrap_err()),
+ "'--command-name' has been specified 2 times. It has to appear exactly once, or exactly 3 times (number of benchmarks)"
+ );
+}
diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs
index f6fdb8f..7210c90 100644
--- a/src/parameter/mod.rs
+++ b/src/parameter/mod.rs
@@ -1,6 +1,6 @@
use crate::util::number::Number;
-pub mod range;
+pub mod range_step;
pub mod tokenize;
#[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/src/parameter/range.rs b/src/parameter/range.rs
deleted file mode 100644
index 480ee0c..0000000
--- a/src/parameter/range.rs
+++ /dev/null
@@ -1,318 +0,0 @@
-use std::convert::TryInto;
-use std::ops::{Add, AddAssign, Div, Sub};
-use std::str::FromStr;
-
-use clap::Values;
-use rust_decimal::Decimal;
-
-use super::ParameterValue;
-use crate::command::Command;
-use crate::error::ParameterScanError;
-use crate::util::number::Number;
-
-trait Numeric:
- Add<Output = Self>
- + Sub<Output = Self>
- + Div<Output = Self>
- + AddAssign
- + PartialOrd
- + Copy
- + Clone
- + From<i32>
- + Into<Number>
-{
-}
-impl<
- T: Add<Output = Self>
- + Sub<Output = Self>
- + Div<Output = Self>
- + AddAssign
- + PartialOrd
- + Copy
- + Clone
- + From<i32>
- + Into<Number>,
- > Numeric for T
-{
-}
-
-#[derive(Debug)]
-struct RangeStep<T> {
- state: T,
- end: T,
- step: T,
-}
-
-impl<T: Numeric> RangeStep<T> {
- fn new(start: T, end: T, step: T) -> Result<Self, ParameterScanError> {
- if end < start {
- return Err(ParameterScanError::EmptyRange);
- }
-
- if step == T::from(0) {
- return Err(ParameterScanError::ZeroStep);
- }
-
- const MAX_PARAMETERS: usize = 100_000;
- match range_step_size_hint(start, end, step) {
- (_, Some(size)) if size <= MAX_PARAMETERS => Ok(Self {
- state: start,
- end,
- step,
- }),
- _ => Err(ParameterScanError::TooLarge),
- }
- }
-}
-
-impl<T: Numeric> Iterator for RangeStep<T> {
- type Item = T;
-
- fn next(&mut self) -> Option<Self::Item> {
- if self.state > self.end {
- return None;
- }
- let return_val = self.state;
- self.state += self.step;
-
- Some(return_val)
- }
-
- fn size_hint(&self) -> (usize, Option<usize>) {
- range_step_size_hint(self.state, self.end, self.step)
- }
-}
-
-fn range_step_size_hint<T: Numeric>(start: T, end: T, step: T) -> (usize, Option<usize>) {
- if step == T::from(0) {
- return (usize::MAX, None);
- }
-
- let steps = (end - start + T::from(1)) / step;
- steps
- .into()
- .try_into()
- .map_or((usize::MAX, None), |u| (u, Some(u)))
-}
-
-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> {
- let param_range = RangeStep::new(param_min, param_max, step)?;
- let param_count = param_range.size_hint().1.unwrap();
- let command_name_count = command_names.len();
-
- // `--command-name` should appear exactly once or exactly B times,
- // where B is the total number of benchmarks.
- if command_name_count > 1 && command_name_count != param_count {
- return Err(ParameterScanError::UnexpectedCommandNameCount(
- command_name_count,
- param_count,
- ));
- }
-
- let mut i = 0;
- let mut commands = vec![];
- for value in param_range {
- for cmd in &command_strings {
- let name = command_names
- .get(i)
- .or_else(|| command_names.get(0))
- .copied();
- commands.push(Command::new_parametrized(
- name,
- cmd,
- vec![(param_name, ParameterValue::Numeric(value.into()))],
- ));
- i += 1;
- }
- }
- Ok(commands)
-}
-
-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();
- let param_max = vals.next().unwrap();
-
- // attempt to parse as integers
- if let (Ok(param_min), Ok(param_max), Ok(step)) = (
- param_min.parse::<i32>(),
- param_max.parse::<i32>(),
- step.unwrap_or("1").parse::<i32>(),
- ) {
- return build_parameterized_commands(
- param_min,
- param_max,
- step,
- command_names,
- command_strings,
- param_name,
- );
- }
-
- // try parsing them as decimals
- let param_min = Decimal::from_str(param_min)?;
- let param_max = Decimal::from_str(param_max)?;
-
- if step.is_none() {
- return Err(ParameterScanError::StepRequired);
- }
-
- let step = Decimal::from_str(step.unwrap())?;
- build_parameterized_commands(
- param_min,
- param_max,
- step,
- command_names,
- command_strings,
- param_name,
- )
-}
-
-#[test]
-fn test_integer_range() {
- let param_range: Vec<i32> = RangeStep::new(0, 10, 3).unwrap().collect();
-
- assert_eq!(param_range.len(), 4);
- assert_eq!(param_range[0], 0);
- assert_eq!(param_range[3], 9);
-}
-
-#[test]
-fn test_decimal_range() {
- let param_min = Decimal::from(0);
- let param_max = Decimal::from(1);
- let step = Decimal::from_str("0.1").unwrap();
-
- let param_range: Vec<Decimal> = RangeStep::new(param_min, param_max, step)
- .unwrap()
- .collect();
-
- assert_eq!(param_range.len(), 11);
- assert_eq!(param_range[0], Decimal::from(0));
- assert_eq!(param_range[10], Decimal::from(1));
-}
-
-#[test]
-fn test_range_step_validate() {
- let result = RangeStep::new(0, 10, 3);
- assert!(result.is_ok());
-
- let result = RangeStep::new(
- Decimal::from(0),
- Decimal::from(1),
- Decimal::from_str("0.1").unwrap(),
- );
- assert!(result.is_ok());
-
- let result = RangeStep::new(11, 10, 1);
- assert_eq!(format!("{}", result.unwrap_err()), "Empty parameter range");
-
- let result = RangeStep::new(0, 10, 0);
- assert_eq!(
- format!("{}", result.unwrap_err()),
- "Zero is not a valid parameter step"
- );
-
- let result = RangeStep::new(0, 100_001, 1);
- assert_eq!(
- format!("{}", result.unwrap_err()),
- "Parameter range is too large"
- );
-}
-
-#[test]
-fn test_get_parameterized_commands_int() {
- let commands =
- 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");
-}
-
-#[test]
-fn test_get_parameterized_commands_decimal() {
- let param_min = Decimal::from_str("0").unwrap();
- 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![],
- 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"]);
-}
-
-#[test]
-fn test_different_command_name_count_with_parameters() {
- let result = build_parameterized_commands(
- 1i32,
- 3i32,
- 1i32,
- vec!["name-1", "name-2"],
- vec!["echo {val}"],
- "val",
- );
- assert_eq!(
- format!("{}", result.unwrap_err()),
- "'--command-name' has been specified 2 times. It has to appear exactly once, or exactly 3 times (number of benchmarks)"
- );
-}
diff --git a/src/parameter/range_step.rs b/src/parameter/range_step.rs
new file mode 100644
index 0000000..bffbeb1
--- /dev/null
+++ b/src/parameter/range_step.rs
@@ -0,0 +1,150 @@
+use std::convert::TryInto;
+use std::ops::{Add, AddAssign, Div, Sub};
+
+use crate::error::ParameterScanError;
+use crate::util::number::Number;
+
+pub trait Numeric:
+ Add<Output = Self>
+ + Sub<Output = Self>
+ + Div<Output = Self>
+ + AddAssign
+ + PartialOrd
+ + Copy
+ + Clone
+ + From<i32>
+ + Into<Number>
+{
+}
+impl<
+ T: Add<Output = Self>
+ + Sub<Output = Self>
+ + Div<Output = Self>
+ + AddAssign
+ + PartialOrd
+ + Copy
+ + Clone
+ + From<i32>
+ + Into<Number>,
+ > Numeric for T
+{
+}
+
+#[derive(Debug)]
+pub struct RangeStep<T> {
+ state: T,
+ end: T,
+ step: T,
+}
+
+impl<T: Numeric> RangeStep<T> {
+ pub fn new(start: T, end: T, step: T) -> Result<Self, ParameterScanError> {
+ if end < start {
+ return Err(ParameterScanError::EmptyRange);
+ }
+
+ if step == T::from(0) {
+ return Err(ParameterScanError::ZeroStep);
+ }
+
+ const MAX_PARAMETERS: usize = 100_000;
+ match range_step_size_hint(start, end, step) {
+ (_, Some(size)) if size <= MAX_PARAMETERS => Ok(Self {
+ state: start,
+ end,
+ step,
+ }),
+ _ => Err(ParameterScanError::TooLarge),
+ }
+ }
+}
+
+impl<T: Numeric> Iterator for RangeStep<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.state > self.end {
+ return None;
+ }
+ let return_val = self.state;
+ self.state += self.step;
+
+ Some(return_val)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ range_step_size_hint(self.state, self.end, self.step)
+ }
+}
+
+fn range_step_size_hint<T: Numeric>(start: T, end: T, step: T) -> (usize, Option<usize>) {
+ if step == T::from(0) {
+ return (usize::MAX, None);
+ }
+
+ let steps = (end - start + T::from(1)) / step;
+ steps
+ .into()
+ .try_into()
+ .map_or((usize::MAX, None), |u| (u, Some(u)))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use rust_decimal::Decimal;
+ use std::str::FromStr;
+
+ #[test]
+ fn test_integer_range() {
+ let param_range: Vec<i32> = RangeStep::new(0, 10, 3).unwrap().collect();
+
+ assert_eq!(param_range.len(), 4);
+ assert_eq!(param_range[0], 0);
+ assert_eq!(param_range[3], 9);
+ }
+
+ #[test]
+ fn test_decimal_range() {
+ let param_min = Decimal::from(0);
+ let param_max = Decimal::from(1);
+ let step = Decimal::from_str("0.1").unwrap();
+
+ let param_range: Vec<Decimal> = RangeStep::new(param_min, param_max, step)
+ .unwrap()
+ .collect();
+
+ assert_eq!(param_range.len(), 11);
+ assert_eq!(param_range[0], Decimal::from(0));
+ assert_eq!(param_range[10], Decimal::from(1));
+ }
+
+ #[test]
+ fn test_range_step_validate() {
+ let result = RangeStep::new(0, 10, 3);
+ assert!(result.is_ok());
+
+ let result = RangeStep::new(
+ Decimal::from(0),
+ Decimal::from(1),
+ Decimal::from_str("0.1").unwrap(),
+ );
+ assert!(result.is_ok());
+
+ let result = RangeStep::new(11, 10, 1);
+ assert_eq!(format!("{}", result.unwrap_err()), "Empty parameter range");
+
+ let result = RangeStep::new(0, 10, 0);
+ assert_eq!(
+ format!("{}", result.unwrap_err()),
+ "Zero is not a valid parameter step"
+ );
+
+ let result = RangeStep::new(0, 100_001, 1);
+ assert_eq!(
+ format!("{}", result.unwrap_err()),
+ "Parameter range is too large"
+ );
+ }
+}