]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! Module `time` contains everything related to the time measurement of unit tests |
2 | //! execution. | |
3 | //! The purposes of this module: | |
4 | //! - Check whether test is timed out. | |
5 | //! - Provide helpers for `report-time` and `measure-time` options. | |
6 | //! - Provide newtypes for executions times. | |
7 | ||
8 | use std::env; | |
9 | use std::fmt; | |
10 | use std::str::FromStr; | |
11 | use std::time::{Duration, Instant}; | |
12 | ||
13 | use super::types::{TestDesc, TestType}; | |
14 | ||
15 | pub const TEST_WARN_TIMEOUT_S: u64 = 60; | |
16 | ||
17 | /// This small module contains constants used by `report-time` option. | |
18 | /// Those constants values will be used if corresponding environment variables are not set. | |
19 | /// | |
20 | /// To override values for unit-tests, use a constant `RUST_TEST_TIME_UNIT`, | |
21 | /// To override values for integration tests, use a constant `RUST_TEST_TIME_INTEGRATION`, | |
22 | /// To override values for doctests, use a constant `RUST_TEST_TIME_DOCTEST`. | |
23 | /// | |
24 | /// Example of the expected format is `RUST_TEST_TIME_xxx=100,200`, where 100 means | |
25 | /// warn time, and 200 means critical time. | |
26 | pub mod time_constants { | |
27 | use super::TEST_WARN_TIMEOUT_S; | |
28 | use std::time::Duration; | |
29 | ||
30 | /// Environment variable for overriding default threshold for unit-tests. | |
31 | pub const UNIT_ENV_NAME: &str = "RUST_TEST_TIME_UNIT"; | |
32 | ||
33 | // Unit tests are supposed to be really quick. | |
34 | pub const UNIT_WARN: Duration = Duration::from_millis(50); | |
35 | pub const UNIT_CRITICAL: Duration = Duration::from_millis(100); | |
36 | ||
37 | /// Environment variable for overriding default threshold for unit-tests. | |
38 | pub const INTEGRATION_ENV_NAME: &str = "RUST_TEST_TIME_INTEGRATION"; | |
39 | ||
40 | // Integration tests may have a lot of work, so they can take longer to execute. | |
41 | pub const INTEGRATION_WARN: Duration = Duration::from_millis(500); | |
42 | pub const INTEGRATION_CRITICAL: Duration = Duration::from_millis(1000); | |
43 | ||
44 | /// Environment variable for overriding default threshold for unit-tests. | |
45 | pub const DOCTEST_ENV_NAME: &str = "RUST_TEST_TIME_DOCTEST"; | |
46 | ||
47 | // Doctests are similar to integration tests, because they can include a lot of | |
48 | // initialization code. | |
49 | pub const DOCTEST_WARN: Duration = INTEGRATION_WARN; | |
50 | pub const DOCTEST_CRITICAL: Duration = INTEGRATION_CRITICAL; | |
51 | ||
52 | // Do not suppose anything about unknown tests, base limits on the | |
53 | // `TEST_WARN_TIMEOUT_S` constant. | |
54 | pub const UNKNOWN_WARN: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S); | |
55 | pub const UNKNOWN_CRITICAL: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S * 2); | |
56 | } | |
57 | ||
58 | /// Returns an `Instance` object denoting when the test should be considered | |
59 | /// timed out. | |
60 | pub fn get_default_test_timeout() -> Instant { | |
61 | Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S) | |
62 | } | |
63 | ||
64 | /// The measured execution time of a unit test. | |
65 | #[derive(Debug, Clone, PartialEq)] | |
66 | pub struct TestExecTime(pub Duration); | |
67 | ||
68 | impl 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 | /// The measured execution time of the whole test suite. | |
75 | #[derive(Debug, Clone, Default, PartialEq)] | |
76 | pub struct TestSuiteExecTime(pub Duration); | |
77 | ||
78 | impl 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 | /// Structure denoting time limits for test execution. | |
85 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | |
86 | pub struct TimeThreshold { | |
87 | pub warn: Duration, | |
88 | pub critical: Duration, | |
89 | } | |
90 | ||
91 | impl TimeThreshold { | |
92 | /// Creates a new `TimeThreshold` instance with provided durations. | |
93 | pub fn new(warn: Duration, critical: Duration) -> Self { | |
94 | Self { warn, critical } | |
95 | } | |
96 | ||
97 | /// Attempts to create a `TimeThreshold` instance with values obtained | |
98 | /// from the environment variable, and returns `None` if the variable | |
99 | /// is not set. | |
100 | /// Environment variable format is expected to match `\d+,\d+`. | |
101 | /// | |
102 | /// # Panics | |
103 | /// | |
104 | /// Panics if variable with provided name is set but contains inappropriate | |
105 | /// value. | |
106 | pub fn from_env_var(env_var_name: &str) -> Option<Self> { | |
107 | let durations_str = env::var(env_var_name).ok()?; | |
108 | ||
109 | // Split string into 2 substrings by comma and try to parse numbers. | |
110 | let mut durations = durations_str.splitn(2, ',').map(|v| { | |
111 | u64::from_str(v).unwrap_or_else(|_| { | |
112 | panic!( | |
113 | "Duration value in variable {} is expected to be a number, but got {}", | |
114 | env_var_name, v | |
115 | ) | |
116 | }) | |
117 | }); | |
118 | ||
119 | // Callback to be called if the environment variable has unexpected structure. | |
120 | let panic_on_incorrect_value = || { | |
121 | panic!( | |
122 | "Duration variable {} expected to have 2 numbers separated by comma, but got {}", | |
123 | env_var_name, durations_str | |
124 | ); | |
125 | }; | |
126 | ||
127 | let (warn, critical) = ( | |
128 | durations.next().unwrap_or_else(panic_on_incorrect_value), | |
129 | durations.next().unwrap_or_else(panic_on_incorrect_value), | |
130 | ); | |
131 | ||
132 | if warn > critical { | |
133 | panic!("Test execution warn time should be less or equal to the critical time"); | |
134 | } | |
135 | ||
136 | Some(Self::new(Duration::from_millis(warn), Duration::from_millis(critical))) | |
137 | } | |
138 | } | |
139 | ||
140 | /// Structure with parameters for calculating test execution time. | |
141 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | |
142 | pub struct TestTimeOptions { | |
143 | /// Denotes if the test critical execution time limit excess should be considered | |
144 | /// a test failure. | |
145 | pub error_on_excess: bool, | |
146 | pub colored: bool, | |
147 | pub unit_threshold: TimeThreshold, | |
148 | pub integration_threshold: TimeThreshold, | |
149 | pub doctest_threshold: TimeThreshold, | |
150 | } | |
151 | ||
152 | impl TestTimeOptions { | |
153 | pub fn new_from_env(error_on_excess: bool, colored: bool) -> Self { | |
154 | let unit_threshold = TimeThreshold::from_env_var(time_constants::UNIT_ENV_NAME) | |
155 | .unwrap_or_else(Self::default_unit); | |
156 | ||
157 | let integration_threshold = | |
158 | TimeThreshold::from_env_var(time_constants::INTEGRATION_ENV_NAME) | |
159 | .unwrap_or_else(Self::default_integration); | |
160 | ||
161 | let doctest_threshold = TimeThreshold::from_env_var(time_constants::DOCTEST_ENV_NAME) | |
162 | .unwrap_or_else(Self::default_doctest); | |
163 | ||
164 | Self { error_on_excess, colored, unit_threshold, integration_threshold, doctest_threshold } | |
165 | } | |
166 | ||
167 | pub fn is_warn(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool { | |
168 | exec_time.0 >= self.warn_time(test) | |
169 | } | |
170 | ||
171 | pub fn is_critical(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool { | |
172 | exec_time.0 >= self.critical_time(test) | |
173 | } | |
174 | ||
175 | fn warn_time(&self, test: &TestDesc) -> Duration { | |
176 | match test.test_type { | |
177 | TestType::UnitTest => self.unit_threshold.warn, | |
178 | TestType::IntegrationTest => self.integration_threshold.warn, | |
179 | TestType::DocTest => self.doctest_threshold.warn, | |
180 | TestType::Unknown => time_constants::UNKNOWN_WARN, | |
181 | } | |
182 | } | |
183 | ||
184 | fn critical_time(&self, test: &TestDesc) -> Duration { | |
185 | match test.test_type { | |
186 | TestType::UnitTest => self.unit_threshold.critical, | |
187 | TestType::IntegrationTest => self.integration_threshold.critical, | |
188 | TestType::DocTest => self.doctest_threshold.critical, | |
189 | TestType::Unknown => time_constants::UNKNOWN_CRITICAL, | |
190 | } | |
191 | } | |
192 | ||
193 | fn default_unit() -> TimeThreshold { | |
194 | TimeThreshold::new(time_constants::UNIT_WARN, time_constants::UNIT_CRITICAL) | |
195 | } | |
196 | ||
197 | fn default_integration() -> TimeThreshold { | |
198 | TimeThreshold::new(time_constants::INTEGRATION_WARN, time_constants::INTEGRATION_CRITICAL) | |
199 | } | |
200 | ||
201 | fn default_doctest() -> TimeThreshold { | |
202 | TimeThreshold::new(time_constants::DOCTEST_WARN, time_constants::DOCTEST_CRITICAL) | |
203 | } | |
204 | } |