-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test(api): Replace pytest-aiohttp with pytest-asyncio for running async tests #9981
Changes from all commits
443f61d
8ec1607
37402db
985ba55
2a9efef
fa4de8e
ac53e0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
import aionotify # type: ignore[import] | ||
except (OSError, ModuleNotFoundError): | ||
aionotify = None | ||
import asyncio | ||
import os | ||
import io | ||
import json | ||
|
@@ -36,16 +37,6 @@ | |
Protocol = namedtuple("Protocol", ["text", "filename", "filelike"]) | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def asyncio_loop_exception_handler(loop): | ||
def exception_handler(loop, context): | ||
pytest.fail(str(context)) | ||
|
||
loop.set_exception_handler(exception_handler) | ||
yield | ||
loop.set_exception_handler(None) | ||
|
||
|
||
Comment on lines
-39
to
-48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the intent of this fixture was to fail any test when the subject accidentally created an orphan task, and that task raised an uncaught exception. I couldn't figure out a robust way to reimplement this, since we no longer have the Note that it would not be sufficient to do: @pytest.fixture(autouse=True)
async def asyncio_loop_exception_handler():
loop = asyncio.get_running_loop()
def exception_handler(loop, context):
pytest.fail(str(context))
loop.set_exception_handler(exception_handler)
yield
loop.set_exception_handler(None) For 2 reasons:
(Actually, now that I'm thinking about this, these might have been problems with the fixture under Honestly, I'm not too bummed about removing this? It's nice to catch these errors if we can, but really, we should never be creating orphan tasks in the first place. And the easiest way to enforce that is often by enforcing good structural practices, rather than tests. Sort of like how we know it's important to use |
||
@pytest.fixture | ||
def ot_config_tempdir(tmp_path: pathlib.Path) -> Generator[pathlib.Path, None, None]: | ||
os.environ["OT_API_CONFIG_DIR"] = str(tmp_path) | ||
|
@@ -153,7 +144,7 @@ def virtual_smoothie_env(monkeypatch): | |
@pytest.fixture( | ||
params=["ot2", "ot3"], | ||
) | ||
async def machine_variant_ffs(request, loop): | ||
async def machine_variant_ffs(request): | ||
if request.node.get_closest_marker("ot3_only") and request.param == "ot2": | ||
pytest.skip() | ||
if request.node.get_closest_marker("ot2_only") and request.param == "ot3": | ||
|
@@ -179,12 +170,14 @@ async def _build_ot2_hw() -> AsyncIterator[ThreadManager[HardwareControlAPI]]: | |
yield hw_sim | ||
finally: | ||
config.robot_configs.clear() | ||
for m in hw_sim.attached_modules: | ||
await m.cleanup() | ||
hw_sim.set_config(old_config) | ||
hw_sim.clean_up() | ||
|
||
|
||
@pytest.fixture | ||
async def ot2_hardware(request, loop, virtual_smoothie_env): | ||
async def ot2_hardware(request, virtual_smoothie_env): | ||
async for hw in _build_ot2_hw(): | ||
yield hw | ||
|
||
|
@@ -198,12 +191,14 @@ async def _build_ot3_hw() -> AsyncIterator[ThreadManager[HardwareControlAPI]]: | |
yield hw_sim | ||
finally: | ||
config.robot_configs.clear() | ||
for m in hw_sim.attached_modules: | ||
await m.cleanup() | ||
hw_sim.set_config(old_config) | ||
hw_sim.clean_up() | ||
|
||
|
||
@pytest.fixture | ||
async def ot3_hardware(request, loop, enable_ot3_hardware_controller): | ||
async def ot3_hardware(request, enable_ot3_hardware_controller): | ||
# this is from the command line parameters added in root conftest | ||
if request.config.getoption("--ot2-only"): | ||
pytest.skip("testing only ot2") | ||
|
@@ -217,7 +212,7 @@ async def ot3_hardware(request, loop, enable_ot3_hardware_controller): | |
params=[lambda: _build_ot2_hw, lambda: _build_ot3_hw], | ||
ids=["ot2", "ot3"], | ||
) | ||
async def hardware(request, loop, virtual_smoothie_env): | ||
async def hardware(request, virtual_smoothie_env): | ||
if request.node.get_closest_marker("ot2_only") and request.param() == _build_ot3_hw: | ||
pytest.skip() | ||
if request.node.get_closest_marker("ot3_only") and request.param() == _build_ot2_hw: | ||
|
@@ -239,12 +234,18 @@ async def hardware(request, loop, virtual_smoothie_env): | |
) | ||
|
||
|
||
# Async because ProtocolContext.__init__() needs an event loop, | ||
# so this fixture needs to run in an event loop. | ||
@pytest.fixture | ||
async def ctx(loop, hardware) -> ProtocolContext: | ||
return ProtocolContext( | ||
async def ctx(hardware) -> AsyncIterator[ProtocolContext]: | ||
c = ProtocolContext( | ||
implementation=ProtocolContextImplementation(sync_hardware=hardware.sync), | ||
loop=loop, | ||
loop=asyncio.get_running_loop(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how I feel about this, but seems like we could use the |
||
) | ||
yield c | ||
# Manually clean up all the modules. | ||
for m in c.loaded_modules.items(): | ||
m[1]._module.cleanup() | ||
|
||
|
||
@pytest.fixture | ||
|
@@ -298,8 +299,8 @@ async def mock_connect(obj, port=None): | |
|
||
|
||
@pytest.fixture | ||
async def hardware_api(loop, is_robot): | ||
hw_api = await API.build_hardware_simulator(loop=loop) | ||
async def hardware_api(is_robot): | ||
hw_api = await API.build_hardware_simulator(loop=asyncio.get_running_loop()) | ||
return hw_api | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,14 +30,12 @@ def mock_serial( | |
|
||
|
||
@pytest.fixture | ||
async def subject( | ||
loop: asyncio.AbstractEventLoop, mock_serial: MagicMock | ||
) -> AsyncSerial: | ||
async def subject(mock_serial: MagicMock) -> AsyncSerial: | ||
"""The test subject.""" | ||
return AsyncSerial( | ||
serial=mock_serial, | ||
executor=ThreadPoolExecutor(), | ||
loop=loop, | ||
loop=asyncio.get_running_loop(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use |
||
reset_buffer_before_write=False, | ||
) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,10 @@ def ack() -> str: | |
return "ack" | ||
|
||
|
||
# Async because SerialConnection.__init__() needs an event loop, | ||
# so this fixture needs to run in an event loop. | ||
@pytest.fixture | ||
def subject(mock_serial_port: AsyncMock, ack: str) -> SerialConnection: | ||
async def subject(mock_serial_port: AsyncMock, ack: str) -> SerialConnection: | ||
"""Create the test subject.""" | ||
SerialConnection.RETRY_WAIT_TIME = 0 # type: ignore[attr-defined] | ||
return SerialConnection( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we should use the async |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,10 @@ def mock_serial_connection() -> AsyncMock: | |
return AsyncMock(spec=AsyncSerial) | ||
|
||
|
||
# Async because SmoothieConnection.__init__() needs an event loop, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about using |
||
# so this fixture needs to run in an event loop. | ||
@pytest.fixture | ||
def subject(mock_serial_connection: AsyncMock) -> SmoothieConnection: | ||
async def subject(mock_serial_connection: AsyncMock) -> SmoothieConnection: | ||
"""The test subject.""" | ||
return SmoothieConnection( | ||
serial=mock_serial_connection, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,15 +10,14 @@ | |
|
||
@pytest.fixture | ||
async def magdeck( | ||
loop: asyncio.BaseEventLoop, | ||
emulation_app: Iterator[None], | ||
emulator_settings: Settings, | ||
) -> AsyncIterator[MagDeck]: | ||
module = await MagDeck.build( | ||
port=f"socket://127.0.0.1:{emulator_settings.magdeck_proxy.driver_port}", | ||
execution_manager=AsyncMock(), | ||
usb_port=USBPort(name="", port_number=1, device_path="", hub=1), | ||
loop=loop, | ||
loop=asyncio.get_running_loop(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to suggest omitting the Calling this out since |
||
) | ||
yield module | ||
await module.cleanup() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://github.com/pytest-dev/pytest-asyncio#modes.
We alternatively could have used strict mode, but it would have created a lot of boilerplate.