Skip to content

Commit a6a2006

Browse files
Add unit tests for actor run utils
Signed-off-by: Daniel Zullo <[email protected]>
1 parent 88b8efc commit a6a2006

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

tests/actor/test_run_utils.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Simple tests for the actor runner."""
5+
6+
import asyncio
7+
import time
8+
from typing import Iterator
9+
10+
import async_solipsism
11+
import pytest
12+
import time_machine
13+
14+
from frequenz.sdk.actor import actor, run
15+
16+
17+
@pytest.fixture(autouse=True)
18+
def event_loop() -> Iterator[async_solipsism.EventLoop]:
19+
"""Replace the loop with one that doesn't interact with the outside world."""
20+
loop = async_solipsism.EventLoop()
21+
yield loop
22+
loop.close()
23+
24+
25+
@pytest.fixture
26+
def fake_time() -> Iterator[time_machine.Coordinates]:
27+
"""Replace real time with a time machine that doesn't automatically tick."""
28+
with time_machine.travel(0, tick=False) as traveller:
29+
yield traveller
30+
31+
32+
@actor
33+
class FaultyActor:
34+
"""A test faulty actor."""
35+
36+
def __init__(self, name: str) -> None:
37+
"""Initialize the faulty actor.
38+
39+
Args:
40+
name: the name of the faulty actor.
41+
"""
42+
self.name = name
43+
self.is_cancelled = False
44+
45+
async def run(self) -> None:
46+
"""Run the faulty actor.
47+
48+
Raises:
49+
CancelledError: the exception causes the actor to be cancelled
50+
"""
51+
self.is_cancelled = True
52+
raise asyncio.CancelledError(f"Faulty Actor {self.name} failed")
53+
54+
55+
@actor
56+
class SleepyActor:
57+
"""A test actor that sleeps a short time."""
58+
59+
def __init__(self, name: str, sleep_duration: float) -> None:
60+
"""Initialize the sleepy actor.
61+
62+
Args:
63+
name: the name of the sleepy actor.
64+
sleep_duration: the virtual duration to sleep while running.
65+
"""
66+
self.name = name
67+
self.sleep_duration = sleep_duration
68+
self.is_joined = False
69+
70+
async def run(self) -> None:
71+
"""Run the sleepy actor."""
72+
while time.time() < self.sleep_duration:
73+
await asyncio.sleep(0.1)
74+
75+
self.is_joined = True
76+
77+
78+
# pylint: disable=redefined-outer-name
79+
async def test_all_actors_done(fake_time: time_machine.Coordinates) -> None:
80+
"""Test the completion of all actors."""
81+
82+
sleepy_actor_1 = SleepyActor("sleepy_actor_1", sleep_duration=1.0)
83+
sleepy_actor_2 = SleepyActor("sleepy_actor_2", sleep_duration=2.0)
84+
85+
test_task = asyncio.create_task(run(sleepy_actor_1, sleepy_actor_2))
86+
87+
sleep_duration = time.time()
88+
89+
assert sleep_duration == 0
90+
assert sleepy_actor_1.is_joined is False
91+
assert sleepy_actor_2.is_joined is False
92+
93+
while not test_task.done():
94+
if sleep_duration < 1:
95+
assert sleepy_actor_1.is_joined is False
96+
assert sleepy_actor_2.is_joined is False
97+
elif sleep_duration < 2:
98+
assert sleepy_actor_1.is_joined is True
99+
assert sleepy_actor_2.is_joined is False
100+
elif sleep_duration == 2:
101+
assert sleepy_actor_1.is_joined is True
102+
assert sleepy_actor_2.is_joined is True
103+
104+
fake_time.shift(0.5)
105+
sleep_duration = time.time()
106+
await asyncio.sleep(1)
107+
108+
assert sleepy_actor_1.is_joined
109+
assert sleepy_actor_2.is_joined
110+
111+
112+
async def test_actors_cancelled() -> None:
113+
"""Test the completion of actors being cancelled."""
114+
115+
faulty_actors = [FaultyActor(f"faulty_actor_{idx}") for idx in range(5)]
116+
117+
await asyncio.wait_for(run(*faulty_actors), timeout=1.0)
118+
119+
for faulty_actor in faulty_actors:
120+
assert faulty_actor.is_cancelled

0 commit comments

Comments
 (0)