From 029aeaeb43dcd67c48a13be99e7f20356a6e69f3 Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 08:22:51 -0700 Subject: [PATCH 1/8] Bump pytest requirement to >=9.0.3 --- pytest-testslide/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest-testslide/requirements.txt b/pytest-testslide/requirements.txt index b69b75d..a846a97 100644 --- a/pytest-testslide/requirements.txt +++ b/pytest-testslide/requirements.txt @@ -1,2 +1,2 @@ -pytest~=7.3 +pytest>=9.0.3 pytest-asyncio>=0.14.0 From b7359c6fd975d81549a391eb7539b573e66194bb Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 08:48:49 -0700 Subject: [PATCH 2/8] Update test_prints_exceptions_with_cause for Pygments whitespace tokenization Pygments 2.20 emits the inter-token space before an underlined identifier as its own Whitespace token, wrapping it in default-color ANSI codes (\x1b[37m \x1b[39;49;00m). Update the expected literal accordingly. --- tests/cli_unittest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cli_unittest.py b/tests/cli_unittest.py index 38ad955..2ed1c91 100644 --- a/tests/cli_unittest.py +++ b/tests/cli_unittest.py @@ -285,10 +285,10 @@ def test_prints_exceptions_with_cause(self): expected_return_code=1, expected_in_stdout=( ' File \x1b[36m"tests/sample_tests.py"\x1b[39;49;00m, line \x1b[94m76\x1b[39;49;00m, in test_failing\x1b[37m\x1b[39;49;00m\r\n' - ' \x1b[37m \x1b[39;49;00m\x1b[94mraise\x1b[39;49;00m \x1b[36mAssertionError\x1b[39;49;00m(\x1b[33m"\x1b[39;49;00m\x1b[33mThird\x1b[39;49;00m\x1b[33m"\x1b[39;49;00m) \x1b[94mfrom\x1b[39;49;00m \x1b[04m\x1b[36mcause\x1b[39;49;00m\x1b[37m\x1b[39;49;00m\r\n' + ' \x1b[37m \x1b[39;49;00m\x1b[94mraise\x1b[39;49;00m \x1b[36mAssertionError\x1b[39;49;00m(\x1b[33m"\x1b[39;49;00m\x1b[33mThird\x1b[39;49;00m\x1b[33m"\x1b[39;49;00m) \x1b[94mfrom\x1b[39;49;00m\x1b[37m \x1b[39;49;00m\x1b[04m\x1b[36mcause\x1b[39;49;00m\x1b[37m\x1b[39;49;00m\r\n' "\x1b[0m\x1b[31m Caused by \x1b[0m\x1b[0m\x1b[31mAssertionError: Second\x1b[0m\r\n" ' File \x1b[36m"tests/sample_tests.py"\x1b[39;49;00m, line \x1b[94m74\x1b[39;49;00m, in test_failing\x1b[37m\x1b[39;49;00m\r\n' - ' \x1b[37m \x1b[39;49;00m\x1b[94mraise\x1b[39;49;00m \x1b[36mAssertionError\x1b[39;49;00m(\x1b[33m"\x1b[39;49;00m\x1b[33mSecond\x1b[39;49;00m\x1b[33m"\x1b[39;49;00m) \x1b[94mfrom\x1b[39;49;00m \x1b[04m\x1b[36mcause\x1b[39;49;00m\x1b[37m\x1b[39;49;00m\r\n' + ' \x1b[37m \x1b[39;49;00m\x1b[94mraise\x1b[39;49;00m \x1b[36mAssertionError\x1b[39;49;00m(\x1b[33m"\x1b[39;49;00m\x1b[33mSecond\x1b[39;49;00m\x1b[33m"\x1b[39;49;00m) \x1b[94mfrom\x1b[39;49;00m\x1b[37m \x1b[39;49;00m\x1b[04m\x1b[36mcause\x1b[39;49;00m\x1b[37m\x1b[39;49;00m\r\n' "\x1b[0m\x1b[31m Caused by \x1b[0m\x1b[0m\x1b[31mAssertionError: First\x1b[0m\r\n" ' File \x1b[36m"tests/sample_tests.py"\x1b[39;49;00m, line \x1b[94m72\x1b[39;49;00m, in test_failing\x1b[37m\x1b[39;49;00m\r\n' ' \x1b[37m \x1b[39;49;00m\x1b[94mraise\x1b[39;49;00m \x1b[36mAssertionError\x1b[39;49;00m(\x1b[33m"\x1b[39;49;00m\x1b[33mFirst\x1b[39;49;00m\x1b[33m"\x1b[39;49;00m)\x1b[37m\x1b[39;49;00m\r\n' From 96f789c05c94087da8e1db50f9b969d1a5b037ad Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 08:55:19 -0700 Subject: [PATCH 3/8] Update typeguard regex for PEP 585 type display typeguard 4.x renders typing.List[str] as list[str] in error messages. Update assertRaisesRegex pattern in raises_TypeCheckError_when_returning_coroutine_instance to match. --- tests/mock_async_callable_testslide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_async_callable_testslide.py b/tests/mock_async_callable_testslide.py index 18c1ed9..161ee4a 100644 --- a/tests/mock_async_callable_testslide.py +++ b/tests/mock_async_callable_testslide.py @@ -176,7 +176,7 @@ async def raises_TypeCheckError_when_returning_coroutine_instance(self): with self.assertRaisesRegex( TypeCheckError, - r"^type of return must be typing.List\[str\]; got .+(asyncio|coroutine)", + r"^type of return must be list\[str\]; got .+(asyncio|coroutine)", ): await self.callable_target(*self.call_args, **self.call_kwargs) From 8774c89ea78d62bb70df5d4f56a0781ead364fa2 Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 09:08:55 -0700 Subject: [PATCH 4/8] Add pytest.ini with pythonpath for in-tree imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows `pytest pytest-testslide/tests` to run from the repo root without needing to pre-set PYTHONPATH. Previously only the Makefile's pytest_tests target worked, since it exported PYTHONPATH=pytest-testslide:. — the bare command failed because the inner pytester run could not import pytest_testslide or testslide (neither is installed in the venv; both are sourced in-tree). --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..0fb60a7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . pytest-testslide From cadd03dfc461cd30078fc67768760aeefb0338cc Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 09:12:32 -0700 Subject: [PATCH 5/8] Cast mock to Mock in _extract_mock_template mypy cannot narrow Union[Mock, StrictMock] through isinstance against a runtime-looked-up class (mock_class comes from MOCK_TEMPLATE_EXTRACTORS iteration), so the call to extract_mock_template was rejected. Add an explicit cast to express what the isinstance check guarantees. --- testslide/core/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testslide/core/lib.py b/testslide/core/lib.py index b6ba14f..57fe61d 100644 --- a/testslide/core/lib.py +++ b/testslide/core/lib.py @@ -14,7 +14,7 @@ from functools import wraps from inspect import Traceback from types import FrameType -from typing import Any, TYPE_CHECKING, Union +from typing import Any, cast, TYPE_CHECKING, Union from unittest.mock import Mock import typeguard @@ -95,7 +95,7 @@ def _extract_mock_template( template = None for mock_class, extract_mock_template in MOCK_TEMPLATE_EXTRACTORS.items(): if isinstance(mock, mock_class): - template = extract_mock_template(mock) + template = extract_mock_template(cast(Mock, mock)) return template From 636688e1f1bafffdbbd0346697e086eefbf04ee9 Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 10:12:58 -0700 Subject: [PATCH 6/8] Clean up flake8 F401/F824 warnings - Drop unused `import sys` from testslide/bdd/lib.py and testslide/core/__init__.py (F401). - Suppress F824 on `global` statements in mock_callable.py that declare names which are read but not assigned in scope. Collapsed multi-line global declarations onto one line so the noqa comment can be attached. --- testslide/bdd/lib.py | 1 - testslide/core/__init__.py | 1 - testslide/core/mock_callable.py | 16 ++++------------ 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/testslide/bdd/lib.py b/testslide/bdd/lib.py index 6583d94..be72e67 100644 --- a/testslide/bdd/lib.py +++ b/testslide/bdd/lib.py @@ -9,7 +9,6 @@ import inspect import os import re -import sys import time import types import unittest diff --git a/testslide/core/__init__.py b/testslide/core/__init__.py index 1706ee9..89c1d1f 100644 --- a/testslide/core/__init__.py +++ b/testslide/core/__init__.py @@ -13,7 +13,6 @@ coverage.process_startup() import os -import sys import unittest from typing import Any diff --git a/testslide/core/mock_callable.py b/testslide/core/mock_callable.py index 2948c00..0a1073a 100644 --- a/testslide/core/mock_callable.py +++ b/testslide/core/mock_callable.py @@ -98,12 +98,7 @@ def unpatch_all_callable_mocks() -> None: This method must be called after every test unconditionally to remove all active mock_callable() patches. """ - global \ - register_assertion, \ - _default_register_assertion, \ - _call_order_assertion_registered, \ - _received_ordered_calls, \ - _expected_ordered_calls + global register_assertion, _default_register_assertion, _call_order_assertion_registered, _received_ordered_calls, _expected_ordered_calls # noqa: F824 register_assertion = _default_register_assertion _call_order_assertion_registered = False @@ -122,7 +117,7 @@ def unpatch_all_callable_mocks() -> None: def _is_setup() -> bool: - global register_assertion, _default_register_assertion + global register_assertion, _default_register_assertion # noqa: F824 return register_assertion is not _default_register_assertion @@ -232,7 +227,7 @@ def __init__( self._accept_partial_call = False def register_call(self, *args: Any, **kwargs: Any) -> None: - global _received_ordered_calls + global _received_ordered_calls # noqa: F824 if self._has_order_assertion: _received_ordered_calls.append((self.target, self.method, self)) @@ -367,10 +362,7 @@ def assertion() -> None: register_assertion(assertion) def add_call_order_assertion(self) -> None: - global \ - _call_order_assertion_registered, \ - _received_ordered_calls, \ - _expected_ordered_calls + global _call_order_assertion_registered, _received_ordered_calls, _expected_ordered_calls # noqa: F824 if not _call_order_assertion_registered: From 8c73a76ec6486637148461c6525ea747424a1265 Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 10:23:23 -0700 Subject: [PATCH 7/8] Drop old python targets, add 3.11 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 609feec..76c83e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.10", "3.11"] # Ensure that all flavours are run to completion even if an other flavor failed fail-fast: false From a5134478f64ac1b986184d4862fc8c253364c270 Mon Sep 17 00:00:00 2001 From: John Oxley Date: Mon, 27 Apr 2026 10:27:48 -0700 Subject: [PATCH 8/8] Drop python 3.11 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76c83e3..785fbfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11"] + python-version: ["3.10"] # Ensure that all flavours are run to completion even if an other flavor failed fail-fast: false