Skip to main content

wae_testing/
assertions.rs

1//! 断言宏和工具模块
2
3use std::time::Duration;
4use wae_types::{WaeError, WaeErrorKind, WaeResult as TestingResult};
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(WaeError::new(WaeErrorKind::OperationTimeout {
137            operation: "eventually".to_string(),
138            timeout_ms: timeout_duration.as_millis() as u64,
139        }))
140    }
141
142    /// 等待条件满足并返回值
143    pub async fn eventually_with_value<F, Fut, T>(
144        condition: F,
145        timeout_duration: Duration,
146        check_interval: Duration,
147    ) -> TestingResult<T>
148    where
149        F: Fn() -> Fut,
150        Fut: std::future::Future<Output = Option<T>>,
151    {
152        let start = std::time::Instant::now();
153
154        while start.elapsed() < timeout_duration {
155            if let Some(value) = condition().await {
156                return Ok(value);
157            }
158            tokio::time::sleep(check_interval).await;
159        }
160
161        Err(WaeError::new(WaeErrorKind::OperationTimeout {
162            operation: "eventually_with_value".to_string(),
163            timeout_ms: timeout_duration.as_millis() as u64,
164        }))
165    }
166}
167
168/// 断言异步条件最终满足
169pub async fn assert_eventually<F, Fut>(condition: F, timeout_duration: Duration, check_interval: Duration) -> TestingResult<()>
170where
171    F: Fn() -> Fut,
172    Fut: std::future::Future<Output = bool>,
173{
174    AsyncAssert::eventually(condition, timeout_duration, check_interval).await
175}
176
177/// 断言两个字符串匹配正则表达式
178pub fn assert_matches_regex(text: &str, pattern: &str) -> TestingResult<()> {
179    let re = regex::Regex::new(pattern)
180        .map_err(|e| WaeError::new(WaeErrorKind::AssertionFailed { message: format!("Invalid regex: {}", e) }))?;
181
182    if !re.is_match(text) {
183        return Err(WaeError::new(WaeErrorKind::AssertionFailed {
184            message: format!("Text '{}' does not match pattern '{}'", text, pattern),
185        }));
186    }
187
188    Ok(())
189}
190
191/// 断言 JSON 值包含指定字段
192pub fn assert_json_contains(json: &serde_json::Value, path: &str) -> TestingResult<()> {
193    let parts: Vec<&str> = path.split('.').collect();
194    let mut current = json;
195
196    for part in parts {
197        if let serde_json::Value::Object(map) = current {
198            current = map.get(part).ok_or_else(|| {
199                WaeError::new(WaeErrorKind::AssertionFailed { message: format!("JSON path '{}' not found", path) })
200            })?;
201        }
202        else {
203            return Err(WaeError::new(WaeErrorKind::AssertionFailed { message: format!("JSON path '{}' not found", path) }));
204        }
205    }
206
207    Ok(())
208}