1use crate::{BenchCases, BenchGroup, BenchRounds, BenchRoutine, BenchSentRoutine, BenchStated};
4use alux_ext::ext;
5use core::time::Duration;
6
7pub trait BenchAlg {
13 type Bench;
15
16 fn nothing(&self) -> Self::Bench;
18
19 fn group(&self, group: &'static str, cases: BenchCases<'static>) -> Self::Bench;
21
22 fn then(&self, first: Self::Bench, next: Self::Bench) -> Self::Bench;
24}
25
26pub trait MeasureBenchAlg {
31 type Bench;
33
34 fn measure(&mut self, bench: Self::Bench);
36}
37
38#[ext(name = BenchExt)]
40pub impl<This> This
41where
42 This: BenchAlg,
43{
44 fn one_at_a_time<Stated, Routine>(&self, group: &'static str, cases: Stated) -> This::Bench
49 where
50 Stated: IntoIterator<Item = (&'static str, Routine)>,
51 Routine: FnMut(BenchRounds) -> Duration + 'static,
52 {
53 let cases = cases.into_iter().map(|(subject, routine)| (subject, Box::new(routine) as BenchRoutine<'static>));
54
55 self.group(group, BenchCases::OneAtATime(cases.collect()))
56 }
57
58 fn together<Stated, Routine>(&self, group: &'static str, cases: Stated) -> This::Bench
60 where
61 Stated: IntoIterator<Item = (&'static str, Routine)>,
62 Routine: FnMut(BenchRounds) -> Duration + Send + 'static,
63 {
64 let cases = cases.into_iter().map(|(subject, routine)| (subject, Box::new(routine) as BenchSentRoutine));
65
66 self.group(group, BenchCases::Together(cases.collect()))
67 }
68
69 fn benches<Stated>(&self, stated: Stated) -> This::Bench
71 where
72 Stated: IntoIterator<Item = This::Bench>,
73 {
74 stated.into_iter().fold(self.nothing(), |bench, next| self.then(bench, next))
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default)]
80pub struct StatingBench;
81
82impl BenchAlg for StatingBench {
83 type Bench = BenchStated<'static>;
84
85 fn nothing(&self) -> Self::Bench {
86 BenchStated::new()
87 }
88
89 fn group(&self, group: &'static str, cases: BenchCases<'static>) -> Self::Bench {
90 BenchStated::new().group(BenchGroup::new(group, cases))
91 }
92
93 fn then(&self, first: Self::Bench, next: Self::Bench) -> Self::Bench {
94 first.then(next)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::BenchRounds;
102 use core::time::Duration;
103
104 fn case(subject: &'static str) -> (&'static str, BenchRoutine<'static>) {
106 (subject, Box::new(|rounds: BenchRounds| Duration::from_millis(rounds.count())))
107 }
108
109 #[test]
110 fn states_groups_in_the_order_stated() {
111 let stating = StatingBench;
112
113 let bench = stating.benches([
114 stating.one_at_a_time("close", [case("hyper"), case("axum")]),
115 stating.together("end", [("poem", Box::new(|_| Duration::from_secs(1)) as BenchSentRoutine)]),
116 ]);
117
118 let groups = bench.groups().map(|group| (group.group(), group.stated().len())).collect::<Vec<_>>();
119 assert_eq!(groups, [("close", 2), ("end", 1)]);
120 }
121
122 #[test]
123 fn states_what_measuring_a_group_at_once_would_cost() {
124 let stating = StatingBench;
125
126 let bench = stating.one_at_a_time("close", [case("hyper")]);
127
128 let stated = bench.groups().next().expect("the group stated");
129 assert!(matches!(stated.stated(), BenchCases::OneAtATime(_)));
130 assert_eq!(stated.stated().subjects().collect::<Vec<_>>(), ["hyper"]);
131 }
132}