mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2406 from DeusData/fix/spill-test-disk-determinism
test(spill): make spill test verdicts independent of host free disk
This commit is contained in:
4 files changed
+78
-1
No files matched your search
@@ -106,6 +106,31 @@ struct cbm_result_spill {
|
||||
* target to race against, and a failed write stays handled underneath. */
|
||||
enum { SPILL_FREE_FLOOR_GB = 10, SPILL_FREE_SHARE_DIV = 2 };
|
||||
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
/* Set by the test's main thread before any worker exists; atomic so a reading
|
||||
* worker and a restoring test can never race under TSan. */
|
||||
static _Atomic size_t g_free_bytes_pin;
|
||||
|
||||
size_t cbm_result_spill_pin_free_bytes_for_tests(size_t bytes) {
|
||||
return atomic_exchange_explicit(&g_free_bytes_pin, bytes, memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t cbm_result_spill_free_floor_bytes_for_tests(void) {
|
||||
return (size_t)SPILL_FREE_FLOOR_GB * 1024 * 1024 * 1024;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Free space under the spill directory. Production always asks the disk. */
|
||||
static size_t spill_free_bytes(const char *spill_dir) {
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
size_t pinned = atomic_load_explicit(&g_free_bytes_pin, memory_order_relaxed);
|
||||
if (pinned > 0) {
|
||||
return pinned;
|
||||
}
|
||||
#endif
|
||||
return cbm_fs_free_bytes(spill_dir);
|
||||
}
|
||||
|
||||
/* Remove spill files left by runs that are no longer here. Only files this
|
||||
* module names (spill-<pid>-<writer>.bin) are touched, and on Windows an open
|
||||
* file refuses deletion, which is exactly the liveness check we want: a running
|
||||
@@ -164,7 +189,7 @@ cbm_result_spill_t *cbm_result_spill_open(const char *dir, int writers, int slot
|
||||
* caller in the memory path it was already in (back-pressure, then its own
|
||||
* budget decision) — the same outcome as a disk that fills mid-run, minus
|
||||
* the filled disk. */
|
||||
size_t free_bytes = cbm_fs_free_bytes(spill_dir);
|
||||
size_t free_bytes = spill_free_bytes(spill_dir);
|
||||
int64_t byte_cap = 0;
|
||||
if (free_bytes > 0) {
|
||||
size_t floor_bytes = (size_t)SPILL_FREE_FLOOR_GB * 1024 * 1024 * 1024;
|
||||
|
||||
@@ -70,4 +70,15 @@ void cbm_result_spill_close(cbm_result_spill_t *sp);
|
||||
* `new_base`. Exposed for tests. */
|
||||
void cbm_result_relocate(CBMFileResult *result, const char *old_base, size_t len, char *new_base);
|
||||
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
/* Test builds only: open's free-space reading (and so its low-disk refusal and
|
||||
* its disk-share cap) answers `bytes` instead of asking the disk; 0 asks the
|
||||
* disk again. Returns the previous pin. A test's verdict must not depend on how
|
||||
* full the machine's disk happens to be, so the test runner pins ample space
|
||||
* for the whole process and the refusal test pins a low value on purpose. */
|
||||
size_t cbm_result_spill_pin_free_bytes_for_tests(size_t bytes);
|
||||
/* The production floor: below this much free space, open refuses. */
|
||||
size_t cbm_result_spill_free_floor_bytes_for_tests(void);
|
||||
#endif
|
||||
|
||||
#endif /* CBM_RESULT_SPILL_H */
|
||||
@@ -8726,6 +8726,37 @@ TEST(extract_spill_round_trip_keeps_every_field) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The low-disk guard, pinned through the free-space seam rather than the host
|
||||
* disk: one byte under the floor refuses (spilling onto a nearly full disk is
|
||||
* a worse failure than the memory pressure it relieves), the floor itself
|
||||
* opens. The runner pins ample space everywhere else; restored before asserts. */
|
||||
TEST(extract_spill_refuses_below_the_free_disk_floor) {
|
||||
char dir[512];
|
||||
snprintf(dir, sizeof(dir), "%s/cbm_spill_XXXXXX", cbm_tmpdir());
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(dir));
|
||||
|
||||
size_t floor_bytes = cbm_result_spill_free_floor_bytes_for_tests();
|
||||
size_t saved = cbm_result_spill_pin_free_bytes_for_tests(floor_bytes - 1);
|
||||
cbm_result_spill_t *below = cbm_result_spill_open(dir, 1, 1);
|
||||
(void)cbm_result_spill_pin_free_bytes_for_tests(floor_bytes);
|
||||
cbm_result_spill_t *at = cbm_result_spill_open(dir, 1, 1);
|
||||
(void)cbm_result_spill_pin_free_bytes_for_tests(saved);
|
||||
|
||||
bool refused = below == NULL;
|
||||
bool opened = at != NULL;
|
||||
cbm_result_spill_close(below);
|
||||
cbm_result_spill_close(at);
|
||||
char spill_sub[600];
|
||||
snprintf(spill_sub, sizeof(spill_sub), "%s/spill", dir);
|
||||
cbm_rmdir(spill_sub);
|
||||
cbm_rmdir(dir);
|
||||
|
||||
ASSERT_EQ(floor_bytes, (size_t)10 * 1024 * 1024 * 1024);
|
||||
ASSERT_TRUE(refused);
|
||||
ASSERT_TRUE(opened);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── A skipped file gets no LSP walk, per-file or cross-file ── */
|
||||
|
||||
/* CBM_TEST_LSP_SKIP_ON names the file: the result carries lsp_skipped, the
|
||||
@@ -8785,6 +8816,7 @@ SUITE(extraction) {
|
||||
RUN_TEST(extract_compact_keeps_every_field_and_shrinks_the_arena);
|
||||
RUN_TEST(extract_compact_is_idempotent_and_survives_empty_results);
|
||||
RUN_TEST(extract_spill_round_trip_keeps_every_field);
|
||||
RUN_TEST(extract_spill_refuses_below_the_free_disk_floor);
|
||||
RUN_TEST(extract_lsp_skipped_file_gets_no_walk);
|
||||
RUN_TEST(extract_walk_truncated_when_a_node_budget_is_set);
|
||||
/* Initialize extraction library */
|
||||
|
||||
@@ -24,6 +24,7 @@ int tf_skip_count = 0;
|
||||
#include "mcp/index_supervisor.h" /* cbm_index_set_worker_role */
|
||||
#include "mcp/mcp.h" /* cbm_mcp_handle_tool — act as a real worker */
|
||||
#include "ui/http_server.h" /* deleted-self executable probe */
|
||||
#include "result_spill.h" /* pinned free disk: spill verdicts ignore the host disk */
|
||||
#include <sqlite3.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
@@ -957,6 +958,14 @@ int main(int argc, char **argv) {
|
||||
(void)printf("sanitized=%d test_seams=%d\n", sanitized, test_seams);
|
||||
return 0;
|
||||
}
|
||||
/* A test's verdict is a pure function of code, test, platform and seed --
|
||||
* never of how full this machine's disk is. The spill store refuses to open
|
||||
* below 10 GB free, so without this pin every spill test failed, and every
|
||||
* budget-driven pipeline test silently took the no-store path, on a host
|
||||
* with less free space. Pin ample space for this process and every worker
|
||||
* it re-execs; the refusal itself is pinned on purpose by
|
||||
* extraction::extract_spill_refuses_below_the_free_disk_floor. */
|
||||
(void)cbm_result_spill_pin_free_bytes_for_tests((size_t)64 * 1024 * 1024 * 1024);
|
||||
/* Skip the multi-hundred-MB executable-image hash that computes the exact
|
||||
* build fingerprint: it is tens of seconds per spawned worker/daemon under
|
||||
* ASan on constrained CI runners and the sole cause of the daemon-family
|
||||
|
||||
Reference in new issue
Block a user