fix(bug-1561): fix flaky test_conf_trim_keeps_only_n_versions/test_conf_get_saved_tag_no_config

Two root causes, confirmed by direct code reading (previously misattributed
to an overlayfs sandbox fdatasync artifact):

conf_trim_old_versions() sorted config-version files by st_mtime (second
resolution). The test writes 3 versions 15ms apart -- well within one
second, so ties are the expected outcome, not a rare edge case. qsort()
isn't stable, so a tied ordering could push the protected current-pointer
file (skipped on deletion, never compensated) past keep_n, leaving one
extra file on disk ("Expected 2 Was 3"). Fixed by sorting on st_mtim
(nanosecond-resolution struct timespec) instead of st_mtime.

cr_setup() keyed its tmpdir on getpid()+time(NULL) (second resolution),
risking collision with a stale directory left by an earlier interrupted
run (ASAN abort, timeout, SIGKILL -- none reach cr_teardown()'s rm -rf)
reusing the same PID within the same second -- test_conf_get_saved_tag_
no_config then observed that directory as "not actually empty" and read
a leftover tag instead of the expected 0. Fixed with a monotonic counter
guaranteeing a unique directory per cr_setup() call.

TDD: RED reproduced on the first attempt (both tests failed with the
exact historical signature). GREEN confirmed across 16+ repeat runs
after the fix, zero other regressions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
This commit is contained in:
Rongsong Shen 2026-08-30 03:55:45 +00:00
parent 9e11bb9e0b
commit a02d47772b
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
3 changed files with 13 additions and 10 deletions

View file

@ -1011,16 +1011,18 @@ int conf_check_stale(const char *workdir, time_t stale_timeout_s)
/* ── conf_trim_old_versions ─────────────────────── */
typedef struct {
char name[NAME_MAX];
time_t mtime;
char name[NAME_MAX];
struct timespec mtime;
} ver_entry_t;
static int ver_entry_cmp_desc(const void *a, const void *b)
{
const ver_entry_t *ea = (const ver_entry_t *)a;
const ver_entry_t *eb = (const ver_entry_t *)b;
if (eb->mtime > ea->mtime) return 1;
if (eb->mtime < ea->mtime) return -1;
if (eb->mtime.tv_sec != ea->mtime.tv_sec)
return (eb->mtime.tv_sec > ea->mtime.tv_sec) ? 1 : -1;
if (eb->mtime.tv_nsec != ea->mtime.tv_nsec)
return (eb->mtime.tv_nsec > ea->mtime.tv_nsec) ? 1 : -1;
return 0;
}
@ -1059,7 +1061,7 @@ void conf_trim_old_versions(const char *workdir, int keep_n)
if (stat(full, &st) == 0) {
strncpy(entries[n].name, e->d_name, NAME_MAX - 1);
entries[n].name[NAME_MAX - 1] = '\0';
entries[n].mtime = st.st_mtime;
entries[n].mtime = st.st_mtim;
n++;
}
}

View file

@ -61,8 +61,9 @@ static char tmpdir[256];
static void cr_setup(void)
{
snprintf(g_tmpdir, sizeof(g_tmpdir), "/tmp/test_cr_%d_%ld",
(int)getpid(), (long)time(NULL));
static int seq = 0;
snprintf(g_tmpdir, sizeof(g_tmpdir), "/tmp/test_cr_%d_%ld_%d",
(int)getpid(), (long)time(NULL), seq++);
mkdir(g_tmpdir, 0700);
/* conf_save_config() writes to workdir/name/conf — mirror that here */
snprintf(g_instdir, sizeof(g_instdir), "%s/ctrl0", g_tmpdir);