test-runner/hooks.py (85 lines of code) (raw):

# Copyright (c) Microsoft. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for # full license information. import asyncio import pytest import traceback import connections import functools from typing import Callable, Awaitable, Any from horton_settings import settings from horton_logging import logger def separator(message=""): return message.center(132, "-") def nodeid_to_xunit_class_and_test(nodeid): # nodeid: 'test_iothub_module.py::TestIotHubModuleClient::test_regression_connect_fails_with_corrupt_connection_string[SharedAccessKey-aGlsbGJpbGx5IHN1bnJpc2UK]' # <--becomes--> # xunit_class= 'test_iothub_module.TestIotHubModuleClient' # xunit_test 'test_regression_connect_fails_with_corrupt_connection_string[SharedAccessKey-aGlsbGJpbGx5IHN1bnJpc2UK]' parts = nodeid.split("::") xunit_class = parts[0][:-3] + "." + parts[1] xunit_test = parts[2] return (xunit_class, xunit_test) @pytest.hookimpl(tryfirst=True) def pytest_runtest_logstart(nodeid, location): (xunit_class, xunit_test) = nodeid_to_xunit_class_and_test(nodeid) logger(separator()) logger("HORTON: Entering function '{}' '{}'".format(xunit_class, xunit_test)) @pytest.hookimpl(trylast=True) def pytest_runtest_logfinish(nodeid, location): (xunit_class, xunit_test) = nodeid_to_xunit_class_and_test(nodeid) logger("HORTON: Exiting function '{}' '{}'".format(xunit_class, xunit_test)) @pytest.hookimpl(trylast=True) def pytest_runtest_teardown(item, nextitem): if settings.test_module.capabilities.checks_for_leaks: logger(separator("checking for leaks")) settings.test_module.wrapper_api.send_command_sync("check_for_leaks") logger(separator("done checking for leaks")) @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): # this hook wraps test runs. this yield runs the actual test outcome = yield try: # this will raise if the outcome was an exception outcome.get_result() logger(separator("TEST PASSED (before cleanup)")) except Exception as e: logger(separator("TEST FAILED BACAUSE OF {}".format(e))) logger(traceback.format_exc()) async def configure_system_control(): if settings.test_module.capabilities.system_control_app: try: await connections.get_adapter(settings.system_control) except Exception: print( "network control server is unavailable. Either start the server or set system_control.adapter_address to '' in _horton_settings.json" ) settings.test_module.capabilities.system_control = False if settings.system_control.adapter: await settings.system_control.adapter.reconnect_network() async def session_init(): print(separator("SESSION INIT")) await configure_system_control() async def session_teardown(): print(separator("SESSION TEARDOWN")) logger("Preforming post-session cleanup") try: pass # BKTODO except Exception: logger("Exception in cleanup") logger(traceback.format_exc()) raise finally: logger("HORTON: post-session cleanup complete") def wrap_in_sync(func: Callable[..., Awaitable[Any]], _loop: asyncio.AbstractEventLoop): """Return a sync wrapper around an async function executing it in the current event loop.""" # adapted form code in pytest_asyncio/plugin.py @functools.wraps(func) def inner(**kwargs): coro = func(**kwargs) task = asyncio.ensure_future(coro, loop=_loop) try: _loop.run_until_complete(task) except BaseException: # run_until_complete doesn't get the result from exceptions # that are not subclasses of `Exception`. Consume all # exceptions to prevent asyncio's warning from logging. if task.done() and not task.cancelled(): task.exception() raise return inner @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtestloop(session): loop = asyncio.get_event_loop_policy().new_event_loop() wrap_in_sync(session_init, loop)() try: yield finally: wrap_in_sync(session_teardown, loop)() loop.close()