Download tests/test_supervisor.py from bryceweiner/fly-trader: direct link, hf CLI and curl.
- Browser
- Download file 5.61 kB
-
https://huggingface.co/bryceweiner/fly-trader/resolve/main/tests/test_supervisor.py
- Command line
-
hf download hf://bryceweiner/fly-trader/tests/test_supervisor.py
-
curl -L -o test_supervisor.py https://huggingface.co/bryceweiner/fly-trader/resolve/main/tests/test_supervisor.py
5.61 kB
| import threading, time | |
| from fly_trader.ops import supervisor as S | |
| def test_supervisor_thread_lifecycle(monkeypatch, db_conn): | |
| ticks = {"n": 0} | |
| def fake(stop_event=None): | |
| while not stop_event.is_set(): | |
| ticks["n"] += 1 | |
| time.sleep(0.05) | |
| monkeypatch.setattr(S, "_entry", lambda name: fake) | |
| monkeypatch.setattr(S.Supervisor, "external", lambda self: {}) | |
| sup = S.Supervisor() | |
| assert sup.start("discover", started_by="test") | |
| time.sleep(0.3) | |
| assert sup.alive("discover") and ticks["n"] > 2 | |
| assert not sup.start("discover") # idempotent while alive | |
| assert sup.stop("discover", grace_s=5) | |
| assert not sup.alive("discover") | |
| st = sup.status()["discover"] | |
| assert st["stopped_at"] is not None and st["error"] is None | |
| def test_supervisor_records_crash(monkeypatch, db_conn): | |
| def boom(stop_event=None): | |
| raise ValueError("kaput") | |
| monkeypatch.setattr(S, "_entry", lambda name: boom) | |
| monkeypatch.setattr(S.Supervisor, "external", lambda self: {}) | |
| sup = S.Supervisor() | |
| sup.start("runner", started_by="test") | |
| sup.threads["runner"].join(5) | |
| assert "kaput" in sup.status()["runner"]["error"] | |
| def test_a_previous_consoles_row_with_our_own_pid_is_stale(monkeypatch, db_conn): | |
| """In a container the console is always pid 1, so the row the last boot left carries this boot's pid. A new console | |
| has registered nothing yet: such a row is stale, and without closing it every restart refused to register.""" | |
| import os | |
| from fly_trader.db.connection import transaction | |
| with transaction() as conn: | |
| conn.execute("UPDATE processes SET stopped_at = now() WHERE stopped_at IS NULL AND cmd[1] = 'thread'") | |
| rid = conn.execute("INSERT INTO processes (name, pid, cmd, log_path, started_by) VALUES ('discover', %s, %s, 'logs/discover.log', 'test') RETURNING id", | |
| (os.getpid(), ["thread", "discover"])).fetchone()["id"] | |
| monkeypatch.setattr(S, "_entry", lambda name: (lambda stop_event=None: stop_event.wait(5))) | |
| monkeypatch.setattr(S.Supervisor, "external", lambda self: {}) | |
| sup = S.Supervisor() | |
| with transaction() as conn: | |
| row = conn.execute("SELECT stopped_at, exit_code FROM processes WHERE id = %s", (rid,)).fetchone() | |
| assert row["stopped_at"] is not None and row["exit_code"] == -1 # closed at construction, not at start | |
| assert sup.start("discover", started_by="test") # and the worker registers again | |
| sup.stop("discover", grace_s=5) | |
| def test_a_new_console_waits_for_the_one_it_replaces(monkeypatch, db_conn): | |
| """Killing a console and starting the next one within seconds left the old one still winding its workers down: their | |
| rows looked live, registration was refused, and the market feed never came back. The new console waits for the old | |
| pid to go before judging its rows stale.""" | |
| from types import SimpleNamespace | |
| from fly_trader.db.connection import transaction | |
| old_pid = 999_999_001; seen = {"calls": 0} | |
| with transaction() as conn: | |
| conn.execute("UPDATE processes SET stopped_at = now() WHERE stopped_at IS NULL AND cmd[1] = 'thread'") | |
| rid = conn.execute("INSERT INTO processes (name, pid, cmd, log_path, started_by) VALUES ('pumpstream', %s, %s, 'logs/pumpstream.log', 'test') RETURNING id", | |
| (old_pid, ["thread", "pumpstream"])).fetchone()["id"] | |
| import psutil | |
| real = psutil.process_iter | |
| def fake_iter(): # the old console is alive for the first two looks, then gone | |
| seen["calls"] += 1 | |
| return list(real()) + ([SimpleNamespace(pid=old_pid)] if seen["calls"] <= 2 else []) | |
| monkeypatch.setattr(psutil, "process_iter", fake_iter); monkeypatch.setattr(S.time, "sleep", lambda s: None) | |
| monkeypatch.setattr(S, "_entry", lambda name: (lambda stop_event=None: stop_event.wait(5))) | |
| monkeypatch.setattr(S.Supervisor, "external", lambda self: {}) | |
| sup = S.Supervisor() | |
| with transaction() as conn: | |
| row = conn.execute("SELECT stopped_at FROM processes WHERE id = %s", (rid,)).fetchone() | |
| assert row["stopped_at"] is not None and seen["calls"] >= 3 # waited, saw it go, closed the row | |
| assert sup.start("pumpstream", started_by="test"); sup.stop("pumpstream", grace_s=5) | |
| def test_rh_stream_autostarts_with_an_autostart_row_from_before_robinhood_chain(monkeypatch, db_conn): | |
| """The shipped autostart names rh_stream; an older install's row does not, and the feed still starts while RH is on.""" | |
| import json | |
| from fly_trader import config | |
| started = [] | |
| monkeypatch.setattr(S.Supervisor, "start", lambda self, n, started_by="console": started.append(n) or True) | |
| monkeypatch.setattr(S.Supervisor, "alive", lambda self, n: False) | |
| with db_conn.cursor() as cur: | |
| cur.execute("INSERT INTO ui_settings (key, value) VALUES ('autostart', %s) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value", | |
| (json.dumps({"pumpstream": True, "runner": True, "vault": False}),)) | |
| db_conn.commit() | |
| try: | |
| monkeypatch.setattr(config, "RH_ENABLED", True) | |
| assert set(S.Supervisor().autostart()) == {"pumpstream", "runner", "rh_stream"} | |
| started.clear(); monkeypatch.setattr(config, "RH_ENABLED", False) | |
| assert set(S.Supervisor().autostart()) == {"pumpstream", "runner"} | |
| finally: | |
| with db_conn.cursor() as cur: | |
| cur.execute("DELETE FROM ui_settings WHERE key = 'autostart'") | |
| db_conn.commit() | |