Skip to content

Commit 94e8f60

Browse files
authored
Merge pull request #86 from matrix-org/michaelk/refactor_style_of_running
Refactor style of running
2 parents c610a0f + 80d5437 commit 94e8f60

10 files changed

+127
-30
lines changed

trafficlight/__init__.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121

2222
from quart import Quart
2323

24-
import trafficlight
2524
import trafficlight.kiwi as kiwi
2625
from trafficlight.homerunner import HomerunnerClient
2726
from trafficlight.http.adapter import (
2827
adapter_shutdown,
28+
loop_check_all_tests_done,
2929
loop_check_for_new_tests,
3030
loop_cleanup_unresponsive_adapters,
3131
)
3232
from trafficlight.internals.testsuite import TestSuite
33-
from trafficlight.store import add_testsuite
33+
from trafficlight.store import add_testsuite, get_testsuites
3434
from trafficlight.tests import load_tests
3535

3636
logger = logging.getLogger(__name__)
@@ -114,14 +114,38 @@ def create_app(test_config: Optional[Dict[str, Any]] = None) -> Quart:
114114
async def startup() -> None:
115115
app.add_background_task(loop_cleanup_unresponsive_adapters)
116116
app.add_background_task(loop_check_for_new_tests)
117+
app.add_background_task(loop_check_all_tests_done)
117118
if kiwi.kiwi_client:
118119
await kiwi.kiwi_client.start_run()
119120

120121
@app.after_serving
121122
async def shutdown() -> None:
122-
trafficlight.http.adapter.stop_background_tasks = True
123+
adapter.stop_background_tasks = True
124+
await adapter.interrupt_tasks()
123125
if kiwi.kiwi_client:
124126
await kiwi.kiwi_client.end_run()
125127
await adapter_shutdown()
126128

129+
print("Results:\n")
130+
exit_code = 0
131+
total_tests = 0
132+
successful_tests = 0
133+
for testsuite in get_testsuites():
134+
print(
135+
f"\n{testsuite.name()}: {testsuite.successes()}/{len(testsuite.test_cases)} successful"
136+
)
137+
for testcase in testsuite.test_cases:
138+
print(f" {testcase.client_types}: {testcase.state}")
139+
total_tests += 1
140+
if testcase.state != "success":
141+
exit_code = 1
142+
else:
143+
successful_tests = successful_tests + 1
144+
if testcase.state != "success" and testcase.state != "waiting":
145+
for exception in testcase.exceptions:
146+
print(exception)
147+
148+
print(f"\nOverall: {successful_tests}/{total_tests} succeeded")
149+
os._exit(exit_code)
150+
127151
return app

trafficlight/http/adapter.py

+60-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import asyncio
1616
import logging
1717
from datetime import datetime, timedelta
18-
from typing import Any, Dict, cast
18+
from typing import Any, Dict, Set, cast
1919

2020
from quart import Blueprint, current_app, request
2121
from werkzeug.utils import secure_filename
@@ -33,6 +33,7 @@
3333
get_adapter,
3434
get_adapters,
3535
get_tests,
36+
get_testsuites,
3637
remove_adapter,
3738
)
3839

@@ -63,6 +64,7 @@ async def run() -> None:
6364

6465
current_app.add_background_task(run)
6566
return
67+
6668
logger.debug(
6769
"Not enough client_types to run any test(have %s)",
6870
[str(item) for item in available_adapters],
@@ -115,21 +117,71 @@ async def cleanup_unresponsive_adapters() -> None:
115117
)
116118

117119

