Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ testtools NEWS

Changes and improvements to testtools_, grouped by release.

NEXT
~~~~

2.8.6
~~~~~

Improvements
------------
* Improve the type annotations for ``testtools.testcase.TestCase.assertRaises``.
This method will now be typed correctly when called as a method or as a
context manager.
(Stephen Finucane, #577, #578)

2.8.5
~~~~~

Improvements
------------
* Fix the type annotations for ``testtools.testcase.TestCase.useFixture``
(Stephen Finucane, #574)

2.8.4
~~~~~

Expand Down Expand Up @@ -70,8 +91,7 @@ Improvements
(Jelmer Vernoo某, #538)

* Allow stream=None to be passed to various TestResult
classes that now support verbosity; fixes a regression
from 2.8.0.
classes that now support verbosity; fixes a regression from 2.8.0.
(Jelmer Vernoo某)

2.8.1
Expand Down
60 changes: 45 additions & 15 deletions testtools/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import types
import unittest
from collections.abc import Callable, Iterator
from typing import TypeVar, cast
from typing import TYPE_CHECKING, ParamSpec, TypeVar, cast, overload
from unittest.case import SkipTest

T = TypeVar("T")
Expand Down Expand Up @@ -56,6 +56,12 @@
TestResult,
)

if TYPE_CHECKING:
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

# Circular import: fixtures imports gather_details from here, we import
# fixtures, leading to gather_details not being available and fixtures being
# unable to import it.
Expand All @@ -82,6 +88,8 @@ class _ExpectedFailure(Exception):


# TypeVar for decorators
_P = ParamSpec("_P")
_R = TypeVar("_R")
_F = TypeVar("_F", bound=Callable[..., object])


Expand Down Expand Up @@ -384,10 +392,10 @@ def _formatTypes(

def addCleanup(
self,
function: Callable[..., object],
function: Callable[_P, _R],
/,
*arguments: object,
**keywordArguments: object,
*args: _P.args,
**kwargs: _P.kwargs,
) -> None:
"""Add a cleanup function to be called after tearDown.

Expand All @@ -401,7 +409,7 @@ def addCleanup(
Cleanup functions are always called before a test finishes running,
even if setUp is aborted by an exception.
"""
self._cleanups.append((function, arguments, keywordArguments))
self._cleanups.append((function, args, kwargs))

def addOnException(self, handler: "Callable[[ExcInfo], None]") -> None:
"""Add a handler to be called when an exception occurs in test code.
Expand Down Expand Up @@ -493,12 +501,28 @@ def assertIsInstance( # type: ignore[override]
matcher = IsInstance(klass)
self.assertThat(obj, matcher, msg or "")

@overload # type: ignore[override]
def assertRaises(
self,
expected_exception: type[BaseException] | tuple[type[BaseException]],
callable: Callable[_P, _R],
*args: _P.args,
**kwargs: _P.kwargs,
) -> BaseException: ...

@overload # type: ignore[override]
def assertRaises(
self,
expected_exception: type[BaseException] | tuple[type[BaseException]],
callable: None = ...,
) -> "_AssertRaisesContext": ...

def assertRaises( # type: ignore[override]
self,
expected_exception: type[BaseException],
callable: Callable[..., object] | None = None,
*args: object,
**kwargs: object,
expected_exception: type[BaseException] | tuple[type[BaseException]],
callable: Callable[_P, _R] | None = None,
*args: _P.args,
**kwargs: _P.kwargs,
) -> "_AssertRaisesContext | BaseException":
"""Fail unless an exception of class expected_exception is thrown
by callable when invoked with arguments args and keyword
Expand Down Expand Up @@ -654,9 +678,9 @@ def defaultTestResult(self) -> TestResult:
def expectFailure(
self,
reason: str,
predicate: Callable[..., object],
*args: object,
**kwargs: object,
predicate: Callable[_P, _R],
*args: _P.args,
**kwargs: _P.kwargs,
) -> None:
"""Check that a test fails in a particular way.

Expand Down Expand Up @@ -1206,7 +1230,10 @@ class _AssertRaisesContext:
"""

def __init__(
self, expected: type[BaseException], test_case: TestCase, msg: str | None = None
self,
expected: type[BaseException] | tuple[type[BaseException]],
test_case: TestCase,
msg: str | None = None,
) -> None:
"""Construct an `_AssertRaisesContext`.

Expand All @@ -1219,7 +1246,7 @@ def __init__(
self.msg = msg
self.exception: BaseException | None = None

def __enter__(self) -> "_AssertRaisesContext":
def __enter__(self) -> "Self":
return self

def __exit__(
Expand Down Expand Up @@ -1322,7 +1349,10 @@ class Nullary:
"""

def __init__(
self, callable_object: Callable[..., object], *args: object, **kwargs: object
self,
callable_object: Callable[_P, _R],
*args: _P.args,
**kwargs: _P.kwargs,
) -> None:
self._callable_object = callable_object
self._args = args
Expand Down
Loading