8000 test: SIGKILL crash test checks committed data (#384, v2.x backport) by adityamukho · Pull Request #485 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions .github/workflows/crash-kill.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
name: SIGKILL crash-safety fuzz (nightly)

# Two branches alternate every 12 hours: main at 03:17 UTC, v3 at 15:17 UTC.
# Scheduled runs always use this file from the default branch, so the branch
# under test is chosen from the cron string that fired (as in bench.yml).
# Keep the cron strings here and in CRASH_REF/run-name identical.
run-name: >-
SIGKILL crash-safety fuzz (${{ github.event_name == 'workflow_dispatch' && inputs.ref
|| (github.event.schedule == '17 15 * * *' && 'v3' || 'main') }})

# #308: an abrupt kill landing mid-checkpoint had a ~26% chance of leaving
# the header on disk with a bumped page_count but a stale checksum --
# permanent corruption with no recovery path. The bug was in the ordering
Expand All @@ -8,17 +16,29 @@ name: SIGKILL crash-safety fuzz (nightly)
# matrix rather than Linux only. tests/crash_kill_test.rs runs a small
# smoke count in the per-PR suite (rust.yml, already cross-platform); this
# workflow runs a much larger round count nightly for statistical
# confidence on each OS.
# confidence on each OS. Since #384 each round also checks that the reopened
# file holds exactly the transactions the child confirmed (EAVT, AEVT, AVET,
# full scan, :as-of; verify on v3). The test prints its base seed; replay a
< 8000 /td> # failure with MINIGRAF_CRASH_KILL_SEED.

on:
schedule:
- cron: "17 3 * * *"
- cron: "17 3 * * *" # main
- cron: "17 15 * * *" # v3
workflow_dispatch:
inputs:
ref:
description: Branch to test
required: true
default: main

permissions:
contents: read
issues: write

env:
CRASH_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || (github.event.schedule == '17 15 * * *' && 'v3' || 'main') }}

