55
66import asyncio
77import dataclasses
8- import typing
8+ import re
9+ from collections .abc import AsyncIterator , Callable
910from datetime import datetime , timedelta , timezone
11+ from typing import cast
1012from unittest .mock import AsyncMock , MagicMock
1113
1214import async_solipsism
1315import pytest
14- from frequenz .channels import LatestValueCache , Sender
16+ from frequenz .channels import LatestValueCache , Receiver , Sender
1517from frequenz .quantities import Power
1618from pytest_mock import MockerFixture
1719
@@ -54,7 +56,7 @@ class Mocks:
5456
5557
5658@pytest .fixture
57- async def mocks (mocker : MockerFixture ) -> typing . AsyncIterator [Mocks ]:
59+ async def mocks (mocker : MockerFixture ) -> AsyncIterator [Mocks ]:
5860 """Fixture for the mocks."""
5961 mockgrid = MockMicrogrid ()
6062 mockgrid .add_batteries (4 )
@@ -71,19 +73,15 @@ async def mocks(mocker: MockerFixture) -> typing.AsyncIterator[Mocks]:
7173 dp = microgrid ._data_pipeline ._DATA_PIPELINE
7274 assert dp is not None
7375
74- yield Mocks (
76+ _mocks = Mocks (
7577 mockgrid ,
7678 streamer ,
7779 dp ._battery_power_wrapper .status_channel .new_sender (),
7880 )
79-
80- await asyncio .gather (
81- * [
82- dp ._stop (),
83- streamer .stop (),
84- mockgrid .cleanup (),
85- ]
86- )
81+ try :
82+ yield _mocks
83+ finally :
84+ await asyncio .gather (dp ._stop (), streamer .stop (), mockgrid .cleanup ())
8785
8886
8987class TestBatteryPoolControl :
@@ -159,16 +157,17 @@ async def _init_data_for_inverters(self, mocks: Mocks) -> None:
159157
160158 def _assert_report ( # pylint: disable=too-many-arguments
161159 self ,
162- report : BatteryPoolReport ,
160+ report : BatteryPoolReport | None ,
163161 * ,
164162 power : float | None ,
165163 lower : float ,
166164 upper : float ,
167165 dist_result : _power_distributing .Result | None = None ,
168166 expected_result_pred : (
169- typing . Callable [[_power_distributing .Result ], bool ] | None
167+ Callable [[_power_distributing .Result ], bool ] | None
170168 ) = None ,
171169 ) -> None :
170+ assert report is not None
172171 assert report .target_power == (
173172 Power .from_watts (power ) if power is not None else None
174173 )
@@ -179,6 +178,22 @@ def _assert_report( # pylint: disable=too-many-arguments
179178 assert dist_result is not None
180179 assert expected_result_pred (dist_result )
181180
181+ async def _recv_reports_until (
182+ self ,
183+ bounds_rx : Receiver [BatteryPoolReport ],
184+ check : Callable [[BatteryPoolReport ], bool ],
185+ ) -> BatteryPoolReport | None :
186+ """Receive reports until the given condition is met."""
187+ max_reports = 10
188+ ctr = 0
189+ while ctr < max_reports :
190+ ctr += 1
191+ async with asyncio .timeout (10.0 ):
192+ report = await bounds_rx .receive ()
193+ if check (report ):
194+ return report
195+ return None
196+
182197 async def test_case_1 (
183198 self ,
184199 mocks : Mocks ,
@@ -189,7 +204,7 @@ async def test_case_1(
189204 - single battery pool with all batteries.
190205 - all batteries are working, then one battery stops working.
191206 """
192- set_power = typing . cast (
207+ set_power = cast (
193208 AsyncMock ,
194209 microgrid .connection_manager .get ().api_client .set_component_power_active ,
195210 )
@@ -205,9 +220,12 @@ async def test_case_1(
205220 battery_pool .power_distribution_results .new_receiver ()
206221 )
207222
208- self ._assert_report (
209- await bounds_rx .receive (), power = None , lower = - 4000.0 , upper = 4000.0
223+ report = await self ._recv_reports_until (
224+ bounds_rx ,
225+ lambda r : r .bounds is not None
226+ and r .bounds .upper == Power .from_watts (4000.0 ),
210227 )
228+ self ._assert_report (report , power = None , lower = - 4000.0 , upper = 4000.0 )
211229
212230 await battery_pool .propose_power (Power .from_watts (1000.0 ))
213231
@@ -293,7 +311,7 @@ async def test_case_2(self, mocks: Mocks, mocker: MockerFixture) -> None:
293311 - two battery pools with different batteries.
294312 - all batteries are working.
295313 """
296- set_power = typing . cast (
314+ set_power = cast (
297315 AsyncMock ,
298316 microgrid .connection_manager .get ().api_client .set_component_power_active ,
299317 )
@@ -351,7 +369,7 @@ async def test_case_3(self, mocks: Mocks, mocker: MockerFixture) -> None:
351369 - two battery pools with same batteries, but different priorities.
352370 - all batteries are working.
353371 """
354- set_power = typing . cast (
372+ set_power = cast (
355373 AsyncMock ,
356374 microgrid .connection_manager .get ().api_client .set_component_power_active ,
357375 )
@@ -415,7 +433,7 @@ async def test_case_4(self, mocks: Mocks, mocker: MockerFixture) -> None:
415433 - single battery pool with all batteries.
416434 - all batteries are working, but have exclusion bounds.
417435 """
418- set_power = typing . cast (
436+ set_power = cast (
419437 AsyncMock ,
420438 microgrid .connection_manager .get ().api_client .set_component_power_active ,
421439 )
@@ -444,6 +462,7 @@ async def test_case_4(self, mocks: Mocks, mocker: MockerFixture) -> None:
444462 mocker .call (inv_id , 250.0 )
445463 for inv_id in mocks .microgrid .battery_inverter_ids
446464 ]
465+
447466 self ._assert_report (
448467 await bounds_rx .receive (),
449468 power = 1000.0 ,
@@ -461,9 +480,10 @@ async def test_case_4(self, mocks: Mocks, mocker: MockerFixture) -> None:
461480 # available power.
462481 await battery_pool .propose_power (Power .from_watts (50.0 ))
463482
464- self ._assert_report (
465- await bounds_rx . receive (), power = 400.0 , lower = - 4000.0 , upper = 4000.0
483+ report = await self ._recv_reports_until (
484+ bounds_rx , lambda r : r . target_power == Power . from_watts ( 400.0 )
466485 )
486+ self ._assert_report (report , power = 400.0 , lower = - 4000.0 , upper = 4000.0 )
467487 await asyncio .sleep (0.0 ) # Wait for the power to be distributed.
468488 assert set_power .call_count == 4
469489 assert sorted (set_power .call_args_list ) == [
@@ -542,7 +562,7 @@ async def test_case_5( # pylint: disable=too-many-statements,too-many-locals
542562 - two battery pools are in the shifting group, two are not.
543563 - all batteries are working.
544564 """
545- set_power = typing . cast (
565+ set_power = cast (
546566 AsyncMock ,
547567 microgrid .connection_manager .get ().api_client .set_component_power_active ,
548568 )
0 commit comments