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` 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 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). 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 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). So the first 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 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 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. 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 12. **Versioning.** `zs_abi_version()` and `ZS_ABI_VERSION` are 1. A change that breaks a
signature or a code bumps it. 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 ## Calling it from Common Lisp
`crates/zset-ffi/tests/smoke.lisp` is a runnable CFFI program (`defcfun` for each call, a `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 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. 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_eq!(zs_circuit_step(c.0), Code::BadValue as i32);
assert!(last_error().contains("exactly one key"), "{}", last_error()); 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);
}