mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
test(cypher): isolate UNION stack checks from elapsed time
Use a thread-local test clock for the large UNION structural regression so sanitizer overhead cannot consume its execution deadline. Preserve all row, ordering, and constrained-stack checks. Add an exact positive budget expiry case alongside the existing deadline tests. Request the fixture's exact 20000-row budget to avoid reserving the default 100000 binding slots for every branch, and assert no truncation. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
df6f674ecb
commit
415abe9c2d
3 files changed
+63
-3
No files matched your search
+19
-2
@@ -3138,11 +3138,28 @@ static _Thread_local bool g_cypher_timed_out = false;
|
||||
static _Thread_local bool g_cypher_truncated = false;
|
||||
static _Thread_local int64_t g_cypher_deadline_override_ms = -1; /* test hook; <0 = default */
|
||||
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
static _Thread_local cbm_cypher_test_clock_fn g_cypher_deadline_clock = NULL;
|
||||
|
||||
void cbm_cypher_test_set_deadline_clock(cbm_cypher_test_clock_fn clock_fn) {
|
||||
g_cypher_deadline_clock = clock_fn;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint64_t cypher_deadline_now(void) {
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
if (g_cypher_deadline_clock) {
|
||||
return g_cypher_deadline_clock();
|
||||
}
|
||||
#endif
|
||||
return cbm_now_ms();
|
||||
}
|
||||
|
||||
static void cypher_deadline_arm(void) {
|
||||
g_cypher_timed_out = false;
|
||||
int64_t budget = g_cypher_deadline_override_ms >= 0 ? g_cypher_deadline_override_ms
|
||||
: CYPHER_DEADLINE_BUDGET_MS;
|
||||
g_cypher_deadline_ms = cbm_now_ms() + (uint64_t)budget;
|
||||
g_cypher_deadline_ms = cypher_deadline_now() + (uint64_t)budget;
|
||||
}
|
||||
|
||||
/* True once the query has run past its wall-clock budget. Sticky: after the
|
||||
@@ -3154,7 +3171,7 @@ static bool cypher_deadline_exceeded(void) {
|
||||
if (g_cypher_deadline_ms == 0) {
|
||||
return false;
|
||||
}
|
||||
if (cbm_now_ms() >= g_cypher_deadline_ms) {
|
||||
if (cypher_deadline_now() >= g_cypher_deadline_ms) {
|
||||
g_cypher_timed_out = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -355,6 +355,12 @@ void cbm_query_free(cbm_query_t *q);
|
||||
* check; a negative value restores the default budget. */
|
||||
void cbm_cypher_test_set_deadline_ms(int64_t budget_ms);
|
||||
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
/* Override only the deadline clock on this thread; NULL restores real time. */
|
||||
typedef uint64_t (*cbm_cypher_test_clock_fn)(void);
|
||||
void cbm_cypher_test_set_deadline_clock(cbm_cypher_test_clock_fn clock_fn);
|
||||
#endif
|
||||
|
||||
/* Worst-case binding slot count for a node cross-join. Computes the count in
|
||||
* size_t and rejects any that would not fit the int binding counter or would
|
||||
* overflow the size_t byte size; returns 0 and writes *out_n on success,
|
||||
|
||||
+38
-1
@@ -5064,6 +5064,35 @@ TEST(cypher_exec_deadline_aborts_runaway_query_issue601) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
static uint64_t cypher_frozen_deadline_clock(void) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
static _Thread_local unsigned cypher_deadline_clock_reads = 0;
|
||||
|
||||
static uint64_t cypher_expiring_deadline_clock(void) {
|
||||
/* Default budget is 30 seconds; expire exactly at the first checkpoint. */
|
||||
return cypher_deadline_clock_reads++ == 0 ? 1000 : 31000;
|
||||
}
|
||||
|
||||
TEST(cypher_exec_deadline_expires_at_positive_budget_boundary) {
|
||||
cbm_store_t *s = setup_cypher_store();
|
||||
cbm_cypher_result_t r = {0};
|
||||
cypher_deadline_clock_reads = 0;
|
||||
cbm_cypher_test_set_deadline_clock(cypher_expiring_deadline_clock);
|
||||
int rc = cbm_cypher_execute(s, "MATCH (n) RETURN n.name", "test", 0, &r);
|
||||
cbm_cypher_test_set_deadline_clock(NULL);
|
||||
|
||||
ASSERT_TRUE(cypher_deadline_clock_reads >= 2);
|
||||
ASSERT_TRUE(rc != 0);
|
||||
ASSERT_NOT_NULL(r.error);
|
||||
ASSERT_NOT_NULL(strstr(r.error, "time limit"));
|
||||
ASSERT_EQ(r.row_count, 0);
|
||||
cbm_cypher_result_free(&r);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #601 companion: the default (ample) budget must NOT false-positive on a
|
||||
* normal small query — it still returns its rows. */
|
||||
TEST(cypher_exec_deadline_allows_normal_query_issue601) {
|
||||
@@ -5442,13 +5471,20 @@ static int union_many_check(void *arg) {
|
||||
|
||||
cbm_store_t *s = setup_cypher_store();
|
||||
cbm_cypher_result_t r = {0};
|
||||
int rc = cbm_cypher_execute(s, query, "test", 0, &r);
|
||||
/* This exercises stack depth and every result, independently of how
|
||||
* slowly instrumentation runs. Dedicated deadline tests retain expiry. */
|
||||
cbm_cypher_test_set_deadline_clock(cypher_frozen_deadline_clock);
|
||||
/* Request exactly the output this fixture needs: the default 100k ceiling
|
||||
* also reserves 100k binding slots separately for every UNION branch. */
|
||||
int rc = cbm_cypher_execute(s, query, "test", CYPHER_UNION_MANY_BRANCHES, &r);
|
||||
cbm_cypher_test_set_deadline_clock(NULL);
|
||||
free(query);
|
||||
if (rc != 0) {
|
||||
printf(" query error: %s\n", r.error ? r.error : "(none)");
|
||||
}
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_EQ(r.row_count, CYPHER_UNION_MANY_BRANCHES);
|
||||
ASSERT_TRUE(!r.truncated);
|
||||
for (int i = 0; i < r.row_count; i++) {
|
||||
ASSERT_STR_EQ(r.rows[i][0], cypher_fixture_nodes[i % CYPHER_FIXTURE_NODE_COUNT]);
|
||||
}
|
||||
@@ -5548,6 +5584,7 @@ SUITE(cypher) {
|
||||
RUN_TEST(cypher_parse_error_frees_partial_pattern);
|
||||
/* Execution */
|
||||
RUN_TEST(cypher_exec_deadline_aborts_runaway_query_issue601);
|
||||
RUN_TEST(cypher_exec_deadline_expires_at_positive_budget_boundary);
|
||||
RUN_TEST(cypher_exec_deadline_allows_normal_query_issue601);
|
||||
RUN_TEST(cypher_deep_nesting_rejected_not_crash);
|
||||
RUN_TEST(cypher_exec_match_all_functions);
|
||||
|
||||
Reference in new issue
Block a user