Skip to content

Commit 540a73f

Browse files
authored
Merge pull request RustPython#3862 from youknowone/unittest
Update unittest from CPython 3.10.5
2 parents 6124781 + 1bc2803 commit 540a73f

33 files changed

+6159
-926
lines changed

Lib/doctest.py

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def _test():
102102
import sys
103103
import traceback
104104
import unittest
105-
from io import StringIO
105+
from io import StringIO # XXX: RUSTPYTHON; , IncrementalNewlineDecoder
106106
from collections import namedtuple
107107

108108
TestResults = namedtuple('TestResults', 'failed attempted')
@@ -211,17 +211,28 @@ def _normalize_module(module, depth=2):
211211
else:
212212
raise TypeError("Expected a module, string, or None")
213213

214+
def _newline_convert(data):
215+
# The IO module provides a handy decoder for universal newline conversion
216+
return IncrementalNewlineDecoder(None, True).decode(data, True)
217+
214218
def _load_testfile(filename, package, module_relative, encoding):
215219
if module_relative:
216220
package = _normalize_module(package, 3)
217221
filename = _module_relative_path(package, filename)
218-
if getattr(package, '__loader__', None) is not None:
219-
if hasattr(package.__loader__, 'get_data'):
220-
file_contents = package.__loader__.get_data(filename)
221-
file_contents = file_contents.decode(encoding)
222-
# get_data() opens files as 'rb', so one must do the equivalent
223-
# conversion as universal newlines would do.
224-
return file_contents.replace(os.linesep, '\n'), filename
222+
if (loader := getattr(package, '__loader__', None)) is None:
223+
try:
224+
loader = package.__spec__.loader
225+
except AttributeError:
226+
pass
227+
if hasattr(loader, 'get_data'):
228+
file_contents = loader.get_data(filename)
229+
file_contents = file_contents.decode(encoding)
230+
# get_data() opens files as 'rb', so one must do the equivalent
231+
# conversion as universal newlines would do.
232+
233+
# TODO: RUSTPYTHON; use _newline_convert once io.IncrementalNewlineDecoder is implemented
234+
return file_contents.replace(os.linesep, '\n'), filename
235+
# return _newline_convert(file_contents), filename
225236
with open(filename, encoding=encoding) as f:
226237
return f.read(), filename
227238

@@ -965,6 +976,17 @@ def _from_module(self, module, object):
965976
else:
966977
raise ValueError("object must be a class or function")
967978

