127 lines
4.7 KiB
Python
127 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from app.main import _app_lifespan, app
|
|
from app.simulation.warmup import (
|
|
SimulationWarmupReport,
|
|
_reset_simulation_warmup_for_tests,
|
|
warm_up_simulation_runtime,
|
|
)
|
|
|
|
|
|
class SimulationWarmupTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
_reset_simulation_warmup_for_tests()
|
|
self.engine_environment = patch.dict(os.environ, {"SIMULATION_NUMERIC_ENGINE": "native"})
|
|
self.engine_environment.start()
|
|
self.addCleanup(self.engine_environment.stop)
|
|
|
|
def tearDown(self) -> None:
|
|
_reset_simulation_warmup_for_tests()
|
|
|
|
def test_warmup_runs_only_once_per_process(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "on"}),
|
|
patch("app.simulation.warmup._run_native_warmup") as run_warmup,
|
|
):
|
|
first = warm_up_simulation_runtime()
|
|
second = warm_up_simulation_runtime()
|
|
|
|
self.assertIs(second, first)
|
|
self.assertEqual(first.status, "completed")
|
|
run_warmup.assert_called_once_with()
|
|
|
|
def test_disabled_warmup_does_not_touch_numerical_runtime(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "off"}),
|
|
patch("app.simulation.warmup._run_native_warmup") as run_warmup,
|
|
):
|
|
report = warm_up_simulation_runtime()
|
|
|
|
self.assertEqual(report.status, "disabled")
|
|
run_warmup.assert_not_called()
|
|
|
|
def test_regular_failure_is_reported_without_blocking_startup(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "on"}),
|
|
patch(
|
|
"app.simulation.warmup._run_native_warmup",
|
|
side_effect=RuntimeError("broken warmup"),
|
|
),
|
|
self.assertLogs("app.simulation.warmup", level="ERROR"),
|
|
):
|
|
report = warm_up_simulation_runtime()
|
|
|
|
self.assertEqual(report.status, "failed")
|
|
self.assertIn("broken warmup", report.error or "")
|
|
|
|
def test_memory_error_remains_fatal(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "on"}),
|
|
patch(
|
|
"app.simulation.warmup._run_native_warmup",
|
|
side_effect=MemoryError("out of memory"),
|
|
),
|
|
self.assertRaises(MemoryError),
|
|
):
|
|
warm_up_simulation_runtime()
|
|
|
|
def test_lifespan_stores_warmup_report_before_serving(self) -> None:
|
|
report = SimulationWarmupReport(status="completed", duration_ms=12.5)
|
|
|
|
async def enter_lifespan() -> None:
|
|
with patch(
|
|
"app.simulation.warmup.warm_up_simulation_runtime",
|
|
return_value=report,
|
|
) as warmup:
|
|
async with _app_lifespan(app):
|
|
self.assertEqual(app.state.simulation_numeric_engine, "native")
|
|
self.assertEqual(
|
|
app.state.simulation_warmup,
|
|
report.as_dict(),
|
|
)
|
|
warmup.assert_called_once_with()
|
|
|
|
asyncio.run(enter_lifespan())
|
|
|
|
def test_default_startup_checks_native_without_python_warmup(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "on"}),
|
|
patch("app.simulation.warmup._run_native_warmup") as native,
|
|
):
|
|
os.environ.pop("SIMULATION_NUMERIC_ENGINE", None)
|
|
|
|
async def enter_lifespan() -> None:
|
|
async with _app_lifespan(app):
|
|
self.assertEqual(app.state.simulation_numeric_engine, "native")
|
|
self.assertEqual(app.state.simulation_warmup["status"], "completed")
|
|
|
|
asyncio.run(enter_lifespan())
|
|
native.assert_called_once_with()
|
|
|
|
def test_native_failure_does_not_fall_back_to_python(self) -> None:
|
|
with (
|
|
patch.dict(os.environ, {"SIMULATION_NUMERIC_ENGINE": "native", "SIMULATIONAPP_WARMUP": "on"}),
|
|
patch("app.simulation.warmup._run_native_warmup", side_effect=RuntimeError("C compiler unavailable")),
|
|
self.assertLogs("app.simulation.warmup", level="ERROR"),
|
|
):
|
|
report = warm_up_simulation_runtime()
|
|
self.assertEqual(report.status, "failed")
|
|
self.assertIn("C compiler unavailable", report.error or "")
|
|
|
|
def test_real_numerical_warmup_completes(self) -> None:
|
|
with patch.dict(os.environ, {"SIMULATIONAPP_WARMUP": "on"}):
|
|
report = warm_up_simulation_runtime()
|
|
|
|
self.assertEqual(report.status, "completed", report.error)
|
|
self.assertGreater(report.duration_ms, 0.0)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|