From 92bb5369ac0d209a0a015f0144ba853b302a5dbc Mon Sep 17 00:00:00 2001 From: ethernet Date: Sat, 12 Sep 2026 10:32:06 -0400 Subject: [PATCH] fix(tests): preserve home guard tracking across descriptor reuse --- tests/hermes_cli/test_real_home_tripwire.py | 27 +++++++++++++++++++++ tests/home_io_guard.py | 8 +++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/tests/hermes_cli/test_real_home_tripwire.py b/tests/hermes_cli/test_real_home_tripwire.py index d3f61d132f..162debe137 100644 --- a/tests/hermes_cli/test_real_home_tripwire.py +++ b/tests/hermes_cli/test_real_home_tripwire.py @@ -98,3 +98,30 @@ def test_explicit_opt_out_allows_only_the_disposable_canary(protected_home): target = protected_home / "file.txt" target.write_text("opted out", encoding="utf-8") assert target.read_text(encoding="utf-8") == "opted out" + + +def test_close_keeps_a_reused_descriptors_new_owner(tmp_path, monkeypatch): + from tests.home_io_guard import HomeIOGuard + + first, second = tmp_path / "first", tmp_path / "second" + first.touch() + second.touch() + original_close = os.close + reopened = [] + + def close_and_reopen(fd): + original_close(fd) + reopened.append(os.open(second, os.O_RDONLY)) + + guard = HomeIOGuard(lambda: []) + try: + with monkeypatch.context() as patcher: + patcher.setattr(os, "close", close_and_reopen) + guard.install(patcher) + fd = os.open(first, os.O_RDONLY) + os.close(fd) + assert reopened == [fd], "the test must exercise descriptor reuse" + assert guard.directories[fd] == second + finally: + for fd in reopened: + original_close(fd) diff --git a/tests/home_io_guard.py b/tests/home_io_guard.py index 16a923270e..d5f5c7adaa 100644 --- a/tests/home_io_guard.py +++ b/tests/home_io_guard.py @@ -85,10 +85,10 @@ class HomeIOGuard: @wraps(original_close) def guarded_close(fd): - try: - return original_close(fd) - finally: - self.directories.pop(fd, None) + # Forget the old owner before close releases the number for reuse + # by another thread's open; afterwards we could erase its mapping. + self.directories.pop(fd, None) + return original_close(fd) monkeypatch.setattr(os, "open", guarded_open) monkeypatch.setattr(os, "close", guarded_close)