Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

234 lines
7.3 KiB
Python

"""Tests for /watch, /watches, /unwatch slash commands."""
from __future__ import annotations
import io
import threading
import time
from unittest.mock import MagicMock
import pytest
from rich.console import Console
from integrations.telegram.credentials import TelegramCredentials
from platform.common.task_types import TaskKind, TaskStatus
from surfaces.interactive_shell.command_registry import SLASH_COMMANDS, dispatch_slash
from surfaces.interactive_shell.command_registry.watch_cmds import (
WatchdogStartSpec,
parse_watch_argv,
)
from surfaces.interactive_shell.session import Session
def _capture() -> tuple[Console, io.StringIO]:
buf = io.StringIO()
return Console(file=buf, force_terminal=False, highlight=False), buf
def test_slash_registry_includes_watchdog_commands() -> None:
assert "/watch" in SLASH_COMMANDS
assert "/watches" in SLASH_COMMANDS
assert "/unwatch" in SLASH_COMMANDS
def test_parse_watch_argv_rejects_non_numeric_pid() -> None:
out = parse_watch_argv(["not-a-pid"])
assert isinstance(out, str)
assert "invalid pid" in out
def test_parse_watch_argv_rejects_non_positive_pid() -> None:
out = parse_watch_argv(["0"])
assert isinstance(out, str)
assert "positive" in out
def test_parse_watch_argv_parses_flags() -> None:
raw = parse_watch_argv(["999", "--max-cpu", "80", "--max-runtime", "10s", "--once"])
assert isinstance(raw, WatchdogStartSpec)
assert raw.pid == 999
assert raw.max_cpu == 80.0
assert raw.max_runtime_seconds == 10.0
assert raw.once is True
def test_dispatch_watch_creates_watchdog_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.load_credentials_from_env",
lambda *_a, **_kw: TelegramCredentials(bot_token="x", chat_id="1"),
)
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.pid_exists",
lambda _pid: True,
)
def _fake_start(**_kwargs: object) -> None:
return None
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.start_watchdog_daemon_thread",
_fake_start,
)
session = Session()
session.terminal.trust_mode = True
console, buf = _capture()
dispatch_slash(
f"/watch {__import__('os').getpid()} --max-cpu 80",
session,
console,
is_tty=True,
)
watchdogs = [t for t in session.task_registry.list_recent(20) if t.kind == TaskKind.WATCHDOG]
assert len(watchdogs) == 1
assert watchdogs[0].status == TaskStatus.RUNNING
assert "max_cpu=80" in (watchdogs[0].command or "")
assert "started" in buf.getvalue()
def test_unwatch_marks_watchdog_cancelled(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.load_credentials_from_env",
lambda *_a, **_kw: TelegramCredentials(bot_token="x", chat_id="1"),
)
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.pid_exists",
lambda _pid: True,
)
barrier = threading.Event()
def _slow_watchdog(**kwargs: object) -> None:
task = kwargs["task"]
barrier.set()
while not task.cancel_requested.is_set():
time.sleep(0.02)
task.mark_cancelled()
monkeypatch.setattr(
"surfaces.interactive_shell.command_registry.watch_cmds.start_watchdog_daemon_thread",
lambda **kw: threading.Thread(target=_slow_watchdog, kwargs=kw, daemon=True).start(),
)
session = Session()
session.terminal.trust_mode = True
console, _ = _capture()
dispatch_slash(
f"/watch {__import__('os').getpid()} --interval 1s",
session,
console,
is_tty=True,
)
assert barrier.wait(timeout=2.0), "watchdog thread should start"
task = next(t for t in session.task_registry.list_recent(20) if t.kind == TaskKind.WATCHDOG)
dispatch_slash(f"/unwatch {task.task_id}", session, console, is_tty=True)
for _ in range(200):
task.refresh_rehydrated_status()
if task.status == TaskStatus.CANCELLED:
break
time.sleep(0.02)
assert task.status == TaskStatus.CANCELLED
def test_unwatch_rejects_non_watchdog_task() -> None:
session = Session()
session.terminal.trust_mode = True
inv = session.task_registry.create(TaskKind.INVESTIGATION, command="x")
inv.mark_running()
console, buf = _capture()
dispatch_slash(f"/unwatch {inv.task_id}", session, console, is_tty=True)
assert "not a watchdog" in buf.getvalue()
def test_run_watchdog_respects_cancel(monkeypatch: pytest.MonkeyPatch) -> None:
from datetime import UTC, datetime, timedelta
from platform.common.task_registry import TaskRegistry
from tools.system.fleet_monitoring.probe import ProcessSnapshot
from tools.system.watch_dog.monitor import run_watchdog
reg = TaskRegistry()
task = reg.create(TaskKind.WATCHDOG, command="watchdog pid=1")
task.mark_running()
dispatcher = MagicMock()
dispatcher.dispatch = MagicMock(return_value=True)
started_at = datetime.now(UTC) - timedelta(seconds=5)
snap = ProcessSnapshot(
pid=1,
cpu_percent=1.0,
rss_mb=10.0,
num_fds=None,
num_connections=None,
status="running",
started_at=started_at,
)
monkeypatch.setattr("tools.system.watch_dog.monitor.probe", lambda *_a, **_kw: snap)
thread = threading.Thread(
target=run_watchdog,
kwargs={
"task": task,
"watched_pid": 1,
"interval_seconds": 0.15,
"max_cpu": None,
"max_runtime_seconds": None,
"max_rss_mib": None,
"once": False,
"dispatcher": dispatcher,
"on_alarm": None,
},
daemon=True,
)
thread.start()
time.sleep(0.05)
task.request_cancel()
thread.join(timeout=3.0)
assert task.status == TaskStatus.CANCELLED
def test_run_watchdog_once_without_thresholds_exits(monkeypatch: pytest.MonkeyPatch) -> None:
"""``--once`` with no threshold flags must finish after one sample (Greptile #1969)."""
from datetime import UTC, datetime, timedelta
from platform.common.task_registry import TaskRegistry
from tools.system.fleet_monitoring.probe import ProcessSnapshot
from tools.system.watch_dog.monitor import run_watchdog
reg = TaskRegistry()
task = reg.create(TaskKind.WATCHDOG, command="watchdog pid=1")
task.mark_running()
dispatcher = MagicMock()
dispatcher.dispatch = MagicMock(return_value=True)
started_at = datetime.now(UTC) - timedelta(seconds=1)
snap = ProcessSnapshot(
pid=1,
cpu_percent=1.0,
rss_mb=10.0,
num_fds=None,
num_connections=None,
status="running",
started_at=started_at,
)
monkeypatch.setattr("tools.system.watch_dog.monitor.probe", lambda *_a, **_kw: snap)
run_watchdog(
task=task,
watched_pid=1,
interval_seconds=0.1,
max_cpu=None,
max_runtime_seconds=None,
max_rss_mib=None,
once=True,
dispatcher=dispatcher,
on_alarm=None,
)
assert task.status == TaskStatus.COMPLETED
assert task.result == "single sample (once)"
dispatcher.dispatch.assert_not_called()