Skip to main content

windjammer_runtime/
test.rs

1//! Test framework for Windjammer
2//!
3//! Provides test primitives similar to Rust's test framework
4
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::sync::Mutex;
7
8static TESTS_PASSED: AtomicUsize = AtomicUsize::new(0);
9static TESTS_FAILED: AtomicUsize = AtomicUsize::new(0);
10static CURRENT_TEST: Mutex<String> = Mutex::new(String::new());
11
12/// Assert that a condition is true
13pub fn assert(condition: bool, message: &str) {
14    if !condition {
15        panic!("Assertion failed: {}", message);
16    }
17}
18
19/// Assert that two values are equal
20pub fn assert_eq<T: PartialEq + std::fmt::Debug>(left: T, right: T) {
21    if left != right {
22        panic!("Assertion failed: {:?} != {:?}", left, right);
23    }
24}
25
26/// Assert that two values are not equal
27pub fn assert_ne<T: PartialEq + std::fmt::Debug>(left: T, right: T) {
28    if left == right {
29        panic!("Assertion failed: {:?} == {:?}", left, right);
30    }
31}
32
33// ============================================================================
34// ENHANCED ASSERTIONS
35// ============================================================================
36
37/// Assert that a value is greater than another
38pub fn assert_gt<T: PartialOrd + std::fmt::Debug>(left: T, right: T) {
39    if left <= right {
40        panic!(
41            "assertion failed: left > right\n  left: {:?}\n right: {:?}",
42            left, right
43        );
44    }
45}
46
47/// Assert that a value is less than another
48pub fn assert_lt<T: PartialOrd + std::fmt::Debug>(left: T, right: T) {
49    if left >= right {
50        panic!(
51            "assertion failed: left < right\n  left: {:?}\n right: {:?}",
52            left, right
53        );
54    }
55}
56
57/// Assert that a value is greater than or equal to another
58pub fn assert_gte<T: PartialOrd + std::fmt::Debug>(left: T, right: T) {
59    if left < right {
60        panic!(
61            "assertion failed: left >= right\n  left: {:?}\n right: {:?}",
62            left, right
63        );
64    }
65}
66
67/// Assert that a value is less than or equal to another
68pub fn assert_lte<T: PartialOrd + std::fmt::Debug>(left: T, right: T) {
69    if left > right {
70        panic!(
71            "assertion failed: left <= right\n  left: {:?}\n right: {:?}",
72            left, right
73        );
74    }
75}
76
77/// Assert that two floating point values are approximately equal
78pub fn assert_approx(actual: f64, expected: f64, epsilon: f64) {
79    let diff = (actual - expected).abs();
80    if diff > epsilon {
81        panic!(
82            "assertion failed: values not approximately equal\n  actual: {}\n  expected: {}\n  epsilon: {}\n  diff: {}",
83            actual, expected, epsilon, diff
84        );
85    }
86}
87
88/// Assert that two f32 values are approximately equal
89pub fn assert_approx_f32(actual: f32, expected: f32, epsilon: f32) {
90    let diff = (actual - expected).abs();
91    if diff > epsilon {
92        panic!(
93            "assertion failed: values not approximately equal\n  actual: {}\n  expected: {}\n  epsilon: {}\n  diff: {}",
94            actual, expected, epsilon, diff
95        );
96    }
97}
98
99// ============================================================================
100// DESIGN BY CONTRACT - @requires, @ensures, @invariant
101// ============================================================================
102
103/// Check a precondition (used by @requires decorator)
104/// Asserts that a condition is true before a function executes
105pub fn requires(condition: bool, description: &str) {
106    if !condition {
107        panic!("Precondition violation (@requires): {}", description);
108    }
109}
110
111/// Check a postcondition (used by @ensures decorator)
112/// Asserts that a condition is true after a function executes
113pub fn ensures(condition: bool, description: &str) {
114    if !condition {
115        panic!("Postcondition violation (@ensures): {}", description);
116    }
117}
118
119/// Check an invariant (used by @invariant decorator)
120/// Asserts that a condition remains true throughout execution
121pub fn invariant(condition: bool, description: &str) {
122    if !condition {
123        panic!("Invariant violation (@invariant): {}", description);
124    }
125}
126
127/// Assert that a collection contains an item
128pub fn assert_contains<T: PartialEq + std::fmt::Debug>(collection: &[T], item: &T) {
129    if !collection.contains(item) {
130        panic!(
131            "assertion failed: collection doesn't contain item\n  collection: {:?}\n  item: {:?}",
132            collection, item
133        );
134    }
135}
136
137/// Assert that a collection has a specific length
138pub fn assert_length<T>(collection: &[T], expected: usize) {
139    let actual = collection.len();
140    if actual != expected {
141        panic!(
142            "assertion failed: collection length mismatch\n  expected: {}\n  actual: {}",
143            expected, actual
144        );
145    }
146}
147
148/// Assert that a collection is empty
149pub fn assert_empty<T>(collection: &[T]) {
150    if !collection.is_empty() {
151        panic!(
152            "assertion failed: collection is not empty\n  length: {}",
153            collection.len()
154        );
155    }
156}
157
158/// Assert that a collection is not empty
159pub fn assert_not_empty<T>(collection: &[T]) {
160    if collection.is_empty() {
161        panic!("assertion failed: collection is empty");
162    }
163}
164
165/// Assert that a string contains a substring
166pub fn assert_str_contains(string: &str, substring: &str) {
167    if !string.contains(substring) {
168        panic!(
169            "assertion failed: string doesn't contain substring\n  string: \"{}\"\n  substring: \"{}\"",
170            string, substring
171        );
172    }
173}
174
175/// Assert that a string starts with a prefix
176pub fn assert_starts_with(string: &str, prefix: &str) {
177    if !string.starts_with(prefix) {
178        panic!(
179            "assertion failed: string doesn't start with prefix\n  string: \"{}\"\n  prefix: \"{}\"",
180            string, prefix
181        );
182    }
183}
184
185/// Assert that a string ends with a suffix
186pub fn assert_ends_with(string: &str, suffix: &str) {
187    if !string.ends_with(suffix) {
188        panic!(
189            "assertion failed: string doesn't end with suffix\n  string: \"{}\"\n  suffix: \"{}\"",
190            string, suffix
191        );
192    }
193}
194
195/// Assert that an Option is Some
196pub fn assert_is_some<T: std::fmt::Debug>(option: &Option<T>) {
197    if option.is_none() {
198        panic!("assertion failed: Option is None, expected Some");
199    }
200}
201
202/// Assert that an Option is None
203pub fn assert_is_none<T: std::fmt::Debug>(option: &Option<T>) {
204    if let Some(ref value) = option {
205        panic!(
206            "assertion failed: Option is Some, expected None\n  value: {:?}",
207            value
208        );
209    }
210}
211
212/// Assert that a Result is Ok
213pub fn assert_is_ok<T: std::fmt::Debug, E: std::fmt::Debug>(result: &Result<T, E>) {
214    if let Err(ref e) = result {
215        panic!(
216            "assertion failed: Result is Err, expected Ok\n  error: {:?}",
217            e
218        );
219    }
220}
221
222/// Assert that a Result is Err
223pub fn assert_is_err<T: std::fmt::Debug, E: std::fmt::Debug>(result: &Result<T, E>) {
224    if let Ok(ref value) = result {
225        panic!(
226            "assertion failed: Result is Ok, expected Err\n  value: {:?}",
227            value
228        );
229    }
230}
231
232/// Assert that a value is in a range
233pub fn assert_in_range<T: PartialOrd + std::fmt::Debug>(value: T, min: T, max: T) {
234    if value < min || value > max {
235        panic!(
236            "assertion failed: value not in range\n  value: {:?}\n  min: {:?}\n  max: {:?}",
237            value, min, max
238        );
239    }
240}
241
242// ============================================================================
243// ADVANCED ASSERTIONS
244// ============================================================================
245
246/// Assert that a closure panics
247///
248/// # Example
249/// ```
250/// use windjammer_runtime::test::assert_panics;
251///
252/// assert_panics(|| {
253///     panic!("This should panic");
254/// });
255/// ```
256pub fn assert_panics<F: FnOnce() + std::panic::UnwindSafe>(f: F) {
257    let result = std::panic::catch_unwind(f);
258    if result.is_ok() {
259        panic!("assertion failed: expected panic, but function completed successfully");
260    }
261}
262
263/// Assert that a closure panics with a specific message
264///
265/// # Example
266/// ```
267/// use windjammer_runtime::test::assert_panics_with;
268///
269/// assert_panics_with("error", || {
270///     panic!("error");
271/// });
272/// ```
273pub fn assert_panics_with<F: FnOnce() + std::panic::UnwindSafe>(expected_msg: &str, f: F) {
274    let result = std::panic::catch_unwind(f);
275    match result {
276        Ok(_) => {
277            panic!(
278                "assertion failed: expected panic with message '{}', but function completed successfully",
279                expected_msg
280            );
281        }
282        Err(err) => {
283            // Try to extract the panic message
284            let panic_msg = if let Some(s) = err.downcast_ref::<&str>() {
285                s.to_string()
286            } else if let Some(s) = err.downcast_ref::<String>() {
287                s.clone()
288            } else {
289                "unknown panic message".to_string()
290            };
291
292            if !panic_msg.contains(expected_msg) {
293                panic!(
294                    "assertion failed: panic message mismatch\n  expected (substring): \"{}\"\n  actual: \"{}\"",
295                    expected_msg, panic_msg
296                );
297            }
298        }
299    }
300}
301
302/// Assert that a Result matches a pattern (Ok or Err)
303/// This is a simplified version - full pattern matching requires compiler support
304pub fn assert_result_ok<T, E: std::fmt::Debug>(result: &Result<T, E>) {
305    if let Err(e) = result {
306        panic!("assertion failed: expected Ok(_), got Err({:?})", e);
307    }
308}
309
310/// Assert that a Result matches Err pattern
311pub fn assert_result_err<T: std::fmt::Debug, E>(result: &Result<T, E>) {
312    if let Ok(val) = result {
313        panic!("assertion failed: expected Err(_), got Ok({:?})", val);
314    }
315}
316
317/// Assert that two values are deeply equal (same as assert_eq, but explicit name)
318pub fn assert_deep_eq<T: PartialEq + std::fmt::Debug>(left: &T, right: &T) {
319    if left != right {
320        panic!(
321            "assertion failed: deep equality check failed\n  left: {:?}\n  right: {:?}",
322            left, right
323        );
324    }
325}
326
327/// Mark a test as passed
328pub fn pass() {
329    TESTS_PASSED.fetch_add(1, Ordering::SeqCst);
330}
331
332/// Mark a test as failed
333pub fn fail(message: &str) {
334    TESTS_FAILED.fetch_add(1, Ordering::SeqCst);
335    panic!("Test failed: {}", message);
336}
337
338/// Get the number of tests passed
339pub fn passed_count() -> usize {
340    TESTS_PASSED.load(Ordering::SeqCst)
341}
342
343/// Get the number of tests failed
344pub fn failed_count() -> usize {
345    TESTS_FAILED.load(Ordering::SeqCst)
346}
347
348/// Reset test counters (for test isolation)
349pub fn reset() {
350    TESTS_PASSED.store(0, Ordering::SeqCst);
351    TESTS_FAILED.store(0, Ordering::SeqCst);
352}
353
354/// Set the current test name
355pub fn set_current_test(name: String) {
356    *CURRENT_TEST.lock().unwrap() = name;
357}
358
359/// Get the current test name
360pub fn current_test() -> String {
361    CURRENT_TEST.lock().unwrap().clone()
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn test_assert() {
370        assert(true, "should pass");
371    }
372
373    #[test]
374    #[should_panic]
375    fn test_assert_fail() {
376        assert(false, "should fail");
377    }
378
379    #[test]
380    fn test_assert_eq() {
381        assert_eq(1, 1);
382        assert_eq("hello", "hello");
383    }
384
385    #[test]
386    #[should_panic]
387    fn test_assert_eq_fail() {
388        assert_eq(1, 2);
389    }
390
391    #[test]
392    fn test_assert_ne() {
393        assert_ne(1, 2);
394        assert_ne("hello", "world");
395    }
396
397    #[test]
398    #[should_panic]
399    fn test_assert_ne_fail() {
400        assert_ne(1, 1);
401    }
402
403    // Enhanced assertions tests
404    #[test]
405    fn test_assert_gt_passes() {
406        assert_gt(5, 3);
407        assert_gt(10.5, 10.4);
408    }
409
410    #[test]
411    #[should_panic(expected = "assertion failed: left > right")]
412    fn test_assert_gt_fails() {
413        assert_gt(3, 5);
414    }
415
416    #[test]
417    fn test_assert_lt_passes() {
418        assert_lt(3, 5);
419        assert_lt(10.4, 10.5);
420    }
421
422    #[test]
423    #[should_panic(expected = "assertion failed: left < right")]
424    fn test_assert_lt_fails() {
425        assert_lt(5, 3);
426    }
427
428    #[test]
429    fn test_assert_approx_passes() {
430        assert_approx(3.1, 3.0, 0.2);
431        assert_approx(1.0, 1.0001, 0.001);
432    }
433
434    #[test]
435    #[should_panic(expected = "assertion failed: values not approximately equal")]
436    fn test_assert_approx_fails() {
437        assert_approx(3.1, 2.0, 0.01);
438    }
439
440    #[test]
441    fn test_assert_contains_passes() {
442        let vec = vec![1, 2, 3, 4, 5];
443        assert_contains(&vec, &3);
444    }
445
446    #[test]
447    #[should_panic(expected = "assertion failed: collection doesn't contain item")]
448    fn test_assert_contains_fails() {
449        let vec = vec![1, 2, 3, 4, 5];
450        assert_contains(&vec, &10);
451    }
452
453    #[test]
454    fn test_assert_length_passes() {
455        let vec = vec![1, 2, 3];
456        assert_length(&vec, 3);
457    }
458
459    #[test]
460    #[should_panic(expected = "assertion failed: collection length mismatch")]
461    fn test_assert_length_fails() {
462        let vec = vec![1, 2, 3];
463        assert_length(&vec, 5);
464    }
465
466    #[test]
467    fn test_assert_str_contains_passes() {
468        assert_str_contains("hello world", "world");
469    }
470
471    #[test]
472    #[should_panic(expected = "assertion failed: string doesn't contain substring")]
473    fn test_assert_str_contains_fails() {
474        assert_str_contains("hello world", "foo");
475    }
476
477    #[test]
478    fn test_assert_is_some_passes() {
479        let opt = Some(42);
480        assert_is_some(&opt);
481    }
482
483    #[test]
484    #[should_panic(expected = "assertion failed: Option is None")]
485    fn test_assert_is_some_fails() {
486        let opt: Option<i32> = None;
487        assert_is_some(&opt);
488    }
489
490    #[test]
491    fn test_assert_in_range_passes() {
492        assert_in_range(5, 0, 10);
493        assert_in_range(3.1, 3.0, 4.0);
494    }
495
496    #[test]
497    #[should_panic(expected = "assertion failed: value not in range")]
498    fn test_assert_in_range_fails() {
499        assert_in_range(15, 0, 10);
500    }
501
502    // Advanced assertions tests
503    #[test]
504    fn test_assert_panics_passes() {
505        assert_panics(|| {
506            panic!("This should panic");
507        });
508    }
509
510    #[test]
511    #[should_panic(expected = "expected panic, but function completed successfully")]
512    fn test_assert_panics_fails() {
513        assert_panics(|| {
514            // This doesn't panic
515        });
516    }
517
518    #[test]
519    fn test_assert_panics_with_passes() {
520        assert_panics_with("division by zero", || {
521            panic!("Error: division by zero occurred");
522        });
523    }
524
525    #[test]
526    #[should_panic(expected = "panic message mismatch")]
527    fn test_assert_panics_with_fails() {
528        assert_panics_with("expected message", || {
529            panic!("different message");
530        });
531    }
532
533    #[test]
534    fn test_assert_result_ok_passes() {
535        let result: Result<i32, &str> = Ok(42);
536        assert_result_ok(&result);
537    }
538
539    #[test]
540    #[should_panic(expected = "expected Ok(_), got Err")]
541    fn test_assert_result_ok_fails() {
542        let result: Result<i32, &str> = Err("error");
543        assert_result_ok(&result);
544    }
545
546    #[test]
547    fn test_assert_result_err_passes() {
548        let result: Result<i32, &str> = Err("error");
549        assert_result_err(&result);
550    }
551
552    #[test]
553    #[should_panic(expected = "expected Err(_), got Ok")]
554    fn test_assert_result_err_fails() {
555        let result: Result<i32, &str> = Ok(42);
556        assert_result_err(&result);
557    }
558
559    #[test]
560    fn test_assert_deep_eq_passes() {
561        let vec1 = vec![1, 2, 3];
562        let vec2 = vec![1, 2, 3];
563        assert_deep_eq(&vec1, &vec2);
564    }
565
566    #[test]
567    #[should_panic(expected = "deep equality check failed")]
568    fn test_assert_deep_eq_fails() {
569        let vec1 = vec![1, 2, 3];
570        let vec2 = vec![1, 2, 4];
571        assert_deep_eq(&vec1, &vec2);
572    }
573}