979+
def _is_routine(self, obj):
980+
"""
981+
Safely unwrap objects and determine if they are functions.
982+
"""
983+
maybe_routine = obj
984+
try:
985+
maybe_routine = inspect.unwrap(maybe_routine)
986+
except ValueError:
987+
pass
988+
return inspect.isroutine(maybe_routine)
989+
968990
def _find(self, tests, obj, name, module, source_lines, globs, seen):
969991
"""
970992
Find tests for the given object and any contained objects, and
@@ -987,9 +1009,9 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
9871009
if inspect.ismodule(obj) and self._recurse:
9881010
for valname, val in obj.__dict__.items():
9891011
valname = '%s.%s' % (name, valname)
1012+
9901013
# Recurse to functions & classes.
991-
if ((inspect.isroutine(inspect.unwrap(val))
992-
or inspect.isclass(val)) and
1014+
if ((self._is_routine(val) or inspect.isclass(val)) and
9931015
self._from_module(module, val)):
9941016
self._find(tests, val, valname, module, source_lines,
9951017
globs, seen)
@@ -1015,10 +1037,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
10151037
if inspect.isclass(obj) and self._recurse:
10161038
for valname, val in obj.__dict__.items():
10171039
# Special handling for staticmethod/classmethod.
1018-
if isinstance(val, staticmethod):
1019-
val = getattr(obj, valname)
1020-
if isinstance(val, classmethod):
1021-
val = getattr(obj, valname).__func__
1040+
if isinstance(val, (staticmethod, classmethod)):
1041+
val = val.__func__
10221042

10231043
# Recurse to methods, properties, and nested classes.
10241044
if ((inspect.isroutine(val) or inspect.isclass(val) or
@@ -1068,19 +1088,21 @@ def _get_test(self, obj, name, module, globs, source_lines):
10681088

10691089
def _find_lineno(self, obj, source_lines):
10701090
"""
1071-
Return a line number of the given object's docstring. Note:
1072-
this method assumes that the object has a docstring.
1091+
Return a line number of the given object's docstring.
1092+
1093+
Returns `None` if the given object does not have a docstring.
10731094
"""
10741095
lineno = None
1096+
docstring = getattr(obj, '__doc__', None)
10751097

10761098
# Find the line number for modules.
1077-
if inspect.ismodule(obj):
1099+
if inspect.ismodule(obj) and docstring is not None:
10781100
lineno = 0
10791101

10801102
# Find the line number for classes.
10811103
# Note: this could be fooled if a class is defined multiple
10821104
# times in a single file.
1083-
if inspect.isclass(obj):
1105+
if inspect.isclass(obj) and docstring is not None:
10841106
if source_lines is None:
10851107
return None
10861108
pat = re.compile(r'^\s*class\s*%s\b' %
@@ -1092,7 +1114,9 @@ def _find_lineno(self, obj, source_lines):
10921114

10931115
# Find the line number for functions & methods.
10941116
if inspect.ismethod(obj): obj = obj.__func__
1095-
if inspect.isfunction(obj): obj = obj.__code__
1117+
if inspect.isfunction(obj) and getattr(obj, '__doc__', None):
1118+
# We don't use `docstring` var here, because `obj` can be changed.
1119+
obj = obj.__code__
10961120
if inspect.istraceback(obj): obj = obj.tb_frame
10971121
if inspect.isframe(obj): obj = obj.f_code
10981122
if inspect.iscode(obj):
@@ -1327,7 +1351,7 @@ def __run(self, test, compileflags, out):
13271351
try:
13281352
# Don't blink! This is where the user's code gets run.
13291353
exec(compile(example.source, filename, "single",
1330-
compileflags, 1), test.globs)
1354+
compileflags, True), test.globs)
13311355
self.debugger.set_continue() # ==== Example Finished ====
13321356
exception = None
13331357
except KeyboardInterrupt:
@@ -2154,6 +2178,7 @@ def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
21542178
unittest.TestCase.__init__(self)
21552179
self._dt_optionflags = optionflags
21562180
self._dt_checker = checker
2181+
self._dt_globs = test.globs.copy()
21572182
self._dt_test = test
21582183
self._dt_setUp = setUp
21592184
self._dt_tearDown = tearDown
@@ -2170,7 +2195,9 @@ def tearDown(self):
21702195
if self._dt_tearDown is not None:
21712196
self._dt_tearDown(test)
21722197

2198+
# restore the original globs
21732199
test.globs.clear()
2200+
test.globs.update(self._dt_globs)
21742201

21752202
def runTest(self):
21762203
test = self._dt_test

Lib/test/test_support.py

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import contextlib
21
import errno
32
import importlib
43
import io
@@ -12,6 +11,8 @@
1211
import textwrap
1312
import time
1413
import unittest
14+
import warnings
15+
1516
from test import support
1617
from test.support import import_helper
1718
from test.support import os_helper
@@ -23,10 +24,38 @@
2324

2425

2526
class TestSupport(unittest.TestCase):
27+
@classmethod
28+
def setUpClass(cls):
29+
orig_filter_len = len(warnings.filters)
30+
cls._warnings_helper_token = support.ignore_deprecations_from(
31+
"test.support.warnings_helper", like=".*used in test_support.*"
32+
)
33+
cls._test_support_token = support.ignore_deprecations_from(
34+
"test.test_support", like=".*You should NOT be seeing this.*"
35+
)
36+
assert len(warnings.filters) == orig_filter_len + 2
37+
38+
@classmethod
39+
def tearDownClass(cls):
40+
orig_filter_len = len(warnings.filters)
41+
support.clear_ignored_deprecations(
42+
cls._warnings_helper_token,
43+
cls._test_support_token,
44+
)
45+
assert len(warnings.filters) == orig_filter_len - 2
46+
47+
def test_ignored_deprecations_are_silent(self):
48+
"""Test support.ignore_deprecations_from() silences warnings"""
49+
with warnings.catch_warnings(record=True) as warning_objs:
50+
warnings_helper._warn_about_deprecation()
51+
warnings.warn("You should NOT be seeing this.", DeprecationWarning)
52+
messages = [str(w.message) for w in warning_objs]
53+
self.assertEqual(len(messages), 0, messages)
2654

2755
def test_import_module(self):
2856
import_helper.import_module("ftplib")
29-
self.assertRaises(unittest.SkipTest, import_helper.import_module, "foo")
57+
self.assertRaises(unittest.SkipTest,
58+
import_helper.import_module, "foo")
3059

3160
def test_import_fresh_module(self):
3261
import_helper.import_fresh_module("ftplib")
@@ -47,7 +76,7 @@ def test_unload(self):
4776
self.assertNotIn("sched", sys.modules)
4877

4978
def test_unlink(self):
50-
with open(TESTFN, "w") as f:
79+
with open(TESTFN, "w", encoding="utf-8") as f:
5180
pass
5281
os_helper.unlink(TESTFN)
5382
self.assertFalse(os.path.exists(TESTFN))
@@ -79,7 +108,7 @@ def test_rmtree(self):
79108

80109
def test_forget(self):
81110
mod_filename = TESTFN + '.py'
82-
with open(mod_filename, 'w') as f:
111+
with open(mod_filename, 'w', encoding="utf-8") as f:
83112
print('foo = 1', file=f)
84113
sys.path.insert(0, os.curdir)
85114
importlib.invalidate_caches()
@@ -177,16 +206,14 @@ def test_temp_dir__forked_child(self):
177206
script_helper.assert_python_ok("-c", textwrap.dedent("""
178207
import os
179208
from test import support
209+
from test.support import os_helper
180210
with os_helper.temp_cwd() as temp_path:
181211
pid = os.fork()
182212
if pid != 0:
183-
# parent process (child has pid == 0)
213+
# parent process
184214
185215
# wait for the child to terminate
186-
(pid, status) = os.waitpid(pid, 0)
187-
if status != 0:
188-
raise AssertionError(f"Child process failed with exit "
189-
f"status indication 0x{status:x}.")
216+
support.wait_process(pid, exitcode=0)
190217
191218
# Make sure that temp_path is still present. When the child
192219
# process leaves the 'temp_cwd'-context, the __exit__()-
@@ -295,8 +322,8 @@ def test_check_syntax_error(self):
295322

