Skip to main content

coven_database/
database_operations_tests.rs

1//! Test transport support using the actual local-write queue.
2
3use super::Database;
4use crate::DbError;
5
6impl Database {
7    /// Count executed work on every connection without including counter reads.
8    /// Install while the database is quiescent, before starting the measured pass.
9    pub async fn test_track_work(
10        &self,
11        counter: crate::test_utils::WorkCounter,
12    ) -> Result<(), DbError> {
13        self.call(move |inner| {
14            inner.access.with_writer(|writer| {
15                writer.test_track_work(counter.clone());
16                Ok::<_, DbError>(())
17            })?;
18            inner.access.readers.test_track_work(&counter);
19            inner.inputs.test_track_work(&counter);
20            Ok(())
21        })
22        .await
23    }
24
25    /// The plaintext bytes each section of a snapshot `id` would hold now:
26    /// what the sealed object's size can't tell apart.
27    pub async fn test_snapshot_section_bytes(
28        &self,
29        id: coven_format::admin::SnapshotId,
30    ) -> Result<
31        [u64; coven_format::snapshot::SECTIONS],
32        crate::SnapshotWriteError<std::convert::Infallible>,
33    > {
34        let sections =
35            std::sync::Arc::new(std::sync::Mutex::new([0; coven_format::snapshot::SECTIONS]));
36        let counted = sections.clone();
37        self.write_snapshot(
38            id,
39            |_| Ok(()),
40            move |frame| {
41                if let Some(section) = coven_format::snapshot::frame_section(&frame) {
42                    counted.lock().expect("no test panics counting")[section] += frame.len() as u64;
43                }
44                Ok(())
45            },
46        )
47        .await?;
48        let bytes = *sections.lock().expect("no test panics counting");
49        Ok(bytes)
50    }
51
52    /// Observe transaction completion and file cleanup on the writer thread.
53    /// Install after setup; the callback may terminate a subprocess to simulate
54    /// a crash without Rust destructors or SQLite connection shutdown.
55    pub async fn test_on_write_checkpoint(
56        &self,
57        callback: impl Fn(crate::test_utils::WriteCheckpoint) + Send + 'static,
58    ) -> Result<(), DbError> {
59        self.call(move |inner| {
60            inner.access.with_writer(|writer| {
61                writer.on_write_checkpoint(callback);
62                Ok(())
63            })
64        })
65        .await
66    }
67
68    /// Panic in the next write the writer judges, before it commits, as a
69    /// bug in it would: the write rolls back, and its call fails with
70    /// `DbError::Bug` carrying `message` (E15).
71    pub async fn test_panic_in_next_write(&self, message: &'static str) -> Result<(), DbError> {
72        let armed = std::sync::atomic::AtomicBool::new(true);
73        self.test_on_write_checkpoint(move |point| {
74            if point == crate::test_utils::WriteCheckpoint::Judged
75                && armed.swap(false, std::sync::atomic::Ordering::SeqCst)
76            {
77                panic!("{message}");
78            }
79        })
80        .await
81    }
82
83    /// Make every later write of how the passes go fail, as a full disk
84    /// would: their move, their end and the last synced time. Counting a
85    /// start still writes, so the loop starts and its first pass fails.
86    pub async fn test_fail_sync_status(&self) -> Result<(), DbError> {
87        self.call(move |inner| {
88            inner.access.with_writer(|writer| {
89                writer.batch(
90                    "CREATE TEMP TRIGGER fail_sync_status
91                     BEFORE UPDATE OF last_pass,moving,last_synced ON coven_sync_status
92                     BEGIN SELECT RAISE(ABORT,'injected sync status failure'); END;",
93                )
94            })
95        })
96        .await
97    }
98
99    /// Make every later write of the reset notice fail, as a full disk would.
100    pub async fn test_fail_device_reset(&self) -> Result<(), DbError> {
101        self.call(move |inner| {
102            inner.access.with_writer(|writer| {
103                writer.batch(
104                    "CREATE TEMP TRIGGER fail_device_reset BEFORE INSERT ON _coven_device_reset
105                     BEGIN SELECT RAISE(ABORT,'injected reset notice failure'); END;",
106                )
107            })
108        })
109        .await
110    }
111
112    /// Make every later write of the retry effect fail, as a full disk would.
113    pub async fn test_fail_retry_effect(&self) -> Result<(), DbError> {
114        self.call(move |inner| {
115            inner.access.with_writer(|writer| {
116                writer.batch(
117                    "CREATE TEMP TRIGGER fail_retry_effect BEFORE UPDATE ON coven_retry_effect
118                     BEGIN SELECT RAISE(ABORT,'injected retry effect failure'); END;",
119                )
120            })
121        })
122        .await
123    }
124
125    /// Test transport: read actual queued records without reauthoring their writes.
126    pub async fn test_queued_writes(
127        &self,
128    ) -> Result<Vec<coven_format::write::WriteRecord>, DbError> {
129        self.call(move |inner| {
130            inner.access.with_writer(|writer| {
131                writer
132                    .query("SELECT record FROM _coven_uploads ORDER BY device,number", [], |r| {
133                        r.get::<_, Vec<u8>>(0)
134                    })?
135                    .iter()
136                    .map(|bytes| {
137                        coven_format::write_stream::decode_plaintext(bytes).map_err(Into::into)
138                    })
139                    .collect()
140            })
141        })
142        .await
143    }
144}
145
146/// Fill this domain's tables from the rows of the plan audit's `fixture`
147/// table, inside its transaction (`internal_schema_scan_reasons_tests.rs`).
148#[cfg(test)]
149pub(crate) fn fill_for_plans(db: &rusqlite::Connection) {
150    db.execute_batch(
151        "INSERT INTO _coven_gone_storage_cleared SELECT n FROM fixture;
152         INSERT INTO _coven_requests_ended SELECT n FROM fixture;",
153    )
154    .unwrap();
155}