fix(desktop): yield single-profile cron to its running gateway
(cherry picked from commit db92ff258f0138a251b2c4e14b32f2fa0b7b8d5b)
This commit is contained in:
@@ -100,7 +100,9 @@ def _start_desktop_cron_ticker(stop_event: "threading.Event", interval: int = 60
|
||||
from hermes_cli.profiles import profiles_to_serve
|
||||
|
||||
profile_homes = list(profiles_to_serve(multiplex=True))
|
||||
if len(profile_homes) > 1:
|
||||
if profile_homes:
|
||||
# Even one profile needs the per-tick gateway gate; otherwise
|
||||
# Desktop races its dedicated gateway for the same cron store.
|
||||
start_kwargs["profile_homes"] = profile_homes
|
||||
# Stand down, per tick, for a profile whose OWN gateway runs:
|
||||
# it ticks with live adapters, and the tick-lock race would
|
||||
|
||||
@@ -15,6 +15,8 @@ import asyncio
|
||||
import threading
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
def test_standalone_fallback_pool_keeps_profile_scope(tmp_path, monkeypatch):
|
||||
@@ -105,16 +107,18 @@ def test_multiplex_ticker_profile_gate_skips_rejected_profile(tmp_path):
|
||||
assert (orphan / "cron" / "ticker_last_success").exists()
|
||||
|
||||
|
||||
def test_desktop_ticker_gates_on_profile_gateway_running(tmp_path, monkeypatch):
|
||||
"""The desktop ticker wires the gate to ``_check_gateway_running``."""
|
||||
@pytest.mark.parametrize("profile_count", [1, 2])
|
||||
def test_desktop_ticker_gates_on_profile_gateway_running(tmp_path, monkeypatch, profile_count):
|
||||
"""Desktop yields to each live gateway, including a single-profile install."""
|
||||
from hermes_cli import web_server
|
||||
|
||||
homes = [("default", tmp_path / "default"), ("ops", tmp_path / "ops")]
|
||||
homes = [("default", tmp_path / "default"), ("ops", tmp_path / "ops")][:profile_count]
|
||||
running = {homes[-1][1]}
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.profiles.profiles_to_serve", lambda multiplex=False: list(homes)
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.profiles._check_gateway_running", lambda home: home.name == "ops"
|
||||
"hermes_cli.profiles._check_gateway_running", lambda home: home in running
|
||||
)
|
||||
captured = {}
|
||||
|
||||
@@ -133,7 +137,15 @@ def test_desktop_ticker_gates_on_profile_gateway_running(tmp_path, monkeypatch):
|
||||
|
||||
web_server._start_desktop_cron_ticker(threading.Event(), interval=0)
|
||||
|
||||
assert captured.get("profile_homes") == homes
|
||||
gate = captured.get("profile_gate")
|
||||
assert gate is not None, "desktop ticker did not install a profile gate"
|
||||
assert gate("default", tmp_path / "default") is True
|
||||
assert gate("ops", tmp_path / "ops") is False
|
||||
for name, home in homes:
|
||||
assert gate(name, home) is (home not in running)
|
||||
|
||||
# Re-evaluate on each tick: Desktop resumes fallback after gateway exit
|
||||
# and stands down again if a gateway starts later.
|
||||
running.clear()
|
||||
assert all(gate(name, home) for name, home in homes)
|
||||
running.update(home for _, home in homes)
|
||||
assert not any(gate(name, home) for name, home in homes)
|
||||
|
||||
@@ -70,19 +70,31 @@ def test_multi_profile_homes_passed_to_builtin(monkeypatch, _providers, tmp_path
|
||||
assert builtin.start_kwargs["profile_homes"] == homes
|
||||
|
||||
|
||||
def test_single_profile_keeps_legacy_path(monkeypatch, _providers, tmp_path):
|
||||
_sp, builtin = _providers
|
||||
@pytest.mark.parametrize("gateway_running", [True, False])
|
||||
def test_single_profile_ticks_only_without_gateway(monkeypatch, tmp_path, gateway_running):
|
||||
"""Exercise Desktop startup through the real built-in scheduler loop."""
|
||||
from cron.scheduler_provider import InProcessCronScheduler
|
||||
from hermes_constants import get_hermes_home
|
||||
import hermes_cli.profiles as profiles_mod
|
||||
|
||||
home = tmp_path / "root"
|
||||
home.mkdir()
|
||||
monkeypatch.setattr(profiles_mod, "profiles_to_serve", lambda **_kw: [("default", home)])
|
||||
monkeypatch.setattr(profiles_mod, "_check_gateway_running", lambda _home: gateway_running)
|
||||
monkeypatch.setattr(
|
||||
profiles_mod,
|
||||
"profiles_to_serve",
|
||||
lambda **_kw: [("default", tmp_path / "root")],
|
||||
"cron.scheduler_provider.resolve_cron_scheduler", InProcessCronScheduler
|
||||
)
|
||||
ticked = []
|
||||
monkeypatch.setattr(
|
||||
"cron.scheduler.tick", lambda **_kw: ticked.append(get_hermes_home())
|
||||
)
|
||||
stop = threading.Event()
|
||||
# One real scheduler cycle, with no wall-clock wait or job dispatch.
|
||||
monkeypatch.setattr(stop, "wait", lambda _timeout: stop.set())
|
||||
|
||||
ws._start_desktop_cron_ticker(threading.Event(), interval=9)
|
||||
ws._start_desktop_cron_ticker(stop, interval=0)
|
||||
|
||||
assert builtin.start_kwargs == {"interval": 9}
|
||||
assert ticked == ([] if gateway_running else [home])
|
||||
|
||||
|
||||
def test_enumeration_failure_fails_open(monkeypatch, _providers):
|
||||
|
||||
Reference in New Issue
Block a user