1use std::str::FromStr;
9use std::time::{Duration, Instant};
10use std::{env, fmt};
11
12use super::types::{TestDesc, TestType};
13
14pub(crate) const TEST_WARN_TIMEOUT_S: u64 = 60;
15
16pub(crate) mod time_constants {
26 use std::time::Duration;
27
28 use super::TEST_WARN_TIMEOUT_S;
29
30 pub(crate) const UNIT_ENV_NAME: &str = "RUST_TEST_TIME_UNIT";
32
33 pub(crate) const UNIT_WARN: Duration = Duration::from_millis(50);
35 pub(crate) const UNIT_CRITICAL: Duration = Duration::from_millis(100);
36
37 pub(crate) const INTEGRATION_ENV_NAME: &str = "RUST_TEST_TIME_INTEGRATION";
39
40 pub(crate) const INTEGRATION_WARN: Duration = Duration::from_millis(500);
42 pub(crate) const INTEGRATION_CRITICAL: Duration = Duration::from_millis(1000);
43
44 pub(crate) const DOCTEST_ENV_NAME: &str = "RUST_TEST_TIME_DOCTEST";
46
47 pub(crate) const DOCTEST_WARN: Duration = INTEGRATION_WARN;
50 pub(crate) const DOCTEST_CRITICAL: Duration = INTEGRATION_CRITICAL;
51
52 pub(crate) const UNKNOWN_WARN: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S);
55 pub(crate) const UNKNOWN_CRITICAL: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S * 2);
56}
57
58pub(crate) fn get_default_test_timeout() -> Instant {
61 Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S)
62}
63
64#[derive(Debug, Clone, PartialEq)]
66pub struct TestExecTime(pub Duration);
67
68impl fmt::Display for TestExecTime {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{:.3}s", self.0.as_secs_f64())
71 }
72}
73
74#[derive(Debug, Clone, Default, PartialEq)]
76pub(crate) struct TestSuiteExecTime(pub Duration);
77
78impl fmt::Display for TestSuiteExecTime {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{:.2}s", self.0.as_secs_f64())
81 }
82}
83
84#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
86pub struct TimeThreshold {
87 pub warn: Duration,
88 pub critical: Duration,
89}
90
91impl TimeThreshold {
92 pub fn new(warn: Duration, critical: Duration) -> Self {
94 Self { warn, critical }
95 }
96
97 pub fn from_env_var(env_var_name: &str) -> Option<Self> {
107 let durations_str = env::var(env_var_name).ok()?;
108 let (warn_str, critical_str) = durations_str.split_once(',').unwrap_or_else(|| {
109 panic!(
110 "Duration variable {env_var_name} expected to have 2 numbers separated by comma, but got {durations_str}"
111 )
112 });
113
114 let parse_u64 = |v| {
115 u64::from_str(v).unwrap_or_else(|_| {
116 panic!(
117 "Duration value in variable {env_var_name} is expected to be a number, but got {v}"
118 )
119 })
120 };
121
122 let warn = parse_u64(warn_str);
123 let critical = parse_u64(critical_str);
124 if warn > critical {
125 panic!("Test execution warn time should be less or equal to the critical time");
126 }
127
128 Some(Self::new(Duration::from_millis(warn), Duration::from_millis(critical)))
129 }
130}
131
132#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
134pub struct TestTimeOptions {
135 pub error_on_excess: bool,
138 pub unit_threshold: TimeThreshold,
139 pub integration_threshold: TimeThreshold,
140 pub doctest_threshold: TimeThreshold,
141}
142
143impl TestTimeOptions {
144 pub fn new_from_env(error_on_excess: bool) -> Self {
145 let unit_threshold = TimeThreshold::from_env_var(time_constants::UNIT_ENV_NAME)
146 .unwrap_or_else(Self::default_unit);
147
148 let integration_threshold =
149 TimeThreshold::from_env_var(time_constants::INTEGRATION_ENV_NAME)
150 .unwrap_or_else(Self::default_integration);
151
152 let doctest_threshold = TimeThreshold::from_env_var(time_constants::DOCTEST_ENV_NAME)
153 .unwrap_or_else(Self::default_doctest);
154
155 Self { error_on_excess, unit_threshold, integration_threshold, doctest_threshold }
156 }
157
158 pub fn is_warn(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool {
159 exec_time.0 >= self.warn_time(test)
160 }
161
162 pub fn is_critical(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool {
163 exec_time.0 >= self.critical_time(test)
164 }
165
166 fn warn_time(&self, test: &TestDesc) -> Duration {
167 match test.test_type {
168 TestType::UnitTest => self.unit_threshold.warn,
169 TestType::IntegrationTest => self.integration_threshold.warn,
170 TestType::DocTest => self.doctest_threshold.warn,
171 TestType::Unknown => time_constants::UNKNOWN_WARN,
172 }
173 }
174
175 fn critical_time(&self, test: &TestDesc) -> Duration {
176 match test.test_type {
177 TestType::UnitTest => self.unit_threshold.critical,
178 TestType::IntegrationTest => self.integration_threshold.critical,
179 TestType::DocTest => self.doctest_threshold.critical,
180 TestType::Unknown => time_constants::UNKNOWN_CRITICAL,
181 }
182 }
183
184 fn default_unit() -> TimeThreshold {
185 TimeThreshold::new(time_constants::UNIT_WARN, time_constants::UNIT_CRITICAL)
186 }
187
188 fn default_integration() -> TimeThreshold {
189 TimeThreshold::new(time_constants::INTEGRATION_WARN, time_constants::INTEGRATION_CRITICAL)
190 }
191
192 fn default_doctest() -> TimeThreshold {
193 TimeThreshold::new(time_constants::DOCTEST_WARN, time_constants::DOCTEST_CRITICAL)
194 }
195}