1717from pytest_mock import MockerFixture
1818
1919from frequenz .sdk import microgrid
20+ from frequenz .sdk .microgrid import _power_distributing
2021from frequenz .sdk .microgrid ._data_pipeline import _DataPipeline
2122from frequenz .sdk .timeseries import ResamplerConfig2
2223from frequenz .sdk .timeseries .pv_pool import PVPoolReport
@@ -75,8 +76,6 @@ async def _init_pv_inverters(self, mocks: _Mocks) -> None:
7576 active_power = 0.0 ,
7677 active_power_inclusion_lower_bound = - 10000.0 * (idx + 1 ),
7778 active_power_inclusion_upper_bound = 0.0 ,
78- active_power_exclusion_lower_bound = 0.0 ,
79- active_power_exclusion_upper_bound = 0.0 ,
8079 ),
8180 0.05 ,
8281 )
@@ -98,25 +97,30 @@ async def _fail_pv_inverters(
9897 active_power = 0.0 ,
9998 active_power_inclusion_lower_bound = - 10000.0 * (idx + 1 ),
10099 active_power_inclusion_upper_bound = 0.0 ,
101- active_power_exclusion_lower_bound = 0.0 ,
102- active_power_exclusion_upper_bound = 0.0 ,
103100 ),
104101 )
105102
106- def _assert_report (
103+ def _assert_report ( # pylint: disable=too-many-arguments
107104 self ,
108105 report : PVPoolReport | None ,
109106 * ,
110107 power : float | None ,
111108 lower : float ,
112109 upper : float ,
110+ dist_result : _power_distributing .Result | None = None ,
111+ expected_result_pred : (
112+ typing .Callable [[_power_distributing .Result ], bool ] | None
113+ ) = None ,
113114 ) -> None :
114115 assert report is not None and report .target_power == (
115116 Power .from_watts (power ) if power is not None else None
116117 )
117118 assert report .bounds is not None
118119 assert report .bounds .lower == Power .from_watts (lower )
119120 assert report .bounds .upper == Power .from_watts (upper )
121+ if expected_result_pred is not None :
122+ assert dist_result is not None
123+ assert expected_result_pred (dist_result )
120124
121125 async def _recv_reports_until (
122126 self ,
@@ -126,13 +130,16 @@ async def _recv_reports_until(
126130 """Receive reports until the given condition is met."""
127131 max_reports = 10
128132 ctr = 0
133+ latest_report : PVPoolReport | None = None
129134 while ctr < max_reports :
130135 ctr += 1
131- async with asyncio .timeout (10.0 ):
132- report = await bounds_rx .receive ()
133- if check (report ):
134- return report
135- return None
136+ print (f"[TEST] Waiting for report #{ ctr } ..." )
137+ latest_report = await bounds_rx .receive ()
138+ print (f"[TEST] Received report #{ ctr } : { latest_report } " )
139+ if check (latest_report ):
140+ break
141+
142+ return latest_report
136143
137144 async def test_setting_power ( # pylint: disable=too-many-statements
138145 self ,
@@ -148,81 +155,117 @@ async def test_setting_power( # pylint: disable=too-many-statements
148155 await self ._init_pv_inverters (mocks )
149156 pv_pool = microgrid .new_pv_pool (priority = 5 )
150157 bounds_rx = pv_pool .power_status .new_receiver ()
151- report = await self ._recv_reports_until (
158+ latest_report = await self ._recv_reports_until (
152159 bounds_rx ,
153- lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 100_000 .0 ,
160+ lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 100000 .0 ,
154161 )
155- assert report is not None , "No report meeting the condition was received"
156- self ._assert_report (report , power = None , lower = - 100_000.0 , upper = 0.0 )
157- await pv_pool .propose_power (Power .from_watts (- 80_000.0 ))
158- report = await self ._recv_reports_until (
162+ dist_results_rx = pv_pool .power_distribution_results .new_receiver ()
163+
164+ self ._assert_report (latest_report , power = None , lower = - 100000.0 , upper = 0.0 )
165+ await pv_pool .propose_power (Power .from_watts (- 80000.0 ))
166+ await self ._recv_reports_until (
159167 bounds_rx ,
160168 lambda x : x .target_power is not None
161- and x .target_power .as_watts () == - 80_000.0 ,
169+ and x .target_power .as_watts () == - 80000.0 ,
170+ )
171+ self ._assert_report (
172+ await bounds_rx .receive (), power = - 80000.0 , lower = - 100000.0 , upper = 0.0
162173 )
163- assert report is not None , "No report meeting the condition was received"
164- self ._assert_report (report , power = - 80_000.0 , lower = - 100_000.0 , upper = 0.0 )
165174 await asyncio .sleep (0.0 )
166175
167176 # Components are set initial power
168177 assert set_power .call_count == 4
169178 inv_ids = mocks .microgrid .pv_inverter_ids
170179 assert sorted (set_power .call_args_list , key = lambda x : x .args [0 ]) == [
171- mocker .call (inv_ids [0 ], - 10_000 .0 ),
172- mocker .call (inv_ids [1 ], - 20_000 .0 ),
173- mocker .call (inv_ids [2 ], - 25_000 .0 ),
174- mocker .call (inv_ids [3 ], - 25_000 .0 ),
180+ mocker .call (inv_ids [0 ], - 10000 .0 ),
181+ mocker .call (inv_ids [1 ], - 20000 .0 ),
182+ mocker .call (inv_ids [2 ], - 25000 .0 ),
183+ mocker .call (inv_ids [3 ], - 25000 .0 ),
175184 ]
185+ dist_results = await dist_results_rx .receive ()
186+ assert isinstance (
187+ dist_results , _power_distributing .Success
188+ ), f"Expected a success, got { dist_results } "
189+ assert dist_results .succeeded_power == Power .from_watts (- 80000.0 )
190+ assert dist_results .excess_power == Power .zero ()
191+ assert dist_results .succeeded_components == {
192+ ComponentId (8 ),
193+ ComponentId (18 ),
194+ ComponentId (28 ),
195+ ComponentId (38 ),
196+ }
176197
177198 set_power .reset_mock ()
178- await pv_pool .propose_power (Power .from_watts (- 4_000 .0 ))
179- report = await self ._recv_reports_until (
199+ await pv_pool .propose_power (Power .from_watts (- 4000 .0 ))
200+ await self ._recv_reports_until (
180201 bounds_rx ,
181202 lambda x : x .target_power is not None
182- and x .target_power .as_watts () == - 4_000.0 ,
203+ and x .target_power .as_watts () == - 4000.0 ,
204+ )
205+ self ._assert_report (
206+ await bounds_rx .receive (), power = - 4000.0 , lower = - 100000.0 , upper = 0.0
183207 )
184- assert report is not None , "No report meeting the condition was received"
185- self ._assert_report (report , power = - 4_000.0 , lower = - 100_000.0 , upper = 0.0 )
186208 await asyncio .sleep (0.0 )
187209
188210 # Components are set initial power
189211 assert set_power .call_count == 4
190212 inv_ids = mocks .microgrid .pv_inverter_ids
191213 assert sorted (set_power .call_args_list , key = lambda x : x .args [0 ]) == [
192- mocker .call (inv_ids [0 ], - 1_000 .0 ),
193- mocker .call (inv_ids [1 ], - 1_000 .0 ),
194- mocker .call (inv_ids [2 ], - 1_000 .0 ),
195- mocker .call (inv_ids [3 ], - 1_000 .0 ),
214+ mocker .call (inv_ids [0 ], - 1000 .0 ),
215+ mocker .call (inv_ids [1 ], - 1000 .0 ),
216+ mocker .call (inv_ids [2 ], - 1000 .0 ),
217+ mocker .call (inv_ids [3 ], - 1000 .0 ),
196218 ]
219+ dist_results = await dist_results_rx .receive ()
220+ assert isinstance (
221+ dist_results , _power_distributing .Success
222+ ), f"Expected a success, got { dist_results } "
223+ assert dist_results .succeeded_power == Power .from_watts (- 4000.0 )
224+ assert dist_results .excess_power == Power .zero ()
225+ assert dist_results .succeeded_components == {
226+ ComponentId (8 ),
227+ ComponentId (18 ),
228+ ComponentId (28 ),
229+ ComponentId (38 ),
230+ }
197231
198232 # After failing 1 inverter, bounds should go down and power shouldn't be
199233 # distributed to that inverter.
200234 print (f"\n [TEST] ========== FAILING INVERTER { inv_ids [1 ]} ==========" )
201235 await self ._fail_pv_inverters ([inv_ids [1 ]], mocks )
202236 print ("[TEST] Waiting for bounds to update to -80_000.0..." )
203- report = await self ._recv_reports_until (
237+ await self ._recv_reports_until (
204238 bounds_rx ,
205- lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 80_000.0 ,
239+ lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 80000.0 ,
240+ )
241+ self ._assert_report (
242+ await bounds_rx .receive (), power = - 4000.0 , lower = - 80000.0 , upper = 0.0
206243 )
207- assert report is not None , "No report meeting the condition was received"
208- print (f"[TEST] Got bounds report: { report } " )
209- self ._assert_report (report , power = - 4_000.0 , lower = - 80_000.0 , upper = 0.0 )
244+ dist_results = await dist_results_rx .receive ()
245+ assert isinstance (
246+ dist_results , _power_distributing .Success
247+ ), f"Expected a success, got { dist_results } "
248+ assert dist_results .succeeded_power == Power .from_watts (- 4000.0 )
249+ assert dist_results .excess_power == Power .zero ()
250+ assert dist_results .succeeded_components == {
251+ ComponentId (8 ),
252+ ComponentId (28 ),
253+ ComponentId (38 ),
254+ }
210255
211256 set_power .reset_mock ()
212257 print (f"\n [TEST] ========== PROPOSING NEW POWER -70_000.0 ==========" )
213- print (f"[TEST] set_power call count before proposal: { set_power .call_count } " )
214- await pv_pool .propose_power (Power .from_watts (- 70_000.0 ))
258+ await pv_pool .propose_power (Power .from_watts (- 70000.0 ))
215259 print ("[TEST] Waiting for target power to be -70_000.0..." )
216- report = await self ._recv_reports_until (
260+ await self ._recv_reports_until (
217261 bounds_rx ,
218262 lambda x : x .target_power is not None
219- and x .target_power .as_watts () == - 70_000 .0 ,
263+ and x .target_power .as_watts () == - 70000 .0 ,
220264 )
221265
222- assert report is not None , "No report meeting the condition was received"
223- print (f"[TEST] Got target power report: { report } " )
224- self ._assert_report (report , power = - 70_000.0 , lower = - 80_000.0 , upper = 0.0 )
225- print (repr (report ))
266+ self ._assert_report (
267+ await bounds_rx .receive (), power = - 70000.0 , lower = - 80000.0 , upper = 0.0
268+ )
226269 await asyncio .sleep (0.0 )
227270
228271 # Components are set initial power
@@ -232,51 +275,89 @@ async def test_setting_power( # pylint: disable=too-many-statements
232275 assert set_power .call_count == 3
233276 inv_ids = mocks .microgrid .pv_inverter_ids
234277 assert sorted (set_power .call_args_list , key = lambda x : x .args [0 ]) == [
235- mocker .call (inv_ids [0 ], - 10_000 .0 ),
236- mocker .call (inv_ids [2 ], - 30_000 .0 ),
237- mocker .call (inv_ids [3 ], - 30_000 .0 ),
278+ mocker .call (inv_ids [0 ], - 10000 .0 ),
279+ mocker .call (inv_ids [2 ], - 30000 .0 ),
280+ mocker .call (inv_ids [3 ], - 30000 .0 ),
238281 ]
282+ dist_results = await dist_results_rx .receive ()
283+ assert isinstance (
284+ dist_results , _power_distributing .Success
285+ ), f"Expected a success, got { dist_results } "
286+ assert dist_results .succeeded_power == Power .from_watts (- 70000.0 )
287+ assert dist_results .excess_power == Power .zero ()
288+ assert dist_results .succeeded_components == {
289+ ComponentId (8 ),
290+ ComponentId (28 ),
291+ ComponentId (38 ),
292+ }
239293
240294 # After the failed inverter recovers, bounds should go back up and power
241295 # should be distributed to all inverters
242296 await self ._fail_pv_inverters ([], mocks )
243- report = await self ._recv_reports_until (
297+ await self ._recv_reports_until (
244298 bounds_rx ,
245- lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 100_000.0 ,
299+ lambda x : x .bounds is not None and x .bounds .lower .as_watts () == - 100000.0 ,
300+ )
301+ self ._assert_report (
302+ await bounds_rx .receive (), power = - 70000.0 , lower = - 100000.0 , upper = 0.0
246303 )
247- assert report is not None , "No report meeting the condition was received"
248- self ._assert_report (report , power = - 70_000.0 , lower = - 100_000.0 , upper = 0.0 )
304+ dist_results = await dist_results_rx .receive ()
305+ assert isinstance (
306+ dist_results , _power_distributing .Success
307+ ), f"Expected a success, got { dist_results } "
308+ assert dist_results .succeeded_power == Power .from_watts (- 70000.0 )
309+ assert dist_results .excess_power == Power .zero ()
310+ assert dist_results .succeeded_components == {
311+ ComponentId (8 ),
312+ ComponentId (18 ),
313+ ComponentId (28 ),
314+ ComponentId (38 ),
315+ }
249316
250317 set_power .reset_mock ()
251- await pv_pool .propose_power (Power .from_watts (- 90_000 .0 ))
252- report = await self ._recv_reports_until (
318+ await pv_pool .propose_power (Power .from_watts (- 200000 .0 ))
319+ await self ._recv_reports_until (
253320 bounds_rx ,
254321 lambda x : x .target_power is not None
255- and x .target_power .as_watts () == - 90_000 .0 ,
322+ and x .target_power .as_watts () == - 100000 .0 ,
256323 )
257324
258- assert report is not None , "No report meeting the condition was received"
259- self ._assert_report (report , power = - 90_000.0 , lower = - 100_000.0 , upper = 0.0 )
325+ self ._assert_report (
326+ await bounds_rx .receive (), power = - 100000.0 , lower = - 100000.0 , upper = 0.0
327+ )
260328 await asyncio .sleep (0.0 )
261329
262330 assert set_power .call_count == 4
263331 inv_ids = mocks .microgrid .pv_inverter_ids
264332 assert sorted (set_power .call_args_list , key = lambda x : x .args [0 ]) == [
265- mocker .call (inv_ids [0 ], - 10_000 .0 ),
266- mocker .call (inv_ids [1 ], - 20_000 .0 ),
267- mocker .call (inv_ids [2 ], - 30_000 .0 ),
268- mocker .call (inv_ids [3 ], - 30_000 .0 ),
333+ mocker .call (inv_ids [0 ], - 10000 .0 ),
334+ mocker .call (inv_ids [1 ], - 20000 .0 ),
335+ mocker .call (inv_ids [2 ], - 30000 .0 ),
336+ mocker .call (inv_ids [3 ], - 40000 .0 ),
269337 ]
338+ dist_results = await dist_results_rx .receive ()
339+ assert isinstance (
340+ dist_results , _power_distributing .Success
341+ ), f"Expected a success, got { dist_results } "
342+ assert dist_results .succeeded_power == Power .from_watts (- 100000.0 )
343+ assert dist_results .excess_power == Power .zero ()
344+ assert dist_results .succeeded_components == {
345+ ComponentId (8 ),
346+ ComponentId (18 ),
347+ ComponentId (28 ),
348+ ComponentId (38 ),
349+ }
270350
271351 # Setting 0 power should set all inverters to 0
272352 set_power .reset_mock ()
273353 await pv_pool .propose_power (Power .zero ())
274- report = await self ._recv_reports_until (
354+ await self ._recv_reports_until (
275355 bounds_rx ,
276356 lambda x : x .target_power is not None and x .target_power .as_watts () == 0.0 ,
277357 )
278- assert report is not None , "No report meeting the condition was received"
279- self ._assert_report (report , power = 0.0 , lower = - 100_000.0 , upper = 0.0 )
358+ self ._assert_report (
359+ await bounds_rx .receive (), power = 0.0 , lower = - 100000.0 , upper = 0.0
360+ )
280361 await asyncio .sleep (0.0 )
281362
282363 assert set_power .call_count == 4
@@ -287,6 +368,18 @@ async def test_setting_power( # pylint: disable=too-many-statements
287368 mocker .call (inv_ids [2 ], 0.0 ),
288369 mocker .call (inv_ids [3 ], 0.0 ),
289370 ]
371+ dist_results = await dist_results_rx .receive ()
372+ assert isinstance (
373+ dist_results , _power_distributing .Success
374+ ), f"Expected a success, got { dist_results } "
375+ assert dist_results .succeeded_power == Power .zero ()
376+ assert dist_results .excess_power == Power .zero ()
377+ assert dist_results .succeeded_components == {
378+ ComponentId (8 ),
379+ ComponentId (18 ),
380+ ComponentId (28 ),
381+ ComponentId (38 ),
382+ }
290383
291384 # Resetting the power should lead to default (full) power getting set for all
292385 # inverters.
@@ -307,3 +400,15 @@ async def test_setting_power( # pylint: disable=too-many-statements
307400 mocker .call (inv_ids [2 ], - 30_000.0 ),
308401 mocker .call (inv_ids [3 ], - 40_000.0 ),
309402 ]
403+ dist_results = await dist_results_rx .receive ()
404+ assert isinstance (
405+ dist_results , _power_distributing .Success
406+ ), f"Expected a success, got { dist_results } "
407+ assert dist_results .succeeded_power == Power .from_watts (- 100000.0 )
408+ assert dist_results .excess_power == Power .zero ()
409+ assert dist_results .succeeded_components == {
410+ ComponentId (8 ),
411+ ComponentId (18 ),
412+ ComponentId (28 ),
413+ ComponentId (38 ),
414+ }
0 commit comments