Skip to content

Commit 4c2efc0

Browse files
committed
WIP: Fix tests
1 parent fa05a2a commit 4c2efc0

File tree

2 files changed

+81
-52
lines changed

2 files changed

+81
-52
lines changed

tests/timeseries/_battery_pool/test_battery_pool_control_methods.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
import asyncio
77
import dataclasses
8-
import typing
8+
import re
9+
from collections.abc import AsyncIterator, Callable
910
from datetime import datetime, timedelta, timezone
11+
from typing import cast
1012
from unittest.mock import AsyncMock, MagicMock
1113

1214
import async_solipsism
1315
import pytest
14-
from frequenz.channels import LatestValueCache, Sender
16+
from frequenz.channels import LatestValueCache, Receiver, Sender
1517
from frequenz.quantities import Power
1618
from 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

8987
class 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
)

tests/timeseries/_ev_charger_pool/test_ev_charger_pool_control_methods.py

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"""Test the EV charger pool control methods."""
55

66
import asyncio
7-
import typing
7+
from collections.abc import AsyncIterator, Callable
88
from datetime import datetime, timedelta, timezone
9+
from typing import cast
910
from unittest.mock import AsyncMock, MagicMock
1011

1112
import async_solipsism
@@ -15,6 +16,7 @@
1516
from frequenz.client.microgrid.component import ComponentStateCode
1617
from frequenz.quantities import Power, Voltage
1718
from pytest_mock import MockerFixture
19+
from regex import E
1820

1921
from frequenz.sdk import microgrid
2022
from frequenz.sdk.microgrid import _power_distributing
@@ -41,7 +43,7 @@ def event_loop_policy() -> async_solipsism.EventLoopPolicy:
4143

4244

4345
@pytest.fixture
44-
async def mocks(mocker: MockerFixture) -> typing.AsyncIterator[_Mocks]:
46+
async def mocks(mocker: MockerFixture) -> AsyncIterator[_Mocks]:
4547
"""Create the mocks."""
4648
mockgrid = MockMicrogrid(grid_meter=True)
4749
mockgrid.add_ev_chargers(4)
@@ -55,13 +57,17 @@ async def mocks(mocker: MockerFixture) -> typing.AsyncIterator[_Mocks]:
5557
)
5658
streamer = MockComponentDataStreamer(mockgrid.mock_client)
5759

58-
dp = typing.cast(_DataPipeline, microgrid._data_pipeline._DATA_PIPELINE)
60+
dp = cast(_DataPipeline, microgrid._data_pipeline._DATA_PIPELINE)
5961

60-
yield _Mocks(
62+
_mocks = _Mocks(
6163
mockgrid,
6264
streamer,
6365
dp._ev_power_wrapper.status_channel.new_sender(),
6466
)
67+
try:
68+
yield _mocks
69+
finally:
70+
await _mocks.stop()
6571

6672

6773
class TestEVChargerPoolControl:
@@ -145,16 +151,17 @@ async def _init_ev_chargers(self, mocks: _Mocks) -> None:
145151

146152
def _assert_report( # pylint: disable=too-many-arguments
147153
self,
148-
report: EVChargerPoolReport,
154+
report: EVChargerPoolReport | None,
149155
*,
150156
power: float | None,
151157
lower: float,
152158
upper: float,
153159
dist_result: _power_distributing.Result | None = None,
154160
expected_result_pred: (
155-
typing.Callable[[_power_distributing.Result], bool] | None
161+
Callable[[_power_distributing.Result], bool] | None
156162
) = None,
157163
) -> None:
164+
assert report is not None
158165
assert report.target_power == (
159166
Power.from_watts(power) if power is not None else None
160167
)
@@ -165,23 +172,21 @@ def _assert_report( # pylint: disable=too-many-arguments
165172
assert dist_result is not None
166173
assert expected_result_pred(dist_result)
167174

168-
async def _get_bounds_receiver(
169-
self, ev_charger_pool: EVChargerPool
170-
) -> Receiver[EVChargerPoolReport]:
171-
bounds_rx = ev_charger_pool.power_status.new_receiver()
172-
173-
# Consume initial reports as chargers are initialized
174-
expected_upper_bounds = 44160.0
175+
async def _recv_reports_until(
176+
self,
177+
bounds_rx: Receiver[EVChargerPoolReport],
178+
check: Callable[[EVChargerPoolReport], bool],
179+
) -> EVChargerPoolReport | None:
180+
"""Receive reports until the given condition is met."""
175181
max_reports = 10
176182
ctr = 0
177183
while ctr < max_reports:
178184
ctr += 1
179-
report = await bounds_rx.receive()
180-
assert report.bounds is not None
181-
if report.bounds.upper == Power.from_watts(expected_upper_bounds):
182-
break
183-
184-
return bounds_rx
185+
async with asyncio.timeout(10.0):
186+
report = await bounds_rx.receive()
187+
if check(report):
188+
return report
189+
return None
185190

186191
async def test_setting_power(
187192
self,
@@ -192,7 +197,7 @@ async def test_setting_power(
192197
traveller = time_machine.travel(datetime(2012, 12, 12))
193198
mock_time = traveller.start()
194199

195-
set_power = typing.cast(
200+
set_power = cast(
196201
AsyncMock,
197202
microgrid.connection_manager.get().api_client.set_component_power_active,
198203
)
@@ -201,23 +206,27 @@ async def test_setting_power(
201206
await self._patch_ev_pool_status(mocks, mocker)
202207
await self._patch_power_distributing_actor(mocker)
203208

204-
bounds_rx = await self._get_bounds_receiver(ev_charger_pool)
209+
bounds_rx = ev_charger_pool.power_status.new_receiver()
210+
# Receive reports until all chargers are initialized
211+
report = await self._recv_reports_until(
212+
bounds_rx,
213+
lambda r: r.bounds is not None
214+
and r.bounds.upper == Power.from_watts(44160.0),
215+
)
205216

206217
# Check that chargers are initialized to Power.zero()
207218
assert set_power.call_count == 4
208219
assert all(x.args[1] == 0.0 for x in set_power.call_args_list)
209-
210-
self._assert_report(
211-
await bounds_rx.receive(), power=None, lower=0.0, upper=44160.0
212-
)
220+
self._assert_report(report, power=None, lower=0.0, upper=44160.0)
213221

214222
set_power.reset_mock()
215223
await ev_charger_pool.propose_power(Power.from_watts(40000.0))
216224
# ignore one report because it is not always immediately updated.
217-
await bounds_rx.receive()
218-
self._assert_report(
219-
await bounds_rx.receive(), power=40000.0, lower=0.0, upper=44160.0
225+
report = await self._recv_reports_until(
226+
bounds_rx,
227+
lambda r: r.target_power == Power.from_watts(40000.0),
220228
)
229+
self._assert_report(report, power=40000.0, lower=0.0, upper=44160.0)
221230
mock_time.shift(timedelta(seconds=60))
222231
await asyncio.sleep(0.15)
223232

@@ -240,7 +249,7 @@ async def test_setting_power(
240249
# Throttle the power
241250
set_power.reset_mock()
242251
await ev_charger_pool.propose_power(Power.from_watts(32000.0))
243-
await bounds_rx.receive()
252+
report = await bounds_rx.receive()
244253
await asyncio.sleep(0.02)
245254
assert set_power.call_count == 1
246255

0 commit comments

Comments
 (0)