120+
sleeping_tasks: Set[asyncio.Future[None]] = set()
121+
122+
123+
async def interrupt_tasks() -> None:
124+
logger.info("Waking up background tasks")
125+
for task in sleeping_tasks:
126+
task.cancel()
127+
128+
129+
def should_finish_tests() -> bool:
130+
for testsuite in get_testsuites():
131+
for testcase in testsuite.test_cases:
132+
if testcase.state not in ("failed", "error", "success"):
133+
logger.info(f"Not exiting because of {testcase}")
134+
return False
135+
return True
136+
137+
138+
async def loop_check_all_tests_done() -> None:
139+
while not stop_background_tasks:
140+
logging.debug("Running check for test completion")
141+
if should_finish_tests():
142+
# do not await because shutdown() awaits all background tasks (inc this one) to shut down first.
143+
asyncio.create_task(current_app.shutdown())
144+
145+
sleep_task: asyncio.Future[None] = asyncio.ensure_future(asyncio.sleep(30))
146+
try:
147+
sleeping_tasks.add(sleep_task)
148+
await sleep_task
149+
except asyncio.CancelledError:
150+
pass # we don't mind this task being cancelled.
151+
finally:
152+
sleeping_tasks.remove(sleep_task)
153+
logging.info("Termination task shutting down")
154+
155+
118156
async def loop_cleanup_unresponsive_adapters() -> None:
119157
while not stop_background_tasks:
120-
logging.info("Running sweep for idle adapters")
158+
logging.debug("Running sweep for idle adapters")
121159
await cleanup_unresponsive_adapters()
122-
await asyncio.sleep(30)
123160

124-
logging.info("Finished sweep task")
161+
sleep_task: asyncio.Future[None] = asyncio.ensure_future(asyncio.sleep(30))
162+
try:
163+
sleeping_tasks.add(sleep_task)
164+
await sleep_task
165+
except asyncio.CancelledError:
166+
pass # we don't mind this task being cancelled.
167+
finally:
168+
sleeping_tasks.remove(sleep_task)
169+
logging.info("Sweep task shutting down")
125170

126171

127172
async def loop_check_for_new_tests() -> None:
128173
while not stop_background_tasks:
129-
logging.info("Running sweep for new tests")
174+
logging.debug("Running sweep for new tests")
130175
await check_for_new_tests()
131-
await asyncio.sleep(30)
132-
logging.info("Finished new test task")
176+
sleep_task: asyncio.Future[None] = asyncio.ensure_future(asyncio.sleep(30))
177+
try:
178+
sleeping_tasks.add(sleep_task)
179+
await sleep_task
180+
except asyncio.CancelledError:
181+
pass # we don't mind this task being cancelled.
182+
finally:
183+
sleeping_tasks.remove(sleep_task)
184+
logging.info("New test task shutting down")
133185

134186

135187
async def adapter_shutdown() -> None:
@@ -153,6 +205,7 @@ async def register(adapter_uuid: str): # type: ignore
153205
return {}
154206
adapter = Adapter(adapter_uuid, registration)
155207
add_adapter(adapter)
208+
await interrupt_tasks()
156209
return {}
157210

158211

trafficlight/internals/client.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async def recreate(self, unload_hooks: bool = False) -> None:
242242
async def reload(self) -> None:
243243
await self._perform_action({"action": "reload", "data": {}})
244244

