Skip to content

Commit 202c504

Browse files
authored
Increment port in microgrid API client tests (#699)
This is a hack to avoid the inherent flakiness of the approach of using a real GRPC (mock) server. The server seems to stay alive for a short time after the test is finished, which causes the next test to fail because the port is already in use. This also improves the test a bit by using an async context manager create the client and server, and stop the server automatically when the test is finished. Related to #662.
2 parents e48809f + 0d6be38 commit 202c504

File tree

1 file changed

+43
-110
lines changed

1 file changed

+43
-110
lines changed

tests/microgrid/test_client.py

Lines changed: 43 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""Tests for the microgrid client thin wrapper."""
55

66
import asyncio
7+
import contextlib
8+
from collections.abc import AsyncIterator
79

810
import grpc
911
import pytest
@@ -33,27 +35,43 @@
3335
# pylint: disable=missing-class-docstring,no-member
3436

3537

36-
class TestMicrogridGrpcClient:
37-
"""Tests for the microgrid client thin wrapper."""
38+
# This incrementing port is a hack to avoid the inherent flakiness of the approach of
39+
# using a real GRPC (mock) server. The server seems to stay alive for a short time after
40+
# the test is finished, which causes the next test to fail because the port is already
41+
# in use.
42+
# This is a workaround until we have a better solution.
43+
# See https://github.com/frequenz-floss/frequenz-sdk-python/issues/662
44+
_CURRENT_PORT: int = 57897
3845

39-
@staticmethod
40-
def create_client(port: int) -> client.MicrogridApiClient:
41-
"""Create a client for the mock API server."""
42-
return client.MicrogridGrpcClient(
43-
grpc.aio.insecure_channel(f"[::]:{port}"),
44-
f"[::]:{port}",
45-
retry_spec=LinearBackoff(interval=0.0, jitter=0.05),
46-
)
4746

48-
async def test_components(self) -> None:
49-
"""Test the components() method."""
47+
@contextlib.asynccontextmanager
48+
async def _gprc_server(
49+
servicer: mock_api.MockMicrogridServicer | None = None,
50+
) -> AsyncIterator[tuple[mock_api.MockMicrogridServicer, client.MicrogridApiClient]]:
51+
global _CURRENT_PORT # pylint: disable=global-statement
52+
port = _CURRENT_PORT
53+
_CURRENT_PORT += 1
54+
if servicer is None:
5055
servicer = mock_api.MockMicrogridServicer()
51-
server = mock_api.MockGrpcServer(servicer, port=57899)
52-
await server.start()
56+
server = mock_api.MockGrpcServer(servicer, port=port)
57+
microgrid = client.MicrogridGrpcClient(
58+
grpc.aio.insecure_channel(f"[::]:{port}"),
59+
f"[::]:{port}",
60+
retry_spec=LinearBackoff(interval=0.0, jitter=0.05),
61+
)
62+
await server.start()
63+
try:
64+
yield servicer, microgrid
65+
finally:
66+
assert await server.graceful_shutdown()
67+
5368

54-
try:
55-
microgrid = self.create_client(57899)
69+
class TestMicrogridGrpcClient:
70+
"""Tests for the microgrid client thin wrapper."""
5671

72+
async def test_components(self) -> None:
73+
"""Test the components() method."""
74+
async with _gprc_server() as (servicer, microgrid):
5775
assert set(await microgrid.components()) == set()
5876

5977
servicer.add_component(
@@ -165,18 +183,9 @@ async def test_components(self) -> None:
165183
Component(999, ComponentCategory.BATTERY),
166184
}
167185

168-
finally:
169-
assert await server.graceful_shutdown()
170-
171186
async def test_connections(self) -> None:
172187
"""Test the connections() method."""
173-
servicer = mock_api.MockMicrogridServicer()
174-
server = mock_api.MockGrpcServer(servicer, port=57898)
175-
await server.start()
176-
177-
try:
178-
microgrid = self.create_client(57898)
179-
188+
async with _gprc_server() as (servicer, microgrid):
180189
assert set(await microgrid.connections()) == set()
181190

182191
servicer.add_connection(0, 0)
@@ -320,9 +329,6 @@ async def test_connections(self) -> None:
320329
Connection(5, 7),
321330
}
322331

323-
finally:
324-
assert await server.graceful_shutdown()
325-
326332
async def test_bad_connections(self) -> None:
327333
"""Validate that the client does not apply connection filters itself."""
328334

@@ -341,13 +347,7 @@ def ListAllComponents(
341347
) -> microgrid_pb.ComponentList:
342348
return microgrid_pb.ComponentList(components=self._components)
343349

344-
servicer = BadServicer()
345-
server = mock_api.MockGrpcServer(servicer, port=57897)
346-
await server.start()
347-
348-
try:
349-
microgrid = self.create_client(57897)
350-
350+
async with _gprc_server(BadServicer()) as (servicer, microgrid):
351351
assert list(await microgrid.connections()) == []
352352
for component_id in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
353353
servicer.add_component(
@@ -391,18 +391,9 @@ def ListAllComponents(
391391
== unfiltered
392392
)
393393

394-
finally:
395-
assert await server.graceful_shutdown()
396-
397394
async def test_meter_data(self) -> None:
398395
"""Test the meter_data() method."""
399-
servicer = mock_api.MockMicrogridServicer()
400-
server = mock_api.MockGrpcServer(servicer, port=57899)
401-
await server.start()
402-
403-
try:
404-
microgrid = self.create_client(57899)
405-
396+
async with _gprc_server() as (servicer, microgrid):
406397
servicer.add_component(
407398
83, components_pb.ComponentCategory.COMPONENT_CATEGORY_METER
408399
)
@@ -420,22 +411,13 @@ async def test_meter_data(self) -> None:
420411
peekable = (await microgrid.meter_data(83)).into_peekable()
421412
await asyncio.sleep(0.2)
422413

423-
finally:
424-
assert await server.graceful_shutdown()
425-
426414
latest = peekable.peek()
427415
assert isinstance(latest, MeterData)
428416
assert latest.component_id == 83
429417

430418
async def test_battery_data(self) -> None:
431419
"""Test the battery_data() method."""
432-
servicer = mock_api.MockMicrogridServicer()
433-
server = mock_api.MockGrpcServer(servicer, port=57899)
434-
await server.start()
435-
436-
try:
437-
microgrid = self.create_client(57899)
438-
420+
async with _gprc_server() as (servicer, microgrid):
439421
servicer.add_component(
440422
83, components_pb.ComponentCategory.COMPONENT_CATEGORY_BATTERY
441423
)
@@ -453,22 +435,13 @@ async def test_battery_data(self) -> None:
453435
peekable = (await microgrid.battery_data(83)).into_peekable()
454436
await asyncio.sleep(0.2)
455437

456-
finally:
457-
assert await server.graceful_shutdown()
458-
459438
latest = peekable.peek()
460439
assert isinstance(latest, BatteryData)
461440
assert latest.component_id == 83
462441

463442
async def test_inverter_data(self) -> None:
464443
"""Test the inverter_data() method."""
465-
servicer = mock_api.MockMicrogridServicer()
466-
server = mock_api.MockGrpcServer(servicer, port=57899)
467-
await server.start()
468-
469-
try:
470-
microgrid = self.create_client(57899)
471-
444+
async with _gprc_server() as (servicer, microgrid):
472445
servicer.add_component(
473446
83, components_pb.ComponentCategory.COMPONENT_CATEGORY_INVERTER
474447
)
@@ -486,22 +459,13 @@ async def test_inverter_data(self) -> None:
486459
peekable = (await microgrid.inverter_data(83)).into_peekable()
487460
await asyncio.sleep(0.2)
488461

489-
finally:
490-
assert await server.graceful_shutdown()
491-
492462
latest = peekable.peek()
493463
assert isinstance(latest, InverterData)
494464
assert latest.component_id == 83
495465

496466
async def test_ev_charger_data(self) -> None:
497467
"""Test the ev_charger_data() method."""
498-
servicer = mock_api.MockMicrogridServicer()
499-
server = mock_api.MockGrpcServer(servicer, port=57899)
500-
await server.start()
501-
502-
try:
503-
microgrid = self.create_client(57899)
504-
468+
async with _gprc_server() as (servicer, microgrid):
505469
servicer.add_component(
506470
83, components_pb.ComponentCategory.COMPONENT_CATEGORY_EV_CHARGER
507471
)
@@ -519,23 +483,13 @@ async def test_ev_charger_data(self) -> None:
519483
peekable = (await microgrid.ev_charger_data(83)).into_peekable()
520484
await asyncio.sleep(0.2)
521485

522-
finally:
523-
assert await server.graceful_shutdown()
524-
525486
latest = peekable.peek()
526487
assert isinstance(latest, EVChargerData)
527488
assert latest.component_id == 83
528489

529490
async def test_charge(self) -> None:
530491
"""Check if charge is able to charge component."""
531-
servicer = mock_api.MockMicrogridServicer()
532-
server = mock_api.MockGrpcServer(servicer, port=57899)
533-
534-
await server.start()
535-
536-
try:
537-
microgrid = self.create_client(57899)
538-
492+
async with _gprc_server() as (servicer, microgrid):
539493
servicer.add_component(
540494
83, components_pb.ComponentCategory.COMPONENT_CATEGORY_METER
541495
)
@@ -546,19 +500,9 @@ async def test_charge(self) -> None:
546500
assert servicer.latest_power.component_id == 83
547501
assert servicer.latest_power.power == 12
548502

549-
finally:
550-
assert await server.graceful_shutdown()
551-
552503
async def test_discharge(self) -> None:
553504
"""Check if discharge is able to discharge component."""
554-
servicer = mock_api.MockMicrogridServicer()
555-
server = mock_api.MockGrpcServer(servicer, port=57899)
556-
557-
await server.start()
558-
559-
try:
560-
microgrid = self.create_client(57899)
561-
505+
async with _gprc_server() as (servicer, microgrid):
562506
servicer.add_component(
563507
73, components_pb.ComponentCategory.COMPONENT_CATEGORY_METER
564508
)
@@ -568,18 +512,10 @@ async def test_discharge(self) -> None:
568512
assert servicer.latest_power is not None
569513
assert servicer.latest_power.component_id == 73
570514
assert servicer.latest_power.power == -15
571-
finally:
572-
assert await server.graceful_shutdown()
573515

574516
async def test_set_bounds(self) -> None:
575517
"""Check if set_bounds is able to set bounds for component."""
576-
servicer = mock_api.MockMicrogridServicer()
577-
server = mock_api.MockGrpcServer(servicer, port=57899)
578-
await server.start()
579-
580-
try:
581-
microgrid = self.create_client(57899)
582-
518+
async with _gprc_server() as (servicer, microgrid):
583519
servicer.add_component(
584520
38, components_pb.ComponentCategory.COMPONENT_CATEGORY_INVERTER
585521
)
@@ -599,9 +535,6 @@ async def test_set_bounds(self) -> None:
599535
await microgrid.set_bounds(cid, -10.0, 2.0)
600536
await asyncio.sleep(0.1)
601537

602-
finally:
603-
assert await server.graceful_shutdown()
604-
605538
assert len(expected_bounds) == len(servicer.get_bounds())
606539

607540
def sort_key(

0 commit comments

Comments
 (0)