test(zset-ffi): the rebuild is one tick and its last chunk is Done (improvise-bw4p.1)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (dbsp-circuit)
2026-10-10 12:10:29 -07:00
committed by Edward Langley
parent c024c4bd6e
commit b3f5cedff0
3 changed files with 46 additions and 2 deletions
+6 -2
View File
@@ -33,8 +33,8 @@ link, so they call the `extern "C"` functions as plain functions.
4. **Failure leaves the Z-set unchanged.** `add`, `sub`, `neg`, `scale` and `decode_add`
compute a new value and then assign it, so an overflow half way through cannot leave a
half-applied update (`zs_zset_insert` is one addition, so it has no half).
5. **A failed step poisons the circuit.** A panic in `step` can leave operator state half
updated, and the circuit has no rollback (ADR 009 clause 11 is the host's). So the first
5. **A failed step poisons the circuit.** A panic in `step` leaves operator state half
updated, and the circuit has no rollback (ADR 009 clause 11 is the host's; `zset.md`). So the first
failure in a step, or in a rebuild step, marks the circuit poisoned, and every later call
that uses it returns `ZS_ERR_POISONED`. A caller drops it and builds another. Ids that do
not exist, and malformed arguments, are refused before any state changes and do not poison.
@@ -69,6 +69,10 @@ link, so they call the `extern "C"` functions as plain functions.
12. **Versioning.** `zs_abi_version()` and `ZS_ABI_VERSION` are 1. A change that breaks a
signature or a code bumps it.
A rebuild is one logical tick however many `zs_rebuild_step` calls it takes (`zset.md`,
decision 8): a `delay` fed by it shows the previous tick once, and `done` is 1 as soon as the
last chunk ran, whatever the deadline callback says.
## Calling it from Common Lisp
`crates/zset-ffi/tests/smoke.lisp` is a runnable CFFI program (`defcfun` for each call, a
+7
View File
@@ -58,3 +58,10 @@ counts; asserts the scanner saw more than 30 functions, so an empty parse cannot
Every `ZS_ERR_*`, `ZS_ABI_VERSION`, `ZS_OK` and the four `ZS_AGG_*` equal the Rust values; the
last code's number equals the number of codes, so a new code without a header line fails.
### a_chunked_rebuild_through_delay_is_one_tick
After #345's review the rebuild is one logical tick. A snapshot of two keys, chunks of one,
an always-expired deadline: the first call is `done == 0`, the second `done == 1` (the last
chunk is Done despite the deadline), and a `delay` sink shows the previous tick's key once.
Under the old per-chunk ticking the delay showed the first chunk instead.
+33
View File
@@ -497,3 +497,36 @@ fn an_index_by_callback_that_emits_nothing_is_a_bad_value() {
assert_eq!(zs_circuit_step(c.0), Code::BadValue as i32);
assert!(last_error().contains("exactly one key"), "{}", last_error());
}
#[test]
fn a_chunked_rebuild_through_delay_is_one_tick() {
let snapshot = zset(&[(b"a", 1), (b"b", 1)]);
let c = C::new();
let i = c.input();
let mut d = 0;
assert_eq!(zs_circuit_delay(c.0, i, &mut d), OK);
let s = c.sink(d);
c.push(i, &[(b"p", 1)]);
assert_eq!(zs_circuit_step(c.0), OK);
c.take(s);
let mut job = null_mut();
assert_eq!(zs_rebuild_new(c.0, i, snapshot, 1, &mut job), OK);
let (mut calls, mut done) = (0u32, 0);
let ctx = (&mut calls as *mut u32).cast();
assert_eq!(
zs_rebuild_step(job, c.0, Some(out_of_time), ctx, &mut done),
OK
);
assert_eq!(done, 0);
assert_eq!(
zs_rebuild_step(job, c.0, Some(out_of_time), ctx, &mut done),
OK
);
assert_eq!(
done, 1,
"the last chunk is Done though the deadline expired"
);
assert_eq!(c.take(s), one(&[(b"p", 1)]));
zs_rebuild_free(job);
zs_zset_free(snapshot);
}