Files
hermes-agent/tests/hermes_cli/test_install_identity.py
T
teknium1 3ef8b384a9 refactor(persistence): 24 hand-rolled atomic JSON/text writers go through utils.atomic_json_write / atomic_write_text
Each copy re-implemented temp+replace by hand and lacked one or more of
fsync, symlink preservation, atomic_replace's Windows-contention retry and
EXDEV/bind-mount fallback, mode preservation, or interrupt-safe temp
cleanup. Three (gateway/session_persistence, cron/suggestions,
agent/shell_hooks) were verbatim inlines of utils._atomic_write; two
modules defined their own directory-fsync helper, now utils.fsync_directory.
plugins/google_meet/_jsonfile.write_json_atomic is deleted (callers use the
canonical helper directly).

Behavior change: every one of these writers now fsyncs the payload, keeps a
pre-existing target's mode, cleans its temp file on BaseException, and
survives Windows AV/indexer contention and cross-device renames the way
config writes already did. cron/suggestions.json is 0600 from creation
(previously chmod'ed after the replace). Skipped on purpose: cron/jobs.py
two-phase staging, gateway/status._write_json_excl (create-only lock),
kanban_transfer staging (not atomic writers); tools/skill_usage.
_write_suppressed_names lives inside a PLUGIN-COMPAT block.
2026-09-13 05:07:11 -07:00

114 lines
3.4 KiB
Python

from concurrent.futures import ThreadPoolExecutor
import multiprocessing
from pathlib import Path
import time
from gateway.hosted_rooms import local_authority_gateway_id
import hermes_cli.install_identity as install_identity
from hermes_cli.install_identity import read_or_create_install_id
def _race_first_install_id(
root_value,
minted,
results,
start_barrier=None,
writer_entered=None,
release_writer=None,
):
root = Path(root_value)
install_identity.uuid.uuid4 = lambda: type("FixedUuid", (), {"hex": minted})()
if start_barrier is not None:
start_barrier.wait(timeout=10)
if writer_entered is not None:
import utils
original_mkstemp = utils.tempfile.mkstemp
def held_mkstemp(*args, **kwargs):
writer_entered.set()
assert release_writer.wait(timeout=10)
return original_mkstemp(*args, **kwargs)
utils.tempfile.mkstemp = held_mkstemp
results.put(read_or_create_install_id(root))
def test_concurrent_first_use_returns_one_persisted_identity(tmp_path):
with ThreadPoolExecutor(max_workers=16) as executor:
values = list(executor.map(lambda _: read_or_create_install_id(tmp_path), range(64)))
assert len(set(values)) == 1
assert values[0]
assert (tmp_path / "install_id").read_text(encoding="utf-8").strip() == values[0]
def test_independent_first_callers_return_the_single_committed_identity(tmp_path, monkeypatch):
context = multiprocessing.get_context("spawn")
results = context.Queue()
writer_entered = context.Event()
release_writer = context.Event()
winner = context.Process(
target=_race_first_install_id,
args=(
str(tmp_path),
"a" * 32,
results,
None,
writer_entered,
release_writer,
),
)
loser = context.Process(
target=_race_first_install_id,
args=(str(tmp_path), "b" * 32, results),
)
winner.start()
assert writer_entered.wait(timeout=10)
loser.start()
time.sleep(0.25)
assert loser.is_alive()
release_writer.set()
processes = [winner, loser]
for process in processes:
process.join(timeout=15)
assert process.exitcode == 0
returned = [results.get(timeout=2) for _ in processes]
persisted = (tmp_path / "install_id").read_text(encoding="utf-8").strip()
assert returned == [persisted, persisted]
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setattr(
install_identity,
"_INSTALL_ID_CACHE",
{"root": None, "value": None},
)
assert local_authority_gateway_id() == f"install:{persisted}"
def test_concurrent_corrupt_file_repair_returns_one_committed_identity(tmp_path):
(tmp_path / "install_id").write_text("corrupt\n", encoding="utf-8")
context = multiprocessing.get_context("spawn")
barrier = context.Barrier(2)
results = context.Queue()
processes = [
context.Process(
target=_race_first_install_id,
args=(str(tmp_path), value, results, barrier),
)
for value in ("a" * 32, "b" * 32)
]
for process in processes:
process.start()
for process in processes:
process.join(timeout=15)
assert process.exitcode == 0
returned = [results.get(timeout=2) for _ in processes]
persisted = (tmp_path / "install_id").read_text(encoding="utf-8").strip()
assert returned == [persisted, persisted]