TradingAgents/web_dashboard/backend/tests/test_executors.py

113 lines
3.2 KiB
Python

import asyncio
from pathlib import Path
import pytest
from services.executor import AnalysisExecutorError, LegacySubprocessAnalysisExecutor
from services.request_context import build_request_context
class _FakeStdout:
def __init__(self, lines, *, stall: bool = False):
self._lines = list(lines)
self._stall = stall
async def readline(self):
if self._stall:
await asyncio.sleep(3600)
if self._lines:
return self._lines.pop(0)
return b""
class _FakeStderr:
def __init__(self, payload: bytes = b""):
self._payload = payload
async def read(self):
return self._payload
class _FakeProcess:
def __init__(self, stdout, *, stderr: bytes = b"", returncode=None):
self.stdout = stdout
self.stderr = _FakeStderr(stderr)
self.returncode = returncode
self.kill_called = False
self.wait_called = False
async def wait(self):
self.wait_called = True
if self.returncode is None:
self.returncode = -9 if self.kill_called else 0
return self.returncode
def kill(self):
self.kill_called = True
self.returncode = -9
def test_executor_raises_when_required_markers_missing(monkeypatch):
process = _FakeProcess(
_FakeStdout(
[
b"STAGE:analysts\n",
b"STAGE:portfolio\n",
b"SIGNAL_DETAIL:{\"quant_signal\":\"BUY\",\"llm_signal\":\"BUY\",\"confidence\":0.8}\n",
],
),
returncode=0,
)
async def fake_create_subprocess_exec(*args, **kwargs):
return process
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
executor = LegacySubprocessAnalysisExecutor(
analysis_python=Path("/usr/bin/python3"),
repo_root=Path("."),
api_key_resolver=lambda: "env-key",
)
async def scenario():
with pytest.raises(AnalysisExecutorError, match="required markers: ANALYSIS_COMPLETE"):
await executor.execute(
task_id="task-1",
ticker="AAPL",
date="2026-04-13",
request_context=build_request_context(api_key="ctx-key"),
)
asyncio.run(scenario())
def test_executor_kills_subprocess_on_timeout(monkeypatch):
process = _FakeProcess(_FakeStdout([], stall=True))
async def fake_create_subprocess_exec(*args, **kwargs):
return process
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
executor = LegacySubprocessAnalysisExecutor(
analysis_python=Path("/usr/bin/python3"),
repo_root=Path("."),
api_key_resolver=lambda: "env-key",
stdout_timeout_secs=0.01,
)
async def scenario():
with pytest.raises(AnalysisExecutorError, match="timed out"):
await executor.execute(
task_id="task-2",
ticker="AAPL",
date="2026-04-13",
request_context=build_request_context(api_key="ctx-key"),
)
asyncio.run(scenario())
assert process.kill_called is True
assert process.wait_called is True