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