245-
async def create_or_join(self, call_name: str) -> bool:
245+
async def create(self, call_name: str) -> bool:
246246
if self.type == self._GUEST_USER:
247247
data = await self._perform_action(
248248
{
@@ -290,6 +290,10 @@ async def get_lobby_data(self) -> LobbyData:
290290
snapshot_file = self.test_case.files[self.name + "_" + data["snapshot"]]
291291
invite_url = response["data"]["invite_url"]
292292
page_url = response["data"]["page_url"]
293+
# Strip trailing & on page URLs until https://github.com/vector-im/element-call/issues/1639 is resolved
294+
if page_url[-1] == "&":
295+
page_url = page_url[1:-1]
296+
293297
call_name = response["data"]["call_name"]
294298
lobby_data = LobbyData(
295299
video_muted=False,

trafficlight/internals/testsuite.py

+13
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,16 @@ def waiting(self) -> int:
4040
1 for tc in self.test_cases if tc.state in ("waiting", "preparing")
4141
)
4242
return 0
43+
44+
def done(self) -> bool:
45+
if self.test_cases is not None:
46+
return (
47+
sum(
48+
1
49+
for tc in self.test_cases
50+
if tc.state in ("waiting", "preparing", "running")
51+
)
52+
> 0
53+
)
54+
else:
55+
return False

trafficlight/tests/video/ec_basic_example.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ def __init__(self) -> None:
1414

1515
async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
1616
room_name = "tl_chat_" + str(datetime.now().timestamp())
17-
(alice_joined, bob_joined) = await asyncio.gather(
18-
alice.create_or_join(room_name), bob.create_or_join(room_name)
19-
)
17+
await alice.create(room_name)
18+
alice_lobby = await alice.get_lobby_data()
2019

21-
# Check only one of alice or bob joined the room (the other created it)
22-
# between two single-bit booleans, this is xor
23-
print(str(alice_joined) + " or " + str(bob_joined))
20+
await bob.join_by_url(alice_lobby.invite_url)
2421

2522
await asyncio.gather(alice.lobby_join(), bob.lobby_join())
2623
await asyncio.sleep(5)

trafficlight/tests/video/handle_invite_base.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ async def _run_test(
1515
await creator.set_video_image(VideoImage.RED)
1616
await joiner.set_video_image(VideoImage.BLUE)
1717

18-
await creator.create_or_join(room_name)
18+
await creator.create(room_name)
1919

2020
creator_lobby_data = await creator.get_lobby_data()
2121
assert_that(creator_lobby_data.call_name).is_equal_to(room_name)
2222

2323
# Now join bob to the call before alice joins the call via page_url
2424

25-
await joiner.join_by_url(creator_lobby_data.page_url)
25+
await joiner.join_by_url(creator_lobby_data.invite_url)
26+
27+
# For now; wait a little so lobby data settles, because page dynamically updates the page_url
28+
await asyncio.sleep(10)
2629

2730
joiner_lobby_data = await joiner.get_lobby_data()
2831

trafficlight/tests/video/join_call_recieve_video_test.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
2323

2424
room_name = "tl_chat_" + str(datetime.now().timestamp())
2525

26-
await asyncio.gather(
27-
alice.create_or_join(room_name), bob.create_or_join(room_name)
28-
)
26+
await alice.create(room_name)
27+
alice_lobby = await alice.get_lobby_data()
28+
29+
await bob.join_by_url(alice_lobby.invite_url)
2930
# lobby screen
3031
await asyncio.gather(alice.lobby_join(), bob.lobby_join())
3132
await asyncio.sleep(5)

trafficlight/tests/video/load_test_call_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
2525
room_name = "tl_chat_" + str(datetime.now().timestamp())
2626

2727
# Create room
28-
await alice.create_or_join(room_name)
28+
await alice.create(room_name)
2929

3030
lobby_data = await alice.get_lobby_data()
3131

trafficlight/tests/video/three_user_spotlight.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
1919

2020
room_name = "tl_chat_" + str(datetime.now().timestamp())
2121

22-
await asyncio.gather(
23-
alice.create_or_join(room_name), bob.create_or_join(room_name)
24-
)
22+
await alice.create(room_name)
23+
alice_lobby = await alice.get_lobby_data()
24+
25+
await bob.join_by_url(alice_lobby.invite_url)
2526

2627
# lobby screen
2728
await asyncio.gather(alice.lobby_join(), bob.lobby_join())

trafficlight/tests/video/two_clients_one_terminated_rejoin_test.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
2323

2424
room_name = "tl_chat_" + str(datetime.now().timestamp())
2525

26-
await asyncio.gather(
27-
alice.create_or_join(room_name), bob.create_or_join(room_name)
28-
)
26+
await alice.create(room_name)
27+
alice_lobby = await alice.get_lobby_data()
28+
29+
await bob.join_by_url(alice_lobby.invite_url)
2930

3031
await alice.set_video_image(VideoImage.BLUE)
3132
await bob.set_video_image(VideoImage.RED)
@@ -53,7 +54,7 @@ async def run(self, alice: ElementCallClient, bob: ElementCallClient) -> None:
5354

5455
await bob.set_video_image(VideoImage.GREEN)
5556

56-
await bob.create_or_join(room_name)
57+
await bob.join_by_url(alice_data.invite_url)
5758

5859
await bob.lobby_join()
5960

0 commit comments

Comments
 (0)