simbiot-harness/tests/test_timeout_kill.py
Simbiot Harness b257c32558 аудит @juno42bot 2026-10-16: медиа без подписи в триаже, мин-тик по входящему, тишина сек-уровня, изоляция тестов
- фото/документ/видео без подписи выпадали из триажа (_pending_inbox и
  build_context пускали только текст/голос): висели до 23 ч, все 4
  неотвеченных за неделю — такие. Один фильтр _is_pending, медиа с тегом
  и путём файла; окно свежести SIMBIOT_MEDIA_PENDING_H=6 — старые не
  будят LLM задним числом; стикер без текста — не повод.
- MinNudge: мин-тик через 3 с тишины после MessageReceived/Transcribed, не
  ждём границы минуты (было: медиана 36 с до старта LLM, p90 93 с при
  24 с самой модели). Слот следующего такта, граница его пропускает;
  SIMBIOT_MIN_NUDGE=0 — выключить.
- сек-уровень: CycleTicked+ReflexObserved каждую секунду = 1.35 млн
  подписанных событий/нед (98% стора 1.37 ГБ). Теперь heartbeat раз в
  60 с + такты с событием (алерт, команды, каналы); «reflex ok» не
  спамит журнал.
- 6 тестов писали в боевой стор под SIMBIOT_HOME тика (TTFTWatchdogRetry
  с бюджетом 0.5 с в проде — это они): изоляция SIMBIOT_DB.
Тест: tests/test_tick_latency_noise.py 20 PASS; полный набор — без регрессий.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:32:15 +00:00

85 lines
No EOL
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Регресс e-261003004801: таймаут LLM-тика не должен валить зачистку.
Было: subprocess.run + TimeoutExpired без .pid → AttributeError в
изолированном runner'е ДО добивания процесс-группы. LLM-потомки воркера
выживали и писали в стор после смерти тика (race, burn роутера).
Теперь: Popen(start_new_session=True) → killpg(proc.pid); исключение —
честный TimeoutExpired, группа (с потомками) мертва до re-raise.
"""
import os
import subprocess
import sys
import tempfile
import time
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# изоляция стора (аудит 2026-10-16): тест не пишет в боевой стор, даже
# когда его гоняет тик харнеса под SIMBIOT_HOME (TTFTWatchdogRetry с
# бюджетом 0.5с из этого набора оседали в проде)
import tempfile as _iso_tf # noqa: E402
os.environ["SIMBIOT_DB"] = os.path.join(_iso_tf.mkdtemp(prefix="simbiot_test_"), "harness.db")
from harness import isolated_runner
class TimeoutKillTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.mkdtemp(prefix="test_timeout_kill_")
self.worker = os.path.join(self.tmp, "fake_worker.py")
# фейковый воркер: спавнит потомка (имитация LLM-сабпроцесса) и спит
with open(self.worker, "w") as f:
f.write(
"import subprocess, time\n"
"child = subprocess.Popen(['sleep', '287'])\n"
"print(child.pid, flush=True)\n"
"time.sleep(300)\n"
)
self._orig = (isolated_runner._VENV_PY, isolated_runner._WORKER)
isolated_runner._VENV_PY = sys.executable
isolated_runner._WORKER = self.worker
def tearDown(self):
isolated_runner._VENV_PY, isolated_runner._WORKER = self._orig
def _cfg(self):
class Cfg:
name = "test"
model = "x"
effort = "low"
mandate = ""
can_shell = False
ctx_tokens = 1000
every = 300
return Cfg()
def test_timeout_kills_group_without_pid_crash(self):
with self.assertRaises(subprocess.TimeoutExpired):
isolated_runner.run_llm_tick(self._cfg(), "ping", timeout=3)
# группа воркера убита: потомок-«LLM» не выжил
time.sleep(0.5)
r = subprocess.run(["pgrep", "-c", "-f", "sleep 287"],
capture_output=True)
# pgrep: exit 1 = совпадений нет → потомок-«LLM» мёртв
self.assertEqual(r.returncode, 1,
f"LLM-потомок пережил таймаут добивания: {r.stdout!r}")
def test_normal_run_unaffected(self):
# короткий воркер: пишет результат в result_path как настоящий
worker = os.path.join(self.tmp, "ok_worker.py")
with open(worker, "w") as f:
f.write(
"import json, os, sys\n"
"payload = json.loads(sys.stdin.read())\n"
"json.dump({'ok': True, 'text': 'done',\n"
" 'usage': {}, 'trace_id': 't'}, open(payload['result_path'], 'w'))\n"
)
isolated_runner._WORKER = worker
text, usage, trace = isolated_runner.run_llm_tick(self._cfg(),
"ping", timeout=15)
self.assertEqual(text, "done")
self.assertEqual(trace, "t")
if __name__ == "__main__":
unittest.main(verbosity=2)