feat(tests): the conformance suite's mem column — the reference row set green (wave 2)
- rows.rs: the reference row set (INV-1…6 + two constructor-mode rows), factory-generic and engine-uniform, every row ADR-stamped and traced to the backlog: round-trips over the content matrix on both put paths, dedup (same/cross-path), stage hygiene (both paths, both injectable failure modes, kv-degenerate residue via sweep outcomes), sweep safety (full-protection-set happy shape, all three GcAbortCause abort shapes incl. the single-flight SweeperLock lease, the sweep-vs-pinned-put race), list correctness through exact sweep outcomes only, stat discipline, mem-only posture, and kv-only put-time rejection (threshold discovered by free probes; immediate/ mid-stream rejections and at-threshold-lands legs) - mem_column.rs: the column's factories — mem-only (reference host) and kv-only(mem) at a declared threshold — fresh isolated opens, no-op idempotent teardown; structural factory-contract tests cover isolation and teardown against a panicking row - README: the reference row set recorded (INV-7's basis — what other columns mirror), the row-lifecycle convention, column plan and current state updated - task suite-column-mem: status completed, notes and summary filled Verification: cargo test --features mem (97 lib + 18 conformance + 4 git-interop), cargo test (default), cargo test --all-features, clippy --all-features --all-targets -D warnings, fmt --check, and cargo doc --no-deps — all green
This commit is contained in:
1 parent
539dabcbf7
commit
f86e20a217
5 files changed
+1601
-13
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: suite-column-mem
|
||||
name: Conformance suite — factory binding and the mem column (the reference column)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [store-read-paths, store-put-paths, gc-protocol]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
@@ -76,8 +76,75 @@ every other engine's rows mirror (store-api.md invariant 7; ADR-011 §2).
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
> Filled by the implementation agent (2026-10-11).
|
||||
|
||||
- **The factory binding was already made and recorded** by the
|
||||
store-construction task: `open()` vends the concrete
|
||||
`alkblobs::Store` facade (the skeleton's `Store` associated type
|
||||
collapsed), recorded in `factory.rs` + README §The factory contract
|
||||
before this task ran. Verified; no further shape correction was
|
||||
needed. The wave-1 binding demonstrator (`binding.rs`) remains as
|
||||
the proof-of-implementability tests.
|
||||
- **The reference row set** lives in `tests/conformance/rows.rs`
|
||||
(engine-uniform, factory-generic, every row ADR-stamped and traced
|
||||
to the backlog): INV-1 (content matrix × both put paths, in-body),
|
||||
INV-2 (same-path + cross-path legs), INV-3 (both paths × both
|
||||
injectable failure modes — mid-stream `Io` failure and
|
||||
declared-len `Verification` mismatch; the kv stage form is
|
||||
degenerate, so residue is the pool itself, observed through exact
|
||||
sweep outcomes), INV-4 (happy shape over the full protection set;
|
||||
all three `GcAbortCause` abort shapes — `NoLivenessSources`,
|
||||
`ProtectFailed`, `SweeperLock`, the last completing the
|
||||
single-node family per ADR-013 §2/ADR-012 §5; the sweep-vs-pinned-
|
||||
put race over both put paths), INV-5 (exact sweep outcomes, never
|
||||
list comparison — honored by every row), INV-6, and the two
|
||||
constructor-mode rows.
|
||||
- **INV-6's slice through the facade**: stat's shape (len +
|
||||
algorithm), purity, and `None`-on-miss; the engine-level
|
||||
non-content-load half rides the tier `size` probes' own bodies
|
||||
(ADR-008 §1) — noted in the row doc; the mismatch-based proof
|
||||
needs a plant seam the factory contract deliberately lacks.
|
||||
- **The kv-only row is threshold-agnostic by discovery**: a
|
||||
known-length rejection reads nothing, so `Unreadable` probes
|
||||
distinguish rejection from acceptance and binary search finds the
|
||||
factory's rejection point (~64 free probes); the row then asserts
|
||||
immediate/mid-stream rejections and both at-threshold-lands legs.
|
||||
Configuration contract recorded in the README: threshold < u64::MAX
|
||||
and materializable (≤ 8 MiB, `MAX_BOUNDARY_MATERIALIZATION`).
|
||||
Any column's kv-only factory mirrors the row unchanged.
|
||||
- **Mem factories** (`mem_column.rs`): `MemOnlyFactory` (the
|
||||
reference host for the INV rows) and `KvOnlyMemFactory` at a
|
||||
column-declared threshold (1024) — fresh isolated store per open by
|
||||
construction, teardown a no-op (mem holds no external state),
|
||||
trivially idempotent and panicking-row-safe. Structural wiring
|
||||
tests (not backlog rows) demonstrate factory isolation and
|
||||
teardown-against-panic: `factory_open_vends_fresh_isolated_stores`,
|
||||
`factory_teardown_is_safe_against_a_panicking_row`.
|
||||
- **Row lifecycle convention recorded**: the row body owns
|
||||
open/assert/teardown; a failing assertion panics before teardown
|
||||
(README §The factory contract grew the bullet). The column runs
|
||||
under feature `mem`; rows.rs compiles ungated so the reference rows
|
||||
stay type-checked in default builds. No SKIP machinery added.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
> Filled on completion (2026-10-11).
|
||||
|
||||
Landed the **mem column** — the conformance suite's contract-reference
|
||||
column (ADR-011 §2). `tests/conformance/rows.rs` carries the reference
|
||||
row set: INV-1…6 families (12 rows total, incl. all three sweep-abort
|
||||
shapes and the sweep-vs-put race) plus the two constructor-mode rows
|
||||
(mem-only posture; kv-only put-time rejection), each ADR-stamped and
|
||||
traceable to the backlog. `tests/conformance/mem_column.rs` binds the
|
||||
factories (`mem-only`, `kv-only(mem)`) and runs every row; the factory
|
||||
contract's isolation and idempotent-teardown guarantees are
|
||||
demonstrated structurally, including the panicking-row path. README
|
||||
records the reference row set (INV-7's basis — what other columns
|
||||
mirror), the row-lifecycle convention, and the new current state. The
|
||||
wave-2 factory binding (concrete `Store` facade) was already made and
|
||||
recorded upstream of this task; verified, nothing further to correct.
|
||||
|
||||
Verification: `cargo test --features mem` green (97 lib + 18
|
||||
conformance + 4 git-interop), `cargo test` (default) and
|
||||
`cargo test --all-features` green, `cargo clippy --all-features
|
||||
--all-targets -- -D warnings` green, `cargo fmt --check` green.
|
||||
@@ -105,6 +105,15 @@ row with its own owner stamp, still one per property.
|
||||
retired with it, replaced by the mem-gated binding demo factories
|
||||
in `main.rs`). A new engine column binds by implementing the trait;
|
||||
there is no longer a vendible type to choose.
|
||||
- **The row body owns its lifecycle.** A row calls `open()` first —
|
||||
a fresh, isolated store per call — drives its assertions, and calls
|
||||
`teardown()` before returning; a failing assertion panics the row
|
||||
*before* teardown runs (the early exit above), which is why
|
||||
`teardown()` must make sense over the abandoned instance — dropped
|
||||
or still alive at unwind. The mem column's structural tests
|
||||
(`factory_teardown_is_safe_against_a_panicking_row`) exercise that
|
||||
path; isolation itself (fresh state per open, nothing shared) is
|
||||
exercised alongside (`factory_open_vends_fresh_isolated_stores`).
|
||||
|
||||
## The SKIP posture
|
||||
|
||||
@@ -139,6 +148,43 @@ additions) needs a backlog entry *and* its stamp first.
|
||||
| INV-6 | `stat`/`size` returns entry length without fetching content | one stat-discipline family per engine column | store-api.md §Invariants 6; ADR-008 (`size` probe); ADR-009 (pg-lo companion-row probe) | wave 2 — mem |
|
||||
| INV-7 | Engine-conformance uniformity: every engine's bodies pass the same contract suite mem passes | the meta-gate — a column's green run *is* the row; no separate test | store-api.md §Invariants 7; ADR-010 §1; ADR-011 §2 (mem's reference role) | wave 2 — mem defines it |
|
||||
|
||||
## The reference row set (INV-7's basis)
|
||||
|
||||
The mem column's rows (rows.rs) are the **reference row set** — the
|
||||
required set every other engine's rows mirror (ADR-011 §2, mem's
|
||||
contract-reference role; store-api.md §Invariants 7). A column mirrors
|
||||
this inventory row-for-row against its own factory; an addition to the
|
||||
set propagates to all columns (INV-7). The INV-row families are the
|
||||
engine mirror targets — the round-trip family covers the tier the
|
||||
column's engine serves (mem: the kv tier; local: the large tier). The
|
||||
two constructor-mode rows mirror wherever that configuration is
|
||||
constructible: a column with a kv-only factory runs the kv-only row
|
||||
against it — the row discovers the mode's threshold by probing, since
|
||||
known-length rejections read nothing, and requires the threshold
|
||||
materializable for its at-threshold legs (≤ 8 MiB,
|
||||
`MAX_BOUNDARY_MATERIALIZATION`).
|
||||
|
||||
| Row (`rows.rs`) | Traces to |
|
||||
|---|---|
|
||||
| `round_trip_puts_and_gets_are_byte_identical` | INV-1 (content matrix, both put paths, in-body) |
|
||||
| `dedup_identical_content_is_one_pool_entry` | INV-2 (same-path and cross-path legs) |
|
||||
| `stage_hygiene_zero_residue_on_failure_exactly_one_entry_on_success` | INV-3 (both paths, both failure modes) |
|
||||
| `sweep_counts_are_exact_across_the_full_protection_set` | INV-4 (happy shape: full protection set) |
|
||||
| `sweep_without_liveness_sources_aborts_and_deletes_nothing` | INV-4 (abort shape: `NoLivenessSources`) |
|
||||
| `sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing` | INV-4 (abort shape: `ProtectFailed`) |
|
||||
| `sweep_is_single_flight_the_contended_second_aborts_as_data` | INV-4 (abort shape: `SweeperLock` lease) |
|
||||
| `sweep_racing_a_pinned_put_never_deletes_the_protected_entry` | INV-4 (the sweep-vs-put race) |
|
||||
| `list_correctness_is_observable_through_sweep_outcomes` | INV-5 (exact counts, never list comparison) |
|
||||
| `stat_reports_len_and_algorithm_without_fetching_content` | INV-6 (the stat-discipline family) |
|
||||
| `mem_only_mode_accepts_every_length_on_both_put_paths` | constructor mode (ADR-011 §4) |
|
||||
| `kv_only_mode_rejects_over_threshold_puts_at_put_time` | constructor mode (ADR-012 §6.2) |
|
||||
|
||||
Stamps live on the rows themselves — `grep -rn "Contract stamp:"
|
||||
tests/conformance/` is the inventory. The INV-4 rows beyond the
|
||||
families' first shapes (the `SweeperLock` abort) complete the three
|
||||
`GcAbortCause` variants within the single-node exact-count family
|
||||
(ADR-013 §2's lease, its in-process ADR-012 §5 form).
|
||||
|
||||
## Column plan
|
||||
|
||||
**mem is the contract-reference engine** (ADR-011 §2): wave 2 lands
|
||||
@@ -151,9 +197,15 @@ and first run in wave 6.
|
||||
|
||||
## Current state
|
||||
|
||||
The harness skeleton is compile-able with **zero rows**: the trait
|
||||
shape, the stamp convention, and the backlog registry are in place;
|
||||
the wave-2 factory binding is **made** — `open()` vends the concrete
|
||||
`alkblobs::Store` facade (see §The factory contract), demonstrated by
|
||||
the mem-gated binding demo factories. The rows arrive with wave 2's
|
||||
mem column.
|
||||
The harness is green on the **mem column** (wave 2): the factory
|
||||
binding is the concrete `alkblobs::Store` facade (see §The factory
|
||||
contract — the binding demonstrator remains in `binding.rs`), the
|
||||
reference row set is landed (§The reference row set — `rows.rs`,
|
||||
every row ADR-stamped and traced to the backlog), the column's
|
||||
factories (`mem-only`, the reference; `kv-only(mem)` at a
|
||||
column-declared threshold) vend fresh isolated stores with idempotent
|
||||
teardown (`mem_column.rs`, the structural factory-contract tests
|
||||
included), and mem never skips. The column runs under feature `mem`
|
||||
(`cargo test --features mem`); the row set compiles ungated so the
|
||||
reference rows stay type-checked in default builds. The local column
|
||||
grows next.
|
||||
@@ -13,11 +13,16 @@
|
||||
//! `factory.rs` and the README. The wave-1 mock probe retired with it;
|
||||
//! the binding demonstrator ([`binding`], mem-gated — mem is the
|
||||
//! skip-free reference shape) proves the contract implementable
|
||||
//! against the real type. Rows arrive with wave 2's columns (mem
|
||||
//! first, the contract-reference engine).
|
||||
//! against the real type.
|
||||
//!
|
||||
//! Items beyond the tests below are the wave-2 drivers' surface, hence
|
||||
//! the scoped `#![allow(dead_code)]` — it shrinks as rows land.
|
||||
//! The rows landed with wave 2's mem column: [`rows`] holds the
|
||||
//! reference row set (INV-1…6 plus the two constructor-mode rows,
|
||||
//! ADR-stamped, traced to [`backlog`]) and [`mem_column`] is the
|
||||
//! contract-reference column ([`README`](./README.md) §The reference
|
||||
//! row set is the recorded inventory other columns mirror).
|
||||
//!
|
||||
//! Items beyond the tests below are the later columns' surface, hence
|
||||
//! the scoped `#![allow(dead_code)]` — it shrinks as columns land.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
@@ -25,6 +30,9 @@ mod backlog;
|
||||
#[cfg(feature = "mem")]
|
||||
mod binding;
|
||||
mod factory;
|
||||
#[cfg(feature = "mem")]
|
||||
mod mem_column;
|
||||
mod rows;
|
||||
mod stamp;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
//! The **mem column** — the contract-reference column (ADR-011 §2):
|
||||
//! the mem engine's column of the reference row set ([`crate::rows`]),
|
||||
//! the first column bound to the real facade, and INV-7's basis —
|
||||
//! every other engine's rows mirror this set.
|
||||
//!
|
||||
//! The factories (README §The factory contract): `open()` vends the
|
||||
//! concrete [`alkblobs::Store`] facade over a fresh, isolated backing
|
||||
//! store per call — the mem engine is `BTreeMap`-shaped and shares
|
||||
//! nothing across instances, so isolation is by construction. Teardown
|
||||
//! is a no-op (the mem engine holds no external state), which makes it
|
||||
//! trivially idempotent and safe against abandoned instances — the
|
||||
//! panicking-row path included, which the structural tests below
|
||||
//! exercise. Mem never skips (README §SKIP posture — the SKIP
|
||||
//! machinery stays unneeded until the SQL waves).
|
||||
//!
|
||||
//! The INV rows run against the reference `mem-only` factory (the kv
|
||||
//! tier, no dispatch threshold in effect); the two constructor-mode
|
||||
//! rows run against the mode factories they name — `mem-only`, and
|
||||
//! `kv-only(mem)` at the column-declared threshold, which the kv-only
|
||||
//! row discovers by probing.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::factory::StoreFactory;
|
||||
use crate::rows;
|
||||
use alkblobs::{Error, Key, Store};
|
||||
|
||||
/// The reference factory: the mem-only mode (ADR-011 §4) — mem as the
|
||||
/// sole engine, no dispatch threshold in effect. The column never
|
||||
/// skips.
|
||||
pub struct MemOnlyFactory;
|
||||
|
||||
impl StoreFactory for MemOnlyFactory {
|
||||
fn name(&self) -> &'static str {
|
||||
"mem-only"
|
||||
}
|
||||
|
||||
async fn open(&self) -> Result<Store, Error> {
|
||||
Ok(Store::mem_only())
|
||||
}
|
||||
|
||||
async fn teardown(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The kv-only(mem) factory (ADR-011 §4; ADR-012 §6.2): no large
|
||||
/// tier; the threshold is the put-time rejection point the kv-only
|
||||
/// row discovers by probing.
|
||||
pub struct KvOnlyMemFactory {
|
||||
/// The rejection point this column's configuration declares. The
|
||||
/// kv-only row discovers it behaviorally and requires it
|
||||
/// materializable for its boundary legs.
|
||||
pub threshold: u64,
|
||||
}
|
||||
|
||||
impl StoreFactory for KvOnlyMemFactory {
|
||||
fn name(&self) -> &'static str {
|
||||
"kv-only(mem)"
|
||||
}
|
||||
|
||||
async fn open(&self) -> Result<Store, Error> {
|
||||
Ok(Store::kv_only_mem(self.threshold))
|
||||
}
|
||||
|
||||
async fn teardown(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The column's declared kv-only threshold — small, so every leg of
|
||||
/// the rejection row stays trivially materializable.
|
||||
const KV_ONLY_THRESHOLD: u64 = 1024;
|
||||
|
||||
const MEM: MemOnlyFactory = MemOnlyFactory;
|
||||
const KV_ONLY: KvOnlyMemFactory = KvOnlyMemFactory {
|
||||
threshold: KV_ONLY_THRESHOLD,
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// The reference row set, run against the mem column's factories.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv1_round_trip_puts_and_gets_are_byte_identical() {
|
||||
rows::round_trip_puts_and_gets_are_byte_identical(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv2_dedup_identical_content_is_one_pool_entry() {
|
||||
rows::dedup_identical_content_is_one_pool_entry(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv3_stage_hygiene_zero_residue_on_failure_one_entry_on_success() {
|
||||
rows::stage_hygiene_zero_residue_on_failure_exactly_one_entry_on_success(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv4_sweep_counts_are_exact_across_the_full_protection_set() {
|
||||
rows::sweep_counts_are_exact_across_the_full_protection_set(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv4_sweep_without_liveness_sources_aborts_and_deletes_nothing() {
|
||||
rows::sweep_without_liveness_sources_aborts_and_deletes_nothing(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv4_sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing() {
|
||||
rows::sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn inv4_sweep_is_single_flight_the_contended_second_aborts_as_data() {
|
||||
rows::sweep_is_single_flight_the_contended_second_aborts_as_data(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn inv4_sweep_racing_a_pinned_put_never_deletes_the_protected_entry() {
|
||||
rows::sweep_racing_a_pinned_put_never_deletes_the_protected_entry(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv5_list_correctness_is_observable_through_sweep_outcomes() {
|
||||
rows::list_correctness_is_observable_through_sweep_outcomes(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inv6_stat_reports_len_and_algorithm_without_fetching_content() {
|
||||
rows::stat_reports_len_and_algorithm_without_fetching_content(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn constructor_mem_only_accepts_every_length_on_both_put_paths() {
|
||||
rows::mem_only_mode_accepts_every_length_on_both_put_paths(&MEM).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn constructor_kv_only_rejects_over_threshold_puts_at_put_time() {
|
||||
rows::kv_only_mode_rejects_over_threshold_puts_at_put_time(&KV_ONLY).await;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// The factory contract's structural shapes (binding.rs' demonstrations
|
||||
// carried to the real column factories): fresh isolated opens and
|
||||
// idempotent teardown safe against a panicking row. These are not
|
||||
// conformance rows — no backlog entry — they are the factory
|
||||
// contract's own wiring-level assertions.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/// Every `open()` vends a fresh, isolated store: two stores live
|
||||
/// concurrently without observable shared state, and a third open sees
|
||||
/// neither island's entries.
|
||||
#[tokio::test]
|
||||
async fn factory_open_vends_fresh_isolated_stores() {
|
||||
let a = MEM.open().await.expect("open vends a fresh store");
|
||||
let b = MEM.open().await.expect("open vends another fresh store");
|
||||
|
||||
let content_a = b"island a's content".to_vec();
|
||||
let content_b = b"island b's content".to_vec();
|
||||
let (key_a, _) = a
|
||||
.put(
|
||||
Some(content_a.len() as u64),
|
||||
Cursor::new(content_a.as_slice()),
|
||||
)
|
||||
.await
|
||||
.expect("island a's put publishes");
|
||||
let (key_b, _) = b
|
||||
.put(None, Cursor::new(content_b.as_slice()))
|
||||
.await
|
||||
.expect("island b's put publishes");
|
||||
|
||||
assert!(a.has(&key_a.as_bytes()).await.unwrap());
|
||||
assert!(
|
||||
!a.has(&key_b.as_bytes()).await.unwrap(),
|
||||
"island a sees nothing of island b"
|
||||
);
|
||||
assert!(b.has(&key_b.as_bytes()).await.unwrap());
|
||||
assert!(
|
||||
!b.has(&key_a.as_bytes()).await.unwrap(),
|
||||
"island b sees nothing of island a"
|
||||
);
|
||||
|
||||
let c = MEM.open().await.expect("a third open vends fresh");
|
||||
assert!(
|
||||
!c.has(&key_a.as_bytes()).await.unwrap(),
|
||||
"a third open shares nothing with the first"
|
||||
);
|
||||
assert!(
|
||||
!c.has(&key_b.as_bytes()).await.unwrap(),
|
||||
"a third open shares nothing with the second"
|
||||
);
|
||||
}
|
||||
|
||||
/// A failing assertion panics the row before teardown runs — and
|
||||
/// teardown must make sense over the abandoned instance anyway: it is
|
||||
/// idempotent against the panicked row's path, and the next open vends
|
||||
/// a fresh store with no residue (README §The factory contract).
|
||||
#[tokio::test]
|
||||
async fn factory_teardown_is_safe_against_a_panicking_row() {
|
||||
let content = b"put before the assertion fails".to_vec();
|
||||
let abandoned = Key::derive(&content, content.len() as u64);
|
||||
let factory = Arc::new(MemOnlyFactory);
|
||||
|
||||
let task = {
|
||||
let factory = Arc::clone(&factory);
|
||||
tokio::spawn(async move {
|
||||
let store = factory.open().await.expect("open vends the store");
|
||||
let (_, pin) = store
|
||||
.put(Some(content.len() as u64), Cursor::new(content.as_slice()))
|
||||
.await
|
||||
.expect("the put publishes");
|
||||
drop(pin);
|
||||
panic!("a failing assertion panics the row (the factory contract's early exit)");
|
||||
})
|
||||
};
|
||||
assert!(
|
||||
task.await.is_err(),
|
||||
"the row's panic surfaced as its failure"
|
||||
);
|
||||
|
||||
factory
|
||||
.teardown()
|
||||
.await
|
||||
.expect("teardown makes sense over the abandoned instance");
|
||||
factory.teardown().await.expect("teardown is idempotent");
|
||||
|
||||
let store = factory
|
||||
.open()
|
||||
.await
|
||||
.expect("the next open still vends a store");
|
||||
assert!(
|
||||
!store.has(&abandoned.as_bytes()).await.unwrap(),
|
||||
"no residue from the panicked row — isolation is factory-owned"
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user