296323
def test_CleanImport(self):
297324
import importlib
298-
with import_helper.CleanImport("asyncore"):
299-
importlib.import_module("asyncore")
325+
with import_helper.CleanImport("pprint"):
326+
importlib.import_module("pprint")
300327

301328
def test_DirsOnSysPath(self):
302329
with import_helper.DirsOnSysPath('foo', 'bar'):
@@ -392,6 +419,8 @@ def test_detect_api_mismatch__ignore(self):
392419
self.OtherClass, self.RefClass, ignore=ignore)
393420
self.assertEqual(set(), missing_items)
394421

422+
# TODO: RUSTPYTHON
423+
@unittest.expectedFailure
395424
def test_check__all__(self):
396425
extra = {'tempdir'}
397426
not_exported = {'template'}
@@ -414,8 +443,10 @@ def test_check__all__(self):
414443

415444
self.assertRaises(AssertionError, support.check__all__, self, unittest)
416445

417-
@unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG') and hasattr(os, 'fork'),
418-
'need os.waitpid() and os.WNOHANG and os.fork()')
446+
# TODO: RUSTPYTHON
447+
@unittest.expectedFailure
448+
@unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG'),
449+
'need os.waitpid() and os.WNOHANG')
419450
def test_reap_children(self):
420451
# Make sure that there is no other pending child process
421452
support.reap_children()
@@ -427,7 +458,7 @@ def test_reap_children(self):
427458
os._exit(0)
428459

429460
t0 = time.monotonic()
430-
deadline = time.monotonic() + 60.0
461+
deadline = time.monotonic() + support.SHORT_TIMEOUT
431462

432463
was_altered = support.environment_altered
433464
try:
@@ -502,7 +533,6 @@ def test_args_from_interpreter_flags(self):
502533
['-Wignore', '-X', 'dev'],
503534
['-X', 'faulthandler'],
504535
['-X', 'importtime'],
505-
['-X', 'showalloccount'],
506536
['-X', 'showrefcount'],
507537
['-X', 'tracemalloc'],
508538
['-X', 'tracemalloc=3'],
@@ -647,7 +677,7 @@ def test_fd_count(self):
647677
def check_print_warning(self, msg, expected):
648678
stderr = io.StringIO()
649679

650-
old_stderr = sys.__stderr__
680+
old_stderr = sys.__stderr__
651681
try:
652682
sys.__stderr__ = stderr
653683
support.print_warning(msg)
@@ -671,7 +701,6 @@ def test_print_warning(self):
671701
# findfile
672702
# check_warnings
673703
# EnvironmentVarGuard
674-
# TransientResource
675704
# transient_internet
676705
# run_with_locale
677706
# set_memlimit
@@ -682,15 +711,10 @@ def test_print_warning(self):
682711
# run_doctest
683712
# threading_cleanup
684713
# reap_threads
685-
# strip_python_stderr
686714
# can_symlink
687715
# skip_unless_symlink
688716
# SuppressCrashReport
689717

690718

691-
def test_main():
692-
tests = [TestSupport]
693-
support.run_unittest(*tests)
694-
695719
if __name__ == '__main__':
696-
test_main()
720+
unittest.main()

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()

0 commit comments

Comments
 (0)