feat: serialize public hardware execution
This commit is contained in:
@@ -52,7 +52,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
session_ttl_seconds=3600,
|
||||
login_attempts=5,
|
||||
login_window_seconds=300,
|
||||
requests_per_minute=20,
|
||||
requests_per_minute=120,
|
||||
trust_proxy_headers=False,
|
||||
)
|
||||
)
|
||||
@@ -105,7 +105,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||
html = response.read().decode("utf-8")
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=seat-hil-7", timeout=3) as response:
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=seat-hil-8", timeout=3) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
with self.opener.open(f"{self.base_url}/api/runtime", timeout=3) as response:
|
||||
runtime = json.loads(response.read().decode("utf-8"))
|
||||
@@ -115,11 +115,13 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
self.assertIn('data-vehicle-model="byd-tang-2024-reference"', html)
|
||||
self.assertIn("整车孪生界面", html)
|
||||
self.assertIn("透明车体 · 实体设备", html)
|
||||
self.assertIn('src="/app.js?v=seat-hil-7"', html)
|
||||
self.assertIn('src="/app.js?v=seat-hil-8"', html)
|
||||
self.assertIn("HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000", app_script)
|
||||
self.assertIn(
|
||||
"requestTimeoutForExecutionMode(state.executionMode)", app_script
|
||||
)
|
||||
self.assertIn('error.code === "hardware_busy"', app_script)
|
||||
self.assertIn("BENCH BUSY", app_script)
|
||||
self.assertIn("FBXLoader.js", html)
|
||||
self.assertIn('data-seat-layout="five-seat"', html)
|
||||
self.assertIn('data-active-seat="front-right"', html)
|
||||
@@ -145,6 +147,11 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
runtime["hardware"]["configured"],
|
||||
)
|
||||
self.assertEqual(runtime["hardware"]["supported_targets"], ["front_right"])
|
||||
self.assertEqual(
|
||||
runtime["hardware"]["concurrency_policy"],
|
||||
"single_flight_fail_fast",
|
||||
)
|
||||
self.assertFalse(runtime["hardware"]["busy"])
|
||||
self.assertEqual(
|
||||
runtime["hardware"]["supported_seat_instances"], ["passenger"]
|
||||
)
|
||||
@@ -517,6 +524,85 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
)
|
||||
self.assertNotIn("must-not-be-persisted", artifact_text)
|
||||
|
||||
def test_overlapping_hardware_request_returns_conflict_without_entering_can(self) -> None:
|
||||
entered = threading.Event()
|
||||
release = threading.Event()
|
||||
first_result: dict[str, object] = {}
|
||||
cookie_header = "; ".join(
|
||||
f"{cookie.name}={cookie.value}" for cookie in self.cookie_jar
|
||||
)
|
||||
|
||||
def request(conversation_id: str) -> urllib.request.Request:
|
||||
return urllib.request.Request(
|
||||
f"{self.base_url}/api/requests",
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Cookie": cookie_header,
|
||||
},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": "关闭副驾座椅加热",
|
||||
"provider": "local",
|
||||
"conversation_id": conversation_id,
|
||||
"execution_mode": "hardware",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
def occupy_bench(*_args: object, **_kwargs: object) -> dict[str, object]:
|
||||
entered.set()
|
||||
self.assertTrue(release.wait(timeout=3))
|
||||
return {"status": "succeeded", "request_id": "req-exclusive-owner"}
|
||||
|
||||
def submit_owner() -> None:
|
||||
with urllib.request.urlopen(
|
||||
request("conv-exclusive-owner"), timeout=5
|
||||
) as response:
|
||||
first_result["http_status"] = response.status
|
||||
first_result["payload"] = json.loads(
|
||||
response.read().decode("utf-8")
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
self.service,
|
||||
"_process_once",
|
||||
side_effect=occupy_bench,
|
||||
) as process_once:
|
||||
owner = threading.Thread(target=submit_owner)
|
||||
owner.start()
|
||||
self.assertTrue(entered.wait(timeout=2))
|
||||
with self.assertRaises(urllib.error.HTTPError) as caught:
|
||||
urllib.request.urlopen(
|
||||
request("conv-exclusive-contender"), timeout=3
|
||||
)
|
||||
payload = json.loads(caught.exception.read().decode("utf-8"))
|
||||
self.assertEqual(caught.exception.code, 409)
|
||||
self.assertEqual(caught.exception.headers["Retry-After"], "5")
|
||||
self.assertEqual(payload["error"], "hardware_busy")
|
||||
self.assertIn("未进入CAN链路", payload["message"])
|
||||
self.assertEqual(payload["retry_after_seconds"], 5)
|
||||
self.assertEqual(process_once.call_count, 1)
|
||||
state_request = urllib.request.Request(
|
||||
f"{self.base_url}/api/hardware/seat-state?"
|
||||
"conversation_id=conv-exclusive-state-reader&vehicle_id=denza_n7",
|
||||
headers={"Cookie": cookie_header},
|
||||
)
|
||||
with self.assertRaises(urllib.error.HTTPError) as state_caught:
|
||||
urllib.request.urlopen(state_request, timeout=3)
|
||||
state_payload = json.loads(
|
||||
state_caught.exception.read().decode("utf-8")
|
||||
)
|
||||
self.assertEqual(state_caught.exception.code, 409)
|
||||
self.assertEqual(state_payload["error"], "hardware_busy")
|
||||
release.set()
|
||||
owner.join(timeout=5)
|
||||
|
||||
self.assertFalse(owner.is_alive())
|
||||
self.assertEqual(first_result["http_status"], 201)
|
||||
self.assertFalse(self.service.hardware_execution.public_info()["busy"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user