mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-03 01:21:25 +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,
|
TooLarge,
|
||||||
ZeroStep,
|
ZeroStep,
|
||||||
StepRequired,
|
StepRequired,
|
||||||
|
DifferentCommandNameCountWithParameters(usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<num::ParseIntError> for ParameterScanError {
|
impl From<num::ParseIntError> for ParameterScanError {
|
||||||
@@ -40,6 +41,13 @@ impl fmt::Display for ParameterScanError {
|
|||||||
floating point numbers. The step size can be specified \
|
floating point numbers. The step size can be specified \
|
||||||
with the '--parameter-step-size' parameter"
|
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,
|
RunsBelowTwo,
|
||||||
EmptyRunsRange,
|
EmptyRunsRange,
|
||||||
TooManyCommandNames(usize),
|
TooManyCommandNames(usize),
|
||||||
|
DifferentCommandNameCountWithParameters(usize, usize),
|
||||||
NumericParsingError(&'a str, ParseIntError),
|
NumericParsingError(&'a str, ParseIntError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +71,13 @@ impl<'a> fmt::Display for OptionsError<'a> {
|
|||||||
OptionsError::TooManyCommandNames(n) => {
|
OptionsError::TooManyCommandNames(n) => {
|
||||||
write!(f, "Too many --command-name options: expected {} at most", 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!(
|
OptionsError::NumericParsingError(cmd, ref err) => write!(
|
||||||
f,
|
f,
|
||||||
"Could not read numeric argument to '--{cmd}': {err}",
|
"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 crate::hyperfine::types::{Command, NumericType, ParameterValue};
|
||||||
use clap::Values;
|
use clap::Values;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::ops::{Add, AddAssign, Div, Sub};
|
use std::ops::{Add, AddAssign, Div, Sub};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -45,6 +46,22 @@ impl<T: Numeric> RangeStep<T> {
|
|||||||
step,
|
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> {
|
impl<T: Numeric> Iterator for RangeStep<T> {
|
||||||
@@ -59,24 +76,18 @@ impl<T: Numeric> Iterator for RangeStep<T> {
|
|||||||
|
|
||||||
Some(return_val)
|
Some(return_val)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_params<T: Numeric>(start: T, end: T, step: T) -> Result<(), ParameterScanError> {
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
if end < start {
|
if self.step == T::from(0) {
|
||||||
return Err(ParameterScanError::EmptyRange);
|
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>(
|
fn build_parameterized_commands<'a, T: Numeric>(
|
||||||
@@ -87,27 +98,33 @@ fn build_parameterized_commands<'a, T: Numeric>(
|
|||||||
command_strings: Vec<&'a str>,
|
command_strings: Vec<&'a str>,
|
||||||
param_name: &'a str,
|
param_name: &'a str,
|
||||||
) -> Result<Vec<Command<'a>>, ParameterScanError> {
|
) -> Result<Vec<Command<'a>>, ParameterScanError> {
|
||||||
validate_params(param_min, param_max, step)?;
|
|
||||||
let param_range = RangeStep::new(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 mut i = 0;
|
||||||
let name_count = command_names.len();
|
let mut commands = vec![];
|
||||||
for value in param_range {
|
for value in param_range {
|
||||||
for cmd in &command_strings {
|
for cmd in &command_strings {
|
||||||
// Sets the command name by index (remainder) if exists.
|
let name = command_names
|
||||||
let name = if name_count > 0 {
|
.get(i)
|
||||||
Some(command_names[i % name_count])
|
.or_else(|| command_names.get(0))
|
||||||
} else {
|
.map(|s| *s);
|
||||||
None
|
|
||||||
};
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
commands.push(Command::new_parametrized(
|
commands.push(Command::new_parametrized(
|
||||||
name,
|
name,
|
||||||
cmd,
|
cmd,
|
||||||
vec![(param_name, ParameterValue::Numeric(value.into()))],
|
vec![(param_name, ParameterValue::Numeric(value.into()))],
|
||||||
));
|
));
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(commands)
|
Ok(commands)
|
||||||
@@ -182,6 +199,37 @@ fn test_decimal_range() {
|
|||||||
assert_eq!(param_range[10], Decimal::from(1));
|
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]
|
#[test]
|
||||||
fn test_get_parameterized_commands_int() {
|
fn test_get_parameterized_commands_int() {
|
||||||
let commands =
|
let commands =
|
||||||
@@ -248,3 +296,19 @@ fn test_get_specified_command_names() {
|
|||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
assert_eq!(command_names, vec!["name-a", "name-b", "name-c"]);
|
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;
|
use rust_decimal::Decimal;
|
||||||
/// This module contains common internal types.
|
/// This module contains common internal types.
|
||||||
use serde::*;
|
use serde::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::hyperfine::units::{Second, Unit};
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ParameterValue {
|
pub enum ParameterValue {
|
||||||
Text(String),
|
Text(String),
|
||||||
|
|||||||
+14
-7
@@ -255,17 +255,24 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
|
|||||||
return Vec::new();
|
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 mut i = 0;
|
||||||
let name_count = command_names.len();
|
|
||||||
let mut commands = Vec::with_capacity(param_space_size);
|
let mut commands = Vec::with_capacity(param_space_size);
|
||||||
let mut index = vec![0usize; dimensions.len()];
|
let mut index = vec![0usize; dimensions.len()];
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
// Sets the command name by index (remainder) if exists.
|
let name = command_names
|
||||||
let name = if name_count > 0 {
|
.get(i)
|
||||||
Some(command_names[i % name_count])
|
.or_else(|| command_names.get(0))
|
||||||
} else {
|
.map(|s| *s);
|
||||||
None
|
|
||||||
};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
let (command_index, params_indices) = index.split_first().unwrap();
|
let (command_index, params_indices) = index.split_first().unwrap();
|
||||||
|
|||||||
Reference in new issue
Block a user