1use crate::error::GeometricError;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
15pub enum TestResult {
16 Pass,
18
19 Fail(GeometricError),
21
22 Skipped(String),
24}
25
26impl TestResult {
27 pub fn is_pass(&self) -> bool {
29 matches!(self, TestResult::Pass)
30 }
31
32 pub fn is_fail(&self) -> bool {
34 matches!(self, TestResult::Fail(_))
35 }
36
37 pub fn error(&self) -> Option<&GeometricError> {
39 match self {
40 TestResult::Fail(e) => Some(e),
41 _ => None,
42 }
43 }
44
45 pub fn pass() -> Self {
47 TestResult::Pass
48 }
49
50 pub fn fail(error: GeometricError) -> Self {
52 TestResult::Fail(error)
53 }
54
55 pub fn fail_with_distance(distance: f64, description: impl Into<String>) -> Self {
57 TestResult::Fail(GeometricError::new(distance, description))
58 }
59
60 pub fn skipped(reason: impl Into<String>) -> Self {
62 TestResult::Skipped(reason.into())
63 }
64
65 pub fn into_result(self) -> Result<(), GeometricError> {
67 match self {
68 TestResult::Pass => Ok(()),
69 TestResult::Fail(e) => Err(e),
70 TestResult::Skipped(reason) => Err(GeometricError::new(0.0, reason)),
71 }
72 }
73
74 pub fn combine(results: impl IntoIterator<Item = TestResult>) -> Self {
79 for result in results {
80 match result {
81 TestResult::Pass => continue,
82 TestResult::Fail(e) => return TestResult::Fail(e),
83 TestResult::Skipped(reason) => return TestResult::Skipped(reason),
84 }
85 }
86 TestResult::Pass
87 }
88}
89
90impl From<bool> for TestResult {
91 fn from(passed: bool) -> Self {
92 if passed {
93 TestResult::Pass
94 } else {
95 TestResult::Fail(GeometricError::new(1.0, "Boolean test failed"))
96 }
97 }
98}
99
100impl From<TestResult> for bool {
101 fn from(result: TestResult) -> Self {
102 result.is_pass()
103 }
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
108pub struct InvariantTestReport {
109 pub name: String,
111
112 pub category: InvariantCategory,
114
115 pub samples: usize,
117
118 pub failures: usize,
120
121 pub failure_rate: f64,
123
124 pub probability_bound: Option<f64>,
126
127 pub verified: bool,
129
130 pub sample_errors: Vec<GeometricError>,
132
133 pub confidence_interval: Option<(f64, f64)>,
138
139 pub confidence_level: Option<f64>,
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
147pub enum InvariantCategory {
148 Impossible,
150
151 Rare,
153
154 Emergent,
156}
157
158impl InvariantTestReport {
159 pub fn is_violated(&self) -> bool {
161 match self.category {
162 InvariantCategory::Impossible => self.failures > 0,
163 InvariantCategory::Rare => {
164 if let Some(bound) = self.probability_bound {
165 self.failure_rate > bound
166 } else {
167 self.failures > 0
168 }
169 }
170 InvariantCategory::Emergent => false, }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_result_pass() {
181 let result = TestResult::pass();
182 assert!(result.is_pass());
183 assert!(!result.is_fail());
184 }
185
186 #[test]
187 fn test_result_fail() {
188 let result = TestResult::fail_with_distance(0.5, "Too far");
189 assert!(!result.is_pass());
190 assert!(result.is_fail());
191 assert_eq!(result.error().unwrap().distance, 0.5);
192 }
193
194 #[test]
195 fn test_result_combine() {
196 let results = vec![TestResult::Pass, TestResult::Pass, TestResult::Pass];
197 assert!(TestResult::combine(results).is_pass());
198
199 let results_with_fail = vec![
200 TestResult::Pass,
201 TestResult::fail_with_distance(0.1, "Error"),
202 TestResult::Pass,
203 ];
204 assert!(TestResult::combine(results_with_fail).is_fail());
205 }
206
207 #[test]
208 fn test_from_bool() {
209 let pass: TestResult = true.into();
210 assert!(pass.is_pass());
211
212 let fail: TestResult = false.into();
213 assert!(fail.is_fail());
214 }
215}