STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_controller_compute_lock.py
Go to the documentation of this file.
1"""One user's controller is touched by one request at a time: the streams and /reset too.
2
31298bf7a moved /init, /step, /simulate and /simulate-layer2 into ``run_compute`` (the thread pool,
4under the session's compute lock) and the SSE streams' controller steps onto bare pool threads.
5On the event loop the two kinds could only interleave between steps; on a bare thread a stream's
6step ran beside a /simulate or /reset of the same ``session.controller`` from another tab, mid-step.
7"""
8
9from __future__ import annotations
10
11import numpy as np
12import pytest
13
14pytest.importorskip("httpx", reason="fastapi TestClient needs httpx")
15
16from fastapi.testclient import TestClient # noqa: E402
17
18import backend.main as backend_main # noqa: E402
19from backend import session as session_mod # noqa: E402
20from backend.routers import control as control_router # noqa: E402
21
22EMAIL = "controller-lock@berkeley.edu"
23H = {"X-Auth-Email": EMAIL}
24
25
26class _Stop(Exception):
27 pass
28
29
30def _lock_held() -> bool:
31 return session_mod.registry.get(EMAIL).compute_lock.locked()
32
33
35 """Records whether the session's compute lock was held at each touch; never solves."""
36
37 touches: list = []
38
39 def __init__(self, cfg, engine_config, logger=None):
40 self.cfg = cfg
41 self.V_u_F = self.V_u_O = 0.01
42 FakeController.touches.append(("build", _lock_held()))
43
44 def reset(self):
45 FakeController.touches.append(("reset", _lock_held()))
46
47 def _build_state(self, meas):
48 return np.zeros(8)
49
50 def step(self, meas, nav, cmd):
51 FakeController.touches.append(("step", _lock_held()))
52 raise _Stop("recorded")
53
54
55@pytest.fixture
56def fake(monkeypatch):
57 FakeController.touches = []
58 monkeypatch.setattr(control_router, "RobustDDPController", FakeController)
59 yield FakeController.touches
60 session_mod.registry.get(EMAIL).controller.controller = None
61
62
64 client = TestClient(backend_main.app)
65 r = client.post("/api/control/simulate-layer2-stream", headers=H,
66 json={"thrust_curve_time": [0.0, 0.1], "thrust_curve_values": [6800.0, 6800.0]})
67 assert r.status_code == 200
68 assert "recorded" in r.text, r.text[:400]
69 kinds = [k for k, _ in fake]
70 assert kinds[:3] == ["build", "reset", "step"], fake
71 assert all(held for _, held in fake), (
72 f"the stream touched the controller without the session's compute lock: {fake}. A /simulate "
73 "or /reset of the same controller could run beside it; go through run_compute."
74 )
75
76
78 client = TestClient(backend_main.app)
79 client.get("/api/health", headers=H)
80 session_mod.registry.get(EMAIL).controller.controller = FakeController(None, None)
81 fake.clear()
82 r = client.post("/api/control/reset", headers=H)
83 assert r.status_code == 200, r.text[:300]
84 assert fake == [("reset", True)], f"/reset touched the controller outside the compute lock: {fake}"
__init__(self, cfg, engine_config, logger=None)