Skip to main content

spatialrust_platform/
conformance.rs

1//! Conformance suite markers and reports.
2
3use crate::{PlatformError, PlatformResult};
4
5/// Outcome of one conformance case.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub enum ConformanceStatus {
8    /// Passed.
9    Pass,
10    /// Failed.
11    Fail,
12    /// Skipped (missing optional feature).
13    Skip,
14}
15
16/// One conformance case result.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct ConformanceCase {
19    /// Case id.
20    pub id: String,
21    /// Status.
22    pub status: ConformanceStatus,
23    /// Optional detail.
24    pub detail: Option<String>,
25}
26
27/// Aggregated conformance report.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct ConformanceReport {
30    cases: Vec<ConformanceCase>,
31}
32
33impl ConformanceReport {
34    /// Creates an empty report.
35    #[must_use]
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Records a case.
41    pub fn record(
42        &mut self,
43        id: impl Into<String>,
44        status: ConformanceStatus,
45        detail: Option<String>,
46    ) {
47        self.cases.push(ConformanceCase { id: id.into(), status, detail });
48    }
49
50    /// Returns cases.
51    #[must_use]
52    pub fn cases(&self) -> &[ConformanceCase] {
53        &self.cases
54    }
55
56    /// Counts passes.
57    #[must_use]
58    pub fn pass_count(&self) -> usize {
59        self.count(ConformanceStatus::Pass)
60    }
61
62    /// Counts failures.
63    #[must_use]
64    pub fn fail_count(&self) -> usize {
65        self.count(ConformanceStatus::Fail)
66    }
67
68    /// Counts skips.
69    #[must_use]
70    pub fn skip_count(&self) -> usize {
71        self.count(ConformanceStatus::Skip)
72    }
73
74    fn count(&self, status: ConformanceStatus) -> usize {
75        self.cases.iter().filter(|case| case.status == status).count()
76    }
77
78    /// Compact summary string for logs/docs.
79    #[must_use]
80    pub fn summary(&self) -> String {
81        format!("pass={} fail={} skip={}", self.pass_count(), self.fail_count(), self.skip_count())
82    }
83
84    /// Fails if any case failed.
85    pub fn assert_no_failures(&self) -> PlatformResult<()> {
86        if self.cases.iter().any(|case| case.status == ConformanceStatus::Fail) {
87            return Err(PlatformError::InvalidConfiguration(
88                "conformance report contains failures".into(),
89            ));
90        }
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::{ConformanceReport, ConformanceStatus};
98
99    #[test]
100    fn rejects_failures_and_summarizes() {
101        let mut report = ConformanceReport::new();
102        report.record("arrow-roundtrip", ConformanceStatus::Pass, None);
103        report.record("mcap-optional", ConformanceStatus::Skip, Some("feature off".into()));
104        assert!(report.assert_no_failures().is_ok());
105        assert_eq!(report.summary(), "pass=1 fail=0 skip=1");
106        report.record("bad", ConformanceStatus::Fail, None);
107        assert!(report.assert_no_failures().is_err());
108        assert_eq!(report.fail_count(), 1);
109    }
110}