diff --git a/Makefile.cbm b/Makefile.cbm index 3ff50b81..78f05366 100644 --- a/Makefile.cbm +++ b/Makefile.cbm @@ -111,7 +111,8 @@ FOUNDATION_SRCS = \ src/foundation/compat_regex.c \ src/foundation/mem.c \ src/foundation/diagnostics.c \ - src/foundation/profile.c + src/foundation/profile.c \ + src/foundation/dump_verify.c # Existing extraction C code (compiled from current location) EXTRACTION_SRCS = \ @@ -292,7 +293,8 @@ TEST_FOUNDATION_SRCS = \ tests/test_str_intern.c \ tests/test_log.c \ tests/test_str_util.c \ - tests/test_platform.c + tests/test_platform.c \ + tests/test_dump_verify.c TEST_EXTRACTION_SRCS = \ tests/test_extraction.c \ @@ -310,7 +312,8 @@ TEST_STORE_SRCS = \ tests/test_store_arch.c \ tests/test_store_bulk.c \ tests/test_store_pragmas.c \ - tests/test_store_checkpoint.c + tests/test_store_checkpoint.c \ + tests/test_dump_verify_io.c TEST_CYPHER_SRCS = \ tests/test_cypher.c diff --git a/README.md b/README.md index b48a297f..cb5e90aa 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,7 @@ codebase-memory-mcp config reset auto_index # reset to default | `CBM_DOWNLOAD_URL` | *(GitHub releases)* | Override the download URL for updates. Used for testing or self-hosted deployments. | | `CBM_LOG_LEVEL` | `info` | Set the minimum log level. Accepted values (case-insensitive): `debug`, `info`, `warn`, `error`, `none` — or their numeric equivalents `0`–`4` matching the internal enum. Logs go to stderr; stdout is reserved for MCP JSON-RPC. | | `CBM_WORKERS` | *(detected)* | Override the parallel-indexing worker count returned by `cbm_default_worker_count`. Useful inside containers where `sysconf(_SC_NPROCESSORS_ONLN)` reports host CPUs rather than the cgroup's effective quota. Range 1–256; invalid values are ignored with a warning. | +| `CBM_DUMP_VERIFY_MIN_RATIO` | `0.5` | After indexing, compare persisted SQLite node count to the in-memory dump count. When persisted nodes fall below this fraction of committed nodes (and committed > 50), `index_repository` returns `status:"degraded"` instead of silent `indexed`. Range 0–1; set `0` to disable. Invalid values are ignored with a warning. | ```bash # Store indexes in a custom directory diff --git a/src/foundation/dump_verify.c b/src/foundation/dump_verify.c new file mode 100644 index 00000000..c4275592 --- /dev/null +++ b/src/foundation/dump_verify.c @@ -0,0 +1,40 @@ +/* + * dump_verify.c — Post-dump plausibility gate (#334). + */ +#include "foundation/dump_verify.h" +#include "foundation/constants.h" +#include "foundation/log.h" +#include "foundation/platform.h" + +#include +#include + +bool cbm_dump_verify_is_degraded(int committed_nodes, int persisted_nodes, double ratio, + int min_floor) { + if (ratio <= 0.0) { + return false; + } + if (committed_nodes < 0) { + return false; + } + if (committed_nodes <= min_floor) { + return false; + } + if (persisted_nodes < 0) { + return true; + } + return (double)persisted_nodes < (double)committed_nodes * ratio; +} + +double cbm_dump_verify_min_ratio(void) { + char buf[CBM_SZ_32]; + if (cbm_safe_getenv("CBM_DUMP_VERIFY_MIN_RATIO", buf, sizeof(buf), NULL) != NULL) { + char *end = NULL; + double r = strtod(buf, &end); + if (end != buf && r >= 0.0 && r <= 1.0) { + return r; + } + cbm_log_warn("dump_verify.env.invalid", "value", buf, "fallback", "0.5"); + } + return CBM_DUMP_VERIFY_DEFAULT_RATIO; +} diff --git a/src/foundation/dump_verify.h b/src/foundation/dump_verify.h new file mode 100644 index 00000000..8b7f6757 --- /dev/null +++ b/src/foundation/dump_verify.h @@ -0,0 +1,32 @@ +/* + * dump_verify.h — Post-dump plausibility gate (#334 design b). + * + * Compares committed in-memory node counts against persisted SQLite rows + * after index_repository completes. Nodes-only gate (edges shrink legitimately + * at dump when endpoints fail to resolve). + */ +#ifndef CBM_DUMP_VERIFY_H +#define CBM_DUMP_VERIFY_H + +#include + +/** Repos with at most this many committed nodes skip the ratio gate. */ +enum { CBM_DUMP_VERIFY_MIN_FLOOR = 50 }; + +/** Default minimum persisted/committed ratio when env is unset. */ +#define CBM_DUMP_VERIFY_DEFAULT_RATIO 0.5 + +/** + * True when persisted_nodes is implausibly below committed_nodes. + * + * Returns false when ratio <= 0 (gate disabled), committed_nodes < 0 (no dump), + * committed_nodes <= min_floor (sparse repo), or persisted >= committed * ratio. + * Returns true when persisted_nodes < 0 (count error). + */ +bool cbm_dump_verify_is_degraded(int committed_nodes, int persisted_nodes, double ratio, + int min_floor); + +/** Read CBM_DUMP_VERIFY_MIN_RATIO (0..1); invalid/unset -> default 0.5. Set 0 to disable. */ +double cbm_dump_verify_min_ratio(void); + +#endif /* CBM_DUMP_VERIFY_H */ diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index d3aa3bf3..33c0d7a0 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -54,6 +54,7 @@ enum { #include "foundation/compat_thread.h" #include "foundation/log.h" #include "foundation/str_util.h" +#include "foundation/dump_verify.h" #include "foundation/compat_regex.h" #include "pipeline/artifact.h" @@ -2525,28 +2526,82 @@ static void add_excluded_summary(yyjson_mut_doc *doc, yyjson_mut_val *root, char yyjson_mut_obj_add_val(doc, root, "excluded", excluded); } -/* Build the success portion of the index_repository response. */ -static void build_index_success_response(cbm_mcp_server_t *srv, yyjson_mut_doc *doc, +/* Build the success portion of the index_repository response. + * Returns true when status should be "degraded" (#334 plausibility gate). */ +static bool build_index_success_response(cbm_mcp_server_t *srv, yyjson_mut_doc *doc, yyjson_mut_val *root, const char *project_name, - const char *repo_path, bool persistence, + const char *repo_path, bool persistence, cbm_pipeline_t *p, char **excluded_dirs, int excluded_count) { add_excluded_summary(doc, root, excluded_dirs, excluded_count); + int exp_nodes = -1; + int exp_edges = -1; + cbm_pipeline_get_committed_counts(p, &exp_nodes, &exp_edges); + + const double ratio = cbm_dump_verify_min_ratio(); + const int min_floor = CBM_DUMP_VERIFY_MIN_FLOOR; + cbm_store_t *store = resolve_store(srv, project_name); + int nodes = 0; + int edges = 0; + bool degraded = false; + if (!store) { - return; + degraded = true; + } else { + nodes = cbm_store_count_nodes(store, project_name); + edges = cbm_store_count_edges(store, project_name); + if (nodes < 0) { + degraded = true; + nodes = 0; + edges = edges >= 0 ? edges : 0; + } else if (cbm_dump_verify_is_degraded(exp_nodes, nodes, ratio, min_floor)) { + (void)cbm_store_checkpoint(store); + int nodes2 = cbm_store_count_nodes(store, project_name); + int edges2 = cbm_store_count_edges(store, project_name); + if (nodes2 >= 0) { + nodes = nodes2; + } + if (edges2 >= 0) { + edges = edges2; + } + degraded = cbm_dump_verify_is_degraded(exp_nodes, nodes, ratio, min_floor); + } } - int nodes = cbm_store_count_nodes(store, project_name); - int edges = cbm_store_count_edges(store, project_name); + yyjson_mut_obj_add_int(doc, root, "nodes", nodes); yyjson_mut_obj_add_int(doc, root, "edges", edges); + if (exp_nodes >= 0) { + yyjson_mut_obj_add_int(doc, root, "expected_nodes", exp_nodes); + yyjson_mut_obj_add_int(doc, root, "expected_edges", exp_edges); + } + + if (degraded) { + if (!store) { + yyjson_mut_obj_add_str(doc, root, "hint", + "Index database failed integrity check and was removed. " + "Re-run index_repository(repo_path=...) to rebuild."); + cbm_log_warn("dump.verify", "reason", "store_missing", "expected_nodes", + exp_nodes >= 0 ? "set" : "unknown"); + } else { + char exp_buf[MCP_FIELD_SIZE]; + char got_buf[MCP_FIELD_SIZE]; + snprintf(exp_buf, sizeof(exp_buf), "%d", exp_nodes); + snprintf(got_buf, sizeof(got_buf), "%d", nodes); + yyjson_mut_obj_add_str( + doc, root, "hint", + "Persisted far fewer nodes than indexed — likely durability loss from a " + "hard-killed sibling process. Re-run index_repository(repo_path=...) to rebuild."); + cbm_log_warn("dump.verify", "expected_nodes", exp_buf, "persisted_nodes", got_buf); + } + } char adr_path[CBM_SZ_4K]; snprintf(adr_path, sizeof(adr_path), "%s/.codebase-memory/adr.md", repo_path); struct stat adr_st; bool adr_exists = (stat(adr_path, &adr_st) == 0); yyjson_mut_obj_add_bool(doc, root, "adr_present", adr_exists); - if (!adr_exists) { + if (!adr_exists && !degraded) { yyjson_mut_obj_add_str( doc, root, "adr_hint", "Project indexed. Consider creating an Architecture Decision Record: " @@ -2561,6 +2616,8 @@ static void build_index_success_response(cbm_mcp_server_t *srv, yyjson_mut_doc * "Persistent artifact written to .codebase-memory/graph.db.zst. " "Commit this file to share the index with teammates."); } + + return degraded; } static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) { @@ -2641,19 +2698,18 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) { yyjson_mut_doc_set_root(doc, root); yyjson_mut_obj_add_str(doc, root, "project", project_name); - yyjson_mut_obj_add_str(doc, root, "status", rc == 0 ? "indexed" : "error"); - if (rc != 0) { + if (rc == 0) { + bool degraded = build_index_success_response(srv, doc, root, project_name, repo_path, + persistence, p, excluded_dirs, excluded_count); + yyjson_mut_obj_add_str(doc, root, "status", degraded ? "degraded" : "indexed"); + } else { + yyjson_mut_obj_add_str(doc, root, "status", "error"); yyjson_mut_obj_add_str(doc, root, "hint", "Pipeline failed. Check repo_path exists and contains source files. " "Try mode='fast' for a quicker diagnostic run."); } - if (rc == 0) { - build_index_success_response(srv, doc, root, project_name, repo_path, persistence, - excluded_dirs, excluded_count); - } - char *json = yy_doc_to_str(doc); yyjson_mut_doc_free(doc); /* Free the pipeline only after the response doc copied the excluded list. */ diff --git a/src/pipeline/pipeline.c b/src/pipeline/pipeline.c index c080a285..a8df0d9c 100644 --- a/src/pipeline/pipeline.c +++ b/src/pipeline/pipeline.c @@ -90,6 +90,10 @@ struct cbm_pipeline { /* User-defined extension overrides (loaded once per run) */ cbm_userconfig_t *userconfig; + + /* Committed graph size at dump time (-1 = dump did not run). #334 gate axis. */ + int committed_nodes; + int committed_edges; }; /* ── Global pkgmap (one active pipeline at a time) ─────────────── */ @@ -149,6 +153,8 @@ cbm_pipeline_t *cbm_pipeline_new(const char *repo_path, const char *db_path, p->project_name = cbm_project_name_from_path(repo_path); p->mode = mode; p->persistence = false; + p->committed_nodes = -1; + p->committed_edges = -1; atomic_init(&p->cancelled, 0); return p; @@ -211,6 +217,15 @@ void cbm_pipeline_get_excluded(const cbm_pipeline_t *p, char ***out, int *count) } } +void cbm_pipeline_get_committed_counts(const cbm_pipeline_t *p, int *nodes, int *edges) { + if (nodes) { + *nodes = p ? p->committed_nodes : -1; + } + if (edges) { + *edges = p ? p->committed_edges : -1; + } +} + /* Resolve the DB path for this pipeline. Caller must free(). */ static char *resolve_db_path(const cbm_pipeline_t *p) { char *path = malloc(CBM_SZ_1K); @@ -814,6 +829,8 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_info_t *fil cbm_log_error("pipeline.err", "phase", "dump"); return rc; } + p->committed_nodes = cbm_gbuf_node_count(p->gbuf); + p->committed_edges = cbm_gbuf_edge_count(p->gbuf); cbm_log_info("pass.timing", "pass", "dump", "elapsed_ms", itoa_buf((int)elapsed_ms(*t))); cbm_store_t *hash_store = cbm_store_open_path(db_path); if (hash_store) { diff --git a/src/pipeline/pipeline.h b/src/pipeline/pipeline.h index 1aa5ab5f..620cddab 100644 --- a/src/pipeline/pipeline.h +++ b/src/pipeline/pipeline.h @@ -72,6 +72,10 @@ int cbm_pipeline_get_mode(const cbm_pipeline_t *p); * to NULL/0 when p is NULL or nothing was excluded. Do not free. */ void cbm_pipeline_get_excluded(const cbm_pipeline_t *p, char ***out, int *count); +/* Committed node/edge counts captured at dump time (-1 when dump did not run). + * Nodes are the #334 plausibility-gate axis; edges are informational only. */ +void cbm_pipeline_get_committed_counts(const cbm_pipeline_t *p, int *nodes, int *edges); + /* ── Index lock (prevents concurrent pipeline runs on same DB) ──── */ /* Try to acquire the global index lock. Returns true if acquired, diff --git a/tests/test_dump_verify.c b/tests/test_dump_verify.c new file mode 100644 index 00000000..9b4ad98f --- /dev/null +++ b/tests/test_dump_verify.c @@ -0,0 +1,89 @@ +/* + * test_dump_verify.c — Post-dump plausibility gate (#334). + * + * Pure-function matrix mirrors sast-ai-app checkSilentDegradation cases. + * I/O-level coverage that drives the gate against a real on-disk SQLite store + * lives in test_dump_verify_io.c (store-linked, excluded from test-foundation). + */ +#include "../src/foundation/compat.h" +#include "../src/foundation/dump_verify.h" +#include "test_framework.h" + +#include + +TEST(dump_verify_no_baseline) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(-1, 500, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_sparse_at_floor) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(50, 10, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + ASSERT_FALSE(cbm_dump_verify_is_degraded(12, 5, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_shortfall_below_ratio) { + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, 400, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_just_above_ratio) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(1000, 500, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_just_below_ratio) { + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, 499, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_zero_persisted) { + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, 0, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_growth) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(500, 750, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_count_error) { + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, -1, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_ratio_zero_disables) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(1000, 10, 0.0, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_loosened_ratio) { + ASSERT_FALSE(cbm_dump_verify_is_degraded(1000, 600, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_tightened_ratio) { + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, 900, 0.95, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +TEST(dump_verify_edges_shrank_nodes_ok) { + /* Edges are not gated; this documents nodes-only semantics for integrators. */ + ASSERT_FALSE(cbm_dump_verify_is_degraded(200, 200, 0.5, CBM_DUMP_VERIFY_MIN_FLOOR)); + PASS(); +} + +SUITE(dump_verify) { + RUN_TEST(dump_verify_no_baseline); + RUN_TEST(dump_verify_sparse_at_floor); + RUN_TEST(dump_verify_shortfall_below_ratio); + RUN_TEST(dump_verify_just_above_ratio); + RUN_TEST(dump_verify_just_below_ratio); + RUN_TEST(dump_verify_zero_persisted); + RUN_TEST(dump_verify_growth); + RUN_TEST(dump_verify_count_error); + RUN_TEST(dump_verify_ratio_zero_disables); + RUN_TEST(dump_verify_loosened_ratio); + RUN_TEST(dump_verify_tightened_ratio); + RUN_TEST(dump_verify_edges_shrank_nodes_ok); +} diff --git a/tests/test_dump_verify_io.c b/tests/test_dump_verify_io.c new file mode 100644 index 00000000..5b0b97f7 --- /dev/null +++ b/tests/test_dump_verify_io.c @@ -0,0 +1,176 @@ +/* + * test_dump_verify_io.c — I/O-level coverage for the #334 plausibility gate. + * + * Drives cbm_dump_verify_is_degraded against a real on-disk SQLite store and + * the actual cbm_store_count_nodes / cbm_store_open_path_query path used by + * resolve_store(). Includes a fork/crash port of bulk_crash_recovery proving + * uncommitted WAL frames are discarded on the next open and the gate trips when + * persisted rows fall short of the committed dump intent. + * + * Store-linked (excluded from the fast test-foundation target); runs under the + * full test-runner. The pure-function matrix lives in test_dump_verify.c. + */ +#include "../src/foundation/compat.h" +#include "../src/foundation/dump_verify.h" +#include "test_framework.h" + +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif + +#define DV_PROJECT "io_dump_verify" + +static void dv_temp_path(char *buf, size_t n) { + snprintf(buf, n, "%s/cmm_dump_verify_%d.db", cbm_tmpdir(), (int)getpid()); +} + +static void dv_cleanup(const char *path) { + remove(path); + char aux[640]; + snprintf(aux, sizeof(aux), "%s-wal", path); + remove(aux); + snprintf(aux, sizeof(aux), "%s-shm", path); + remove(aux); +} + +/* Commit `count` distinct nodes to the on-disk store at `path`. */ +static int dv_write_nodes(const char *path, int count) { + cbm_store_t *s = cbm_store_open_path(path); + if (!s) + return -1; + if (cbm_store_upsert_project(s, DV_PROJECT, "/tmp/io_dump_verify") != CBM_STORE_OK) { + cbm_store_close(s); + return -1; + } + cbm_store_begin(s); + for (int i = 0; i < count; i++) { + char qn[64]; + snprintf(qn, sizeof(qn), "io.node.%d", i); + cbm_node_t node = {.project = DV_PROJECT, + .label = "Function", + .name = "n", + .qualified_name = qn, + .file_path = "io.c", + .start_line = i, + .end_line = i}; + if (cbm_store_upsert_node(s, &node) < 0) { + cbm_store_close(s); + return -1; + } + } + cbm_store_commit(s); + cbm_store_close(s); + return count; +} + +/* Persisted node count via the same query path resolve_store() uses. */ +static int dv_persisted_nodes(const char *path) { + cbm_store_t *s = cbm_store_open_path_query(path); + if (!s) + return CBM_STORE_ERR; + int n = cbm_store_count_nodes(s, DV_PROJECT); + cbm_store_close(s); + return n; +} + +TEST(dump_verify_io_full_persist_ok) { + char path[640]; + dv_temp_path(path, sizeof(path)); + dv_cleanup(path); + + ASSERT_EQ(dv_write_nodes(path, 200), 200); + int persisted = dv_persisted_nodes(path); + ASSERT_EQ(persisted, 200); + /* committed == persisted: a faithful dump is never degraded. */ + ASSERT_FALSE(cbm_dump_verify_is_degraded(200, persisted, CBM_DUMP_VERIFY_DEFAULT_RATIO, + CBM_DUMP_VERIFY_MIN_FLOOR)); + + dv_cleanup(path); + PASS(); +} + +TEST(dump_verify_io_shortfall_after_delete) { + char path[640]; + dv_temp_path(path, sizeof(path)); + dv_cleanup(path); + + ASSERT_EQ(dv_write_nodes(path, 1000), 1000); + ASSERT_FALSE(cbm_dump_verify_is_degraded(1000, dv_persisted_nodes(path), + CBM_DUMP_VERIFY_DEFAULT_RATIO, + CBM_DUMP_VERIFY_MIN_FLOOR)); + + /* Simulate persisted loss: most rows vanish while the committed intent + * (1000) is unchanged — the #334 silent-degradation signature. */ + cbm_store_t *s = cbm_store_open_path(path); + ASSERT_NOT_NULL(s); + ASSERT_EQ(cbm_store_exec(s, "DELETE FROM nodes WHERE start_line >= 100"), CBM_STORE_OK); + cbm_store_close(s); + + int persisted = dv_persisted_nodes(path); + ASSERT_EQ(persisted, 100); + ASSERT_TRUE(cbm_dump_verify_is_degraded(1000, persisted, CBM_DUMP_VERIFY_DEFAULT_RATIO, + CBM_DUMP_VERIFY_MIN_FLOOR)); + + dv_cleanup(path); + PASS(); +} + +#ifndef _WIN32 +TEST(dump_verify_io_fork_crash_uncommitted_discarded) { + char path[640]; + dv_temp_path(path, sizeof(path)); + dv_cleanup(path); + + /* Baseline above the sparse floor is durably committed before the crash. */ + ASSERT_EQ(dv_write_nodes(path, 60), 60); + + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + /* Child: write a large uncommitted batch then crash without commit. */ + cbm_store_t *s = cbm_store_open_path(path); + if (!s) + _exit(2); + cbm_store_begin(s); + for (int i = 1000; i < 6000; i++) { + char qn[64]; + snprintf(qn, sizeof(qn), "io.node.%d", i); + cbm_node_t node = {.project = DV_PROJECT, + .label = "Function", + .name = "n", + .qualified_name = qn, + .file_path = "io.c", + .start_line = i, + .end_line = i}; + cbm_store_upsert_node(s, &node); + } + /* No commit, no clean close: simulate a crash mid-dump. */ + _exit(0); + } + + int status = 0; + ASSERT_TRUE(waitpid(pid, &status, 0) == pid); + + /* WAL recovery discards the child's uncommitted frames: only the baseline + * survives, so persisted (60) falls far short of the dump intent (5060). */ + int persisted = dv_persisted_nodes(path); + ASSERT_EQ(persisted, 60); + ASSERT_TRUE(cbm_dump_verify_is_degraded(5060, persisted, CBM_DUMP_VERIFY_DEFAULT_RATIO, + CBM_DUMP_VERIFY_MIN_FLOOR)); + + dv_cleanup(path); + PASS(); +} +#endif /* _WIN32 */ + +SUITE(dump_verify_io) { + RUN_TEST(dump_verify_io_full_persist_ok); + RUN_TEST(dump_verify_io_shortfall_after_delete); +#ifndef _WIN32 + RUN_TEST(dump_verify_io_fork_crash_uncommitted_discarded); +#endif +} diff --git a/tests/test_main.c b/tests/test_main.c index 042d613e..ce0005c7 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -97,6 +97,8 @@ extern void suite_grammar_probe_g(void); extern void suite_incremental(void); extern void suite_simhash(void); extern void suite_stack_overflow(void); +extern void suite_dump_verify(void); +extern void suite_dump_verify_io(void); /* Free the main thread's thread-local node-type bitset cache before exit so * LeakSanitizer (Linux x64) doesn't report it. Worker threads free their own @@ -114,6 +116,7 @@ int main(void) { RUN_SUITE(log); RUN_SUITE(str_util); RUN_SUITE(platform); + RUN_SUITE(dump_verify); /* Existing C code regression tests */ RUN_SUITE(ac); @@ -131,6 +134,7 @@ int main(void) { RUN_SUITE(store_bulk); RUN_SUITE(store_pragmas); RUN_SUITE(store_checkpoint); + RUN_SUITE(dump_verify_io); /* Cypher (M6) */ RUN_SUITE(cypher);