1use crate::{UtilsError, UtilsResult};
7use std::collections::HashMap;
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone)]
12pub struct Timer {
13 start: Option<Instant>,
14 measurements: HashMap<String, Duration>,
15}
16
17impl Timer {
18 pub fn new() -> Self {
20 Self {
21 start: None,
22 measurements: HashMap::new(),
23 }
24 }
25
26 pub fn start(&mut self) {
28 self.start = Some(Instant::now());
29 }
30
31 pub fn stop(&mut self) -> UtilsResult<Duration> {
33 let start = self
34 .start
35 .take()
36 .ok_or_else(|| UtilsError::InvalidParameter("Timer not started".to_string()))?;
37 Ok(start.elapsed())
38 }
39
40 pub fn stop_and_store(&mut self, label: String) -> UtilsResult<Duration> {
42 let duration = self.stop()?;
43 self.measurements.insert(label, duration);
44 Ok(duration)
45 }
46
47 pub fn time<F, R>(&mut self, f: F) -> (R, Duration)
49 where
50 F: FnOnce() -> R,
51 {
52 let start = Instant::now();
53 let result = f();
54 let duration = start.elapsed();
55 (result, duration)
56 }
57
58 pub fn time_and_store<F, R>(&mut self, label: String, f: F) -> R
60 where
61 F: FnOnce() -> R,
62 {
63 let (result, duration) = self.time(f);
64 self.measurements.insert(label, duration);
65 result
66 }
67
68 pub fn measurements(&self) -> &HashMap<String, Duration> {
70 &self.measurements
71 }
72
73 pub fn get_measurement(&self, label: &str) -> Option<Duration> {
75 self.measurements.get(label).copied()
76 }
77
78 pub fn clear(&mut self) {
80 self.measurements.clear();
81 }
82
83 pub fn summary(&self) -> TimerSummary {
85 let mut total = Duration::from_secs(0);
86 let mut min = Duration::from_secs(u64::MAX);
87 let mut max = Duration::from_secs(0);
88
89 for &duration in self.measurements.values() {
90 total += duration;
91 min = min.min(duration);
92 max = max.max(duration);
93 }
94
95 let count = self.measurements.len();
96 let average = if count > 0 {
97 total / count as u32
98 } else {
99 Duration::from_secs(0)
100 };
101
102 TimerSummary {
103 count,
104 total,
105 average,
106 min: if min == Duration::from_secs(u64::MAX) {
107 Duration::from_secs(0)
108 } else {
109 min
110 },
111 max,
112 }
113 }
114}
115
116impl Default for Timer {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub struct TimerSummary {
125 pub count: usize,
126 pub total: Duration,
127 pub average: Duration,
128 pub min: Duration,
129 pub max: Duration,
130}
131
132#[derive(Debug, Clone)]
134pub struct MemoryTracker {
135 baseline: Option<usize>,
136 measurements: HashMap<String, usize>,
137}
138
139impl MemoryTracker {
140 pub fn new() -> Self {
142 Self {
143 baseline: None,
144 measurements: HashMap::new(),
145 }
146 }
147
148 pub fn set_baseline(&mut self) {
150 if let Ok(usage) = get_memory_usage() {
151 self.baseline = Some(usage);
152 }
153 }
154
155 pub fn record(&mut self, label: String) -> UtilsResult<usize> {
157 let usage = get_memory_usage()?;
158 self.measurements.insert(label, usage);
159 Ok(usage)
160 }
161
162 pub fn relative_usage(&self) -> UtilsResult<Option<isize>> {
164 if let Some(baseline) = self.baseline {
165 let current = get_memory_usage()?;
166 Ok(Some(current as isize - baseline as isize))
167 } else {
168 Ok(None)
169 }
170 }
171
172 pub fn measurements(&self) -> &HashMap<String, usize> {
174 &self.measurements
175 }
176
177 pub fn peak_usage(&self) -> Option<usize> {
179 self.measurements.values().max().copied()
180 }
181
182 pub fn clear(&mut self) {
184 self.measurements.clear();
185 self.baseline = None;
186 }
187}
188
189impl Default for MemoryTracker {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct Profiler {
198 timer: Timer,
199 memory: MemoryTracker,
200 active_sessions: HashMap<String, (Instant, usize)>,
201}
202
203impl Profiler {
204 pub fn new() -> Self {
206 Self {
207 timer: Timer::new(),
208 memory: MemoryTracker::new(),
209 active_sessions: HashMap::new(),
210 }
211 }
212
213 pub fn start_session(&mut self, name: String) -> UtilsResult<()> {
215 let start_time = Instant::now();
216 let start_memory = get_memory_usage()?;
217 self.active_sessions
218 .insert(name, (start_time, start_memory));
219 Ok(())
220 }
221
222 pub fn end_session(&mut self, name: &str) -> UtilsResult<ProfileResult> {
224 let (start_time, start_memory) = self
225 .active_sessions
226 .remove(name)
227 .ok_or_else(|| UtilsError::InvalidParameter(format!("Session '{name}' not found")))?;
228
229 let duration = start_time.elapsed();
230 let end_memory = get_memory_usage()?;
231 let memory_delta = end_memory as isize - start_memory as isize;
232
233 self.timer.measurements.insert(name.to_string(), duration);
234 self.memory
235 .measurements
236 .insert(name.to_string(), end_memory);
237
238 Ok(ProfileResult {
239 name: name.to_string(),
240 duration,
241 memory_delta,
242 start_memory,
243 end_memory,
244 })
245 }
246
247 pub fn profile<F, R>(&mut self, name: String, f: F) -> UtilsResult<(R, ProfileResult)>
249 where
250 F: FnOnce() -> R,
251 {
252 self.start_session(name.clone())?;
253 let result = f();
254 let profile_result = self.end_session(&name)?;
255 Ok((result, profile_result))
256 }
257
258 pub fn timer(&self) -> &Timer {
260 &self.timer
261 }
262
263 pub fn memory(&self) -> &MemoryTracker {
265 &self.memory
266 }
267
268 pub fn clear(&mut self) {
270 self.timer.clear();
271 self.memory.clear();
272 self.active_sessions.clear();
273 }
274
275 pub fn report(&self) -> ProfileReport {
277 ProfileReport {
278 timer_summary: self.timer.summary(),
279 peak_memory: self.memory.peak_usage(),
280 total_sessions: self.timer.measurements.len(),
281 active_sessions: self.active_sessions.len(),
282 }
283 }
284}
285
286impl Default for Profiler {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292#[derive(Debug, Clone)]
294pub struct ProfileResult {
295 pub name: String,
296 pub duration: Duration,
297 pub memory_delta: isize,
298 pub start_memory: usize,
299 pub end_memory: usize,
300}
301
302#[derive(Debug, Clone)]
304pub struct ProfileReport {
305 pub timer_summary: TimerSummary,
306 pub peak_memory: Option<usize>,
307 pub total_sessions: usize,
308 pub active_sessions: usize,
309}
310
311#[derive(Debug)]
313pub struct Benchmark {
314 name: String,
315 iterations: usize,
316 warmup_iterations: usize,
317 measurements: Vec<Duration>,
318}
319
320impl Benchmark {
321 pub fn new(name: String, iterations: usize) -> Self {
323 Self {
324 name,
325 iterations,
326 warmup_iterations: 10,
327 measurements: Vec::with_capacity(iterations),
328 }
329 }
330
331 pub fn with_warmup(mut self, warmup_iterations: usize) -> Self {
333 self.warmup_iterations = warmup_iterations;
334 self
335 }
336
337 pub fn run<F>(&mut self, mut f: F) -> BenchmarkResult
339 where
340 F: FnMut(),
341 {
342 for _ in 0..self.warmup_iterations {
344 f();
345 }
346
347 self.measurements.clear();
349 for _ in 0..self.iterations {
350 let start = Instant::now();
351 f();
352 let duration = start.elapsed();
353 self.measurements.push(duration);
354 }
355
356 self.analyze()
357 }
358
359 fn analyze(&self) -> BenchmarkResult {
360 let mut measurements = self.measurements.clone();
361 measurements.sort();
362
363 let len = measurements.len();
364 let total: Duration = measurements.iter().sum();
365 let average = total / len as u32;
366
367 let median = if len % 2 == 0 {
368 (measurements[len / 2 - 1] + measurements[len / 2]) / 2
369 } else {
370 measurements[len / 2]
371 };
372
373 let min = measurements[0];
374 let max = measurements[len - 1];
375
376 let p95_index = ((len as f64) * 0.95) as usize;
378 let p99_index = ((len as f64) * 0.99) as usize;
379 let p95 = measurements[p95_index.min(len - 1)];
380 let p99 = measurements[p99_index.min(len - 1)];
381
382 let variance_sum: f64 = measurements
384 .iter()
385 .map(|&d| {
386 let diff = d.as_secs_f64() - average.as_secs_f64();
387 diff * diff
388 })
389 .sum();
390 let variance = variance_sum / len as f64;
391 let std_dev = Duration::from_secs_f64(variance.sqrt());
392
393 BenchmarkResult {
394 name: self.name.clone(),
395 iterations: len,
396 total,
397 average,
398 median,
399 min,
400 max,
401 std_dev,
402 p95,
403 p99,
404 }
405 }
406}
407
408#[derive(Debug, Clone)]
410pub struct BenchmarkResult {
411 pub name: String,
412 pub iterations: usize,
413 pub total: Duration,
414 pub average: Duration,
415 pub median: Duration,
416 pub min: Duration,
417 pub max: Duration,
418 pub std_dev: Duration,
419 pub p95: Duration,
420 pub p99: Duration,
421}
422
423impl BenchmarkResult {
424 pub fn format(&self) -> String {
426 format!(
427 "Benchmark: {}\n\
428 Iterations: {}\n\
429 Average: {:?}\n\
430 Median: {:?}\n\
431 Min: {:?}\n\
432 Max: {:?}\n\
433 Std Dev: {:?}\n\
434 95th percentile: {:?}\n\
435 99th percentile: {:?}",
436 self.name,
437 self.iterations,
438 self.average,
439 self.median,
440 self.min,
441 self.max,
442 self.std_dev,
443 self.p95,
444 self.p99
445 )
446 }
447}
448
449fn get_memory_usage() -> UtilsResult<usize> {
451 #[cfg(target_os = "linux")]
452 {
453 use std::fs;
454 let status = fs::read_to_string("/proc/self/status").map_err(|e| {
455 UtilsError::InvalidParameter(format!("Failed to read memory info: {e}"))
456 })?;
457
458 for line in status.lines() {
459 if line.starts_with("VmRSS:") {
460 let parts: Vec<&str> = line.split_whitespace().collect();
461 if parts.len() >= 2 {
462 let kb: usize = parts[1].parse().map_err(|e| {
463 UtilsError::InvalidParameter(format!("Failed to parse memory: {e}"))
464 })?;
465 return Ok(kb * 1024); }
467 }
468 }
469 Err(UtilsError::InvalidParameter(
470 "Memory info not found".to_string(),
471 ))
472 }
473
474 #[cfg(target_os = "macos")]
475 {
476 use std::process::Command;
479 let output = Command::new("ps")
480 .args(["-o", "rss=", "-p"])
481 .arg(std::process::id().to_string())
482 .output()
483 .map_err(|e| {
484 UtilsError::InvalidParameter(format!("Failed to get memory usage: {e}"))
485 })?;
486
487 let output_str = String::from_utf8_lossy(&output.stdout);
488 let kb: usize = output_str
489 .trim()
490 .parse()
491 .map_err(|e| UtilsError::InvalidParameter(format!("Failed to parse memory: {e}")))?;
492 Ok(kb * 1024) }
494
495 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
496 {
497 Ok(0)
499 }
500}
501
502#[derive(Debug, Clone)]
504pub struct RegressionDetector {
505 baselines: HashMap<String, BaselineMetrics>,
506 threshold_factor: f64,
507 min_samples: usize,
508}
509
510#[derive(Debug, Clone)]
512pub struct BaselineMetrics {
513 pub average_duration: Duration,
514 pub std_dev: Duration,
515 pub sample_count: usize,
516 pub last_updated: std::time::SystemTime,
517 pub historical_durations: Vec<Duration>,
518}
519
520#[derive(Debug, Clone)]
522pub struct RegressionResult {
523 pub test_name: String,
524 pub current_duration: Duration,
525 pub baseline_average: Duration,
526 pub deviation_factor: f64,
527 pub is_regression: bool,
528 pub confidence_level: f64,
529}
530
531impl RegressionDetector {
532 pub fn new() -> Self {
534 Self {
535 baselines: HashMap::new(),
536 threshold_factor: 1.5, min_samples: 5,
538 }
539 }
540
541 pub fn with_threshold(mut self, threshold_factor: f64) -> Self {
543 self.threshold_factor = threshold_factor;
544 self
545 }
546
547 pub fn with_min_samples(mut self, min_samples: usize) -> Self {
549 self.min_samples = min_samples;
550 self
551 }
552
553 pub fn record_baseline(&mut self, test_name: String, duration: Duration) {
555 let test_name_clone = test_name.clone();
556 let entry = self
557 .baselines
558 .entry(test_name)
559 .or_insert_with(|| BaselineMetrics {
560 average_duration: Duration::from_secs(0),
561 std_dev: Duration::from_secs(0),
562 sample_count: 0,
563 last_updated: std::time::SystemTime::now(),
564 historical_durations: Vec::new(),
565 });
566
567 entry.historical_durations.push(duration);
568 entry.sample_count += 1;
569 entry.last_updated = std::time::SystemTime::now();
570
571 if entry.historical_durations.len() > 100 {
573 entry.historical_durations.remove(0);
574 }
575
576 let mut entry_clone = entry.clone();
578 let _ = entry; self.update_statistics(&mut entry_clone, &test_name_clone);
580
581 if let Some(stored_entry) = self.baselines.get_mut(&test_name_clone) {
583 *stored_entry = entry_clone;
584 }
585 }
586
587 pub fn check_regression(
589 &self,
590 test_name: &str,
591 current_duration: Duration,
592 ) -> Option<RegressionResult> {
593 let baseline = self.baselines.get(test_name)?;
594
595 if baseline.sample_count < self.min_samples {
596 return None;
597 }
598
599 let current_secs = current_duration.as_secs_f64();
600 let baseline_secs = baseline.average_duration.as_secs_f64();
601 let deviation_factor = current_secs / baseline_secs;
602
603 let is_regression = deviation_factor > self.threshold_factor;
604
605 let std_dev_secs = baseline.std_dev.as_secs_f64();
607 let z_score =
608 (current_secs - baseline_secs) / (std_dev_secs / (baseline.sample_count as f64).sqrt());
609 let confidence_level = if z_score > 0.0 {
610 1.0 - (-z_score * z_score / 2.0).exp() } else {
612 0.0
613 };
614
615 Some(RegressionResult {
616 test_name: test_name.to_string(),
617 current_duration,
618 baseline_average: baseline.average_duration,
619 deviation_factor,
620 is_regression,
621 confidence_level,
622 })
623 }
624
625 pub fn get_baseline(&self, test_name: &str) -> Option<&BaselineMetrics> {
627 self.baselines.get(test_name)
628 }
629
630 pub fn tracked_tests(&self) -> Vec<&String> {
632 self.baselines.keys().collect()
633 }
634
635 pub fn clear_baselines(&mut self) {
637 self.baselines.clear();
638 }
639
640 fn update_statistics(&mut self, baseline: &mut BaselineMetrics, test_name: &str) {
642 if baseline.historical_durations.is_empty() {
643 return;
644 }
645
646 let total: Duration = baseline.historical_durations.iter().sum();
648 baseline.average_duration = total / baseline.historical_durations.len() as u32;
649
650 let mean_secs = baseline.average_duration.as_secs_f64();
652 let variance_sum: f64 = baseline
653 .historical_durations
654 .iter()
655 .map(|d| {
656 let diff = d.as_secs_f64() - mean_secs;
657 diff * diff
658 })
659 .sum();
660
661 let variance = variance_sum / baseline.historical_durations.len() as f64;
662 baseline.std_dev = Duration::from_secs_f64(variance.sqrt());
663
664 self.baselines
666 .insert(test_name.to_string(), baseline.clone());
667 }
668}
669
670impl Default for RegressionDetector {
671 fn default() -> Self {
672 Self::new()
673 }
674}
675
676impl RegressionResult {
677 pub fn format(&self) -> String {
679 let status = if self.is_regression {
680 "REGRESSION DETECTED"
681 } else {
682 "OK"
683 };
684 format!(
685 "Test: {} [{}]\n\
686 Current: {:?}\n\
687 Baseline: {:?}\n\
688 Deviation: {:.2}x\n\
689 Confidence: {:.1}%",
690 self.test_name,
691 status,
692 self.current_duration,
693 self.baseline_average,
694 self.deviation_factor,
695 self.confidence_level * 100.0
696 )
697 }
698}
699
700#[allow(non_snake_case)]
701#[cfg(test)]
702mod tests {
703 use super::*;
704 use std::thread;
705
706 #[test]
707 fn test_timer_basic() {
708 let mut timer = Timer::new();
709
710 timer.start();
711 thread::sleep(Duration::from_millis(10));
712 let duration = timer.stop().unwrap();
713
714 assert!(duration >= Duration::from_millis(10));
715 assert!(duration < Duration::from_millis(50)); }
717
718 #[test]
719 fn test_timer_closure() {
720 let mut timer = Timer::new();
721
722 let (result, duration) = timer.time(|| {
723 thread::sleep(Duration::from_millis(10));
724 42
725 });
726
727 assert_eq!(result, 42);
728 assert!(duration >= Duration::from_millis(10));
729 }
730
731 #[test]
732 fn test_timer_store() {
733 let mut timer = Timer::new();
734
735 timer.start();
736 thread::sleep(Duration::from_millis(10));
737 timer.stop_and_store("test".to_string()).unwrap();
738
739 assert!(timer.get_measurement("test").is_some());
740 assert!(timer.get_measurement("test").unwrap() >= Duration::from_millis(10));
741 }
742
743 #[test]
744 fn test_timer_summary() {
745 let mut timer = Timer::new();
746
747 timer
748 .measurements
749 .insert("test1".to_string(), Duration::from_millis(100));
750 timer
751 .measurements
752 .insert("test2".to_string(), Duration::from_millis(200));
753 timer
754 .measurements
755 .insert("test3".to_string(), Duration::from_millis(300));
756
757 let summary = timer.summary();
758 assert_eq!(summary.count, 3);
759 assert_eq!(summary.min, Duration::from_millis(100));
760 assert_eq!(summary.max, Duration::from_millis(300));
761 assert_eq!(summary.average, Duration::from_millis(200));
762 }
763
764 #[test]
765 fn test_memory_tracker() {
766 let mut tracker = MemoryTracker::new();
767
768 tracker.set_baseline();
769 tracker.record("test".to_string()).unwrap();
770
771 assert!(tracker.measurements().contains_key("test"));
772 assert!(tracker.peak_usage().is_some());
773 }
774
775 #[test]
776 fn test_profiler() {
777 let mut profiler = Profiler::new();
778
779 let (result, profile_result) = profiler
780 .profile("test".to_string(), || {
781 thread::sleep(Duration::from_millis(10));
782 42
783 })
784 .unwrap();
785
786 assert_eq!(result, 42);
787 assert_eq!(profile_result.name, "test");
788 assert!(profile_result.duration >= Duration::from_millis(10));
789 }
790
791 #[test]
792 fn test_benchmark() {
793 let mut benchmark = Benchmark::new("test_benchmark".to_string(), 10).with_warmup(2);
794
795 let result = benchmark.run(|| {
796 thread::sleep(Duration::from_millis(1));
797 });
798
799 assert_eq!(result.name, "test_benchmark");
800 assert_eq!(result.iterations, 10);
801 assert!(result.average >= Duration::from_millis(1));
802 assert!(result.min <= result.median);
803 assert!(result.median <= result.max);
804 }
805
806 #[test]
807 fn test_benchmark_result_format() {
808 let result = BenchmarkResult {
809 name: "test".to_string(),
810 iterations: 100,
811 total: Duration::from_millis(1000),
812 average: Duration::from_millis(10),
813 median: Duration::from_millis(9),
814 min: Duration::from_millis(5),
815 max: Duration::from_millis(20),
816 std_dev: Duration::from_millis(2),
817 p95: Duration::from_millis(15),
818 p99: Duration::from_millis(18),
819 };
820
821 let formatted = result.format();
822 assert!(formatted.contains("test"));
823 assert!(formatted.contains("100"));
824 assert!(formatted.contains("10ms"));
825 }
826
827 #[test]
828 fn test_profiler_sessions() {
829 let mut profiler = Profiler::new();
830
831 profiler.start_session("session1".to_string()).unwrap();
832 thread::sleep(Duration::from_millis(10));
833 let result = profiler.end_session("session1").unwrap();
834
835 assert_eq!(result.name, "session1");
836 assert!(result.duration >= Duration::from_millis(10));
837 }
838
839 #[test]
840 fn test_profiler_report() {
841 let mut profiler = Profiler::new();
842
843 profiler.profile("test1".to_string(), || {}).unwrap();
844 profiler.profile("test2".to_string(), || {}).unwrap();
845
846 let report = profiler.report();
847 assert_eq!(report.total_sessions, 2);
848 assert_eq!(report.active_sessions, 0);
849 }
850
851 #[test]
852 fn test_regression_detector_baseline() {
853 let mut detector = RegressionDetector::new();
854
855 for i in 0..10 {
857 detector.record_baseline("test_func".to_string(), Duration::from_millis(100 + i));
858 }
859
860 let baseline = detector.get_baseline("test_func").unwrap();
861 assert_eq!(baseline.sample_count, 10);
862 assert!(baseline.average_duration >= Duration::from_millis(100));
863 assert!(baseline.average_duration <= Duration::from_millis(110));
864 }
865
866 #[test]
867 fn test_regression_detector_no_regression() {
868 let mut detector = RegressionDetector::new().with_min_samples(3);
869
870 for _ in 0..5 {
872 detector.record_baseline("test_func".to_string(), Duration::from_millis(100));
873 }
874
875 let result = detector.check_regression("test_func", Duration::from_millis(105));
877 assert!(result.is_some());
878 let result = result.unwrap();
879 assert!(!result.is_regression);
880 assert_eq!(result.test_name, "test_func");
881 }
882
883 #[test]
884 fn test_regression_detector_with_regression() {
885 let mut detector = RegressionDetector::new()
886 .with_min_samples(3)
887 .with_threshold(1.5);
888
889 for _ in 0..5 {
891 detector.record_baseline("test_func".to_string(), Duration::from_millis(100));
892 }
893
894 let result = detector.check_regression("test_func", Duration::from_millis(200));
896 assert!(result.is_some());
897 let result = result.unwrap();
898 assert!(result.is_regression);
899 assert!(result.deviation_factor > 1.5);
900 }
901
902 #[test]
903 fn test_regression_detector_insufficient_samples() {
904 let mut detector = RegressionDetector::new().with_min_samples(5);
905
906 for _ in 0..2 {
908 detector.record_baseline("test_func".to_string(), Duration::from_millis(100));
909 }
910
911 let result = detector.check_regression("test_func", Duration::from_millis(200));
913 assert!(result.is_none());
914 }
915
916 #[test]
917 fn test_regression_detector_tracked_tests() {
918 let mut detector = RegressionDetector::new();
919
920 detector.record_baseline("test1".to_string(), Duration::from_millis(100));
921 detector.record_baseline("test2".to_string(), Duration::from_millis(200));
922
923 let tracked = detector.tracked_tests();
924 assert_eq!(tracked.len(), 2);
925 assert!(tracked.contains(&&"test1".to_string()));
926 assert!(tracked.contains(&&"test2".to_string()));
927 }
928
929 #[test]
930 fn test_regression_result_format() {
931 let result = RegressionResult {
932 test_name: "test_function".to_string(),
933 current_duration: Duration::from_millis(200),
934 baseline_average: Duration::from_millis(100),
935 deviation_factor: 2.0,
936 is_regression: true,
937 confidence_level: 0.95,
938 };
939
940 let formatted = result.format();
941 assert!(formatted.contains("test_function"));
942 assert!(formatted.contains("REGRESSION DETECTED"));
943 assert!(formatted.contains("2.00x"));
944 assert!(formatted.contains("95.0%"));
945 }
946
947 #[test]
948 fn test_regression_detector_clear() {
949 let mut detector = RegressionDetector::new();
950
951 detector.record_baseline("test1".to_string(), Duration::from_millis(100));
952 detector.record_baseline("test2".to_string(), Duration::from_millis(200));
953
954 assert_eq!(detector.tracked_tests().len(), 2);
955
956 detector.clear_baselines();
957 assert_eq!(detector.tracked_tests().len(), 0);
958 }
959}