Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
8.6 KiB
Test evidence: zset-ffi
What each test guards and where a rival failed. Keyed by test name; the reasoning is in
context/code/zset-ffi.md. The tests are crates/zset-ffi/tests/{abi,header,dylib}.rs.
Injected in the source, one at a time, each reverted and the suite rerun (a build that
printed Running was confirmed each time). The failing tests, by name:
guardwithoutcatch_unwind: the overflow tests abort the test binary (SIGABRT, a panic acrossextern "C"), which is the failure this crate exists to prevent;zs_zset_addthat adds in place:overflow_is_an_error_code_and_leaves_the_set_unchanged;- every panic classed
ZS_ERR_PANIC:overflow_is_an_error_code_and_leaves_the_set_unchanged,a_weight_overflow_in_a_step_is_a_code_and_poisons_the_circuit,a_loaded_library_adds_queries_frees_and_reports_overflow; - a failed step that does not poison:
a_weight_overflow_in_a_step_is_a_code_and_poisons_the_circuit; - a recorded fault that does not poison:
a_malformed_pair_and_an_unknown_id_are_codes; rebuild_stepthat ignores the deadline:a_rebuild_resumes_across_calls_and_matches_one_step;Minthat reads non-positive weights:min_and_max_read_only_values_of_positive_weight(the first version of the suite did not catch this; the test was added for it);- a header declaration with a parameter dropped or retyped (
int64_t weightasint32_t):the_header_declares_exactly_the_exported_functions_with_their_types; - a header code renumbered, and a new
Codevariant with no header line:the_header_constants_are_the_rust_constants; zs_zset_encodethat reports the wrong size: eight ABI tests, among thema_small_buffer_reports_the_size_it_needs.
overflow_is_an_error_code_and_leaves_the_set_unchanged
a holds k = MAX and j = 1; adding a set that overflows k must return the overflow code
and leave both keys as they were. The key j sorts before k, so a partial in-place add
changes j first; a fixture where the overflowing key came first would pass that rival.
a_weight_overflow_in_a_step_is_a_code_and_poisons_the_circuit
A weight reaches MAX in an integrate, then one more overflows inside step. The step
returns the overflow code, the next step and a new operator return the poisoned code.
a_rebuild_resumes_across_calls_and_matches_one_step
Nine keys, chunks of two, a deadline callback that is always expired: the job needs five calls' worth of chunks (the callback is counted: five, one per chunk), and the sum of what the sink yields equals one uninterrupted step on a second circuit. The loop is bounded at 20.
min_and_max_read_only_values_of_positive_weight
Weights of mixed sign in one group, where the smallest value has a negative weight, so
> 0 and != 0 give different minima.
a_loaded_library_adds_queries_frees_and_reports_overflow
The built libzset_ffi.{so,dylib} is opened with libloading and driven through symbol
lookups: the contract a CFFI caller meets, including the exported names, not the rlib.
the_header_declares_exactly_the_exported_functions_with_their_types
Parses src/lib.rs and the header (comments stripped), maps each Rust type to its C spelling
and compares names, parameter types and return types; asserts the scanner saw more than 30
functions, so an empty parse cannot pass. The first version compared only arity and passed a
retyped parameter.
the_header_constants_are_the_rust_constants
Every ZS_ERR_*, ZS_ABI_VERSION, ZS_OK and the four ZS_AGG_* equal the Rust values; the
the header's ZS_ERR_* lines are counted against Code::ALL, which the codes! macro
generates, so a new variant without a line fails (it did, injected as Novel = 9).
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.
Review of #347 (8a76469b)
Injected afterwards, one at a time, each failing by name: a null buffer written through
(the test binary aborts, which is the failure); zs_circuit_input adding the input before
checking out, and zs_circuit_take clearing the sink first:
a_null_out_pointer_is_refused_before_any_state_changes; a rebuild stepped on another circuit:
a_rebuild_is_stepped_only_on_its_own_circuit; the sink bit unchecked in take:
stream_ids_and_sink_ids_are_not_interchangeable; a flat_map or keep failure ignored:
a_callback_that_reports_failure_fails_the_step_and_kills_the_circuit; re-entry undetected, or
step not setting in_step:
a_callback_that_calls_back_into_its_circuit_gets_poisoned_and_changes_nothing
(and the unit test below for the first); a count that drops its zero row:
a_count_or_sum_that_cancels_to_zero_keeps_its_row; len32 truncating:
a_length_of_4_gib_or_more_is_refused_not_truncated.
The aliasing finding (zs_zset_add(a, a)) is not an assertion failure on a native build:
only Miri finds it, and a_set_added_to_or_subtracted_from_itself_is_defined is the test
Miri failed before the fix and passes after (recipe in zset-ffi.md).
a_set_added_to_or_subtracted_from_itself_is_defined
Self-add doubles, self-sub empties, and a self-add that overflows leaves the set unchanged.
a_null_buffer_is_refused_before_it_is_written
cap >= needed with a null buffer is ZS_ERR_NULL_ARGUMENT; a null buffer with too small a
cap still probes the size.
a_null_out_pointer_is_refused_before_any_state_changes
The next input id, the sink's contents and the job's progress are observed after a refused call with a null out-pointer; each is as before.
a_rebuild_is_stepped_only_on_its_own_circuit
A job made from one circuit stepped on another is ZS_ERR_BAD_HANDLE and neither sink moves.
stream_ids_and_sink_ids_are_not_interchangeable
A stream id given to take, and a sink id given to delay, output and push, are refused.
a_callback_that_reports_failure_fails_the_step_and_kills_the_circuit
keep, flat_map and expired callbacks each return their failure value: ZS_ERR_CALLBACK,
then ZS_ERR_POISONED.
a_callback_that_calls_back_into_its_circuit_gets_poisoned_and_changes_nothing
A filter callback calls zs_circuit_input on its own circuit: it gets ZS_ERR_POISONED, the
outer step completes, and no input was added.
tests::a_circuit_left_mid_step_by_a_skipped_frame_answers_poisoned_to_everything
What a Lisp non-local exit through zs_circuit_step leaves behind is the in_step flag set;
every later call is ZS_ERR_POISONED and writes nothing.
a_count_or_sum_that_cancels_to_zero_keeps_its_row
Codex's finding: Some(0) was filtered away for a non-empty group.
a_length_of_4_gib_or_more_is_refused_not_truncated
len32 refuses 1 << 32 and accepts u32::MAX.
Delta review of #347 (9bcfa9d1)
Injected, one at a time: a fault that overwrites the first (note replaced by a plain
set) fails an_index_by_callback_that_fails_reports_the_callback_code_not_a_later_fault;
zs_rebuild_step borrowing the job before checking the circuit (Miri only), and a free that ignores
in_step or busy, fail a_callback_cannot_step_or_free_the_job_or_circuit_it_runs_under
(and under Miri, as use-after-free); an owner compared by address instead of id fails
a_circuit_freed_and_another_made_does_not_inherit_its_jobs; the interrupted check removed
fails a_host_step_between_rebuild_calls_is_interrupted_not_a_poison; a retyped
ZsKeepFn fails the_header_callback_typedefs_match_the_rust_callback_types.
an_index_by_callback_that_fails_reports_the_callback_code_not_a_later_fault
The callback fails (returns 3) and emits nothing, so index_by also sees zero keys. The step
must report ZS_ERR_CALLBACK, the first fault, not the consequent BAD_VALUE.
a_callback_cannot_step_or_free_the_job_or_circuit_it_runs_under
An expired callback steps its own job, frees the job and frees the circuit. The step is
refused with ZS_ERR_POISONED, both frees do nothing, and the job then finishes normally.
a_circuit_freed_and_another_made_does_not_inherit_its_jobs
A job outlives its circuit; sixty-four new circuits (some at the old address) all refuse it.
a_host_step_between_rebuild_calls_is_interrupted_not_a_poison
ZS_ERR_INTERRUPTED, and the circuit still steps afterwards.
the_header_callback_typedefs_match_the_rust_callback_types
Retyping ZsKeepFn's return in the header fails. The unary_op! signatures are now read
from the macro body rather than written into the test.