1 | use std::{cmp::Ordering, error::Error, str::FromStr, time::Duration}; |
2 | |
3 | use crate::util::sort::natural_cmp; |
4 | |
5 | pub mod filter; |
6 | |
7 | /// `Duration` wrapper for parsing seconds from the CLI. |
8 | #[derive (Clone, Copy)] |
9 | pub(crate) struct ParsedSeconds(pub Duration); |
10 | |
11 | impl FromStr for ParsedSeconds { |
12 | type Err = Box<dyn Error + Send + Sync>; |
13 | |
14 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
15 | Ok(Self(Duration::try_from_secs_f64(secs:f64::from_str(s)?)?)) |
16 | } |
17 | } |
18 | |
19 | /// The primary action to perform. |
20 | #[derive (Clone, Copy, Default)] |
21 | pub(crate) enum Action { |
22 | /// Run benchmark loops. |
23 | #[default] |
24 | Bench, |
25 | |
26 | /// Run benchmarked functions once to ensure they run successfully. |
27 | Test, |
28 | |
29 | /// List benchmarks. |
30 | List, |
31 | |
32 | /// List benchmarks in the style of `cargo test --list --format terse`. |
33 | /// |
34 | /// This only applies when running under `cargo-nextest` (`NEXTEST=1`). |
35 | ListTerse, |
36 | } |
37 | |
38 | #[allow (dead_code)] |
39 | impl Action { |
40 | #[inline ] |
41 | pub fn is_bench(&self) -> bool { |
42 | matches!(self, Self::Bench) |
43 | } |
44 | |
45 | #[inline ] |
46 | pub fn is_test(&self) -> bool { |
47 | matches!(self, Self::Test) |
48 | } |
49 | |
50 | #[inline ] |
51 | pub fn is_list(&self) -> bool { |
52 | matches!(self, Self::List) |
53 | } |
54 | |
55 | #[inline ] |
56 | pub fn is_list_terse(&self) -> bool { |
57 | matches!(self, Self::ListTerse) |
58 | } |
59 | } |
60 | |
61 | /// How to treat benchmarks based on whether they're marked as `#[ignore]`. |
62 | #[derive (Copy, Clone, Default)] |
63 | pub(crate) enum RunIgnored { |
64 | /// Skip ignored. |
65 | #[default] |
66 | No, |
67 | |
68 | /// `--include-ignored`. |
69 | Yes, |
70 | |
71 | /// `--ignored`. |
72 | Only, |
73 | } |
74 | |
75 | impl RunIgnored { |
76 | pub fn run_ignored(self) -> bool { |
77 | matches!(self, Self::Yes | Self::Only) |
78 | } |
79 | |
80 | pub fn run_non_ignored(self) -> bool { |
81 | matches!(self, Self::Yes | Self::No) |
82 | } |
83 | |
84 | pub fn should_run(self, ignored: bool) -> bool { |
85 | if ignored { |
86 | self.run_ignored() |
87 | } else { |
88 | self.run_non_ignored() |
89 | } |
90 | } |
91 | } |
92 | |
93 | /// The attribute to sort benchmarks by. |
94 | #[derive (Clone, Copy, Default)] |
95 | pub(crate) enum SortingAttr { |
96 | /// Sort by kind, then by name and location. |
97 | #[default] |
98 | Kind, |
99 | |
100 | /// Sort by name, then by location and kind. |
101 | Name, |
102 | |
103 | /// Sort by location, then by kind and name. |
104 | Location, |
105 | } |
106 | |
107 | impl SortingAttr { |
108 | /// Returns an array containing `self` along with other attributes that |
109 | /// should break ties if attributes are equal. |
110 | pub fn with_tie_breakers(self) -> [Self; 3] { |
111 | use SortingAttr::*; |
112 | |
113 | match self { |
114 | Kind => [self, Name, Location], |
115 | Name => [self, Location, Kind], |
116 | Location => [self, Kind, Name], |
117 | } |
118 | } |
119 | |
120 | /// Compares benchmark runtime argument names. |
121 | /// |
122 | /// This takes `&&str` to handle `SortingAttr::Location` since the strings |
123 | /// are considered to be within the same `&[&str]`. |
124 | pub fn cmp_bench_arg_names(self, a: &&str, b: &&str) -> Ordering { |
125 | for attr in self.with_tie_breakers() { |
126 | let ordering = match attr { |
127 | SortingAttr::Kind => Ordering::Equal, |
128 | |
129 | SortingAttr::Name => 'ordering: { |
130 | // Compare as integers. |
131 | match (a.parse::<u128>(), a.parse::<u128>()) { |
132 | (Ok(a_u128), Ok(b_u128)) => break 'ordering a_u128.cmp(&b_u128), |
133 | |
134 | (Ok(_), Err(_)) => { |
135 | if b.parse::<i128>().is_ok() { |
136 | // a > b, because b is negative. |
137 | break 'ordering Ordering::Greater; |
138 | } |
139 | } |
140 | |
141 | (Err(_), Ok(_)) => { |
142 | if a.parse::<i128>().is_ok() { |
143 | // a < b, because a is negative. |
144 | break 'ordering Ordering::Less; |
145 | } |
146 | } |
147 | |
148 | (Err(_), Err(_)) => { |
149 | if let (Ok(a_i128), Ok(b_i128)) = (a.parse::<i128>(), a.parse::<i128>()) |
150 | { |
151 | break 'ordering a_i128.cmp(&b_i128); |
152 | } |
153 | } |
154 | } |
155 | |
156 | // Compare as floats. |
157 | if let (Ok(a), Ok(b)) = (a.parse::<f64>(), b.parse::<f64>()) { |
158 | if let Some(ordering) = a.partial_cmp(&b) { |
159 | break 'ordering ordering; |
160 | } |
161 | } |
162 | |
163 | natural_cmp(a, b) |
164 | } |
165 | |
166 | SortingAttr::Location => { |
167 | let a: *const &str = a; |
168 | let b: *const &str = b; |
169 | a.cmp(&b) |
170 | } |
171 | }; |
172 | |
173 | if ordering != Ordering::Equal { |
174 | return ordering; |
175 | } |
176 | } |
177 | |
178 | Ordering::Equal |
179 | } |
180 | } |
181 | |