From 8bcb6b79b3f3b4fbce0274fe170a32b0a6f5f580 Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Mon, 11 Oct 2021 17:08:51 -0600 Subject: [PATCH 1/5] test/api: allow choosing which bootloader type --- subiquity/tests/api/test_api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py index 5deff0d4..f9a5668c 100755 --- a/subiquity/tests/api/test_api.py +++ b/subiquity/tests/api/test_api.py @@ -91,10 +91,11 @@ class Server: except aiohttp.client_exceptions.ServerDisconnectedError: return - async def spawn(self, machine_config): + async def spawn(self, machine_config, bootloader='uefi'): env = os.environ.copy() env['SUBIQUITY_REPLAY_TIMESCALE'] = '100' - cmd = 'python3 -m subiquity.cmd.server --dry-run --bootloader uefi' \ + cmd = 'python3 -m subiquity.cmd.server --dry-run' \ + + ' --bootloader ' + bootloader \ + ' --machine-config ' + machine_config cmd = cmd.split(' ') self.proc = await astart_command(cmd, env=env) @@ -114,12 +115,12 @@ class TestAPI(unittest.IsolatedAsyncioTestCase): @contextlib.asynccontextmanager -async def start_server(machine_config): +async def start_server(*args, **kwargs): conn = aiohttp.UnixConnector(path=socket_path) async with aiohttp.ClientSession(connector=conn) as session: server = Server(session) try: - await server.spawn(machine_config) + await server.spawn(*args, **kwargs) await server.poll_startup() yield server finally: From 3ce9e193ccc42f14a3223b9d604db0b1bbffdef8 Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Thu, 7 Oct 2021 11:33:14 -0600 Subject: [PATCH 2/5] test/api: split client connector from server Add a connect_server() that is a drop-in replacement for start_server(), which allows failing tests to be converted into something to connect to an already running server instance with a single line change. Useful for setting a breakpoint in the server against the test. --- subiquity/tests/api/test_api.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py index f9a5668c..7fa5dd0f 100755 --- a/subiquity/tests/api/test_api.py +++ b/subiquity/tests/api/test_api.py @@ -41,7 +41,7 @@ def json_print(json_data): print(json.dumps(json_data, indent=4)) -class Server: +class Client: def __init__(self, session): self.session = session @@ -85,6 +85,8 @@ class Server: await asyncio.sleep(.5) raise Exception('timeout on server startup') + +class Server(Client): async def server_shutdown(self, immediate=True): try: await self.post('/shutdown', mode='POWEROFF', immediate=immediate) @@ -127,6 +129,13 @@ async def start_server(*args, **kwargs): await server.close() +@contextlib.asynccontextmanager +async def connect_server(*args, **kwargs): + conn = aiohttp.UnixConnector(path=socket_path) + async with aiohttp.ClientSession(connector=conn) as session: + yield Client(session) + + class TestBitlocker(TestAPI): @timeout(5) async def test_has_bitlocker(self): From b95ba8744035f9405593ff6d9471c21d805065fb Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Thu, 7 Oct 2021 11:33:46 -0600 Subject: [PATCH 3/5] test/api: print body of incoming exceptions --- subiquity/tests/api/test_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py index 7fa5dd0f..4d45def7 100755 --- a/subiquity/tests/api/test_api.py +++ b/subiquity/tests/api/test_api.py @@ -72,9 +72,12 @@ class Client: async with self.session.request(method, f'http://a{query}', data=data, params=params) as resp: print(unquote(str(resp.url))) - resp.raise_for_status() content = await resp.content.read() - return self.loads(content.decode()) + content = content.decode() + if 400 <= resp.status: + print(content) + resp.raise_for_status() + return self.loads(content) async def poll_startup(self): for _ in range(20): From 1c5ed98430cc18edad2e81873370b9c0f0ad9175 Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Thu, 7 Oct 2021 13:12:40 -0600 Subject: [PATCH 4/5] test/api: fix hang on server shutdown If the server was fully hung, then these shutdown steps were also stuck. --- subiquity/tests/api/test_api.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py index 4d45def7..0042b4e8 100755 --- a/subiquity/tests/api/test_api.py +++ b/subiquity/tests/api/test_api.py @@ -107,12 +107,14 @@ class Server(Client): self.server_task = asyncio.create_task(self.proc.communicate()) async def close(self): - await self.server_shutdown() - await self.server_task try: - self.proc.kill() - except ProcessLookupError: - pass + await asyncio.wait_for(self.server_shutdown(), timeout=2.0) + await asyncio.wait_for(self.server_task, timeout=1.0) + finally: + try: + self.proc.kill() + except ProcessLookupError: + pass class TestAPI(unittest.IsolatedAsyncioTestCase): From 8561e431454853274a29a244ac49ce89f318b53a Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Thu, 7 Oct 2021 14:29:22 -0600 Subject: [PATCH 5/5] test/api: use a tempdir socket Moving the socket to a tempdir solves several problems * helps enable parallel testing * doesn't conflict with locally running real server instance * a test won't connect by accident to an old server instance --- subiquity/tests/api/test_api.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py index 0042b4e8..d7f3e805 100755 --- a/subiquity/tests/api/test_api.py +++ b/subiquity/tests/api/test_api.py @@ -8,15 +8,13 @@ import contextlib from functools import wraps import json import os +import tempfile import unittest from urllib.parse import unquote from subiquitycore.utils import astart_command -socket_path = '.subiquity/socket' - - def find(items, key, value): for item in items: if item[key] == value: @@ -96,11 +94,12 @@ class Server(Client): except aiohttp.client_exceptions.ServerDisconnectedError: return - async def spawn(self, machine_config, bootloader='uefi'): + async def spawn(self, socket_path, machine_config, bootloader='uefi'): env = os.environ.copy() env['SUBIQUITY_REPLAY_TIMESCALE'] = '100' cmd = 'python3 -m subiquity.cmd.server --dry-run' \ + ' --bootloader ' + bootloader \ + + ' --socket ' + socket_path \ + ' --machine-config ' + machine_config cmd = cmd.split(' ') self.proc = await astart_command(cmd, env=env) @@ -123,19 +122,22 @@ class TestAPI(unittest.IsolatedAsyncioTestCase): @contextlib.asynccontextmanager async def start_server(*args, **kwargs): - conn = aiohttp.UnixConnector(path=socket_path) - async with aiohttp.ClientSession(connector=conn) as session: - server = Server(session) - try: - await server.spawn(*args, **kwargs) - await server.poll_startup() - yield server - finally: - await server.close() + with tempfile.TemporaryDirectory() as tempdir: + socket_path = f'{tempdir}/socket' + conn = aiohttp.UnixConnector(path=socket_path) + async with aiohttp.ClientSession(connector=conn) as session: + server = Server(session) + try: + await server.spawn(socket_path, *args, **kwargs) + await server.poll_startup() + yield server + finally: + await server.close() @contextlib.asynccontextmanager async def connect_server(*args, **kwargs): + socket_path = '.subiquity/socket' conn = aiohttp.UnixConnector(path=socket_path) async with aiohttp.ClientSession(connector=conn) as session: yield Client(session)