1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! `#[doc(hidden)]` test-support projections on [`Database`].
//!
//! These methods exist ONLY so the e2e suites can observe internal state
//! (promise ballots, stored stamp envelopes, live epochs) or force faults
//! (shard shutdown) through the public handle without widening the production
//! API. None of them are part of the supported surface; they live in this
//! sibling file to keep `db.rs` focused on the production API.
use super::Database;
impl Database {
/// Test-support: stop every shard actor so subsequent storage commands fail.
///
/// Used by the 2a-4 receiver tests to force a genuine apply fault (a
/// disconnected/timed-out shard reply, distinct from a CAS mismatch) and prove
/// it surfaces as `Rejected(ApplyError)`. Not part of the production API.
#[doc(hidden)]
pub fn shutdown_shards_for_test(&self) {
for handle in self.router.materialised_handles() {
drop(handle.shutdown(self.timeout));
}
}
/// Test-support: read a shard's current durably-`promised` ballot (AA-3-2).
///
/// Used by the election e2e tests to assert the swing voter's monotonic
/// `promised` reflects the MAX winner (the single-live-owner invariant). Not
/// part of the production API.
#[doc(hidden)]
#[must_use]
pub fn promised_ballot_for_test(&self, shard_id: usize) -> Option<crate::sync::Ballot> {
let handle = self.router.handle_for_shard(shard_id).ok()?;
handle
.read_promise_state(self.timeout)
.ok()
.map(|state| state.promised)
}
/// Test-support: durably advance a shard's `promised` ballot (AA-3-2), the same
/// way an inbound `Prepare` would, WITHOUT needing a live election/transport.
///
/// Returns `true` iff the ballot strictly exceeded the prior `promised` (so it
/// was recorded). Used by the A1b receiver fence test to put a shard's
/// `promised` above an inbound batch's epoch so the batch is fenced. Not part of
/// the production API.
#[doc(hidden)]
pub fn record_promise_for_test(&self, shard_id: usize, ballot: crate::sync::Ballot) -> bool {
let Ok(handle) = self.router.handle_for_shard(shard_id) else {
return false;
};
matches!(
handle.record_promise(ballot, self.timeout),
Ok(crate::shard::actor::RecordPromiseOutcome::Promised)
)
}
/// Test-support: decode the committed commit-stamp `(epoch, seq)` a node
/// stored for `key` (AA-3-4a). Reads the RAW stored envelope (stamp NOT
/// stripped) and decodes its stamp, so a test can prove every replica stored
/// the IDENTICAL owner-assigned stamp. Returns `None` if the key is absent or
/// was stored without a stamp envelope.
#[doc(hidden)]
#[must_use]
pub fn stored_stamp_for_test(&self, key: &[u8]) -> Option<crate::sync::Stamp> {
let handle = self.router.handle_for(key).ok()?;
let raw = handle.get_raw(key.to_vec(), self.timeout).ok()??;
crate::ttl::entry::StampedEntry::decode(&raw)
.ok()
.flatten()
.map(|entry| entry.stamp().clone())
}
/// Test-support (AA-3-4b): `Some(true)` if `key` is stored as a STAMPED
/// TOMBSTONE on this node, `Some(false)` if it is stored as a stamped value,
/// `None` if absent or stored without a stamp envelope. Reads the RAW stored
/// envelope so a test can prove a committed delete landed a tombstone (not a
/// removal) on a peer, and that R-TOMB kept it through a sweep.
#[doc(hidden)]
#[must_use]
pub fn stored_is_tombstone_for_test(&self, key: &[u8]) -> Option<bool> {
let handle = self.router.handle_for(key).ok()?;
let raw = handle.get_raw(key.to_vec(), self.timeout).ok()??;
crate::ttl::entry::StampedEntry::decode(&raw)
.ok()
.flatten()
.map(|entry| entry.is_tombstone())
}
/// Test-support: read a shard's IN-MEMORY `live_epoch` (R-LE, AA-3-4a). This
/// is `Ballot::bottom()` until a successful `acquire_shard` THIS lifetime, and
/// is NEVER seeded from the disk-recovered `owner_epoch`.
#[doc(hidden)]
#[must_use]
pub fn live_epoch_for_test(&self, shard_id: usize) -> crate::sync::Ballot {
self.owner_stamps.live_epoch(shard_id)
}
/// Test-support: the commit stamp the NEXT write to `shard_id` would draw,
/// WITHOUT advancing the counter (R-LE / R-SEQ peek). Used by the crash gate
/// to prove a recovered owner would stamp `bottom`, never the recovered `e'`.
#[doc(hidden)]
#[must_use]
pub fn next_stamp_for_test(&self, shard_id: usize) -> crate::sync::Stamp {
self.owner_stamps.peek_stamp(shard_id)
}
}