From f6308368b0a680483ad856ddb4ea28f21bc3ae60 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 31 Mar 2025 10:43:57 +0200 Subject: [PATCH] Test behavior of statistic_during_period when circular mean is undefined (#141554) * Test behavior of statistic_during_period when circular mean is undefined * Improve comment --- .../components/recorder/test_websocket_api.py | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index a4e4fe45db1..2460de994ec 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -698,17 +698,33 @@ def _circular_mean(values: Iterable[StatisticData]) -> dict[str, float]: } -def _circular_mean_approx(values: Iterable[StatisticData]) -> ApproxBase: - return pytest.approx(_circular_mean(values)["mean"]) +def _circular_mean_approx( + values: Iterable[StatisticData], tolerance: float | None = None +) -> ApproxBase: + return pytest.approx(_circular_mean(values)["mean"], abs=tolerance) @pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC)) @pytest.mark.usefixtures("recorder_mock") @pytest.mark.parametrize("offset", [0, 1, 2]) +@pytest.mark.parametrize( + ("step_size", "tolerance"), + [ + (123.456, 1e-4), + # In this case the angles are uniformly distributed and the mean is undefined. + # This edge case is not handled by the current implementation, but the test + # checks the behavior is consistent. + # We could consider returning None in this case, or returning also an estimate + # of the variance. + (120, 10), + ], +) async def test_statistic_during_period_circular_mean( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, offset: int, + step_size: float, + tolerance: float, ) -> None: """Test statistic_during_period.""" now = dt_util.utcnow() @@ -724,7 +740,7 @@ async def test_statistic_during_period_circular_mean( imported_stats_5min: list[StatisticData] = [ { "start": (start + timedelta(minutes=5 * i)), - "mean": (123.456 * i) % 360, + "mean": (step_size * i) % 360, "mean_weight": 1, } for i in range(39) @@ -807,7 +823,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min), + "mean": _circular_mean_approx(imported_stats_5min, tolerance), "max": None, "min": None, "change": None, @@ -835,7 +851,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min), + "mean": _circular_mean_approx(imported_stats_5min, tolerance), "max": None, "min": None, "change": None, @@ -863,7 +879,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min), + "mean": _circular_mean_approx(imported_stats_5min, tolerance), "max": None, "min": None, "change": None, @@ -887,7 +903,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[26:]), + "mean": _circular_mean_approx(imported_stats_5min[26:], tolerance), "max": None, "min": None, "change": None, @@ -910,7 +926,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[26:]), + "mean": _circular_mean_approx(imported_stats_5min[26:], tolerance), "max": None, "min": None, "change": None, @@ -934,7 +950,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[:26]), + "mean": _circular_mean_approx(imported_stats_5min[:26], tolerance), "max": None, "min": None, "change": None, @@ -964,7 +980,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[26:32]), + "mean": _circular_mean_approx(imported_stats_5min[26:32], tolerance), "max": None, "min": None, "change": None, @@ -986,7 +1002,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[24 - offset :]), + "mean": _circular_mean_approx(imported_stats_5min[24 - offset :], tolerance), "max": None, "min": None, "change": None, @@ -1005,7 +1021,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[24 - offset :]), + "mean": _circular_mean_approx(imported_stats_5min[24 - offset :], tolerance), "max": None, "min": None, "change": None, @@ -1027,7 +1043,9 @@ async def test_statistic_during_period_circular_mean( slice_start = 24 - offset slice_end = 36 - offset assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min[slice_start:slice_end]), + "mean": _circular_mean_approx( + imported_stats_5min[slice_start:slice_end], tolerance + ), "max": None, "min": None, "change": None, @@ -1044,7 +1062,7 @@ async def test_statistic_during_period_circular_mean( response = await client.receive_json() assert response["success"] assert response["result"] == { - "mean": _circular_mean_approx(imported_stats_5min), + "mean": _circular_mean_approx(imported_stats_5min, tolerance), }