From 51f732304a226b932e1bbc4ceb3fe79b4ad78edc Mon Sep 17 00:00:00 2001 From: onhm <160473513+luoyi0000@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:25:44 +0800 Subject: [PATCH] test(channels): drop the POSIX-only utime flag that breaks 9 tests on Windows (#7750) * test(channels): drop the POSIX-only utime flag that breaks 9 tests on Windows os.utime(..., follow_symlinks=False) is not implemented on Windows, so every test in tests/test_channel_credential_staging.py that ages a staging file dies in _age before it exercises anything (NotImplementedError). CI runs backend unit tests on ubuntu only, so this never surfaced. Every file aged here is a regular file, and the other 15+ os.utime call sites in the suite already omit the flag. Verified on Windows 11 / Python 3.12: 9 failed, 2 passed before; 11 passed after. * test(channels): keep the symlink aging the flag was there for Review follow-up on #7750. Dropping follow_symlinks=False kept Windows green but quietly lost POSIX coverage: _age(link) in the os.name != "nt" block used to stamp the symlink's own mtime two days into the past, and without the flag os.utime follows the link and ages backup.tmp instead. cleanup_stale_credential_staging keeps that symlink alive through its entry.stat(follow_symlinks=False) + S_ISREG type guard, and with a fresh mtime the age guard alone would retain it too, so the test no longer proved that an old staging-pattern symlink is spared by the type check. Pass the flag through instead of hard-coding it: _age(path, *, follow_symlinks=True) forwards it, and only the POSIX-only symlink call site asks for follow_symlinks=False. Windows never passes it, so os.utime still cannot raise NotImplementedError there. Also corrects the comment: _age also ages a directory, so "every file aged here is a regular file" overstated the case. os.utime on a directory works on Windows; the flag only matters for the symlink below. Verified on Windows 11 / Python 3.12: 11 passed. Addresses review feedback on #7750. --------- Co-authored-by: luoyi0000 --- backend/tests/test_channel_credential_staging.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/tests/test_channel_credential_staging.py b/backend/tests/test_channel_credential_staging.py index 98ca9183e7..691db026ad 100644 --- a/backend/tests/test_channel_credential_staging.py +++ b/backend/tests/test_channel_credential_staging.py @@ -16,9 +16,12 @@ from app.channels.runtime_config_store import ChannelRuntimeConfigStore, SqlChan from app.channels.wechat import WechatChannel -def _age(path: Path) -> None: +def _age(path: Path, *, follow_symlinks: bool = True) -> None: old = time.time() - 2 * 24 * 60 * 60 - os.utime(path, (old, old), follow_symlinks=False) + # The flag is only meaningful for the symlink aged in the POSIX-only block below, + # where the mtime must land on the link itself rather than on its target. os.utime + # raises NotImplementedError for it on Windows, so callers there never pass it. + os.utime(path, (old, old), follow_symlinks=follow_symlinks) def _load(kind: str, target: Path): @@ -95,7 +98,7 @@ def test_restart_sweeps_crash_orphans_without_touching_other_state(tmp_path, mon if os.name != "nt": link = tmp_path / f".{target.name}.deerflow-symlink.tmp" link.symlink_to(retained[0]) - _age(link) + _age(link, follow_symlinks=False) retained.append(link) if not destination_exists: target.unlink()