from __future__ import annotations import asyncio from contextlib import contextmanager from functools import lru_cache import importlib import os import unittest from unittest.mock import patch import app.simulation.performance as performance @contextmanager def profiling_mode(mode: str): previous = os.environ.get("SIMULATIONAPP_PROFILE") os.environ["SIMULATIONAPP_PROFILE"] = mode module = importlib.reload(performance) try: yield module finally: if previous is None: os.environ.pop("SIMULATIONAPP_PROFILE", None) else: os.environ["SIMULATIONAPP_PROFILE"] = previous importlib.reload(performance) class SimulationPerformanceTests(unittest.TestCase): def test_off_mode_decorators_return_original_callables(self) -> None: with profiling_mode("off") as module: def phase_function(value: int) -> int: return value + 1 def property_function(value: int) -> int: return value * 2 self.assertIs( module.profile_phase("phase")(phase_function), phase_function, ) self.assertIs( module.profile_phase("phase", minimum_mode="off")(phase_function), phase_function, ) self.assertIs( module.profile_property("property")(property_function), property_function, ) self.assertIs( module.profile_property( "property", minimum_mode="off", )(property_function), property_function, ) with module.profile_run() as trace: self.assertEqual(phase_function(2), 3) self.assertEqual( trace.snapshot(), { "mode": "off", "phases": {}, "properties": {}, "propertyOutermostNs": 0, }, ) def test_standard_mode_records_nested_inclusive_and_self_time(self) -> None: with profiling_mode("standard") as module: @module.profile_phase("inner") def inner() -> None: return None @module.profile_phase("outer") def outer() -> None: inner() clock = iter((0, 10, 20, 30, 50, 80)) with patch.object(module, "perf_counter_ns", side_effect=clock): with module.profile_run() as trace: outer() snapshot = trace.snapshot() self.assertEqual( snapshot["phases"]["inner"], { "calls": 1, "inclusiveNs": 10, "selfNs": 10, "maxNs": 10, "errors": 0, }, ) self.assertEqual(snapshot["phases"]["outer"]["inclusiveNs"], 40) self.assertEqual(snapshot["phases"]["outer"]["selfNs"], 30) self.assertEqual( snapshot["phases"]["simulation.total"]["inclusiveNs"], 80, ) self.assertEqual( snapshot["phases"]["simulation.total"]["selfNs"], 40, ) def test_errors_are_recorded_and_propagated(self) -> None: with profiling_mode("standard") as module: @module.profile_phase("explode") def explode() -> None: raise RuntimeError("expected") with self.assertRaisesRegex(RuntimeError, "expected"): with module.profile_run() as trace: explode() snapshot = trace.snapshot() self.assertEqual(snapshot["phases"]["explode"]["errors"], 1) self.assertEqual( snapshot["phases"]["simulation.total"]["errors"], 1, ) def test_manual_performance_span_uses_the_current_trace(self) -> None: with profiling_mode("standard") as module: clock = iter((0, 10, 20, 30)) with patch.object(module, "perf_counter_ns", side_effect=clock): with module.profile_run() as trace: with module.performance_span("manual"): pass snapshot = trace.snapshot() self.assertEqual(snapshot["phases"]["manual"]["inclusiveNs"], 10) self.assertEqual(snapshot["phases"]["simulation.total"]["selfNs"], 20) def test_nested_properties_only_add_outermost_time_once(self) -> None: with profiling_mode("standard") as module: class TestMedium: name = "TestMedium" @module.profile_property("inner", minimum_mode="standard") def inner(self) -> float: return 1.0 @module.profile_property("outer", minimum_mode="standard") def outer(self) -> float: return self.inner() medium = TestMedium() clock = iter((0, 10, 20, 30, 50, 80)) with patch.object(module, "perf_counter_ns", side_effect=clock): with module.profile_run() as trace: self.assertEqual(medium.outer(), 1.0) snapshot = trace.snapshot() outer = snapshot["properties"]["semantic.TestMedium.outer"] inner = snapshot["properties"]["semantic.TestMedium.inner"] self.assertEqual(outer["inclusiveNs"], 40) self.assertEqual(outer["selfNs"], 30) self.assertEqual(inner["inclusiveNs"], 10) self.assertEqual(snapshot["propertyOutermostNs"], 40) def test_audit_resets_exact_input_shadow_and_records_iterations_and_cache(self) -> None: with profiling_mode("audit") as module: class TestMedium: name = "AuditMedium" @module.profile_property("inverse", track_cache=True) @lru_cache(maxsize=4) def inverse(self, value: float) -> float: module.record_property_iterations( "inverse", 3 if value == 1.0 else 4, value == 1.0, ) return value * 2.0 medium = TestMedium() @module.profile_phase("closure", reset_property_shadow=True) def closure() -> None: medium.inverse(1.0) medium.inverse(1.0) medium.inverse(2.0) with module.profile_run() as trace: closure() closure() metric = trace.snapshot()["properties"][ "semantic.AuditMedium.inverse" ] self.assertEqual(metric["calls"], 6) self.assertEqual(metric["exactInputUnique"], 4) self.assertEqual(metric["exactInputRepeats"], 2) self.assertEqual(metric["iterationCalls"], 2) self.assertEqual(metric["iterationTotal"], 7) self.assertEqual(metric["iterationMax"], 4) self.assertEqual(metric["iterationConverged"], 1) self.assertEqual(metric["iterationNonconverged"], 1) self.assertEqual(metric["cacheLookups"], 6) self.assertEqual(metric["cacheHits"], 4) self.assertEqual(metric["cacheMisses"], 2) self.assertTrue(callable(medium.inverse.cache_clear)) self.assertTrue(callable(medium.inverse.cache_info)) self.assertTrue(callable(medium.inverse.cache_parameters)) medium.inverse.cache_clear() self.assertEqual(medium.inverse.cache_info().currsize, 0) def test_audit_minimum_decorator_is_absent_in_standard_mode(self) -> None: with profiling_mode("standard") as module: def kernel(value: float) -> float: return value self.assertIs( module.profile_property( "kernel", layer="kernel", minimum_mode="audit", )(kernel), kernel, ) def test_async_profile_runs_are_context_isolated(self) -> None: with profiling_mode("standard") as module: @module.profile_phase("work") async def work() -> None: await asyncio.sleep(0) async def one_run() -> dict[str, object]: with module.profile_run() as trace: await work() return trace.snapshot() async def exercise() -> list[dict[str, object]]: return await asyncio.gather(one_run(), one_run()) snapshots = asyncio.run(exercise()) self.assertEqual( [snapshot["phases"]["work"]["calls"] for snapshot in snapshots], [1, 1], ) if __name__ == "__main__": unittest.main()