1use std::ffi::OsString;
3use std::fmt::Display;
4#[cfg(feature = "runner")]
5use std::fs::File;
6use std::net::SocketAddr;
7use std::path::{Path, PathBuf};
8#[cfg(feature = "runner")]
9use std::process::{Child, Command as StdCommand, Stdio as StdStdio};
10use std::time::Duration;
11
12#[cfg(feature = "schema")]
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "runner")]
17use crate::runner::metrics::Summarize;
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct BinaryBenchmark {
22 pub config: Option<BinaryBenchmarkConfig>,
23 pub benches: Vec<BinaryBenchmarkBench>,
24}
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct BinaryBenchmarkBench {
29 pub id: Option<String>,
30 pub function_name: String,
31 pub args: Option<String>,
32 pub command: Command,
33 pub config: Option<BinaryBenchmarkConfig>,
34 pub has_setup: bool,
35 pub has_teardown: bool,
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
39pub struct BinaryBenchmarkConfig {
40 pub env_clear: Option<bool>,
41 pub current_dir: Option<PathBuf>,
42 pub entry_point: Option<String>,
43 pub exit_with: Option<ExitWith>,
44 pub callgrind_args: RawArgs,
45 pub valgrind_args: RawArgs,
46 pub envs: Vec<(OsString, Option<OsString>)>,
47 pub flamegraph_config: Option<FlamegraphConfig>,
48 pub regression_config: Option<RegressionConfig>,
49 pub tools: Tools,
50 pub tools_override: Option<Tools>,
51 pub sandbox: Option<Sandbox>,
52 pub setup_parallel: Option<bool>,
53 pub output_format: Option<OutputFormat>,
54}
55
56#[derive(Debug, Clone, Default, Serialize, Deserialize)]
58pub struct BinaryBenchmarkGroup {
59 pub id: String,
60 pub config: Option<BinaryBenchmarkConfig>,
61 pub has_setup: bool,
62 pub has_teardown: bool,
63 pub binary_benchmarks: Vec<BinaryBenchmark>,
64 pub compare_by_id: Option<bool>,
65}
66
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub struct BinaryBenchmarkGroups {
70 pub config: BinaryBenchmarkConfig,
71 pub groups: Vec<BinaryBenchmarkGroup>,
72 pub command_line_args: Vec<String>,
74 pub has_setup: bool,
75 pub has_teardown: bool,
76}
77
78#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
79pub struct Delay {
80 pub poll: Option<Duration>,
81 pub timeout: Option<Duration>,
82 pub kind: DelayKind,
83}
84
85#[non_exhaustive]
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub enum DelayKind {
89 DurationElapse(Duration),
91 TcpConnect(SocketAddr),
93 UdpResponse(SocketAddr, Vec<u8>),
95 PathExists(PathBuf),
97}
98
99#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
100pub struct Command {
101 pub path: PathBuf,
102 pub args: Vec<OsString>,
103 pub stdin: Option<Stdin>,
104 pub stdout: Option<Stdio>,
105 pub stderr: Option<Stdio>,
106 pub config: BinaryBenchmarkConfig,
107 pub delay: Option<Delay>,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114pub enum Direction {
115 TopToBottom,
117 BottomToTop,
119}
120
121#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
123#[cfg_attr(feature = "schema", derive(JsonSchema))]
124pub enum DhatMetricKind {
125 TotalBytes,
127 TotalBlocks,
129 AtTGmaxBytes,
131 AtTGmaxBlocks,
133 AtTEndBytes,
137 AtTEndBlocks,
141 ReadsBytes,
143 WritesBytes,
145 TotalLifetimes,
147 MaximumBytes,
149 MaximumBlocks,
151}
152
153#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
155pub enum EntryPoint {
156 None,
158 #[default]
160 Default,
161 Custom(String),
165}
166
167#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
173#[cfg_attr(feature = "schema", derive(JsonSchema))]
174pub enum ErrorMetricKind {
175 Errors,
177 Contexts,
179 SuppressedErrors,
181 SuppressedContexts,
183}
184
185#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
191#[cfg_attr(feature = "schema", derive(JsonSchema))]
192pub enum EventKind {
193 Ir,
195 SysCount,
197 SysTime,
199 SysCpuTime,
201 Ge,
203 Dr,
205 Dw,
207 I1mr,
209 D1mr,
211 D1mw,
213 ILmr,
215 DLmr,
217 DLmw,
219 L1hits,
221 LLhits,
223 RamHits,
225 TotalRW,
227 EstimatedCycles,
229 Bc,
231 Bcm,
233 Bi,
235 Bim,
237 ILdmr,
239 DLdmr,
241 DLdmw,
243 AcCost1,
245 AcCost2,
247 SpLoss1,
249 SpLoss2,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
254pub enum ExitWith {
255 Success,
256 Failure,
257 Code(i32),
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Fixtures {
262 pub path: PathBuf,
263 pub follow_symlinks: bool,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
267pub struct FlamegraphConfig {
268 pub kind: Option<FlamegraphKind>,
269 pub negate_differential: Option<bool>,
270 pub normalize_differential: Option<bool>,
271 pub event_kinds: Option<Vec<EventKind>>,
272 pub direction: Option<Direction>,
273 pub title: Option<String>,
274 pub subtitle: Option<String>,
275 pub min_width: Option<f64>,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
280pub enum FlamegraphKind {
281 Regular,
283 Differential,
285 All,
288 None,
290}
291
292#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
294pub struct LibraryBenchmark {
295 pub config: Option<LibraryBenchmarkConfig>,
296 pub benches: Vec<LibraryBenchmarkBench>,
297}
298
299#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
301pub struct LibraryBenchmarkBench {
302 pub id: Option<String>,
303 pub function_name: String,
304 pub args: Option<String>,
305 pub config: Option<LibraryBenchmarkConfig>,
306}
307
308#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
309pub struct LibraryBenchmarkConfig {
310 pub env_clear: Option<bool>,
311 pub callgrind_args: RawArgs,
312 pub valgrind_args: RawArgs,
313 pub envs: Vec<(OsString, Option<OsString>)>,
314 pub flamegraph_config: Option<FlamegraphConfig>,
315 pub regression_config: Option<RegressionConfig>,
316 pub tools: Tools,
317 pub tools_override: Option<Tools>,
318 pub entry_point: Option<EntryPoint>,
319 pub output_format: Option<OutputFormat>,
320}
321
322#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
324pub struct LibraryBenchmarkGroup {
325 pub id: String,
326 pub config: Option<LibraryBenchmarkConfig>,
327 pub compare_by_id: Option<bool>,
328 pub library_benchmarks: Vec<LibraryBenchmark>,
329 pub has_setup: bool,
330 pub has_teardown: bool,
331}
332
333#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
335pub struct LibraryBenchmarkGroups {
336 pub config: LibraryBenchmarkConfig,
337 pub groups: Vec<LibraryBenchmarkGroup>,
338 pub command_line_args: Vec<String>,
340 pub has_setup: bool,
341 pub has_teardown: bool,
342}
343
344#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
346pub struct OutputFormat {
347 pub truncate_description: Option<Option<usize>>,
348 pub show_intermediate: Option<bool>,
349 pub show_grid: Option<bool>,
350}
351
352#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
353pub struct RawArgs(pub Vec<String>);
354
355#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
356pub struct RegressionConfig {
357 pub limits: Vec<(EventKind, f64)>,
358 pub fail_fast: Option<bool>,
359}
360
361#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
362pub struct Sandbox {
363 pub enabled: Option<bool>,
364 pub fixtures: Vec<PathBuf>,
365 pub follow_symlinks: Option<bool>,
366}
367
368#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
372pub enum Pipe {
373 #[default]
375 Stdout,
376 Stderr,
378}
379
380#[cfg(feature = "runner")]
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub(crate) enum Stream {
384 Stdin,
385 Stdout,
386 Stderr,
387}
388
389#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
394pub enum Stdio {
395 #[default]
397 Inherit,
398 Null,
400 File(PathBuf),
403 Pipe,
405}
406
407#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
411pub enum Stdin {
412 Setup(Pipe),
416 #[default]
417 Inherit,
419 Null,
421 File(PathBuf),
423 Pipe,
425}
426
427#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
428pub struct Tool {
429 pub kind: ValgrindTool,
430 pub enable: Option<bool>,
431 pub raw_args: RawArgs,
432 pub show_log: Option<bool>,
433}
434
435#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
436pub struct Tools(pub Vec<Tool>);
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
440pub enum ValgrindTool {
441 Memcheck,
443 Helgrind,
445 DRD,
447 Massif,
449 DHAT,
451 BBV,
453}
454
455impl BinaryBenchmarkConfig {
456 pub fn update_from_all<'a, T>(mut self, others: T) -> Self
457 where
458 T: IntoIterator<Item = Option<&'a Self>>,
459 {
460 for other in others.into_iter().flatten() {
461 self.env_clear = update_option(&self.env_clear, &other.env_clear);
462 self.current_dir = update_option(&self.current_dir, &other.current_dir);
463 self.entry_point = update_option(&self.entry_point, &other.entry_point);
464 self.exit_with = update_option(&self.exit_with, &other.exit_with);
465
466 self.callgrind_args
467 .extend_ignore_flag(other.callgrind_args.0.iter());
468
469 self.valgrind_args
470 .extend_ignore_flag(other.valgrind_args.0.iter());
471
472 self.envs.extend_from_slice(&other.envs);
473 self.flamegraph_config =
474 update_option(&self.flamegraph_config, &other.flamegraph_config);
475 self.regression_config =
476 update_option(&self.regression_config, &other.regression_config);
477 if let Some(other_tools) = &other.tools_override {
478 self.tools = other_tools.clone();
479 } else if !other.tools.is_empty() {
480 self.tools.update_from_other(&other.tools);
481 } else {
482 }
484
485 self.sandbox = update_option(&self.sandbox, &other.sandbox);
486 self.setup_parallel = update_option(&self.setup_parallel, &other.setup_parallel);
487 self.output_format = update_option(&self.output_format, &other.output_format);
488 }
489 self
490 }
491
492 pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
493 self.envs
494 .iter()
495 .filter_map(|(key, value)| match value {
496 Some(value) => Some((key.clone(), value.clone())),
497 None => std::env::var_os(key).map(|value| (key.clone(), value)),
498 })
499 .collect()
500 }
501
502 pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
503 self.envs
504 .iter()
505 .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
506 .collect()
507 }
508}
509
510impl Default for DelayKind {
511 fn default() -> Self {
512 Self::DurationElapse(Duration::from_secs(60))
513 }
514}
515
516impl Default for Direction {
517 fn default() -> Self {
518 Self::BottomToTop
519 }
520}
521
522impl Display for DhatMetricKind {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 match self {
525 DhatMetricKind::TotalBytes => f.write_str("Total bytes"),
526 DhatMetricKind::TotalBlocks => f.write_str("Total blocks"),
527 DhatMetricKind::AtTGmaxBytes => f.write_str("At t-gmax bytes"),
528 DhatMetricKind::AtTGmaxBlocks => f.write_str("At t-gmax blocks"),
529 DhatMetricKind::AtTEndBytes => f.write_str("At t-end bytes"),
530 DhatMetricKind::AtTEndBlocks => f.write_str("At t-end blocks"),
531 DhatMetricKind::ReadsBytes => f.write_str("Reads bytes"),
532 DhatMetricKind::WritesBytes => f.write_str("Writes bytes"),
533 DhatMetricKind::TotalLifetimes => f.write_str("Total lifetimes"),
534 DhatMetricKind::MaximumBytes => f.write_str("Maximum bytes"),
535 DhatMetricKind::MaximumBlocks => f.write_str("Maximum blocks"),
536 }
537 }
538}
539
540#[cfg(feature = "runner")]
541impl Summarize for DhatMetricKind {}
542
543impl<T> From<T> for EntryPoint
544where
545 T: Into<String>,
546{
547 fn from(value: T) -> Self {
548 EntryPoint::Custom(value.into())
549 }
550}
551
552#[cfg(feature = "runner")]
553impl Summarize for ErrorMetricKind {}
554
555impl Display for ErrorMetricKind {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 match self {
558 ErrorMetricKind::Errors => f.write_str("Errors"),
559 ErrorMetricKind::Contexts => f.write_str("Contexts"),
560 ErrorMetricKind::SuppressedErrors => f.write_str("Suppressed Errors"),
561 ErrorMetricKind::SuppressedContexts => f.write_str("Suppressed Contexts"),
562 }
563 }
564}
565
566impl EventKind {
567 pub fn is_derived(&self) -> bool {
579 matches!(
580 self,
581 EventKind::L1hits
582 | EventKind::LLhits
583 | EventKind::RamHits
584 | EventKind::TotalRW
585 | EventKind::EstimatedCycles
586 )
587 }
588
589 pub fn from_str_ignore_case(value: &str) -> Option<Self> {
590 match value.to_lowercase().as_str() {
591 "ir" => Some(Self::Ir),
592 "dr" => Some(Self::Dr),
593 "dw" => Some(Self::Dw),
594 "i1mr" => Some(Self::I1mr),
595 "ilmr" => Some(Self::ILmr),
596 "d1mr" => Some(Self::D1mr),
597 "dlmr" => Some(Self::DLmr),
598 "d1mw" => Some(Self::D1mw),
599 "dlmw" => Some(Self::DLmw),
600 "syscount" => Some(Self::SysCount),
601 "systime" => Some(Self::SysTime),
602 "syscputime" => Some(Self::SysCpuTime),
603 "ge" => Some(Self::Ge),
604 "bc" => Some(Self::Bc),
605 "bcm" => Some(Self::Bcm),
606 "bi" => Some(Self::Bi),
607 "bim" => Some(Self::Bim),
608 "ildmr" => Some(Self::ILdmr),
609 "dldmr" => Some(Self::DLdmr),
610 "dldmw" => Some(Self::DLdmw),
611 "accost1" => Some(Self::AcCost1),
612 "accost2" => Some(Self::AcCost2),
613 "sploss1" => Some(Self::SpLoss1),
614 "sploss2" => Some(Self::SpLoss2),
615 "l1hits" => Some(Self::L1hits),
616 "llhits" => Some(Self::LLhits),
617 "ramhits" => Some(Self::RamHits),
618 "totalrw" => Some(Self::TotalRW),
619 "estimatedcycles" => Some(Self::EstimatedCycles),
620 _ => None,
621 }
622 }
623
624 pub fn to_name(&self) -> String {
625 format!("{:?}", *self)
626 }
627}
628
629impl Display for EventKind {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 match self {
632 EventKind::Ir => f.write_str("Instructions"),
633 EventKind::L1hits => f.write_str("L1 Hits"),
634 EventKind::LLhits => f.write_str("L2 Hits"),
635 EventKind::RamHits => f.write_str("RAM Hits"),
636 EventKind::TotalRW => f.write_str("Total read+write"),
637 EventKind::EstimatedCycles => f.write_str("Estimated Cycles"),
638 _ => f.write_fmt(format_args!("{self:?}")),
639 }
640 }
641}
642
643impl<T> From<T> for EventKind
644where
645 T: AsRef<str>,
646{
647 fn from(value: T) -> Self {
648 match value.as_ref() {
649 "Ir" => Self::Ir,
650 "Dr" => Self::Dr,
651 "Dw" => Self::Dw,
652 "I1mr" => Self::I1mr,
653 "ILmr" => Self::ILmr,
654 "D1mr" => Self::D1mr,
655 "DLmr" => Self::DLmr,
656 "D1mw" => Self::D1mw,
657 "DLmw" => Self::DLmw,
658 "sysCount" => Self::SysCount,
659 "sysTime" => Self::SysTime,
660 "sysCpuTime" => Self::SysCpuTime,
661 "Ge" => Self::Ge,
662 "Bc" => Self::Bc,
663 "Bcm" => Self::Bcm,
664 "Bi" => Self::Bi,
665 "Bim" => Self::Bim,
666 "ILdmr" => Self::ILdmr,
667 "DLdmr" => Self::DLdmr,
668 "DLdmw" => Self::DLdmw,
669 "AcCost1" => Self::AcCost1,
670 "AcCost2" => Self::AcCost2,
671 "SpLoss1" => Self::SpLoss1,
672 "SpLoss2" => Self::SpLoss2,
673 "L1hits" => Self::L1hits,
674 "LLhits" => Self::LLhits,
675 "RamHits" => Self::RamHits,
676 "TotalRW" => Self::TotalRW,
677 "EstimatedCycles" => Self::EstimatedCycles,
678 unknown => panic!("Unknown event type: {unknown}"),
679 }
680 }
681}
682
683impl LibraryBenchmarkConfig {
684 pub fn update_from_all<'a, T>(mut self, others: T) -> Self
685 where
686 T: IntoIterator<Item = Option<&'a Self>>,
687 {
688 for other in others.into_iter().flatten() {
689 self.env_clear = update_option(&self.env_clear, &other.env_clear);
690
691 self.callgrind_args
692 .extend_ignore_flag(other.callgrind_args.0.iter());
693 self.valgrind_args
694 .extend_ignore_flag(other.valgrind_args.0.iter());
695
696 self.envs.extend_from_slice(&other.envs);
697 self.flamegraph_config =
698 update_option(&self.flamegraph_config, &other.flamegraph_config);
699 self.regression_config =
700 update_option(&self.regression_config, &other.regression_config);
701 if let Some(other_tools) = &other.tools_override {
702 self.tools = other_tools.clone();
703 } else if !other.tools.is_empty() {
704 self.tools.update_from_other(&other.tools);
705 } else {
706 }
708
709 self.entry_point = update_option(&self.entry_point, &other.entry_point);
710 self.output_format = update_option(&self.output_format, &other.output_format);
711 }
712 self
713 }
714
715 pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
716 self.envs
717 .iter()
718 .filter_map(|(key, value)| match value {
719 Some(value) => Some((key.clone(), value.clone())),
720 None => std::env::var_os(key).map(|value| (key.clone(), value)),
721 })
722 .collect()
723 }
724
725 pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
726 self.envs
727 .iter()
728 .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
729 .collect()
730 }
731}
732
733impl RawArgs {
734 pub fn new(args: Vec<String>) -> Self {
735 Self(args)
736 }
737
738 pub fn extend_ignore_flag<I, T>(&mut self, args: T)
739 where
740 I: AsRef<str>,
741 T: IntoIterator<Item = I>,
742 {
743 self.0.extend(
744 args.into_iter()
745 .filter(|s| !s.as_ref().is_empty())
746 .map(|s| {
747 let string = s.as_ref();
748 if string.starts_with('-') {
749 string.to_owned()
750 } else {
751 format!("--{string}")
752 }
753 }),
754 );
755 }
756
757 pub fn from_command_line_args(args: Vec<String>) -> Self {
758 let mut this = Self(Vec::default());
759 if !args.is_empty() {
760 let mut iter = args.into_iter();
761 let mut last = iter.next().unwrap();
763 for elem in iter {
764 this.0.push(last);
765 last = elem;
766 }
767 if last.as_str() != "--bench" {
768 this.0.push(last);
769 }
770 }
771 this
772 }
773
774 pub fn is_empty(&self) -> bool {
775 self.0.is_empty()
776 }
777}
778
779impl<I> FromIterator<I> for RawArgs
780where
781 I: AsRef<str>,
782{
783 fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
784 let mut this = Self::default();
785 this.extend_ignore_flag(iter);
786 this
787 }
788}
789
790impl Stdin {
791 #[cfg(feature = "runner")]
792 pub(crate) fn apply(
793 &self,
794 command: &mut StdCommand,
795 stream: Stream,
796 child: Option<&mut Child>,
797 ) -> Result<(), String> {
798 match (self, child) {
799 (Self::Setup(Pipe::Stdout), Some(child)) => {
800 command.stdin(
801 child
802 .stdout
803 .take()
804 .ok_or_else(|| "Error piping setup stdout".to_owned())?,
805 );
806 Ok(())
807 }
808 (Self::Setup(Pipe::Stderr), Some(child)) => {
809 command.stdin(
810 child
811 .stderr
812 .take()
813 .ok_or_else(|| "Error piping setup stderr".to_owned())?,
814 );
815 Ok(())
816 }
817 (Self::Setup(_) | Stdin::Pipe, _) => Stdio::Pipe.apply(command, stream),
818 (Self::Inherit, _) => Stdio::Inherit.apply(command, stream),
819 (Self::Null, _) => Stdio::Null.apply(command, stream),
820 (Self::File(path), _) => Stdio::File(path.clone()).apply(command, stream),
821 }
822 }
823}
824
825impl From<Stdio> for Stdin {
826 fn from(value: Stdio) -> Self {
827 match value {
828 Stdio::Inherit => Stdin::Inherit,
829 Stdio::Null => Stdin::Null,
830 Stdio::File(file) => Stdin::File(file),
831 Stdio::Pipe => Stdin::Pipe,
832 }
833 }
834}
835
836impl From<PathBuf> for Stdin {
837 fn from(value: PathBuf) -> Self {
838 Self::File(value)
839 }
840}
841
842impl From<&PathBuf> for Stdin {
843 fn from(value: &PathBuf) -> Self {
844 Self::File(value.to_owned())
845 }
846}
847
848impl From<&Path> for Stdin {
849 fn from(value: &Path) -> Self {
850 Self::File(value.to_path_buf())
851 }
852}
853
854impl Stdio {
855 #[cfg(feature = "runner")]
856 pub(crate) fn apply(&self, command: &mut StdCommand, stream: Stream) -> Result<(), String> {
857 let stdio = match self {
858 Stdio::Pipe => StdStdio::piped(),
859 Stdio::Inherit => StdStdio::inherit(),
860 Stdio::Null => StdStdio::null(),
861 Stdio::File(path) => match stream {
862 Stream::Stdin => StdStdio::from(File::open(path).map_err(|error| {
863 format!(
864 "Failed to open file '{}' in read mode for {stream}: {error}",
865 path.display()
866 )
867 })?),
868 Stream::Stdout | Stream::Stderr => {
869 StdStdio::from(File::create(path).map_err(|error| {
870 format!(
871 "Failed to create file '{}' for {stream}: {error}",
872 path.display()
873 )
874 })?)
875 }
876 },
877 };
878
879 match stream {
880 Stream::Stdin => command.stdin(stdio),
881 Stream::Stdout => command.stdout(stdio),
882 Stream::Stderr => command.stderr(stdio),
883 };
884
885 Ok(())
886 }
887
888 #[cfg(feature = "runner")]
889 pub(crate) fn is_pipe(&self) -> bool {
890 match self {
891 Stdio::Inherit => false,
892 Stdio::Null | Stdio::File(_) | Stdio::Pipe => true,
893 }
894 }
895}
896
897impl From<PathBuf> for Stdio {
898 fn from(value: PathBuf) -> Self {
899 Self::File(value)
900 }
901}
902
903impl From<&PathBuf> for Stdio {
904 fn from(value: &PathBuf) -> Self {
905 Self::File(value.to_owned())
906 }
907}
908
909impl From<&Path> for Stdio {
910 fn from(value: &Path) -> Self {
911 Self::File(value.to_path_buf())
912 }
913}
914
915#[cfg(feature = "runner")]
916impl Display for Stream {
917 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
918 f.write_str(&format!("{self:?}").to_lowercase())
919 }
920}
921
922impl Tools {
923 pub fn is_empty(&self) -> bool {
925 self.0.is_empty()
926 }
927
928 pub fn update(&mut self, tool: Tool) {
936 if let Some(pos) = self.0.iter().position(|t| t.kind == tool.kind) {
937 self.0.remove(pos);
938 }
939 self.0.push(tool);
940 }
941
942 pub fn update_all<T>(&mut self, tools: T)
944 where
945 T: IntoIterator<Item = Tool>,
946 {
947 for tool in tools {
948 self.update(tool);
949 }
950 }
951
952 pub fn update_from_other(&mut self, tools: &Tools) {
954 self.update_all(tools.0.iter().cloned());
955 }
956}
957
958pub fn update_option<T: Clone>(first: &Option<T>, other: &Option<T>) -> Option<T> {
959 other.clone().or_else(|| first.clone())
960}
961
962#[cfg(test)]
963mod tests {
964 use pretty_assertions::assert_eq;
965 use rstest::rstest;
966
967 use super::*;
968
969 #[test]
970 fn test_library_benchmark_config_update_from_all_when_default() {
971 assert_eq!(
972 LibraryBenchmarkConfig::default()
973 .update_from_all([Some(&LibraryBenchmarkConfig::default())]),
974 LibraryBenchmarkConfig::default()
975 );
976 }
977
978 #[test]
979 fn test_library_benchmark_config_update_from_all_when_no_tools_override() {
980 let base = LibraryBenchmarkConfig::default();
981 let other = LibraryBenchmarkConfig {
982 env_clear: Some(true),
983 callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
984 valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
985 envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
986 flamegraph_config: Some(FlamegraphConfig::default()),
987 regression_config: Some(RegressionConfig::default()),
988 tools: Tools(vec![Tool {
989 kind: ValgrindTool::DHAT,
990 enable: None,
991 raw_args: RawArgs(vec![]),
992 show_log: None,
993 }]),
994 tools_override: None,
995 entry_point: None,
996 output_format: None,
997 };
998
999 assert_eq!(base.update_from_all([Some(&other.clone())]), other);
1000 }
1001
1002 #[test]
1003 fn test_library_benchmark_config_update_from_all_when_tools_override() {
1004 let base = LibraryBenchmarkConfig::default();
1005 let other = LibraryBenchmarkConfig {
1006 env_clear: Some(true),
1007 callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
1008 valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
1009 envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
1010 flamegraph_config: Some(FlamegraphConfig::default()),
1011 regression_config: Some(RegressionConfig::default()),
1012 tools: Tools(vec![Tool {
1013 kind: ValgrindTool::DHAT,
1014 enable: None,
1015 raw_args: RawArgs(vec![]),
1016 show_log: None,
1017 }]),
1018 tools_override: Some(Tools(vec![])),
1019 entry_point: Some(EntryPoint::default()),
1020 output_format: Some(OutputFormat::default()),
1021 };
1022 let expected = LibraryBenchmarkConfig {
1023 tools: other.tools_override.as_ref().unwrap().clone(),
1024 tools_override: None,
1025 ..other.clone()
1026 };
1027
1028 assert_eq!(base.update_from_all([Some(&other)]), expected);
1029 }
1030
1031 #[rstest]
1032 #[case::env_clear(
1033 LibraryBenchmarkConfig {
1034 env_clear: Some(true),
1035 ..Default::default()
1036 }
1037 )]
1038 fn test_library_benchmark_config_update_from_all_truncate_description(
1039 #[case] config: LibraryBenchmarkConfig,
1040 ) {
1041 let actual = LibraryBenchmarkConfig::default().update_from_all([Some(&config)]);
1042 assert_eq!(actual, config);
1043 }
1044
1045 #[rstest]
1046 #[case::all_none(None, None, None)]
1047 #[case::some_and_none(Some(true), None, Some(true))]
1048 #[case::none_and_some(None, Some(true), Some(true))]
1049 #[case::some_and_some(Some(false), Some(true), Some(true))]
1050 #[case::some_and_some_value_does_not_matter(Some(true), Some(false), Some(false))]
1051 fn test_update_option(
1052 #[case] first: Option<bool>,
1053 #[case] other: Option<bool>,
1054 #[case] expected: Option<bool>,
1055 ) {
1056 assert_eq!(update_option(&first, &other), expected);
1057 }
1058
1059 #[rstest]
1060 #[case::empty(vec![], &[], vec![])]
1061 #[case::empty_base(vec![], &["--a=yes"], vec!["--a=yes"])]
1062 #[case::no_flags(vec![], &["a=yes"], vec!["--a=yes"])]
1063 #[case::already_exists_single(vec!["--a=yes"], &["--a=yes"], vec!["--a=yes","--a=yes"])]
1064 #[case::already_exists_when_multiple(vec!["--a=yes", "--b=yes"], &["--a=yes"], vec!["--a=yes", "--b=yes", "--a=yes"])]
1065 fn test_raw_args_extend_ignore_flags(
1066 #[case] base: Vec<&str>,
1067 #[case] data: &[&str],
1068 #[case] expected: Vec<&str>,
1069 ) {
1070 let mut base = RawArgs(base.iter().map(std::string::ToString::to_string).collect());
1071 base.extend_ignore_flag(data.iter().map(std::string::ToString::to_string));
1072
1073 assert_eq!(base.0.into_iter().collect::<Vec<String>>(), expected);
1074 }
1075}