1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! 模块 `time` 包含与单元测试执行的时间度量有关的所有内容。
//!
//! 该模块的目的:
//! - 检查测试是否超时。
//! - 为 `report-time` 和 `measure-time` 选项提供帮助。
//! - 提供执行时间的新类型。

use std::env;
use std::fmt;
use std::str::FromStr;
use std::time::{Duration, Instant};

use super::types::{TestDesc, TestType};

pub const TEST_WARN_TIMEOUT_S: u64 = 60;

/// 这个小模块包含 `report-time` 选项使用的常量。
/// 如果未设置相应的环境变量,则将使用这些常量值。
///
/// 要覆盖单元测试的值,请使用常量 `RUST_TEST_TIME_UNIT`; 要覆盖集成测试的值,请使用常量 `RUST_TEST_TIME_INTEGRATION`; 要覆盖 doctests 的值,请使用常量 `RUST_TEST_TIME_DOCTEST`。
///
///
/// 预期格式的示例是 `RUST_TEST_TIME_xxx=100,200`,其中 100 表示警告时间,而 200 表示临界时间。
///
///
pub mod time_constants {
    use super::TEST_WARN_TIMEOUT_S;
    use std::time::Duration;

    /// 用于覆盖单元测试的默认阈值的环境变量。
    pub const UNIT_ENV_NAME: &str = "RUST_TEST_TIME_UNIT";

    // 单元测试应该是非常快速的。
    pub const UNIT_WARN: Duration = Duration::from_millis(50);
    pub const UNIT_CRITICAL: Duration = Duration::from_millis(100);

    /// 用于覆盖单元测试的默认阈值的环境变量。
    pub const INTEGRATION_ENV_NAME: &str = "RUST_TEST_TIME_INTEGRATION";

    // 集成测试可能有很多工作要做,因此它们可能需要更长的时间才能执行。
    pub const INTEGRATION_WARN: Duration = Duration::from_millis(500);
    pub const INTEGRATION_CRITICAL: Duration = Duration::from_millis(1000);

    /// 用于覆盖单元测试的默认阈值的环境变量。
    pub const DOCTEST_ENV_NAME: &str = "RUST_TEST_TIME_DOCTEST";

    // Doctests 与集成测试相似,因为它们可以包含很多初始化代码。
    //
    pub const DOCTEST_WARN: Duration = INTEGRATION_WARN;
    pub const DOCTEST_CRITICAL: Duration = INTEGRATION_CRITICAL;

    // 不要对未知测试有任何猜想,它是 `TEST_WARN_TIMEOUT_S` 常量的基础限制。
    //
    pub const UNKNOWN_WARN: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S);
    pub const UNKNOWN_CRITICAL: Duration = Duration::from_secs(TEST_WARN_TIMEOUT_S * 2);
}

/// 返回一个 `Instance` 对象,该对象指示何时应将测试视为超时。
///
pub fn get_default_test_timeout() -> Instant {
    Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S)
}

/// 单元测试的测量执行时间。
#[derive(Debug, Clone, PartialEq)]
pub struct TestExecTime(pub Duration);

impl fmt::Display for TestExecTime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.3}s", self.0.as_secs_f64())
    }
}

/// 整个测试套件的测量执行时间。
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TestSuiteExecTime(pub Duration);

impl fmt::Display for TestSuiteExecTime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.2}s", self.0.as_secs_f64())
    }
}

/// 表示测试执行时间限制的结构体。
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct TimeThreshold {
    pub warn: Duration,
    pub critical: Duration,
}

impl TimeThreshold {
    /// 使用提供的持续时间创建一个新的 `TimeThreshold` 实例。
    pub fn new(warn: Duration, critical: Duration) -> Self {
        Self { warn, critical }
    }

    /// 尝试使用从环境变量获取的值创建 `TimeThreshold` 实例,如果未设置该变量,则返回 `None`。
    ///
    /// 环境变量格式应与 `\d+,\d+` 匹配。
    ///
    /// # Panics
    ///
    /// 如果设置了具有提供名称的变量,但包含了不适当的值,就会出现 panics。
    ///
    ///
    pub fn from_env_var(env_var_name: &str) -> Option<Self> {
        let durations_str = env::var(env_var_name).ok()?;
        let (warn_str, critical_str) = durations_str.split_once(',').unwrap_or_else(|| {
            panic!(
                "Duration variable {env_var_name} expected to have 2 numbers separated by comma, but got {durations_str}"
            )
        });

        let parse_u64 = |v| {
            u64::from_str(v).unwrap_or_else(|_| {
                panic!(
                    "Duration value in variable {env_var_name} is expected to be a number, but got {v}"
                )
            })
        };

        let warn = parse_u64(warn_str);
        let critical = parse_u64(critical_str);
        if warn > critical {
            panic!("Test execution warn time should be less or equal to the critical time");
        }

        Some(Self::new(Duration::from_millis(warn), Duration::from_millis(critical)))
    }
}

/// 具有用于计算测试执行时间的参数的结构体。
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct TestTimeOptions {
    /// 表示是否将超出测试关键执行时间限制视为测试失败。
    ///
    pub error_on_excess: bool,
    pub unit_threshold: TimeThreshold,
    pub integration_threshold: TimeThreshold,
    pub doctest_threshold: TimeThreshold,
}

impl TestTimeOptions {
    pub fn new_from_env(error_on_excess: bool) -> Self {
        let unit_threshold = TimeThreshold::from_env_var(time_constants::UNIT_ENV_NAME)
            .unwrap_or_else(Self::default_unit);

        let integration_threshold =
            TimeThreshold::from_env_var(time_constants::INTEGRATION_ENV_NAME)
                .unwrap_or_else(Self::default_integration);

        let doctest_threshold = TimeThreshold::from_env_var(time_constants::DOCTEST_ENV_NAME)
            .unwrap_or_else(Self::default_doctest);

        Self { error_on_excess, unit_threshold, integration_threshold, doctest_threshold }
    }

    pub fn is_warn(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool {
        exec_time.0 >= self.warn_time(test)
    }

    pub fn is_critical(&self, test: &TestDesc, exec_time: &TestExecTime) -> bool {
        exec_time.0 >= self.critical_time(test)
    }

    fn warn_time(&self, test: &TestDesc) -> Duration {
        match test.test_type {
            TestType::UnitTest => self.unit_threshold.warn,
            TestType::IntegrationTest => self.integration_threshold.warn,
            TestType::DocTest => self.doctest_threshold.warn,
            TestType::Unknown => time_constants::UNKNOWN_WARN,
        }
    }

    fn critical_time(&self, test: &TestDesc) -> Duration {
        match test.test_type {
            TestType::UnitTest => self.unit_threshold.critical,
            TestType::IntegrationTest => self.integration_threshold.critical,
            TestType::DocTest => self.doctest_threshold.critical,
            TestType::Unknown => time_constants::UNKNOWN_CRITICAL,
        }
    }

    fn default_unit() -> TimeThreshold {
        TimeThreshold::new(time_constants::UNIT_WARN, time_constants::UNIT_CRITICAL)
    }

    fn default_integration() -> TimeThreshold {
        TimeThreshold::new(time_constants::INTEGRATION_WARN, time_constants::INTEGRATION_CRITICAL)
    }

    fn default_doctest() -> TimeThreshold {
        TimeThreshold::new(time_constants::DOCTEST_WARN, time_constants::DOCTEST_CRITICAL)
    }
}