From ecfb9642e153ffc656d0bc2344b17e9d454ff916 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 8 Sep 2026 23:45:24 +0200 Subject: [PATCH] test(daemon): assert fast-fail by mechanism, not wall-clock, in the bootstrap ENOSPC test (#2113, O9) The #1828 fail-fast test asserted `elapsed_ms < 5000`, a wall-clock budget that flaked on the ASan test-diag lane under CI contention while the fail-fast mechanism itself engaged correctly: the whole daemon_bootstrap suite ran in 11 s, so the test never reached the 30 s slow-timeout path -- it only overran the 5 s wall-clock budget. Per O9 a gate never asserts a transient timing window, and a rerun is not a fix. Drop the wall-clock measurement and rely on the deterministic mechanism the test already observes: the client surfaces the recorded ENOSPC cause verbatim ("failed to start" + errno + path), the slow "active or starting" timeout wording is absent, and exactly one daemon is spawned. That "failed to start" message is produced only on the fast-fail break (cbm_daemon_bootstrap_start_failure_format), never on the 30 s deadline path, so these assertions bind the fast-fail contract without any timing. Verified RED-on-revert: defeating the fast-fail detection makes the client hang to the 30 s deadline and report "active or starting", tripping ASSERT_FALSE(stale_wording). Full daemon_bootstrap suite green (28/28) under the ASan/UBSan build. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Vogel --- tests/test_daemon_bootstrap.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_daemon_bootstrap.c b/tests/test_daemon_bootstrap.c index 20213ef5b..0114d6329 100644 --- a/tests/test_daemon_bootstrap.c +++ b/tests/test_daemon_bootstrap.c @@ -1029,7 +1029,9 @@ TEST(daemon_bootstrap_start_failure_record_round_trip) { * waiting the full 30 s and then reporting "active or starting" -- the * opposite of the truth. A real host is spawned against a runtime directory * whose record writes fail with ENOSPC; the client must report "failed to - * start" naming the errno and the path, within seconds, not the deadline. */ + * start" naming the errno and the path by ending the wait on the recorded + * cause, not by exhausting the deadline. The proof is the surfaced record and + * the single spawn (see the assertions), never a wall-clock measurement. */ TEST(daemon_bootstrap_fails_fast_when_daemon_dies_at_publication) { const char *old_cache = getenv("CBM_CACHE_DIR"); char *saved_cache = old_cache ? cbm_strdup(old_cache) : NULL; @@ -1071,13 +1073,10 @@ TEST(daemon_bootstrap_fails_fast_when_daemon_dies_at_publication) { cbm_daemon_bootstrap_result_t result; memset(&result, 0, sizeof(result)); cbm_daemon_bootstrap_status_t status = CBM_DAEMON_BOOTSTRAP_FAILED; - uint64_t elapsed_ms = UINT64_MAX; if (identity_ready) { cbm_daemon_ipc_posix_record_write_failure_set_for_test(ENOSPC); cbm_daemon_bootstrap_spawn_override_set_for_test(bootstrap_enospc_host_spawn, &host); - uint64_t started = cbm_now_ms(); status = cbm_daemon_bootstrap_execute(&config, &result); - elapsed_ms = cbm_now_ms() - started; cbm_daemon_bootstrap_spawn_override_set_for_test(NULL, NULL); cbm_daemon_ipc_posix_record_write_failure_set_for_test(0); } @@ -1115,7 +1114,14 @@ TEST(daemon_bootstrap_fails_fast_when_daemon_dies_at_publication) { ASSERT_TRUE(host.child_count >= 1); ASSERT_TRUE(log_read); ASSERT_TRUE(daemon_named_cause); - ASSERT(elapsed_ms < 5000); + /* Fast-fail is proven by the MECHANISM, never by wall-clock (O9: a gate + * never asserts a transient timing window). The recorded ENOSPC cause is + * surfaced verbatim ("failed to start" + errno + path) and the slow + * "active or starting" timeout wording is absent -- that message is emitted + * ONLY on the fast-fail break (cbm_daemon_bootstrap_start_failure_format), + * never on the 30 s deadline path -- and the client stopped after exactly + * one spawn instead of respawning a doomed daemon until the deadline. Any + * regression to the pre-#1828 30 s hang trips these deterministically. */ ASSERT_FALSE(stale_wording); ASSERT_TRUE(message_names_failure); ASSERT_TRUE(message_names_errno);