Skip to content

Commit 98af3eb

Browse files
authored
Add unittest.runner._WritelnDecorator (#12407)
1 parent 7865a78 commit 98af3eb

File tree

1 file changed

+31
-15
lines changed

1 file changed

+31
-15
lines changed

stdlib/unittest/runner.pyi

+31-15
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,52 @@ import sys
22
import unittest.case
33
import unittest.result
44
import unittest.suite
5-
from _typeshed import Incomplete
5+
from _typeshed import SupportsFlush, SupportsWrite
66
from collections.abc import Callable, Iterable
7-
from typing import TextIO
8-
from typing_extensions import TypeAlias
7+
from typing import Any, Generic, Protocol, TypeVar
8+
from typing_extensions import Never, TypeAlias
99

10-
_ResultClassType: TypeAlias = Callable[[TextIO, bool, int], unittest.result.TestResult]
10+
_ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult]
1111

12-
class TextTestResult(unittest.result.TestResult):
12+
class _SupportsWriteAndFlush(SupportsWrite[str], SupportsFlush, Protocol): ...
13+
14+
# All methods used by unittest.runner.TextTestResult's stream
15+
class _TextTestStream(_SupportsWriteAndFlush, Protocol):
16+
def writeln(self, arg: str | None = None) -> str: ...
17+
18+
# _WritelnDecorator should have all the same attrs as its stream param.
19+
# But that's not feasible to do Generically
20+
# We can expand the attributes if requested
21+
class _WritelnDecorator(_TextTestStream):
22+
def __init__(self, stream: _TextTestStream) -> None: ...
23+
def __getattr__(self, attr: str) -> Any: ... # Any attribute from the stream type passed to __init__
24+
# These attributes are prevented by __getattr__
25+
stream: Never
26+
__getstate__: Never
27+
28+
_StreamT = TypeVar("_StreamT", bound=_TextTestStream, default=_WritelnDecorator)
29+
30+
class TextTestResult(unittest.result.TestResult, Generic[_StreamT]):
1331
descriptions: bool # undocumented
1432
dots: bool # undocumented
1533
separator1: str
1634
separator2: str
1735
showAll: bool # undocumented
18-
stream: TextIO # undocumented
36+
stream: _StreamT # undocumented
1937
if sys.version_info >= (3, 12):
2038
durations: unittest.result._DurationsType | None
2139
def __init__(
22-
self, stream: TextIO, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
40+
self, stream: _StreamT, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
2341
) -> None: ...
2442
else:
25-
def __init__(self, stream: TextIO, descriptions: bool, verbosity: int) -> None: ...
43+
def __init__(self, stream: _StreamT, descriptions: bool, verbosity: int) -> None: ...
2644

2745
def getDescription(self, test: unittest.case.TestCase) -> str: ...
2846
def printErrorList(self, flavour: str, errors: Iterable[tuple[unittest.case.TestCase, str]]) -> None: ...
2947

3048
class TextTestRunner:
3149
resultclass: _ResultClassType
32-
# TODO: add `_WritelnDecorator` type
33-
# stream: _WritelnDecorator
34-
stream: Incomplete
50+
stream: _WritelnDecorator
3551
descriptions: bool
3652
verbosity: int
3753
failfast: bool
@@ -43,7 +59,7 @@ class TextTestRunner:
4359
durations: unittest.result._DurationsType | None
4460
def __init__(
4561
self,
46-
stream: TextIO | None = None,
62+
stream: _SupportsWriteAndFlush | None = None,
4763
descriptions: bool = True,
4864
verbosity: int = 1,
4965
failfast: bool = False,
@@ -57,7 +73,7 @@ class TextTestRunner:
5773
else:
5874
def __init__(
5975
self,
60-
stream: TextIO | None = None,
76+
stream: _SupportsWriteAndFlush | None = None,
6177
descriptions: bool = True,
6278
verbosity: int = 1,
6379
failfast: bool = False,
@@ -68,5 +84,5 @@ class TextTestRunner:
6884
tb_locals: bool = False,
6985
) -> None: ...
7086

71-
def _makeResult(self) -> unittest.result.TestResult: ...
72-
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> unittest.result.TestResult: ...
87+
def _makeResult(self) -> TextTestResult: ...
88+
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> TextTestResult: ...

0 commit comments

Comments
 (0)