Skip to main content

wae_testing/
assertions.rs

1//! 断言宏和工具模块
2
3use crate::error::{TestingError, TestingResult};
4use std::time::Duration;
5
6/// 断言 Result 是 Ok
7#[macro_export]
8macro_rules! assert_ok {
9    ($expr:expr) => {
10        match $expr {
11            Ok(v) => v,
12            Err(e) => panic!("Expected Ok, got Err: {:?}", e),
13        }
14    };
15    ($expr:expr, $msg:expr) => {
16        match $expr {
17            Ok(v) => v,
18            Err(e) => panic!("{}: Expected Ok, got Err: {:?}", $msg, e),
19        }
20    };
21}
22
23/// 断言 Result 是 Err
24#[macro_export]
25macro_rules! assert_err {
26    ($expr:expr) => {
27        match $expr {
28            Err(e) => e,
29            Ok(v) => panic!("Expected Err, got Ok: {:?}", v),
30        }
31    };
32    ($expr:expr, $msg:expr) => {
33        match $expr {
34            Err(e) => e,
35            Ok(v) => panic!("{}: Expected Err, got Ok: {:?}", $msg, v),
36        }
37    };
38}
39
40/// 断言 Option 是 Some
41#[macro_export]
42macro_rules! assert_some {
43    ($expr:expr) => {
44        match $expr {
45            Some(v) => v,
46            None => panic!("Expected Some, got None"),
47        }
48    };
49    ($expr:expr, $msg:expr) => {
50        match $expr {
51            Some(v) => v,
52            None => panic!("{}: Expected Some, got None", $msg),
53        }
54    };
55}
56
57/// 断言 Option 是 None
58#[macro_export]
59macro_rules! assert_none {
60    ($expr:expr) => {
61        match $expr {
62            None => (),
63            Some(v) => panic!("Expected None, got Some: {:?}", v),
64        }
65    };
66    ($expr:expr, $msg:expr) => {
67        match $expr {
68            None => (),
69            Some(v) => panic!("{}: Expected None, got Some: {:?}", $msg, v),
70        }
71    };
72}
73
74/// 断言集合包含元素
75#[macro_export]
76macro_rules! assert_contains {
77    ($container:expr, $item:expr) => {
78        if !$container.contains(&$item) {
79            panic!("Expected container to contain item: {:?}", $item);
80        }
81    };
82    ($container:expr, $item:expr, $msg:expr) => {
83        if !$container.contains(&$item) {
84            panic!("{}: Expected container to contain item: {:?}", $msg, $item);
85        }
86    };
87}
88
89/// 断言集合不包含元素
90#[macro_export]
91macro_rules! assert_not_contains {
92    ($container:expr, $item:expr) => {
93        if $container.contains(&$item) {
94            panic!("Expected container to NOT contain item: {:?}", $item);
95        }
96    };
97    ($container:expr, $item:expr, $msg:expr) => {
98        if $container.contains(&$item) {
99            panic!("{}: Expected container to NOT contain item: {:?}", $msg, $item);
100        }
101    };
102}
103
104/// 断言两个值近似相等 (用于浮点数比较)
105#[macro_export]
106macro_rules! assert_approx_eq {
107    ($left:expr, $right:expr, $epsilon:expr) => {
108        let left = $left;
109        let right = $right;
110        let diff = if left > right { left - right } else { right - left };
111        if diff > $epsilon {
112            panic!("Values are not approximately equal: left={}, right={}, diff={}", left, right, diff);
113        }
114    };
115}
116
117/// 异步断言工具
118pub struct AsyncAssert;
119
120impl AsyncAssert {
121    /// 等待条件满足
122    pub async fn eventually<F, Fut>(condition: F, timeout_duration: Duration, check_interval: Duration) -> TestingResult<()>
123    where
124        F: Fn() -> Fut,
125        Fut: std::future::Future<Output = bool>,
126    {
127        let start = std::time::Instant::now();
128
129        while start.elapsed() < timeout_duration {
130            if condition().await {
131                return Ok(());
132            }
133            tokio::time::sleep(check_interval).await;
134        }
135
136        Err(TestingError::Timeout(format!("Condition not met within {:?}", timeout_duration)))
137    }
138
139    /// 等待条件满足并返回值
140    pub async fn eventually_with_value<F, Fut, T>(
141        condition: F,
142        timeout_duration: Duration,
143        check_interval: Duration,
144    ) -> TestingResult<T>
145    where
146        F: Fn() -> Fut,
147        Fut: std::future::Future<Output = Option<T>>,
148    {
149        let start = std::time::Instant::now();
150
151        while start.elapsed() < timeout_duration {
152            if let Some(value) = condition().await {
153                return Ok(value);
154            }
155            tokio::time::sleep(check_interval).await;
156        }
157
158        Err(TestingError::Timeout(format!("Condition not met within {:?}", timeout_duration)))
159    }
160}
161
162/// 断言异步条件最终满足
163pub async fn assert_eventually<F, Fut>(condition: F, timeout_duration: Duration, check_interval: Duration) -> TestingResult<()>
164where
165    F: Fn() -> Fut,
166    Fut: std::future::Future<Output = bool>,
167{
168    AsyncAssert::eventually(condition, timeout_duration, check_interval).await
169}
170
171/// 断言两个字符串匹配正则表达式
172pub fn assert_matches_regex(text: &str, pattern: &str) -> TestingResult<()> {
173    let re = regex::Regex::new(pattern).map_err(|e| TestingError::AssertionFailed(format!("Invalid regex: {}", e)))?;
174
175    if !re.is_match(text) {
176        return Err(TestingError::AssertionFailed(format!("Text '{}' does not match pattern '{}'", text, pattern)));
177    }
178
179    Ok(())
180}
181
182/// 断言 JSON 值包含指定字段
183pub fn assert_json_contains(json: &serde_json::Value, path: &str) -> TestingResult<()> {
184    let parts: Vec<&str> = path.split('.').collect();
185    let mut current = json;
186
187    for part in parts {
188        if let serde_json::Value::Object(map) = current {
189            current = map.get(part).ok_or_else(|| TestingError::AssertionFailed(format!("JSON path '{}' not found", path)))?;
190        }
191        else {
192            return Err(TestingError::AssertionFailed(format!("JSON path '{}' not found", path)));
193        }
194    }
195
196    Ok(())
197}