mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-10-30 16:40:18 +00:00
Fixes to limit --command-name count appear exactly once or same count with parameters.
This commit is contained in:
1 parent
538dd06c16
commit
d4a0e9afb6
4 files changed
+137
-34
No files matched your search
@@ -12,6 +12,7 @@ pub enum ParameterScanError {
|
||||
TooLarge,
|
||||
ZeroStep,
|
||||
StepRequired,
|
||||
DifferentCommandNameCountWithParameters(usize, usize),
|
||||
}
|
||||
|
||||
impl From<num::ParseIntError> for ParameterScanError {
|
||||
@@ -40,6 +41,13 @@ impl fmt::Display for ParameterScanError {
|
||||
floating point numbers. The step size can be specified \
|
||||
with the '--parameter-step-size' parameter"
|
||||
),
|
||||
ParameterScanError::DifferentCommandNameCountWithParameters(real, expected) => {
|
||||
write!(
|
||||
f,
|
||||
"Current --command-name count is {}: expected {} as parameters",
|
||||
real, expected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +59,7 @@ pub enum OptionsError<'a> {
|
||||
RunsBelowTwo,
|
||||
EmptyRunsRange,
|
||||
TooManyCommandNames(usize),
|
||||
DifferentCommandNameCountWithParameters(usize, usize),
|
||||
NumericParsingError(&'a str, ParseIntError),
|
||||
}
|
||||
|
||||
@@ -62,6 +71,13 @@ impl<'a> fmt::Display for OptionsError<'a> {
|
||||
OptionsError::TooManyCommandNames(n) => {
|
||||
write!(f, "Too many --command-name options: expected {} at most", n)
|
||||
}
|
||||
OptionsError::DifferentCommandNameCountWithParameters(real, expected) => {
|
||||
write!(
|
||||
f,
|
||||
"Current --command-name count is {}: expected {} as parameters",
|
||||
real, expected
|
||||
)
|
||||
}
|
||||
OptionsError::NumericParsingError(cmd, ref err) => write!(
|
||||
f,
|
||||
"Could not read numeric argument to '--{cmd}': {err}",
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::hyperfine::error::ParameterScanError;
|
||||
use crate::hyperfine::types::{Command, NumericType, ParameterValue};
|
||||
use clap::Values;
|
||||
use rust_decimal::Decimal;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::{Add, AddAssign, Div, Sub};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -45,6 +46,22 @@ impl<T: Numeric> RangeStep<T> {
|
||||
step,
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), ParameterScanError> {
|
||||
if self.end < self.state {
|
||||
return Err(ParameterScanError::EmptyRange);
|
||||
}
|
||||
|
||||
if self.step == T::from(0) {
|
||||
return Err(ParameterScanError::ZeroStep);
|
||||
}
|
||||
|
||||
const MAX_PARAMETERS: usize = 100_000;
|
||||
match self.size_hint() {
|
||||
(_, Some(size)) if size <= MAX_PARAMETERS => Ok(()),
|
||||
_ => Err(ParameterScanError::TooLarge),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Iterator for RangeStep<T> {
|
||||
@@ -59,24 +76,18 @@ impl<T: Numeric> Iterator for RangeStep<T> {
|
||||
|
||||
Some(return_val)
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_params<T: Numeric>(start: T, end: T, step: T) -> Result<(), ParameterScanError> {
|
||||
if end < start {
|
||||
return Err(ParameterScanError::EmptyRange);
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.step == T::from(0) {
|
||||
return (usize::MAX, None);
|
||||
}
|
||||
|
||||
let steps = (self.end - self.state + T::from(1)) / self.step;
|
||||
steps
|
||||
.into()
|
||||
.try_into()
|
||||
.map_or((usize::MAX, None), |u| (u, Some(u)))
|
||||
}
|
||||
|
||||
if step == T::from(0) {
|
||||
return Err(ParameterScanError::ZeroStep);
|
||||
}
|
||||
|
||||
const MAX_PARAMETERS: i32 = 100_000;
|
||||
let steps = (end - start + T::from(1)) / step;
|
||||
if steps > T::from(MAX_PARAMETERS) {
|
||||
return Err(ParameterScanError::TooLarge);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_parameterized_commands<'a, T: Numeric>(
|
||||
@@ -87,27 +98,33 @@ fn build_parameterized_commands<'a, T: Numeric>(
|
||||
command_strings: Vec<&'a str>,
|
||||
param_name: &'a str,
|
||||
) -> Result<Vec<Command<'a>>, ParameterScanError> {
|
||||
validate_params(param_min, param_max, step)?;
|
||||
let param_range = RangeStep::new(param_min, param_max, step);
|
||||
let mut commands = vec![];
|
||||
param_range.validate()?;
|
||||
let param_count = param_range.size_hint().1.unwrap();
|
||||
let command_name_count = command_names.len();
|
||||
|
||||
// `--command-name` should appear exactly once or same count with parameters.
|
||||
if command_name_count > 1 && command_name_count != param_count {
|
||||
return Err(ParameterScanError::DifferentCommandNameCountWithParameters(
|
||||
command_name_count,
|
||||
param_count,
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let name_count = command_names.len();
|
||||
let mut commands = vec![];
|
||||
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;
|
||||
|
||||
let name = command_names
|
||||
.get(i)
|
||||
.or_else(|| command_names.get(0))
|
||||
.map(|s| *s);
|
||||
commands.push(Command::new_parametrized(
|
||||
name,
|
||||
cmd,
|
||||
vec![(param_name, ParameterValue::Numeric(value.into()))],
|
||||
));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
Ok(commands)
|
||||
@@ -182,6 +199,37 @@ fn test_decimal_range() {
|
||||
assert_eq!(param_range[10], Decimal::from(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_step_validate() {
|
||||
let range_step = RangeStep::new(0, 10, 3);
|
||||
assert!(range_step.validate().is_ok());
|
||||
|
||||
let range_step = RangeStep::new(
|
||||
Decimal::from(0),
|
||||
Decimal::from(1),
|
||||
Decimal::from_str("0.1").unwrap(),
|
||||
);
|
||||
assert!(range_step.validate().is_ok());
|
||||
|
||||
let range_step = RangeStep::new(11, 10, 1);
|
||||
assert_eq!(
|
||||
format!("{}", range_step.validate().unwrap_err()),
|
||||
"Empty parameter range"
|
||||
);
|
||||
|
||||
let range_step = RangeStep::new(0, 10, 0);
|
||||
assert_eq!(
|
||||
format!("{}", range_step.validate().unwrap_err()),
|
||||
"Zero is not a valid parameter step"
|
||||
);
|
||||
|
||||
let range_step = RangeStep::new(0, 100_001, 1);
|
||||
assert_eq!(
|
||||
format!("{}", range_step.validate().unwrap_err()),
|
||||
"Parameter range is too large"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_parameterized_commands_int() {
|
||||
let commands =
|
||||
@@ -248,3 +296,19 @@ fn test_get_specified_command_names() {
|
||||
.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()),
|
||||
"Current --command-name count is 2: expected 3 as parameters"
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
/// This module contains common internal types.
|
||||
use serde::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use crate::hyperfine::units::{Second, Unit};
|
||||
@@ -40,6 +42,20 @@ impl Into<NumericType> for Decimal {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumericType> for usize {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(numeric: NumericType) -> Result<Self, Self::Error> {
|
||||
match numeric {
|
||||
NumericType::Int(i) => usize::try_from(i).map_err(|_| ()),
|
||||
NumericType::Decimal(d) => match d.to_u64() {
|
||||
Some(u) => usize::try_from(u).map_err(|_| ()),
|
||||
None => Err(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParameterValue {
|
||||
Text(String),
|
||||
|
||||
+14
-7
@@ -255,17 +255,24 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// `--command-name` should appear exactly once or same count with parameters.
|
||||
let command_name_count = command_names.len();
|
||||
if command_name_count > 1 && command_name_count != param_space_size {
|
||||
let err = OptionsError::DifferentCommandNameCountWithParameters(
|
||||
command_name_count,
|
||||
param_space_size,
|
||||
);
|
||||
error(&err.to_string());
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
let name = command_names
|
||||
.get(i)
|
||||
.or_else(|| command_names.get(0))
|
||||
.map(|s| *s);
|
||||
i += 1;
|
||||
|
||||
let (command_index, params_indices) = index.split_first().unwrap();
|
||||
|
||||
Reference in new issue
Block a user