jobs:
crash-kill:
name: SIGKILL fuzz suite
Expand All @@ -31,6 +51,8 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.CRASH_REF }}

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
Expand All @@ -49,16 +71,16 @@ jobs:
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ISSUE_TITLE: "Nightly SIGKILL crash-safety failure (${{ matrix.os }})"
ISSUE_TITLE: "Nightly SIGKILL crash-safety failure (${{ env.CRASH_REF }}, ${{ matrix.os }})"
run: |
report_file="crash-kill-failure.md"
{
echo "The nightly SIGKILL crash-safety fuzz suite failed on ${{ matrix.os }}."
echo "The nightly SIGKILL crash-safety fuzz suite failed on ${{ env.CRASH_REF }} (${{ matrix.os }})."
echo
echo "- Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo "- Commit: $GITHUB_SHA"
echo "- Commit: $(git rev-parse HEAD)"
echo "- Reproduce: cargo test --test crash_kill_test -- --nocapture"
echo " (set MINIGRAF_CRASH_KILL_ROUNDS to raise the round count)"
echo " (set MINIGRAF_CRASH_KILL_ROUNDS to raise the round count; MINIGRAF_CRASH_KILL_SEED to the printed base seed to replay)"
echo
echo "## Test output"
echo
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Tests

- **Golden-file compatibility corpus (#391).** `tests/golden/` holds v7 files written by minigraf 2.0.3, each with a JSON manifest of its CRC32, `tx_count` and expected query results. They cover one checkpoint, several checkpoints, indexes rebuilt on open, a WAL left by a crash, a WAL whose entries were already checkpointed, and same-transaction multi-values (#371). `tests/golden_corpus_test.rs` opens a copy of each file and checks its manifest after open, after a checkpoint and reopen, and after one more write. Golden files are never regenerated, only added; a new `golden-corpus` job in `policy.yml` fails a pull request that changes one. The generator is a standalone crate pinned to `minigraf = "=2.0.3"` (`tests/golden/gen/v7/`).
- **SIGKILL crash test checks committed data (#384).** `tests/crash_kill_test.rs` used to check only that the file reopened and one attribute query ran, so it missed #370. The child now runs a seeded workload (batches over several entities and attributes, batched retracts, re-assertions after a retract) and logs each transaction after `execute` returns; it checkpoints after every transaction, every third, never (WAL replay), automatically every 2 writes, or every third with `SyncMode::Normal`. After a `SIGKILL` at a random point, the reopened file must hold exactly the model after the last logged transaction or the one in flight, through a full scan, attribute (AEVT), attribute+value (AVET) and entity-bound (EAVT) queries and `:as-of`; again after a second reopen and after a checkpoint plus reopen. On v2.x the workload avoids the known issues #371 (two values of one attribute in one transaction) and #435 (asserting a triple that is already live returns one row per assertion); the v3 version covers both. Per-PR CI runs 5 rounds; the nightly crash-kill workflow runs 100 per OS. `MINIGRAF_CRASH_KILL_SEED` replays a failure.

## v2.0.3 — 2026-10-06

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Scope — format and storage integrity:
Scope — release-gate testing and documentation:

- Golden-file compatibility corpus for every format version, frozen before v8 work goes further (#391)
- SIGKILL crash test that checks committed data through EAVT, AEVT and full scan (#384)
- SIGKILL crash test that checks committed data through EAVT, AEVT and full scan (#384; also on v2.x, without the #371/#435 cases)
- Model-based test of transact/retract/checkpoint/reopen (#385)
- Wider property tests: file-backed, batched, retracts, temporal, joins (#386)
- Persistent fuzz corpus and an operation-sequence target (#387)
Expand Down
2 changes: 2 additions & 0 deletions docs/TEST_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

**Result**: 1,213 passing tests and 8 ignored tests (1,221 total), including the golden-file corpus added after v2.0.3

`tests/crash_kill_test.rs` kills a child process running a seeded workload under five checkpoint modes at a random point and checks that the reopened file holds exactly the transactions the child logged as committed, or those plus the one in flight, through full-scan, AEVT, AVET, EAVT and `:as-of` queries, again after a second reopen and after a checkpoint plus reopen (#384). The workload leaves out the v2.x known issues #371 and #435.

Not counted above: the `BrowserDb` tests run only on wasm32 with `wasm-bindgen-test` in a browser. Two of them check that the IndexedDB flush returns every page a write dirtied, and that a dirty page that cannot be read is an error rather than left out.

The v2.0.3 suite covers the public database API, the Datalog engine, storage and recovery paths, and the v2.0.0 locking and structured-error-code changes, plus v2.0.1's regression test for rebuilding indexes on open after several checkpoints (#370). Since v2.0.1, `tests/point_query_history_test.rs` and new storage unit tests check that attribute-narrowed bound-entity lookups return exactly what a full scan returns, and that `net_asserted_facts` matches its previous implementation on randomized histories (#323). Attribute-scan tests check that the AEVT range covers exactly the queried attribute, including non-ASCII names such as `:丿` and prefix siblings such as `:ab` for `:a` (#381). Incremental B+tree rebuild tests check that checkpoints which copy untouched index leaves produce exactly the entries and file checksum a full rebuild would, including over repeated checkpoints, that single-fact checkpoints do not fragment the index, and that a cyclic leaf chain or damaged slot directory is rejected rather than copied (#315). Directory-sync unit tests check that creating the `.graph` file or the WAL, and deleting the WAL, fsync the parent directory after the file operation, and that reopening an existing file does not (#389). Disjunction tests check that `or-join` returns an empty result, not INT-031, when no rows reach it: on an empty database, after a pattern that matches nothing, after a `not` that removes every row, and in a rule body (#405). A stratification unit test builds the same two-rule negative cycle 64 times and checks that the INT-054 message names the same predicate pair every time (#410). Magic-sets tests check that a recursive rule with a literal start that recurses through another rule returns the same results as without the literal (#297). API tests check that `execute()` and `WriteTransaction::execute()` reject `$slot` bind tokens in every position with API-010 (#407). The REPL `run()` test now runs in a child process with null stdin, so it no longer hangs when the test runner's stdin is an open non-TTY stream (#415). A fault-injection test crashes `save()` at each of its 47 page writes and syncs and checks that every reopen finds exactly the old or the new checkpointed facts (#374). For v2.0.3, storage unit tests and two `edge_cases_test.rs` tests check that more than 65,535 uncheckpointed facts each resolve to their own fact through entity lookups and attribute scans, in memory and file-backed, before and after a checkpoint, and that the widened `FactRef` encodes to the same bytes as before (#445). A WAL test reopens a database next to a WAL whose entries are all checkpointed, writes again, and checks that `:as-of` history is unchanged before and after another reopen; a unit test checks that restoring the transaction counter never lowers it (#447). Since v2.0.3, `tests/golden_corpus_test.rs` opens a copy of each committed golden file in `tests/golden/` (v7 files written by minigraf 2.0.3: one checkpoint, several checkpoints, indexes rebuilt on open, a crash-left WAL, an already-checkpointed WAL, same-transaction multi-values) and checks its manifest's CRC32, `tx_count` and query results after open, after a checkpoint and reopen, and after one more write; a second test checks that every golden file has a manifest (#391).
Expand Down
Loading
Loading
0