diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f5705961..ecdbae1f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: runs-on: ["ubuntu-latest", "windows-latest"] # can add macos-latest - python-version: ["3.10", "3.11"] # 3.12 should be added when p4p is updated + python-version: ["3.10","3.11","3.12"] include: # Include one that runs in the dev environment - runs-on: "ubuntu-latest" diff --git a/pyproject.toml b/pyproject.toml index ef4834750..0b71859b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] description = "Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango" dependencies = [ @@ -98,8 +99,12 @@ addopts = """ --doctest-glob="*.rst" --doctest-glob="*.md" --ignore=docs/examples --ignore=src/ophyd_async/epics/signal.py """ -# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings -filterwarnings = "error" +# "error" for https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings +# "tango" for https://github.com/bluesky/ophyd-async/issues/681 +filterwarnings = """ +error +ignore::DeprecationWarning:tango.asyncio_executor: +""" # Doctest python code in docs, python code in src docstrings, test functions in tests testpaths = "docs src tests" log_format = "%(asctime)s,%(msecs)03d %(levelname)s (%(threadName)s) %(message)s" diff --git a/tests/conftest.py b/tests/conftest.py index ce6221841..871162d99 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,7 +89,9 @@ def _error_and_kill_pending_tasks( unfinished_tasks = { task for task in asyncio.all_tasks(loop) - if task.get_coro().__name__ not in _ALLOWED_PYTEST_TASKS and not task.done() + if (coro := task.get_coro()) is not None + and coro.__name__ not in _ALLOWED_PYTEST_TASKS + and not task.done() } for task in unfinished_tasks: task.cancel() @@ -113,15 +115,22 @@ def fail_test_on_unclosed_tasks(request: FixtureRequest): by the end of the test. """ - fail_count = request.session.testsfailed - loop = asyncio.get_event_loop() - loop.set_debug(True) + try: + fail_count = request.session.testsfailed + loop = asyncio.get_running_loop() + + loop.set_debug(True) - request.addfinalizer( - lambda: _error_and_kill_pending_tasks( - loop, request.node.name, request.session.testsfailed == fail_count + request.addfinalizer( + lambda: _error_and_kill_pending_tasks( + loop, request.node.name, request.session.testsfailed == fail_count + ) ) - ) + # Once https://github.com/bluesky/ophyd-async/issues/683 + # is finished we can remove this try, except. + except RuntimeError as error: + if str(error) != "no running event loop": + raise error @pytest.fixture(scope="function") diff --git a/tests/core/test_mock_signal_backend.py b/tests/core/test_mock_signal_backend.py index 046f0097f..f44e8ef5f 100644 --- a/tests/core/test_mock_signal_backend.py +++ b/tests/core/test_mock_signal_backend.py @@ -185,6 +185,7 @@ async def test_blocks_during_put(mock_signals): with mock_puts_blocked(signal1, signal2): status1 = signal1.set("second_value", wait=True, timeout=None) status2 = signal2.set("second_value", wait=True, timeout=None) + await asyncio.sleep(0.1) assert await signal1.get_value() == "second_value" assert await signal2.get_value() == "second_value" assert not status1.done diff --git a/tests/core/test_readable.py b/tests/core/test_readable.py index 8b39fb404..d0c781d19 100644 --- a/tests/core/test_readable.py +++ b/tests/core/test_readable.py @@ -32,13 +32,13 @@ def test_standard_readable_hints(): assert sr.hints == {} - hint1 = MagicMock() + hint1 = MagicMock(spec=HasHints) hint1.hints = {"fields": ["abc"], "dimensions": [(["f1", "f2"], "s1")]} - hint2 = MagicMock() + hint2 = MagicMock(spec=HasHints) hint2.hints = {"fields": ["def", "ghi"]} - hint3 = MagicMock() + hint3 = MagicMock(spec=HasHints) hint3.hints = {"fields": ["jkl"], "gridding": "rectilinear_nonsequential"} sr.add_readables([hint1, hint2, hint3]) @@ -53,10 +53,10 @@ def test_standard_readable_hints(): def test_standard_readable_hints_raises_when_overriding_string_literal(): sr = StandardReadable() - hint1 = MagicMock() + hint1 = MagicMock(spec=HasHints) hint1.hints = {"gridding": "rectilinear_nonsequential"} - hint2 = MagicMock() + hint2 = MagicMock(spec=HasHints) hint2.hints = {"gridding": "a different string"} sr._has_hints = ( @@ -71,10 +71,10 @@ def test_standard_readable_hints_raises_when_overriding_string_literal(): def test_standard_readable_hints_raises_when_overriding_sequence(): sr = StandardReadable() - hint1 = MagicMock() + hint1 = MagicMock(spec=HasHints) hint1.hints = {"fields": ["field1", "field2"]} - hint2 = MagicMock() + hint2 = MagicMock(spec=HasHints) hint2.hints = {"fields": ["field2"]} sr._has_hints = ( @@ -90,7 +90,7 @@ def test_standard_readable_hints_raises_when_overriding_sequence(): def test_standard_readable_hints_invalid_types(invalid_type): sr = StandardReadable() - hint1 = MagicMock() + hint1 = MagicMock(spec=HasHints) hint1.hints = {"test": invalid_type} sr._has_hints = (hint1,) diff --git a/tests/epics/adcore/test_drivers.py b/tests/epics/adcore/test_drivers.py index f2b2dcadc..e04ee9ff1 100644 --- a/tests/epics/adcore/test_drivers.py +++ b/tests/epics/adcore/test_drivers.py @@ -90,10 +90,12 @@ async def wait_then_fail(): await asyncio.sleep(0) set_mock_value(driver.detector_state, adcore.DetectorState.DISCONNECTED) + await wait_then_fail() + acquiring = await adcore.start_acquiring_driver_and_ensure_status( driver, timeout=0.1 ) - await wait_then_fail() - - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Final detector state DetectorState.DISCONNECTED" + ): await acquiring diff --git a/tests/sim/demo/test_sim_motor.py b/tests/sim/demo/test_sim_motor.py index 9a8ac75a6..d879c717b 100644 --- a/tests/sim/demo/test_sim_motor.py +++ b/tests/sim/demo/test_sim_motor.py @@ -1,6 +1,7 @@ import asyncio import time +import pytest from bluesky.plans import spiral_square from bluesky.run_engine import RunEngine @@ -49,6 +50,7 @@ async def test_stop(): new_pos = await m1.user_readback.get_value() assert new_pos < 10 assert new_pos >= 0.1 - # move should not be successful as we stopped it - assert move_status.done + assert not move_status.success + with pytest.raises(RuntimeError, match="Motor was stopped"): + await move_status