Skip to content
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Update doctest from CPython 3.10.5
  • Loading branch information
CPython developers authored and youknowone committed Jul 17, 2022
commit e12b3a60d406a78c015b667a7db028bafddc4a76
62 changes: 43 additions & 19 deletions Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,25 @@ def _normalize_module(module, depth=2):
else:
raise TypeError("Expected a module, string, or None")

def _newline_convert(data):
# The IO module provides a handy decoder for universal newline conversion
return IncrementalNewlineDecoder(None, True).decode(data, True)

def _load_testfile(filename, package, module_relative, encoding):
if module_relative:
package = _normalize_module(package, 3)
filename = _module_relative_path(package, filename)
if getattr(package, '__loader__', None) is not None:
if hasattr(package.__loader__, 'get_data'):
file_contents = package.__loader__.get_data(filename)
file_contents = file_contents.decode(encoding)
# get_data() opens files as 'rb', so one must do the equivalent
# conversion as universal newlines would do.
return file_contents.replace(os.linesep, '\n'), filename
if (loader := getattr(package, '__loader__', None)) is None:
try:
loader = package.__spec__.loader
except AttributeError:
pass
if hasattr(loader, 'get_data'):
file_contents = loader.get_data(filename)
file_contents = file_contents.decode(encoding)
# get_data() opens files as 'rb', so one must do the equivalent
# conversion as universal newlines would do.
return _newline_convert(file_contents), filename
with open(filename, encoding=encoding) as f:
return f.read(), filename

Expand Down Expand Up @@ -965,6 +973,17 @@ def _from_module(self, module, object):
else:
raise ValueError("object must be a class or function")

def _is_routine(self, obj):
"""
Safely unwrap objects and determine if they are functions.
"""
maybe_routine = obj
try:
maybe_routine = inspect.unwrap(maybe_routine)
except ValueError:
pass
return inspect.isroutine(maybe_routine)

def _find(self, tests, obj, name, module, source_lines, globs, seen):
"""
Find tests for the given object and any contained objects, and
Expand All @@ -987,9 +1006,9 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
if inspect.ismodule(obj) and self._recurse:
for valname, val in obj.__dict__.items():
valname = '%s.%s' % (name, valname)

# Recurse to functions & classes.
if ((inspect.isroutine(inspect.unwrap(val))
or inspect.isclass(val)) and
if ((self._is_routine(val) or inspect.isclass(val)) and
self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines,
globs, seen)
Expand All @@ -1015,10 +1034,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
if inspect.isclass(obj) and self._recurse:
for valname, val in obj.__dict__.items():
# Special handling for staticmethod/classmethod.
if isinstance(val, staticmethod):
val = getattr(obj, valname)
if isinstance(val, classmethod):
val = getattr(obj, valname).__func__
if isinstance(val, (staticmethod, classmethod)):
val = val.__func__

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

def _find_lineno(self, obj, source_lines):
"""
Return a line number of the given object's docstring. Note:
this method assumes that the object has a docstring.
Return a line number of the given object's docstring.

Returns `None` if the given object does not have a docstring.
"""
lineno = None
docstring = getattr(obj, '__doc__', None)

# Find the line number for modules.
if inspect.ismodule(obj):
if inspect.ismodule(obj) and docstring is not None:
lineno = 0

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

# Find the line number for functions & methods.
if inspect.ismethod(obj): obj = obj.__func__
if inspect.isfunction(obj): obj = obj.__code__
if inspect.isfunction(obj) and getattr(obj, '__doc__', None):
# We don't use `docstring` var here, because `obj` can be changed.
obj = obj.__code__
if inspect.istraceback(obj): obj = obj.tb_frame
if inspect.isframe(obj): obj = obj.f_code
if inspect.iscode(obj):
Expand Down Expand Up @@ -1327,7 +1348,7 @@ def __run(self, test, compileflags, out):
try:
# Don't blink! This is where the user's code gets run.
exec(compile(example.source, filename, "single",
compileflags, 1), test.globs)
compileflags, True), test.globs)
self.debugger.set_continue() # ==== Example Finished ====
exception = None
except KeyboardInterrupt:
Expand Down Expand Up @@ -2154,6 +2175,7 @@ def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
unittest.TestCase.__init__(self)
self._dt_optionflags = optionflags
self._dt_checker = checker
self._dt_globs = test.globs.copy()
self._dt_test = test
self._dt_setUp = setUp
self._dt_tearDown = tearDown
Expand All @@ -2170,7 +2192,9 @@ def tearDown(self):
if self._dt_tearDown is not None:
self._dt_tearDown(test)

# restore the original globs
test.globs.clear()
test.globs.update(self._dt_globs)

def runTest(self):
test = self._dt_test
Expand Down