nextest_runner/time/
stopwatch.rs1use chrono::{DateTime, Local};
11use std::time::{Duration, Instant};
12
13pub(crate) fn stopwatch() -> StopwatchStart {
14 StopwatchStart::new()
15}
16
17#[derive(Clone, Debug)]
19pub(crate) struct StopwatchStart {
20 start_time: DateTime<Local>,
21 instant: Instant,
22 paused_time: Duration,
23 pause_state: StopwatchPauseState,
24}
25
26impl StopwatchStart {
27 fn new() -> Self {
28 Self {
29 start_time: Local::now(),
32 instant: Instant::now(),
33 paused_time: Duration::ZERO,
34 pause_state: StopwatchPauseState::Running,
35 }
36 }
37
38 pub(crate) fn is_paused(&self) -> bool {
39 matches!(self.pause_state, StopwatchPauseState::Paused { .. })
40 }
41
42 pub(crate) fn pause(&mut self) {
43 match &self.pause_state {
44 StopwatchPauseState::Running => {
45 self.pause_state = StopwatchPauseState::Paused {
46 paused_at: Instant::now(),
47 };
48 }
49 StopwatchPauseState::Paused { .. } => {
50 panic!("illegal state transition: pause() called while stopwatch was paused")
51 }
52 }
53 }
54
55 pub(crate) fn resume(&mut self) {
56 match &self.pause_state {
57 StopwatchPauseState::Paused { paused_at } => {
58 self.paused_time += paused_at.elapsed();
59 self.pause_state = StopwatchPauseState::Running;
60 }
61 StopwatchPauseState::Running => {
62 panic!("illegal state transition: resume() called while stopwatch was running")
63 }
64 }
65 }
66
67 pub(crate) fn snapshot(&self) -> StopwatchSnapshot {
68 StopwatchSnapshot {
69 start_time: self.start_time,
70 active: self.instant.elapsed().saturating_sub(self.paused_time),
73 paused: self.paused_time,
74 }
75 }
76}
77
78#[derive(Clone, Copy, Debug)]
80pub(crate) struct StopwatchSnapshot {
81 pub(crate) start_time: DateTime<Local>,
83
84 pub(crate) active: Duration,
86
87 #[expect(dead_code)]
89 pub(crate) paused: Duration,
90}
91
92#[derive(Clone, Debug)]
93enum StopwatchPauseState {
94 Running,
95 Paused { paused_at: Instant },
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn stopwatch_pause() {
104 let mut start = stopwatch();
105 let unpaused_start = start.clone();
106
107 start.pause();
108 std::thread::sleep(Duration::from_millis(250));
109 start.resume();
110
111 start.pause();
112 std::thread::sleep(Duration::from_millis(300));
113 start.resume();
114
115 let end = start.snapshot();
116 let unpaused_end = unpaused_start.snapshot();
117
118 let difference = unpaused_end.active - end.active;
124 assert!(
125 difference > Duration::from_millis(450),
126 "difference between unpaused_end and end ({difference:?}) is at least 450ms"
127 );
128 }
129}