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