Skip to main content

jugar_probar/assertion/
mod.rs

1//! Assertions for test validation.
2//!
3//! This module provides assertion helpers for testing including:
4//! - Basic assertions (equality, boolean, etc.)
5//! - Soft assertions (collect multiple failures)
6//! - Retry assertions (poll until success or timeout)
7//! - Equation verification (physics, game invariants - EDD compliance)
8
9mod equation;
10mod retry;
11mod soft;
12
13use std::fmt::Debug;
14
15// Re-export submodules
16pub use equation::{
17    EnergyVerifier, EquationContext, EquationResult, EquationVerifier, InvariantVerifier,
18    KinematicVerifier, MomentumVerifier, Variable,
19};
20pub use retry::{
21    retry_contains, retry_eq, retry_none, retry_some, retry_true, AssertionCheckResult,
22    RetryAssertion, RetryConfig, RetryError, RetryResult,
23};
24pub use soft::{
25    AssertionFailure, AssertionMode, AssertionSummary, SoftAssertionError, SoftAssertions,
26};
27
28/// Result of an assertion
29#[derive(Debug, Clone)]
30pub struct AssertionResult {
31    /// Whether the assertion passed
32    pub passed: bool,
33    /// Human-readable message
34    pub message: String,
35}
36
37impl AssertionResult {
38    /// Create a passing assertion result
39    #[must_use]
40    pub const fn pass() -> Self {
41        Self {
42            passed: true,
43            message: String::new(),
44        }
45    }
46
47    /// Create a failing assertion result
48    #[must_use]
49    pub fn fail(message: impl Into<String>) -> Self {
50        Self {
51            passed: false,
52            message: message.into(),
53        }
54    }
55}
56
57/// Assertion helpers for testing
58#[derive(Debug)]
59pub struct Assertion;
60
61impl Assertion {
62    /// Assert two values are equal
63    #[must_use]
64    pub fn equals<T: PartialEq + Debug>(expected: &T, actual: &T) -> AssertionResult {
65        contract_pre_assertion_evaluation!();
66        if expected == actual {
67            AssertionResult::pass()
68        } else {
69            AssertionResult::fail(format!("expected {expected:?}, got {actual:?}"))
70        }
71    }
72
73    /// Assert a string contains a substring
74    #[must_use]
75    pub fn contains(haystack: &str, needle: &str) -> AssertionResult {
76        if haystack.contains(needle) {
77            AssertionResult::pass()
78        } else {
79            AssertionResult::fail(format!("expected '{haystack}' to contain '{needle}'"))
80        }
81    }
82
83    /// Assert a value is in a range
84    #[must_use]
85    pub fn in_range(value: f64, min: f64, max: f64) -> AssertionResult {
86        contract_pre_assertion_evaluation!();
87        if value >= min && value <= max {
88            AssertionResult::pass()
89        } else {
90            AssertionResult::fail(format!("expected {value} to be in range [{min}, {max}]"))
91        }
92    }
93
94    /// Assert a condition is true
95    #[must_use]
96    pub fn is_true(condition: bool, message: &str) -> AssertionResult {
97        if condition {
98            AssertionResult::pass()
99        } else {
100            AssertionResult::fail(message)
101        }
102    }
103
104    /// Assert a condition is false
105    #[must_use]
106    pub fn is_false(condition: bool, message: &str) -> AssertionResult {
107        if condition {
108            AssertionResult::fail(message)
109        } else {
110            AssertionResult::pass()
111        }
112    }
113
114    /// Assert an Option is Some
115    #[must_use]
116    pub fn is_some<T>(opt: &Option<T>) -> AssertionResult {
117        if opt.is_some() {
118            AssertionResult::pass()
119        } else {
120            AssertionResult::fail("expected Some, got None")
121        }
122    }
123
124    /// Assert an Option is None
125    #[must_use]
126    pub fn is_none<T>(opt: &Option<T>) -> AssertionResult {
127        if opt.is_none() {
128            AssertionResult::pass()
129        } else {
130            AssertionResult::fail("expected None, got Some")
131        }
132    }
133
134    /// Assert a Result is Ok
135    #[must_use]
136    pub fn is_ok<T, E>(result: &Result<T, E>) -> AssertionResult {
137        if result.is_ok() {
138            AssertionResult::pass()
139        } else {
140            AssertionResult::fail("expected Ok, got Err")
141        }
142    }
143
144    /// Assert a Result is Err
145    #[must_use]
146    pub fn is_err<T, E>(result: &Result<T, E>) -> AssertionResult {
147        if result.is_err() {
148            AssertionResult::pass()
149        } else {
150            AssertionResult::fail("expected Err, got Ok")
151        }
152    }
153
154    /// Assert two floats are approximately equal
155    #[must_use]
156    pub fn approx_eq(a: f64, b: f64, epsilon: f64) -> AssertionResult {
157        contract_pre_assertion_evaluation!();
158        if (a - b).abs() < epsilon {
159            AssertionResult::pass()
160        } else {
161            AssertionResult::fail(format!("expected {a} ≈ {b} (epsilon: {epsilon})"))
162        }
163    }
164
165    /// Assert a collection has expected length
166    #[must_use]
167    pub fn has_length<T>(collection: &[T], expected: usize) -> AssertionResult {
168        if collection.len() == expected {
169            AssertionResult::pass()
170        } else {
171            AssertionResult::fail(format!(
172                "expected length {expected}, got {}",
173                collection.len()
174            ))
175        }
176    }
177}