Skip to content

Commit 799c786

Browse files
CPython developersyouknowone
authored andcommitted
Update unittest from CPython 3.10.5
1 parent 760bda6 commit 799c786

31 files changed

+6054
-883
lines changed

Lib/test/test_unittest.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from test import support
44

55

6-
def test_main():
7-
# used by regrtest
8-
support.run_unittest(unittest.test.suite())
9-
support.reap_children()
10-
116
def load_tests(*_):
127
# used by unittest
138
return unittest.test.suite()
149

10+
11+
def tearDownModule():
12+
support.reap_children()
13+
14+
1515
if __name__ == "__main__":
16-
test_main()
16+
unittest.main()

Lib/unittest/__init__.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,28 @@ def testMultiply(self):
4444
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
4545
"""
4646

47-
__all__ = ['TestResult', 'TestCase', 'TestSuite',
47+
__all__ = ['TestResult', 'TestCase', 'IsolatedAsyncioTestCase', 'TestSuite',
4848
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
4949
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
5050
'expectedFailure', 'TextTestResult', 'installHandler',
51-
'registerResult', 'removeResult', 'removeHandler']
51+
'registerResult', 'removeResult', 'removeHandler',
52+
'addModuleCleanup']
5253

5354
# Expose obsolete functions for backwards compatibility
5455
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
5556

5657
__unittest = True
5758

5859
from .result import TestResult
59-
from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf,
60-
skipUnless, expectedFailure)
60+
from .case import (addModuleCleanup, TestCase, FunctionTestCase, SkipTest, skip,
61+
skipIf, skipUnless, expectedFailure)
6162
from .suite import BaseTestSuite, TestSuite
6263
from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
6364
findTestCases)
6465
from .main import TestProgram, main
6566
from .runner import TextTestRunner, TextTestResult
6667
from .signals import installHandler, registerResult, removeResult, removeHandler
68+
# IsolatedAsyncioTestCase will be imported lazily.
6769

6870
# deprecated
6971
_TextTestResult = TextTestResult
@@ -76,3 +78,18 @@ def load_tests(loader, tests, pattern):
7678
# top level directory cached on loader instance
7779
this_dir = os.path.dirname(__file__)
7880
return loader.discover(start_dir=this_dir, pattern=pattern)
81+
82+
83+
# Lazy import of IsolatedAsyncioTestCase from .async_case
84+
# It imports asyncio, which is relatively heavy, but most tests
85+
# do not need it.
86+
87+
def __dir__():
88+
return globals().keys() | {'IsolatedAsyncioTestCase'}
89+
90+
def __getattr__(name):
91+
if name == 'IsolatedAsyncioTestCase':
92+
global IsolatedAsyncioTestCase
93+
from .async_case import IsolatedAsyncioTestCase
94+
return IsolatedAsyncioTestCase
95+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Lib/unittest/_log.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import logging
2+
import collections
3+
4+
from .case import _BaseTestCaseContext
5+
6+
7+
_LoggingWatcher = collections.namedtuple("_LoggingWatcher",
8+
["records", "output"])
9+
10+
class _CapturingHandler(logging.Handler):
11+
"""
12+
A logging handler capturing all (raw and formatted) logging output.
13+
"""
14+
15+
def __init__(self):
16+
logging.Handler.__init__(self)
17+
self.watcher = _LoggingWatcher([], [])
18+
19+
def flush(self):
20+
pass
21+
22+
def emit(self, record):
23+
self.watcher.records.append(record)
24+
msg = self.format(record)
25+
self.watcher.output.append(msg)
26+
27+
28+
class _AssertLogsContext(_BaseTestCaseContext):
29+
"""A context manager for assertLogs() and assertNoLogs() """
30+
31+
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
32+
33+
def __init__(self, test_case, logger_name, level, no_logs):
34+
_BaseTestCaseContext.__init__(self, test_case)
35+
self.logger_name = logger_name
36+
if level:
37+
self.level = logging._nameToLevel.get(level, level)
38+
else:
39+
self.level = logging.INFO
40+
self.msg = None
41+
self.no_logs = no_logs
42+
43+
def __enter__(self):
44+
if isinstance(self.logger_name, logging.Logger):
45+
logger = self.logger = self.logger_name
46+
else:
47+
logger = self.logger = logging.getLogger(self.logger_name)
48+
formatter = logging.Formatter(self.LOGGING_FORMAT)
49+
handler = _CapturingHandler()
50+
handler.setLevel(self.level)
51+
handler.setFormatter(formatter)
52+
self.watcher = handler.watcher
53+
self.old_handlers = logger.handlers[:]
54+
self.old_level = logger.level
55+
self.old_propagate = logger.propagate
56+
logger.handlers = [handler]
57+
logger.setLevel(self.level)
58+
logger.propagate = False
59+
if self.no_logs:
60+
return
61+
return handler.watcher
62+
63+
def __exit__(self, exc_type, exc_value, tb):
64+
self.logger.handlers = self.old_handlers
65+
self.logger.propagate = self.old_propagate
66+
self.logger.setLevel(self.old_level)
67+
68+
if exc_type is not None:
69+
# let unexpected exceptions pass through
70+
return False
71+
72+
if self.no_logs:
73+
# assertNoLogs
74+
if len(self.watcher.records) > 0:
75+
self._raiseFailure(
76+
"Unexpected logs found: {!r}".format(
77+
self.watcher.output
78+
)
79+
)
80+
81+
else:
82+
# assertLogs
83+
if len(self.watcher.records) == 0:
84+
self._raiseFailure(
85+
"no logs of level {} or higher triggered on {}"
86+
.format(logging.getLevelName(self.level), self.logger.name))

Lib/unittest/async_case.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import asyncio
2+
import inspect
3+
4+
from .case import TestCase
5+
6+
7+
class IsolatedAsyncioTestCase(TestCase):
8+
# Names intentionally have a long prefix
9+
# to reduce a chance of clashing with user-defined attributes
10+
# from inherited test case
11+
#
12+
# The class doesn't call loop.run_until_complete(self.setUp()) and family
13+
# but uses a different approach:
14+
# 1. create a long-running task that reads self.setUp()
15+
# awaitable from queue along with a future
16+
# 2. await the awaitable object passing in and set the result
17+
# into the future object
18+
# 3. Outer code puts the awaitable and the future object into a queue
19+
# with waiting for the future
20+
# The trick is necessary because every run_until_complete() call
21+
# creates a new task with embedded ContextVar context.
22+
# To share contextvars between setUp(), test and tearDown() we need to execute
23+
# them inside the same task.
24+
25+
# Note: the test case modifies event loop policy if the policy was not instantiated
26+
# yet.
27+
# asyncio.get_event_loop_policy() creates a default policy on demand but never
28+
# returns None
29+
# I believe this is not an issue in user level tests but python itself for testing
30+
# should reset a policy in every test module
31+
# by calling asyncio.set_event_loop_policy(None) in tearDownModule()
32+
33+
def __init__(self, methodName='runTest'):
34+
super().__init__(methodName)
35+
self._asyncioTestLoop = None
36+
self._asyncioCallsQueue = None
37+
38+
async def asyncSetUp(self):
39+
pass
40+
41+
async def asyncTearDown(self):
42+
pass
43+
44+
def addAsyncCleanup(self, func, /, *args, **kwargs):
45+
# A trivial trampoline to addCleanup()
46+
# the function exists because it has a different semantics
47+
# and signature:
48+
# addCleanup() accepts regular functions
49+
# but addAsyncCleanup() accepts coroutines
50+
#
51+
# We intentionally don't add inspect.iscoroutinefunction() check
52+
# for func argument because there is no way
53+
# to check for async function reliably:
54+
# 1. It can be "async def func()" itself
55+
# 2. Class can implement "async def __call__()" method
56+
# 3. Regular "def func()" that returns awaitable object
57+
self.addCleanup(*(func, *args), **kwargs)
58+
59+
def _callSetUp(self):
60+
self.setUp()
61+
self._callAsync(self.asyncSetUp)
62+
63+
def _callTestMethod(self, method):
64+
self._callMaybeAsync(method)
65+
66+
def _callTearDown(self):
67+
self._callAsync(self.asyncTearDown)
68+
self.tearDown()
69+
70+
def _callCleanup(self, function, *args, **kwargs):
71+
self._callMaybeAsync(function, *args, **kwargs)
72+
73+
def _callAsync(self, func, /, *args, **kwargs):
74+
assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
75+
ret = func(*args, **kwargs)
76+
assert inspect.isawaitable(ret), f'{func!r} returned non-awaitable'
77+
fut = self._asyncioTestLoop.create_future()
78+
self._asyncioCallsQueue.put_nowait((fut, ret))
79+
return self._asyncioTestLoop.run_until_complete(fut)
80+
81+
def _callMaybeAsync(self, func, /, *args, **kwargs):
82+
assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
83+
ret = func(*args, **kwargs)
84+
if inspect.isawaitable(ret):
85+
fut = self._asyncioTestLoop.create_future()
86+
self._asyncioCallsQueue.put_nowait((fut, ret))
87+
return self._asyncioTestLoop.run_until_complete(fut)
88+
else:
89+
return ret
90+
91+
async def _asyncioLoopRunner(self, fut):
92+
self._asyncioCallsQueue = queue = asyncio.Queue()
93+
fut.set_result(None)
94+
while True:
95+
query = await queue.get()
96+
queue.task_done()
97+
if query is None:
98+
return
99+
fut, awaitable = query
100+
try:
101+
ret = await awaitable
102+
if not fut.cancelled():
103+
fut.set_result(ret)
104+
except (SystemExit, KeyboardInterrupt):
105+
raise
106+
except (BaseException, asyncio.CancelledError) as ex:
107+
if not fut.cancelled():
108+
fut.set_exception(ex)
109+
110+
def _setupAsyncioLoop(self):
111+
assert self._asyncioTestLoop is None, 'asyncio test loop already initialized'
112+
loop = asyncio.new_event_loop()
113+
asyncio.set_event_loop(loop)
114+
loop.set_debug(True)
115+
self._asyncioTestLoop = loop
116+
fut = loop.create_future()
117+
self._asyncioCallsTask = loop.create_task(self._asyncioLoopRunner(fut))
118+
loop.run_until_complete(fut)
119+
120+
def _tearDownAsyncioLoop(self):
121+
assert self._asyncioTestLoop is not None, 'asyncio test loop is not initialized'
122+
loop = self._asyncioTestLoop
123+
self._asyncioTestLoop = None
124+
self._asyncioCallsQueue.put_nowait(None)
125+
loop.run_until_complete(self._asyncioCallsQueue.join())
126+
127+
try:
128+
# cancel all tasks
129+
to_cancel = asyncio.all_tasks(loop)
130+
if not to_cancel:
131+
return
132+
133+
for task in to_cancel:
134+
task.cancel()
135+
136+
loop.run_until_complete(
137+
asyncio.gather(*to_cancel, return_exceptions=True))
138+
139+
for task in to_cancel:
140+
if task.cancelled():
141+
continue
142+
if task.exception() is not None:
143+
loop.call_exception_handler({
144+
'message': 'unhandled exception during test shutdown',
145+
'exception': task.exception(),
146+
'task': task,
147+
})
148+
# shutdown asyncgens
149+
loop.run_until_complete(loop.shutdown_asyncgens())
150+
finally:
151+
# Prevent our executor environment from leaking to future tests.
152+
loop.run_until_complete(loop.shutdown_default_executor())
153+
asyncio.set_event_loop(None)
154+
loop.close()
155+
156+
def run(self, result=None):
157+
self._setupAsyncioLoop()
158+
try:
159+
return super().run(result)
160+
finally:
161+
self._tearDownAsyncioLoop()
162+
163+
def debug(self):
164+
self._setupAsyncioLoop()
165+
super().debug()
166+
self._tearDownAsyncioLoop()
167+
168+
def __del__(self):
169+
if self._asyncioTestLoop is not None:
170+
self._tearDownAsyncioLoop()

0 commit comments

Comments
 (0)