use std::ffi::OsString;
use std::fmt::Display;
#[cfg(feature = "runner")]
use std::fs::File;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
#[cfg(feature = "runner")]
use std::process::{Child, Command as StdCommand, Stdio as StdStdio};
use std::time::Duration;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(feature = "runner")]
use crate::runner::metrics::Summarize;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BinaryBenchmark {
pub config: Option<BinaryBenchmarkConfig>,
pub benches: Vec<BinaryBenchmarkBench>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BinaryBenchmarkBench {
pub id: Option<String>,
pub function_name: String,
pub args: Option<String>,
pub command: Command,
pub config: Option<BinaryBenchmarkConfig>,
pub has_setup: bool,
pub has_teardown: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct BinaryBenchmarkConfig {
pub env_clear: Option<bool>,
pub current_dir: Option<PathBuf>,
pub entry_point: Option<String>,
pub exit_with: Option<ExitWith>,
pub callgrind_args: RawArgs,
pub valgrind_args: RawArgs,
pub envs: Vec<(OsString, Option<OsString>)>,
pub flamegraph_config: Option<FlamegraphConfig>,
pub regression_config: Option<RegressionConfig>,
pub tools: Tools,
pub tools_override: Option<Tools>,
pub sandbox: Option<Sandbox>,
pub setup_parallel: Option<bool>,
pub output_format: Option<OutputFormat>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BinaryBenchmarkGroup {
pub id: String,
pub config: Option<BinaryBenchmarkConfig>,
pub has_setup: bool,
pub has_teardown: bool,
pub binary_benchmarks: Vec<BinaryBenchmark>,
pub compare_by_id: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BinaryBenchmarkGroups {
pub config: BinaryBenchmarkConfig,
pub groups: Vec<BinaryBenchmarkGroup>,
pub command_line_args: Vec<String>,
pub has_setup: bool,
pub has_teardown: bool,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct Delay {
pub poll: Option<Duration>,
pub timeout: Option<Duration>,
pub kind: DelayKind,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DelayKind {
DurationElapse(Duration),
TcpConnect(SocketAddr),
UdpResponse(SocketAddr, Vec<u8>),
PathExists(PathBuf),
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Command {
pub path: PathBuf,
pub args: Vec<OsString>,
pub stdin: Option<Stdin>,
pub stdout: Option<Stdio>,
pub stderr: Option<Stdio>,
pub config: BinaryBenchmarkConfig,
pub delay: Option<Delay>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
TopToBottom,
BottomToTop,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum DhatMetricKind {
TotalBytes,
TotalBlocks,
AtTGmaxBytes,
AtTGmaxBlocks,
AtTEndBytes,
AtTEndBlocks,
ReadsBytes,
WritesBytes,
TotalLifetimes,
MaximumBytes,
MaximumBlocks,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub enum EntryPoint {
None,
#[default]
Default,
Custom(String),
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum ErrorMetricKind {
Errors,
Contexts,
SuppressedErrors,
SuppressedContexts,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum EventKind {
Ir,
SysCount,
SysTime,
SysCpuTime,
Ge,
Dr,
Dw,
I1mr,
D1mr,
D1mw,
ILmr,
DLmr,
DLmw,
L1hits,
LLhits,
RamHits,
TotalRW,
EstimatedCycles,
Bc,
Bcm,
Bi,
Bim,
ILdmr,
DLdmr,
DLdmw,
AcCost1,
AcCost2,
SpLoss1,
SpLoss2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExitWith {
Success,
Failure,
Code(i32),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fixtures {
pub path: PathBuf,
pub follow_symlinks: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct FlamegraphConfig {
pub kind: Option<FlamegraphKind>,
pub negate_differential: Option<bool>,
pub normalize_differential: Option<bool>,
pub event_kinds: Option<Vec<EventKind>>,
pub direction: Option<Direction>,
pub title: Option<String>,
pub subtitle: Option<String>,
pub min_width: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlamegraphKind {
Regular,
Differential,
All,
None,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LibraryBenchmark {
pub config: Option<LibraryBenchmarkConfig>,
pub benches: Vec<LibraryBenchmarkBench>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LibraryBenchmarkBench {
pub id: Option<String>,
pub function_name: String,
pub args: Option<String>,
pub config: Option<LibraryBenchmarkConfig>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LibraryBenchmarkConfig {
pub env_clear: Option<bool>,
pub callgrind_args: RawArgs,
pub valgrind_args: RawArgs,
pub envs: Vec<(OsString, Option<OsString>)>,
pub flamegraph_config: Option<FlamegraphConfig>,
pub regression_config: Option<RegressionConfig>,
pub tools: Tools,
pub tools_override: Option<Tools>,
pub entry_point: Option<EntryPoint>,
pub output_format: Option<OutputFormat>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LibraryBenchmarkGroup {
pub id: String,
pub config: Option<LibraryBenchmarkConfig>,
pub compare_by_id: Option<bool>,
pub library_benchmarks: Vec<LibraryBenchmark>,
pub has_setup: bool,
pub has_teardown: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LibraryBenchmarkGroups {
pub config: LibraryBenchmarkConfig,
pub groups: Vec<LibraryBenchmarkGroup>,
pub command_line_args: Vec<String>,
pub has_setup: bool,
pub has_teardown: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct OutputFormat {
pub truncate_description: Option<Option<usize>>,
pub show_intermediate: Option<bool>,
pub show_grid: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawArgs(pub Vec<String>);
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct RegressionConfig {
pub limits: Vec<(EventKind, f64)>,
pub fail_fast: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Sandbox {
pub enabled: Option<bool>,
pub fixtures: Vec<PathBuf>,
pub follow_symlinks: Option<bool>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Pipe {
#[default]
Stdout,
Stderr,
}
#[cfg(feature = "runner")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Stream {
Stdin,
Stdout,
Stderr,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Stdio {
#[default]
Inherit,
Null,
File(PathBuf),
Pipe,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Stdin {
Setup(Pipe),
#[default]
Inherit,
Null,
File(PathBuf),
Pipe,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tool {
pub kind: ValgrindTool,
pub enable: Option<bool>,
pub raw_args: RawArgs,
pub show_log: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tools(pub Vec<Tool>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ValgrindTool {
Memcheck,
Helgrind,
DRD,
Massif,
DHAT,
BBV,
}
impl BinaryBenchmarkConfig {
pub fn update_from_all<'a, T>(mut self, others: T) -> Self
where
T: IntoIterator<Item = Option<&'a Self>>,
{
for other in others.into_iter().flatten() {
self.env_clear = update_option(&self.env_clear, &other.env_clear);
self.current_dir = update_option(&self.current_dir, &other.current_dir);
self.entry_point = update_option(&self.entry_point, &other.entry_point);
self.exit_with = update_option(&self.exit_with, &other.exit_with);
self.callgrind_args
.extend_ignore_flag(other.callgrind_args.0.iter());
self.valgrind_args
.extend_ignore_flag(other.valgrind_args.0.iter());
self.envs.extend_from_slice(&other.envs);
self.flamegraph_config =
update_option(&self.flamegraph_config, &other.flamegraph_config);
self.regression_config =
update_option(&self.regression_config, &other.regression_config);
if let Some(other_tools) = &other.tools_override {
self.tools = other_tools.clone();
} else if !other.tools.is_empty() {
self.tools.update_from_other(&other.tools);
} else {
}
self.sandbox = update_option(&self.sandbox, &other.sandbox);
self.setup_parallel = update_option(&self.setup_parallel, &other.setup_parallel);
self.output_format = update_option(&self.output_format, &other.output_format);
}
self
}
pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
self.envs
.iter()
.filter_map(|(key, value)| match value {
Some(value) => Some((key.clone(), value.clone())),
None => std::env::var_os(key).map(|value| (key.clone(), value)),
})
.collect()
}
pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
self.envs
.iter()
.filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
.collect()
}
}
impl Default for DelayKind {
fn default() -> Self {
Self::DurationElapse(Duration::from_secs(60))
}
}
impl Default for Direction {
fn default() -> Self {
Self::BottomToTop
}
}
impl Display for DhatMetricKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DhatMetricKind::TotalBytes => f.write_str("Total bytes"),
DhatMetricKind::TotalBlocks => f.write_str("Total blocks"),
DhatMetricKind::AtTGmaxBytes => f.write_str("At t-gmax bytes"),
DhatMetricKind::AtTGmaxBlocks => f.write_str("At t-gmax blocks"),
DhatMetricKind::AtTEndBytes => f.write_str("At t-end bytes"),
DhatMetricKind::AtTEndBlocks => f.write_str("At t-end blocks"),
DhatMetricKind::ReadsBytes => f.write_str("Reads bytes"),
DhatMetricKind::WritesBytes => f.write_str("Writes bytes"),
DhatMetricKind::TotalLifetimes => f.write_str("Total lifetimes"),
DhatMetricKind::MaximumBytes => f.write_str("Maximum bytes"),
DhatMetricKind::MaximumBlocks => f.write_str("Maximum blocks"),
}
}
}
#[cfg(feature = "runner")]
impl Summarize for DhatMetricKind {}
impl<T> From<T> for EntryPoint
where
T: Into<String>,
{
fn from(value: T) -> Self {
EntryPoint::Custom(value.into())
}
}
#[cfg(feature = "runner")]
impl Summarize for ErrorMetricKind {}
impl Display for ErrorMetricKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorMetricKind::Errors => f.write_str("Errors"),
ErrorMetricKind::Contexts => f.write_str("Contexts"),
ErrorMetricKind::SuppressedErrors => f.write_str("Suppressed Errors"),
ErrorMetricKind::SuppressedContexts => f.write_str("Suppressed Contexts"),
}
}
}
impl EventKind {
pub fn is_derived(&self) -> bool {
matches!(
self,
EventKind::L1hits
| EventKind::LLhits
| EventKind::RamHits
| EventKind::TotalRW
| EventKind::EstimatedCycles
)
}
pub fn from_str_ignore_case(value: &str) -> Option<Self> {
match value.to_lowercase().as_str() {
"ir" => Some(Self::Ir),
"dr" => Some(Self::Dr),
"dw" => Some(Self::Dw),
"i1mr" => Some(Self::I1mr),
"ilmr" => Some(Self::ILmr),
"d1mr" => Some(Self::D1mr),
"dlmr" => Some(Self::DLmr),
"d1mw" => Some(Self::D1mw),
"dlmw" => Some(Self::DLmw),
"syscount" => Some(Self::SysCount),
"systime" => Some(Self::SysTime),
"syscputime" => Some(Self::SysCpuTime),
"ge" => Some(Self::Ge),
"bc" => Some(Self::Bc),
"bcm" => Some(Self::Bcm),
"bi" => Some(Self::Bi),
"bim" => Some(Self::Bim),
"ildmr" => Some(Self::ILdmr),
"dldmr" => Some(Self::DLdmr),
"dldmw" => Some(Self::DLdmw),
"accost1" => Some(Self::AcCost1),
"accost2" => Some(Self::AcCost2),
"sploss1" => Some(Self::SpLoss1),
"sploss2" => Some(Self::SpLoss2),
"l1hits" => Some(Self::L1hits),
"llhits" => Some(Self::LLhits),
"ramhits" => Some(Self::RamHits),
"totalrw" => Some(Self::TotalRW),
"estimatedcycles" => Some(Self::EstimatedCycles),
_ => None,
}
}
pub fn to_name(&self) -> String {
format!("{:?}", *self)
}
}
impl Display for EventKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventKind::Ir => f.write_str("Instructions"),
EventKind::L1hits => f.write_str("L1 Hits"),
EventKind::LLhits => f.write_str("L2 Hits"),
EventKind::RamHits => f.write_str("RAM Hits"),
EventKind::TotalRW => f.write_str("Total read+write"),
EventKind::EstimatedCycles => f.write_str("Estimated Cycles"),
_ => f.write_fmt(format_args!("{self:?}")),
}
}
}
impl<T> From<T> for EventKind
where
T: AsRef<str>,
{
fn from(value: T) -> Self {
match value.as_ref() {
"Ir" => Self::Ir,
"Dr" => Self::Dr,
"Dw" => Self::Dw,
"I1mr" => Self::I1mr,
"ILmr" => Self::ILmr,
"D1mr" => Self::D1mr,
"DLmr" => Self::DLmr,
"D1mw" => Self::D1mw,
"DLmw" => Self::DLmw,
"sysCount" => Self::SysCount,
"sysTime" => Self::SysTime,
"sysCpuTime" => Self::SysCpuTime,
"Ge" => Self::Ge,
"Bc" => Self::Bc,
"Bcm" => Self::Bcm,
"Bi" => Self::Bi,
"Bim" => Self::Bim,
"ILdmr" => Self::ILdmr,
"DLdmr" => Self::DLdmr,
"DLdmw" => Self::DLdmw,
"AcCost1" => Self::AcCost1,
"AcCost2" => Self::AcCost2,
"SpLoss1" => Self::SpLoss1,
"SpLoss2" => Self::SpLoss2,
"L1hits" => Self::L1hits,
"LLhits" => Self::LLhits,
"RamHits" => Self::RamHits,
"TotalRW" => Self::TotalRW,
"EstimatedCycles" => Self::EstimatedCycles,
unknown => panic!("Unknown event type: {unknown}"),
}
}
}
impl LibraryBenchmarkConfig {
pub fn update_from_all<'a, T>(mut self, others: T) -> Self
where
T: IntoIterator<Item = Option<&'a Self>>,
{
for other in others.into_iter().flatten() {
self.env_clear = update_option(&self.env_clear, &other.env_clear);
self.callgrind_args
.extend_ignore_flag(other.callgrind_args.0.iter());
self.valgrind_args
.extend_ignore_flag(other.valgrind_args.0.iter());
self.envs.extend_from_slice(&other.envs);
self.flamegraph_config =
update_option(&self.flamegraph_config, &other.flamegraph_config);
self.regression_config =
update_option(&self.regression_config, &other.regression_config);
if let Some(other_tools) = &other.tools_override {
self.tools = other_tools.clone();
} else if !other.tools.is_empty() {
self.tools.update_from_other(&other.tools);
} else {
}
self.entry_point = update_option(&self.entry_point, &other.entry_point);
self.output_format = update_option(&self.output_format, &other.output_format);
}
self
}
pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
self.envs
.iter()
.filter_map(|(key, value)| match value {
Some(value) => Some((key.clone(), value.clone())),
None => std::env::var_os(key).map(|value| (key.clone(), value)),
})
.collect()
}
pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
self.envs
.iter()
.filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
.collect()
}
}
impl RawArgs {
pub fn new(args: Vec<String>) -> Self {
Self(args)
}
pub fn extend_ignore_flag<I, T>(&mut self, args: T)
where
I: AsRef<str>,
T: IntoIterator<Item = I>,
{
self.0.extend(
args.into_iter()
.filter(|s| !s.as_ref().is_empty())
.map(|s| {
let string = s.as_ref();
if string.starts_with('-') {
string.to_owned()
} else {
format!("--{string}")
}
}),
);
}
pub fn from_command_line_args(args: Vec<String>) -> Self {
let mut this = Self(Vec::default());
if !args.is_empty() {
let mut iter = args.into_iter();
let mut last = iter.next().unwrap();
for elem in iter {
this.0.push(last);
last = elem;
}
if last.as_str() != "--bench" {
this.0.push(last);
}
}
this
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<I> FromIterator<I> for RawArgs
where
I: AsRef<str>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
let mut this = Self::default();
this.extend_ignore_flag(iter);
this
}
}
impl Stdin {
#[cfg(feature = "runner")]
pub(crate) fn apply(
&self,
command: &mut StdCommand,
stream: Stream,
child: Option<&mut Child>,
) -> Result<(), String> {
match (self, child) {
(Self::Setup(Pipe::Stdout), Some(child)) => {
command.stdin(
child
.stdout
.take()
.ok_or_else(|| "Error piping setup stdout".to_owned())?,
);
Ok(())
}
(Self::Setup(Pipe::Stderr), Some(child)) => {
command.stdin(
child
.stderr
.take()
.ok_or_else(|| "Error piping setup stderr".to_owned())?,
);
Ok(())
}
(Self::Setup(_) | Stdin::Pipe, _) => Stdio::Pipe.apply(command, stream),
(Self::Inherit, _) => Stdio::Inherit.apply(command, stream),
(Self::Null, _) => Stdio::Null.apply(command, stream),
(Self::File(path), _) => Stdio::File(path.clone()).apply(command, stream),
}
}
}
impl From<Stdio> for Stdin {
fn from(value: Stdio) -> Self {
match value {
Stdio::Inherit => Stdin::Inherit,
Stdio::Null => Stdin::Null,
Stdio::File(file) => Stdin::File(file),
Stdio::Pipe => Stdin::Pipe,
}
}
}
impl From<PathBuf> for Stdin {
fn from(value: PathBuf) -> Self {
Self::File(value)
}
}
impl From<&PathBuf> for Stdin {
fn from(value: &PathBuf) -> Self {
Self::File(value.to_owned())
}
}
impl From<&Path> for Stdin {
fn from(value: &Path) -> Self {
Self::File(value.to_path_buf())
}
}
impl Stdio {
#[cfg(feature = "runner")]
pub(crate) fn apply(&self, command: &mut StdCommand, stream: Stream) -> Result<(), String> {
let stdio = match self {
Stdio::Pipe => StdStdio::piped(),
Stdio::Inherit => StdStdio::inherit(),
Stdio::Null => StdStdio::null(),
Stdio::File(path) => match stream {
Stream::Stdin => StdStdio::from(File::open(path).map_err(|error| {
format!(
"Failed to open file '{}' in read mode for {stream}: {error}",
path.display()
)
})?),
Stream::Stdout | Stream::Stderr => {
StdStdio::from(File::create(path).map_err(|error| {
format!(
"Failed to create file '{}' for {stream}: {error}",
path.display()
)
})?)
}
},
};
match stream {
Stream::Stdin => command.stdin(stdio),
Stream::Stdout => command.stdout(stdio),
Stream::Stderr => command.stderr(stdio),
};
Ok(())
}
#[cfg(feature = "runner")]
pub(crate) fn is_pipe(&self) -> bool {
match self {
Stdio::Inherit => false,
Stdio::Null | Stdio::File(_) | Stdio::Pipe => true,
}
}
}
impl From<PathBuf> for Stdio {
fn from(value: PathBuf) -> Self {
Self::File(value)
}
}
impl From<&PathBuf> for Stdio {
fn from(value: &PathBuf) -> Self {
Self::File(value.to_owned())
}
}
impl From<&Path> for Stdio {
fn from(value: &Path) -> Self {
Self::File(value.to_path_buf())
}
}
#[cfg(feature = "runner")]
impl Display for Stream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{self:?}").to_lowercase())
}
}
impl Tools {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn update(&mut self, tool: Tool) {
if let Some(pos) = self.0.iter().position(|t| t.kind == tool.kind) {
self.0.remove(pos);
}
self.0.push(tool);
}
pub fn update_all<T>(&mut self, tools: T)
where
T: IntoIterator<Item = Tool>,
{
for tool in tools {
self.update(tool);
}
}
pub fn update_from_other(&mut self, tools: &Tools) {
self.update_all(tools.0.iter().cloned());
}
}
pub fn update_option<T: Clone>(first: &Option<T>, other: &Option<T>) -> Option<T> {
other.clone().or_else(|| first.clone())
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
#[test]
fn test_library_benchmark_config_update_from_all_when_default() {
assert_eq!(
LibraryBenchmarkConfig::default()
.update_from_all([Some(&LibraryBenchmarkConfig::default())]),
LibraryBenchmarkConfig::default()
);
}
#[test]
fn test_library_benchmark_config_update_from_all_when_no_tools_override() {
let base = LibraryBenchmarkConfig::default();
let other = LibraryBenchmarkConfig {
env_clear: Some(true),
callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
flamegraph_config: Some(FlamegraphConfig::default()),
regression_config: Some(RegressionConfig::default()),
tools: Tools(vec![Tool {
kind: ValgrindTool::DHAT,
enable: None,
raw_args: RawArgs(vec![]),
show_log: None,
}]),
tools_override: None,
entry_point: None,
output_format: None,
};
assert_eq!(base.update_from_all([Some(&other.clone())]), other);
}
#[test]
fn test_library_benchmark_config_update_from_all_when_tools_override() {
let base = LibraryBenchmarkConfig::default();
let other = LibraryBenchmarkConfig {
env_clear: Some(true),
callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
flamegraph_config: Some(FlamegraphConfig::default()),
regression_config: Some(RegressionConfig::default()),
tools: Tools(vec![Tool {
kind: ValgrindTool::DHAT,
enable: None,
raw_args: RawArgs(vec![]),
show_log: None,
}]),
tools_override: Some(Tools(vec![])),
entry_point: Some(EntryPoint::default()),
output_format: Some(OutputFormat::default()),
};
let expected = LibraryBenchmarkConfig {
tools: other.tools_override.as_ref().unwrap().clone(),
tools_override: None,
..other.clone()
};
assert_eq!(base.update_from_all([Some(&other)]), expected);
}
#[rstest]
#[case::env_clear(
LibraryBenchmarkConfig {
env_clear: Some(true),
..Default::default()
}
)]
fn test_library_benchmark_config_update_from_all_truncate_description(
#[case] config: LibraryBenchmarkConfig,
) {
let actual = LibraryBenchmarkConfig::default().update_from_all([Some(&config)]);
assert_eq!(actual, config);
}
#[rstest]
#[case::all_none(None, None, None)]
#[case::some_and_none(Some(true), None, Some(true))]
#[case::none_and_some(None, Some(true), Some(true))]
#[case::some_and_some(Some(false), Some(true), Some(true))]
#[case::some_and_some_value_does_not_matter(Some(true), Some(false), Some(false))]
fn test_update_option(
#[case] first: Option<bool>,
#[case] other: Option<bool>,
#[case] expected: Option<bool>,
) {
assert_eq!(update_option(&first, &other), expected);
}
#[rstest]
#[case::empty(vec![], &[], vec![])]
#[case::empty_base(vec![], &["--a=yes"], vec!["--a=yes"])]
#[case::no_flags(vec![], &["a=yes"], vec!["--a=yes"])]
#[case::already_exists_single(vec!["--a=yes"], &["--a=yes"], vec!["--a=yes","--a=yes"])]
#[case::already_exists_when_multiple(vec!["--a=yes", "--b=yes"], &["--a=yes"], vec!["--a=yes", "--b=yes", "--a=yes"])]
fn test_raw_args_extend_ignore_flags(
#[case] base: Vec<&str>,
#[case] data: &[&str],
#[case] expected: Vec<&str>,
) {
let mut base = RawArgs(base.iter().map(std::string::ToString::to_string).collect());
base.extend_ignore_flag(data.iter().map(std::string::ToString::to_string));
assert_eq!(base.0.into_iter().collect::<Vec<String>>(), expected);
}
}