Skip to main content

persistant_core/
verdict.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use crate::{CapabilityId, Config, Reason, Row};
3use std::collections::BTreeMap;
4use std::fmt;
5
6/// The result of running one capability's test, before anything is recorded.
7///
8/// The four outcomes of `docs/DESIGN-KINDS.md` ยง2.1 #3.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Verdict {
12    /// The result matches the capability's written contract.
13    Exact,
14    /// Correct but different (pi as `3.141592` where `3.14159` was expected). The strict leaf stays
15    /// off: either widen the test, if it was stricter than the written contract, or mint a new
16    /// leaf with its own contract and a generalisation over the leaves. Never by editing the
17    /// strict leaf's meaning.
18    EquallyCorrectButDifferent,
19    /// The backend broke the contract: off, and a bug report to the backend's developer.
20    Wrong,
21    /// The backend does not give it.
22    Missing,
23}
24
25/// Whether the two controls of a test ran as they must.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Controls {
28    /// The test passed against something known to be right.
29    pub positive_passed: bool,
30    /// The test **failed** against something known to be wrong (the known-bad run).
31    pub known_bad_failed: bool,
32}
33
34/// An `on` was requested without both controls: the test proves nothing.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ControlsNotRun(pub Controls);
37
38impl fmt::Display for ControlsNotRun {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(
41            f,
42            "an exact result is not recorded as on without both controls (positive passed: {}, known-bad failed: {})",
43            self.0.positive_passed, self.0.known_bad_failed
44        )
45    }
46}
47
48impl std::error::Error for ControlsNotRun {}
49
50impl Verdict {
51    /// Turn a verdict into the row to record.
52    ///
53    /// Only [`Verdict::Exact`] needs [`Controls`], because only `on` is a claim; an off row is the
54    /// safe default and can always be recorded.
55    pub fn into_row(
56        self,
57        subject: CapabilityId,
58        backend: impl Into<String>,
59        version: impl Into<String>,
60        config: Config,
61        metadata: Config,
62        controls: Controls,
63    ) -> Result<Row, ControlsNotRun> {
64        let (enabled, reason, note) = match self {
65            Verdict::Exact => {
66                if !(controls.positive_passed && controls.known_bad_failed) {
67                    return Err(ControlsNotRun(controls));
68                }
69                (true, None, "")
70            }
71            Verdict::EquallyCorrectButDifferent => (
72                false,
73                Some(Reason::Wrong),
74                "equally correct but different: widen the test only if stricter than the contract, else mint a leaf",
75            ),
76            Verdict::Wrong => (false, Some(Reason::Wrong), ""),
77            Verdict::Missing => (false, Some(Reason::Missing), ""),
78        };
79        Ok(Row {
80            subject_kind: "capability".to_string(),
81            subject,
82            backend: backend.into(),
83            version: version.into(),
84            config,
85            enabled,
86            reason,
87            metadata,
88            note: note.to_string(),
89            extra: BTreeMap::new(),
90        })
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    fn row(v: Verdict, c: Controls) -> Result<Row, ControlsNotRun> {
99        v.into_row(
100            CapabilityId::new("kv.put_if_absent").unwrap(),
101            "sqlite",
102            "3.46",
103            Config::new(),
104            Config::new().with("test", "kv::put_if_absent_race"),
105            c,
106        )
107    }
108
109    const BOTH: Controls = Controls {
110        positive_passed: true,
111        known_bad_failed: true,
112    };
113
114    #[test]
115    fn exact_is_on_only_with_both_controls() {
116        let r = row(Verdict::Exact, BOTH).unwrap();
117        assert!(r.enabled && r.reason.is_none());
118        let no_known_bad = Controls {
119            known_bad_failed: false,
120            ..BOTH
121        };
122        assert_eq!(
123            row(Verdict::Exact, no_known_bad),
124            Err(ControlsNotRun(no_known_bad))
125        );
126        let no_positive = Controls {
127            positive_passed: false,
128            ..BOTH
129        };
130        assert!(row(Verdict::Exact, no_positive).is_err());
131    }
132
133    #[test]
134    fn everything_else_is_off_with_the_right_reason_and_needs_no_controls() {
135        let none = Controls {
136            positive_passed: false,
137            known_bad_failed: false,
138        };
139        assert_eq!(
140            row(Verdict::Wrong, none).unwrap().reason,
141            Some(Reason::Wrong)
142        );
143        assert_eq!(
144            row(Verdict::Missing, none).unwrap().reason,
145            Some(Reason::Missing)
146        );
147        let e = row(Verdict::EquallyCorrectButDifferent, none).unwrap();
148        assert!(!e.enabled && e.reason == Some(Reason::Wrong) && e.note.contains("mint a leaf"));
149    }
150}