Files
hermes-agent/tests/tools/test_search_files_cpu_windows.py
ethernet 92686159d1 fix(pm): integrate audited runtime and lifecycle repairs
Prepare dependency generations before selecting them. Keep shipped tool
bytes separate from writable additions, and store facts beside their entries.
Validate proposed plugin sets before config publication. Restore the previous
config if the facts write fails.

Consolidate duplicate updater, backup, setup, and voice helpers. Repair
launcher selection, dependency consumers, download ownership, update feeds,
and native Windows process and file handling.

Verification: 206 changed/prior-failing Python files reported 4630 passed,
one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary.
The final targeted rerun reported 234 passed and two skipped. The store
review regression batch reported 83 passed and one skipped. Desktop
TypeScript checks, 56 selected Electron tests, 24 release tests, and the
removed-import/compatibility guards passed.

This is an integration checkpoint, not full audit acceptance. The complete
Python suite has not run on this fixed tree. Crash-atomic plugin publication,
generation cleanup, receipt correlation, and packaged lifecycle acceptance
remain open in docs/pm-audit-status.md.
2026-09-05 22:36:48 -04:00

300 lines
10 KiB
Python

"""Concurrency admission tests for expensive filename walks."""
from concurrent.futures import ThreadPoolExecutor
import threading
import types
import pytest
from tools.environments.local import LocalEnvironment
from tools.file_operations import SearchResult, ShellFileOperations
from tools.file_operations_search import (
_ACTIVE_FILENAME_SEARCH_ROOTS,
_FILENAME_SEARCH_ADMISSION,
_normalized_filename_search_root,
)
from tools.interrupt import set_interrupt
class RemoteEnvironment:
is_local = False
cwd = "/workspace"
def execute(self, command, **kwargs):
raise AssertionError(f"unexpected backend command: {command}")
def _operations(env, scan):
operations = ShellFileOperations(env)
operations._resolve_command = lambda command: "/usr/bin/rg" if command == "rg" else None
operations._search_files_rg = types.MethodType(scan, operations)
return operations
def test_same_backend_class_and_root_serialize_five_filename_walks():
entered = threading.Event()
release = threading.Event()
counter_lock = threading.Lock()
active = 0
maximum_active = 0
completed = 0
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
nonlocal active, maximum_active, completed
with counter_lock:
active += 1
maximum_active = max(maximum_active, active)
entered.set()
assert release.wait(5)
with counter_lock:
active -= 1
completed += 1
return SearchResult(files=[str(path)], total_count=1)
operations = [_operations(RemoteEnvironment(), scan) for _ in range(5)]
with ThreadPoolExecutor(max_workers=5) as pool:
futures = [
pool.submit(operation._search_files, "*.py", "/repo", 50, 0)
for operation in operations
]
assert entered.wait(5)
release.set()
results = [future.result(timeout=5) for future in futures]
assert all(result.error is None for result in results)
assert completed == 5
assert maximum_active == 1
def test_different_roots_can_enter_filename_walks_together():
both_entered = threading.Barrier(2)
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
both_entered.wait(5)
return SearchResult(files=[str(path)], total_count=1)
first = _operations(RemoteEnvironment(), scan)
second = _operations(RemoteEnvironment(), scan)
with ThreadPoolExecutor(max_workers=2) as pool:
futures = [
pool.submit(first._search_files, "*.py", "/one", 50, 0),
pool.submit(second._search_files, "*.py", "/two", 50, 0),
]
assert [future.result(timeout=5).error for future in futures] == [None, None]
def test_different_backend_classes_can_walk_the_same_root_together():
class OtherRemoteEnvironment(RemoteEnvironment):
pass
both_entered = threading.Barrier(2)
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
both_entered.wait(5)
return SearchResult(files=[str(path)], total_count=1)
first = _operations(RemoteEnvironment(), scan)
second = _operations(OtherRemoteEnvironment(), scan)
with ThreadPoolExecutor(max_workers=2) as pool:
futures = [
pool.submit(first._search_files, "*.py", "/same", 50, 0),
pool.submit(second._search_files, "*.py", "/same", 50, 0),
]
assert [future.result(timeout=5).error for future in futures] == [None, None]
def test_overlapping_multi_root_sets_are_claimed_atomically(monkeypatch):
first_entered = threading.Event()
release_first = threading.Event()
second_waiting = threading.Event()
lock = threading.Lock()
active = 0
maximum_active = 0
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
nonlocal active, maximum_active
with lock:
active += 1
maximum_active = max(maximum_active, active)
if path == ["/a", "/b"]:
first_entered.set()
if path == ["/a", "/b"]:
assert release_first.wait(5)
with lock:
active -= 1
return SearchResult(files=[str(path)], total_count=1)
first = _operations(RemoteEnvironment(), scan)
second = _operations(RemoteEnvironment(), scan)
original_wait = _FILENAME_SEARCH_ADMISSION.wait
def observed_wait(timeout=None):
second_waiting.set()
return original_wait(timeout)
monkeypatch.setattr(_FILENAME_SEARCH_ADMISSION, "wait", observed_wait)
with ThreadPoolExecutor(max_workers=2) as pool:
first_future = pool.submit(first._search_files, "*.py", ["/a", "/b"], 50, 0)
assert first_entered.wait(5)
second_future = pool.submit(second._search_files, "*.py", ["/b", "/c"], 50, 0)
assert second_waiting.wait(5)
release_first.set()
assert first_future.result(timeout=5).error is None
assert second_future.result(timeout=5).error is None
assert maximum_active == 1
def test_interrupted_waiter_returns_without_dispatch_or_late_dispatch(monkeypatch):
holder_entered = threading.Event()
release_holder = threading.Event()
waiter_waiting = threading.Event()
waiter_tid = []
dispatches = []
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
dispatches.append(threading.get_ident())
holder_entered.set()
assert release_holder.wait(5)
return SearchResult(files=[str(path)], total_count=1)
holder = _operations(RemoteEnvironment(), scan)
waiter = _operations(RemoteEnvironment(), scan)
original_wait = _FILENAME_SEARCH_ADMISSION.wait
def observed_wait(timeout=None):
waiter_waiting.set()
return original_wait(timeout)
monkeypatch.setattr(_FILENAME_SEARCH_ADMISSION, "wait", observed_wait)
def run_waiter():
waiter_tid.append(threading.get_ident())
return waiter._search_files("*.py", "/repo", 50, 0)
with ThreadPoolExecutor(max_workers=2) as pool:
holder_future = pool.submit(holder._search_files, "*.py", "/repo", 50, 0)
assert holder_entered.wait(5)
waiter_future = pool.submit(run_waiter)
assert waiter_waiting.wait(5)
set_interrupt(True, waiter_tid[0])
try:
interrupted = waiter_future.result(timeout=5)
assert "interrupted" in (interrupted.error or "").lower()
assert len(dispatches) == 1
release_holder.set()
assert holder_future.result(timeout=5).error is None
assert len(dispatches) == 1
finally:
set_interrupt(False, waiter_tid[0])
release_holder.set()
def test_interrupt_published_after_final_sample_prevents_filename_dispatch(monkeypatch):
sampled_clear = threading.Event()
resume_acquire = threading.Event()
worker_tid = []
dispatches = []
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
dispatches.append(threading.get_ident())
return SearchResult(files=[str(path)], total_count=1)
operations = _operations(RemoteEnvironment(), scan)
original_is_interrupted = __import__(
"tools.interrupt", fromlist=["is_interrupted"]
).is_interrupted
def pause_after_clear_sample():
interrupted = original_is_interrupted()
if not interrupted and threading.get_ident() == worker_tid[0]:
sampled_clear.set()
assert resume_acquire.wait(5)
return interrupted
monkeypatch.setattr(
"tools.file_operations_search.tool_interrupt.is_interrupted",
pause_after_clear_sample,
)
def run_search():
worker_tid.append(threading.get_ident())
return operations._search_files("*.py", "/repo", 50, 0)
with ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit(run_search)
assert sampled_clear.wait(5)
set_interrupt(True, worker_tid[0])
resume_acquire.set()
try:
result = future.result(timeout=5)
finally:
set_interrupt(False, worker_tid[0])
resume_acquire.set()
assert "interrupted" in (result.error or "").lower()
assert dispatches == []
assert _ACTIVE_FILENAME_SEARCH_ROOTS == set()
def test_empty_filename_roots_are_rejected_before_engine_resolution():
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
raise AssertionError("filename engine dispatched")
operations = _operations(RemoteEnvironment(), scan)
operations._resolve_command = lambda command: (_ for _ in ()).throw(
AssertionError(f"engine resolution attempted: {command}")
)
result = operations._search_files("*.py", [], 50, 0)
assert "at least one search root" in (result.error or "").lower()
assert _ACTIVE_FILENAME_SEARCH_ROOTS == set()
@pytest.mark.parametrize("raised", [Exception, KeyboardInterrupt, SystemExit, BaseException])
def test_admission_releases_after_every_base_exception_path(raised):
attempts = 0
def scan(self, pattern, path, limit, offset, order, rg_executable=None):
nonlocal attempts
attempts += 1
if attempts == 1:
raise raised("engine failed")
return SearchResult(files=[str(path)], total_count=1)
operations = _operations(RemoteEnvironment(), scan)
with pytest.raises(raised, match="engine failed"):
operations._search_files("*.py", "/repo", 50, 0)
result = operations._search_files("*.py", "/repo", 50, 0)
assert result.error is None
assert attempts == 2
assert _ACTIVE_FILENAME_SEARCH_ROOTS == set()
def test_remote_roots_are_normalized_lexically_against_backend_cwd(monkeypatch):
env = RemoteEnvironment()
monkeypatch.setattr(
"tools.file_operations.os.path.abspath",
lambda path: (_ for _ in ()).throw(AssertionError("controller resolution used")),
)
relative = _normalized_filename_search_root(env, "repo/../repo", "/controller")
absolute = _normalized_filename_search_root(env, "/workspace/repo", "/controller")
assert relative == "/workspace/repo"
assert absolute == relative
@pytest.mark.platforms("windows")
def test_windows_local_root_spellings_share_one_normalized_key():
env = LocalEnvironment.__new__(LocalEnvironment)
env.cwd = "C:/Repo"
native = _normalized_filename_search_root(env, r"C:\Repo\src\..", "C:/ignored")
msys = _normalized_filename_search_root(env, "/c/Repo", "C:/ignored")
assert native == msys