mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
- Added "testfile" function, a simple function for running & verifying
all examples in a given text file. (analagous to "testmod") - Minor docstring fixes. - Added module_relative parameter to DocTestFile/DocTestSuite, which controls whether paths are module-relative & os-independent, or os-specific.
This commit is contained in:
parent
1be1a79ff9
commit
052d0cd291
2 changed files with 317 additions and 42 deletions
222
Lib/doctest.py
222
Lib/doctest.py
|
@ -200,6 +200,7 @@ __all__ = [
|
||||||
'DebugRunner',
|
'DebugRunner',
|
||||||
# 6. Test Functions
|
# 6. Test Functions
|
||||||
'testmod',
|
'testmod',
|
||||||
|
'testfile',
|
||||||
'run_docstring_examples',
|
'run_docstring_examples',
|
||||||
# 7. Tester
|
# 7. Tester
|
||||||
'Tester',
|
'Tester',
|
||||||
|
@ -478,6 +479,30 @@ class _OutputRedirectingPdb(pdb.Pdb):
|
||||||
# Restore stdout.
|
# Restore stdout.
|
||||||
sys.stdout = save_stdout
|
sys.stdout = save_stdout
|
||||||
|
|
||||||
|
def _module_relative_path(module, path):
|
||||||
|
if not inspect.ismodule(module):
|
||||||
|
raise TypeError, 'Expected a module: %r' % module
|
||||||
|
if path.startswith('/'):
|
||||||
|
raise ValueError, 'Module-relative files may not have absolute paths'
|
||||||
|
|
||||||
|
# Find the base directory for the path.
|
||||||
|
if hasattr(module, '__file__'):
|
||||||
|
# A normal module/package
|
||||||
|
basedir = os.path.split(module.__file__)[0]
|
||||||
|
elif module.__name__ == '__main__':
|
||||||
|
# An interactive session.
|
||||||
|
if len(sys.argv)>0 and sys.argv[0] != '':
|
||||||
|
basedir = os.path.split(sys.argv[0])[0]
|
||||||
|
else:
|
||||||
|
basedir = os.curdir
|
||||||
|
else:
|
||||||
|
# A module w/o __file__ (this includes builtins)
|
||||||
|
raise ValueError("Can't resolve paths relative to the module " +
|
||||||
|
module + " (it has no __file__)")
|
||||||
|
|
||||||
|
# Combine the base directory and the path.
|
||||||
|
return os.path.join(basedir, *(path.split('/')))
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
## 2. Example & DocTest
|
## 2. Example & DocTest
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -1881,6 +1906,7 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
|
||||||
DONT_ACCEPT_BLANKLINE
|
DONT_ACCEPT_BLANKLINE
|
||||||
NORMALIZE_WHITESPACE
|
NORMALIZE_WHITESPACE
|
||||||
ELLIPSIS
|
ELLIPSIS
|
||||||
|
IGNORE_EXCEPTION_DETAIL
|
||||||
REPORT_UDIFF
|
REPORT_UDIFF
|
||||||
REPORT_CDIFF
|
REPORT_CDIFF
|
||||||
REPORT_NDIFF
|
REPORT_NDIFF
|
||||||
|
@ -1896,9 +1922,7 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
|
||||||
treat all functions as public. Optionally, "isprivate" can be
|
treat all functions as public. Optionally, "isprivate" can be
|
||||||
set to doctest.is_private to skip over functions marked as private
|
set to doctest.is_private to skip over functions marked as private
|
||||||
using the underscore naming convention; see its docs for details.
|
using the underscore naming convention; see its docs for details.
|
||||||
"""
|
|
||||||
|
|
||||||
""" [XX] This is no longer true:
|
|
||||||
Advanced tomfoolery: testmod runs methods of a local instance of
|
Advanced tomfoolery: testmod runs methods of a local instance of
|
||||||
class doctest.Tester, then merges the results into (or creates)
|
class doctest.Tester, then merges the results into (or creates)
|
||||||
global Tester instance doctest.master. Methods of doctest.master
|
global Tester instance doctest.master. Methods of doctest.master
|
||||||
|
@ -1950,6 +1974,121 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
|
||||||
|
|
||||||
return runner.failures, runner.tries
|
return runner.failures, runner.tries
|
||||||
|
|
||||||
|
def testfile(filename, module_relative=True, name=None, package=None,
|
||||||
|
globs=None, verbose=None, report=True, optionflags=0,
|
||||||
|
extraglobs=None, raise_on_error=False):
|
||||||
|
"""
|
||||||
|
Test examples in the given file. Return (#failures, #tests).
|
||||||
|
|
||||||
|
Optional keyword arg "module_relative" specifies how filenames
|
||||||
|
should be interpreted:
|
||||||
|
|
||||||
|
- If "module_relative" is True (the default), then "filename"
|
||||||
|
specifies a module-relative path. By default, this path is
|
||||||
|
relative to the calling module's directory; but if the
|
||||||
|
"package" argument is specified, then it is relative to that
|
||||||
|
package. To ensure os-independence, "filename" should use
|
||||||
|
"/" characters to separate path segments, and should not
|
||||||
|
be an absolute path (i.e., it may not begin with "/").
|
||||||
|
|
||||||
|
- If "module_relative" is False, then "filename" specifies an
|
||||||
|
os-specific path. The path may be absolute or relative (to
|
||||||
|
the current working directory).
|
||||||
|
|
||||||
|
Optional keyword arg "name" gives the name of the file; by default
|
||||||
|
use the file's name.
|
||||||
|
|
||||||
|
Optional keyword argument "package" is a Python package or the
|
||||||
|
name of a Python package whose directory should be used as the
|
||||||
|
base directory for a module relative filename. If no package is
|
||||||
|
specified, then the calling module's directory is used as the base
|
||||||
|
directory for module relative filenames. It is an error to
|
||||||
|
specify "package" if "module_relative" is False.
|
||||||
|
|
||||||
|
Optional keyword arg "globs" gives a dict to be used as the globals
|
||||||
|
when executing examples; by default, use {}. A copy of this dict
|
||||||
|
is actually used for each docstring, so that each docstring's
|
||||||
|
examples start with a clean slate.
|
||||||
|
|
||||||
|
Optional keyword arg "extraglobs" gives a dictionary that should be
|
||||||
|
merged into the globals that are used to execute examples. By
|
||||||
|
default, no extra globals are used.
|
||||||
|
|
||||||
|
Optional keyword arg "verbose" prints lots of stuff if true, prints
|
||||||
|
only failures if false; by default, it's true iff "-v" is in sys.argv.
|
||||||
|
|
||||||
|
Optional keyword arg "report" prints a summary at the end when true,
|
||||||
|
else prints nothing at the end. In verbose mode, the summary is
|
||||||
|
detailed, else very brief (in fact, empty if all tests passed).
|
||||||
|
|
||||||
|
Optional keyword arg "optionflags" or's together module constants,
|
||||||
|
and defaults to 0. Possible values (see the docs for details):
|
||||||
|
|
||||||
|
DONT_ACCEPT_TRUE_FOR_1
|
||||||
|
DONT_ACCEPT_BLANKLINE
|
||||||
|
NORMALIZE_WHITESPACE
|
||||||
|
ELLIPSIS
|
||||||
|
IGNORE_EXCEPTION_DETAIL
|
||||||
|
REPORT_UDIFF
|
||||||
|
REPORT_CDIFF
|
||||||
|
REPORT_NDIFF
|
||||||
|
REPORT_ONLY_FIRST_FAILURE
|
||||||
|
|
||||||
|
Optional keyword arg "raise_on_error" raises an exception on the
|
||||||
|
first unexpected exception or failure. This allows failures to be
|
||||||
|
post-mortem debugged.
|
||||||
|
|
||||||
|
Advanced tomfoolery: testmod runs methods of a local instance of
|
||||||
|
class doctest.Tester, then merges the results into (or creates)
|
||||||
|
global Tester instance doctest.master. Methods of doctest.master
|
||||||
|
can be called directly too, if you want to do something unusual.
|
||||||
|
Passing report=0 to testmod is especially useful then, to delay
|
||||||
|
displaying a summary. Invoke doctest.master.summarize(verbose)
|
||||||
|
when you're done fiddling.
|
||||||
|
"""
|
||||||
|
global master
|
||||||
|
|
||||||
|
if package and not module_relative:
|
||||||
|
raise ValueError("Package may only be specified for module-"
|
||||||
|
"relative paths.")
|
||||||
|
|
||||||
|
# Relativize the path
|
||||||
|
if module_relative:
|
||||||
|
package = _normalize_module(package)
|
||||||
|
filename = _module_relative_path(package, filename)
|
||||||
|
|
||||||
|
# If no name was given, then use the file's name.
|
||||||
|
if name is None:
|
||||||
|
name = os.path.split(filename)[-1]
|
||||||
|
|
||||||
|
# Assemble the globals.
|
||||||
|
if globs is None:
|
||||||
|
globs = {}
|
||||||
|
else:
|
||||||
|
globs = globs.copy()
|
||||||
|
if extraglobs is not None:
|
||||||
|
globs.update(extraglobs)
|
||||||
|
|
||||||
|
if raise_on_error:
|
||||||
|
runner = DebugRunner(verbose=verbose, optionflags=optionflags)
|
||||||
|
else:
|
||||||
|
runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
|
||||||
|
|
||||||
|
# Read the file, convert it to a test, and run it.
|
||||||
|
s = open(filename).read()
|
||||||
|
test = DocTestParser().get_doctest(s, globs, name, filename, 0)
|
||||||
|
runner.run(test)
|
||||||
|
|
||||||
|
if report:
|
||||||
|
runner.summarize()
|
||||||
|
|
||||||
|
if master is None:
|
||||||
|
master = runner
|
||||||
|
else:
|
||||||
|
master.merge(runner)
|
||||||
|
|
||||||
|
return runner.failures, runner.tries
|
||||||
|
|
||||||
def run_docstring_examples(f, globs, verbose=False, name="NoName",
|
def run_docstring_examples(f, globs, verbose=False, name="NoName",
|
||||||
compileflags=None, optionflags=0):
|
compileflags=None, optionflags=0):
|
||||||
"""
|
"""
|
||||||
|
@ -2311,52 +2450,59 @@ class DocFileCase(DocTestCase):
|
||||||
% (self._dt_test.name, self._dt_test.filename, err)
|
% (self._dt_test.name, self._dt_test.filename, err)
|
||||||
)
|
)
|
||||||
|
|
||||||
def DocFileTest(path, package=None, globs=None, **options):
|
def DocFileTest(path, module_relative=True, package=None,
|
||||||
name = path.split('/')[-1]
|
globs=None, **options):
|
||||||
|
|
||||||
# Interpret relative paths as relative to the given package's
|
|
||||||
# directory (or the current module, if no package is specified).
|
|
||||||
if not os.path.isabs(path):
|
|
||||||
package = _normalize_module(package)
|
|
||||||
if hasattr(package, '__file__'):
|
|
||||||
# A normal package/module.
|
|
||||||
dir = os.path.split(package.__file__)[0]
|
|
||||||
path = os.path.join(dir, *(path.split('/')))
|
|
||||||
elif package.__name__ == '__main__':
|
|
||||||
# An interactive session.
|
|
||||||
if sys.argv[0] != '':
|
|
||||||
dir = os.path.split(sys.argv[0])[0]
|
|
||||||
path = os.path.join(dir, *(path.split('/')))
|
|
||||||
else:
|
|
||||||
# A module w/o __file__ (this includes builtins)
|
|
||||||
raise ValueError("Can't resolve paths relative to " +
|
|
||||||
"the module %s (it has" % package +
|
|
||||||
"no __file__)")
|
|
||||||
|
|
||||||
doc = open(path).read()
|
|
||||||
|
|
||||||
if globs is None:
|
if globs is None:
|
||||||
globs = {}
|
globs = {}
|
||||||
|
|
||||||
test = DocTestParser().get_doctest(doc, globs, name, path, 0)
|
if package and not module_relative:
|
||||||
|
raise ValueError("Package may only be specified for module-"
|
||||||
|
"relative paths.")
|
||||||
|
|
||||||
|
# Relativize the path.
|
||||||
|
if module_relative:
|
||||||
|
package = _normalize_module(package)
|
||||||
|
path = _module_relative_path(package, path)
|
||||||
|
|
||||||
|
# Find the file and read it.
|
||||||
|
name = os.path.split(path)[-1]
|
||||||
|
|
||||||
|
doc = open(path).read()
|
||||||
|
|
||||||
|
# Convert it to a test, and wrap it in a DocFileCase.
|
||||||
|
test = DocTestParser().get_doctest(doc, globs, name, path, 0)
|
||||||
return DocFileCase(test, **options)
|
return DocFileCase(test, **options)
|
||||||
|
|
||||||
def DocFileSuite(*paths, **kw):
|
def DocFileSuite(*paths, **kw):
|
||||||
"""Creates a suite of doctest files.
|
"""A unittest suite for one or more doctest files.
|
||||||
|
|
||||||
One or more text file paths are given as strings. These should
|
The path to each doctest file is given as a string; the
|
||||||
use "/" characters to separate path segments. Paths are relative
|
interpretation of that string depends on the keyword argument
|
||||||
to the directory of the calling module, or relative to the package
|
"module_relative".
|
||||||
passed as a keyword argument.
|
|
||||||
|
|
||||||
A number of options may be provided as keyword arguments:
|
A number of options may be provided as keyword arguments:
|
||||||
|
|
||||||
|
module_relative
|
||||||
|
If "module_relative" is True, then the given file paths are
|
||||||
|
interpreted as os-independent module-relative paths. By
|
||||||
|
default, these paths are relative to the calling module's
|
||||||
|
directory; but if the "package" argument is specified, then
|
||||||
|
they are relative to that package. To ensure os-independence,
|
||||||
|
"filename" should use "/" characters to separate path
|
||||||
|
segments, and may not be an absolute path (i.e., it may not
|
||||||
|
begin with "/").
|
||||||
|
|
||||||
|
If "module_relative" is False, then the given file paths are
|
||||||
|
interpreted as os-specific paths. These paths may be absolute
|
||||||
|
or relative (to the current working directory).
|
||||||
|
|
||||||
package
|
package
|
||||||
The name of a Python package. Text-file paths will be
|
A Python package or the name of a Python package whose directory
|
||||||
interpreted relative to the directory containing this package.
|
should be used as the base directory for module relative paths.
|
||||||
The package may be supplied as a package object or as a dotted
|
If "package" is not specified, then the calling module's
|
||||||
package name.
|
directory is used as the base directory for module relative
|
||||||
|
filenames. It is an error to specify "package" if
|
||||||
|
"module_relative" is False.
|
||||||
|
|
||||||
setUp
|
setUp
|
||||||
The name of a set-up function. This is called before running the
|
The name of a set-up function. This is called before running the
|
||||||
|
@ -2375,14 +2521,14 @@ def DocFileSuite(*paths, **kw):
|
||||||
|
|
||||||
optionflags
|
optionflags
|
||||||
A set of doctest option flags expressed as an integer.
|
A set of doctest option flags expressed as an integer.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
|
|
||||||
# We do this here so that _normalize_module is called at the right
|
# We do this here so that _normalize_module is called at the right
|
||||||
# level. If it were called in DocFileTest, then this function
|
# level. If it were called in DocFileTest, then this function
|
||||||
# would be the caller and we might guess the package incorrectly.
|
# would be the caller and we might guess the package incorrectly.
|
||||||
kw['package'] = _normalize_module(kw.get('package'))
|
if kw.get('module_relative', True):
|
||||||
|
kw['package'] = _normalize_module(kw.get('package'))
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
suite.addTest(DocFileTest(path, **kw))
|
suite.addTest(DocFileTest(path, **kw))
|
||||||
|
|
|
@ -1829,8 +1829,9 @@ def test_DocFileSuite():
|
||||||
... package=new.module('__main__'))
|
... package=new.module('__main__'))
|
||||||
>>> sys.argv = save_argv
|
>>> sys.argv = save_argv
|
||||||
|
|
||||||
Absolute paths may also be used; they should use the native
|
By setting `module_relative=False`, os-specific paths may be
|
||||||
path separator (*not* '/').
|
used (including absolute paths and paths relative to the
|
||||||
|
working directory):
|
||||||
|
|
||||||
>>> # Get the absolute path of the test package.
|
>>> # Get the absolute path of the test package.
|
||||||
>>> test_doctest_path = os.path.abspath(test.test_doctest.__file__)
|
>>> test_doctest_path = os.path.abspath(test.test_doctest.__file__)
|
||||||
|
@ -1839,10 +1840,17 @@ def test_DocFileSuite():
|
||||||
>>> # Use it to find the absolute path of test_doctest.txt.
|
>>> # Use it to find the absolute path of test_doctest.txt.
|
||||||
>>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt')
|
>>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt')
|
||||||
|
|
||||||
>>> suite = doctest.DocFileSuite(test_file)
|
>>> suite = doctest.DocFileSuite(test_file, module_relative=False)
|
||||||
>>> suite.run(unittest.TestResult())
|
>>> suite.run(unittest.TestResult())
|
||||||
<unittest.TestResult run=1 errors=0 failures=1>
|
<unittest.TestResult run=1 errors=0 failures=1>
|
||||||
|
|
||||||
|
It is an error to specify `package` when `module_relative=False`:
|
||||||
|
|
||||||
|
>>> suite = doctest.DocFileSuite(test_file, module_relative=False,
|
||||||
|
... package='test')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
ValueError: Package may only be specified for module-relative paths.
|
||||||
|
|
||||||
You can specify initial global variables:
|
You can specify initial global variables:
|
||||||
|
|
||||||
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||||||
|
@ -1991,6 +1999,127 @@ def test_unittest_reportflags():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_testfile(): r"""
|
||||||
|
Tests for the `testfile()` function. This function runs all the
|
||||||
|
doctest examples in a given file. In its simple invokation, it is
|
||||||
|
called with the name of a file, which is taken to be relative to the
|
||||||
|
calling module. The return value is (#failures, #tests).
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt') # doctest: +ELLIPSIS
|
||||||
|
**********************************************************************
|
||||||
|
File "...", line 6, in test_doctest.txt
|
||||||
|
Failed example:
|
||||||
|
favorite_color
|
||||||
|
Exception raised:
|
||||||
|
...
|
||||||
|
NameError: name 'favorite_color' is not defined
|
||||||
|
**********************************************************************
|
||||||
|
1 items had failures:
|
||||||
|
1 of 2 in test_doctest.txt
|
||||||
|
***Test Failed*** 1 failures.
|
||||||
|
(1, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
(Note: we'll be clearing doctest.master after each call to
|
||||||
|
`doctest.testfile`, to supress warnings about multiple tests with the
|
||||||
|
same name.)
|
||||||
|
|
||||||
|
Globals may be specified with the `globs` and `extraglobs` parameters:
|
||||||
|
|
||||||
|
>>> globs = {'favorite_color': 'blue'}
|
||||||
|
>>> doctest.testfile('test_doctest.txt', globs=globs)
|
||||||
|
(0, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
>>> extraglobs = {'favorite_color': 'red'}
|
||||||
|
>>> doctest.testfile('test_doctest.txt', globs=globs,
|
||||||
|
... extraglobs=extraglobs) # doctest: +ELLIPSIS
|
||||||
|
**********************************************************************
|
||||||
|
File "...", line 6, in test_doctest.txt
|
||||||
|
Failed example:
|
||||||
|
favorite_color
|
||||||
|
Expected:
|
||||||
|
'blue'
|
||||||
|
Got:
|
||||||
|
'red'
|
||||||
|
**********************************************************************
|
||||||
|
1 items had failures:
|
||||||
|
1 of 2 in test_doctest.txt
|
||||||
|
***Test Failed*** 1 failures.
|
||||||
|
(1, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
The file may be made relative to a given module or package, using the
|
||||||
|
optional `module_relative` parameter:
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt', globs=globs,
|
||||||
|
... module_relative='test')
|
||||||
|
(0, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
Verbosity can be increased with the optional `verbose` paremter:
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt', globs=globs, verbose=True)
|
||||||
|
Trying:
|
||||||
|
favorite_color
|
||||||
|
Expecting:
|
||||||
|
'blue'
|
||||||
|
ok
|
||||||
|
Trying:
|
||||||
|
if 1:
|
||||||
|
print 'a'
|
||||||
|
print
|
||||||
|
print 'b'
|
||||||
|
Expecting:
|
||||||
|
a
|
||||||
|
<BLANKLINE>
|
||||||
|
b
|
||||||
|
ok
|
||||||
|
1 items passed all tests:
|
||||||
|
2 tests in test_doctest.txt
|
||||||
|
2 tests in 1 items.
|
||||||
|
2 passed and 0 failed.
|
||||||
|
Test passed.
|
||||||
|
(0, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
The name of the test may be specified with the optional `name`
|
||||||
|
parameter:
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt', name='newname')
|
||||||
|
... # doctest: +ELLIPSIS
|
||||||
|
**********************************************************************
|
||||||
|
File "...", line 6, in newname
|
||||||
|
...
|
||||||
|
(1, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
The summary report may be supressed with the optional `report`
|
||||||
|
parameter:
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt', report=False)
|
||||||
|
... # doctest: +ELLIPSIS
|
||||||
|
**********************************************************************
|
||||||
|
File "...", line 6, in test_doctest.txt
|
||||||
|
Failed example:
|
||||||
|
favorite_color
|
||||||
|
Exception raised:
|
||||||
|
...
|
||||||
|
NameError: name 'favorite_color' is not defined
|
||||||
|
(1, 2)
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
|
||||||
|
The optional keyword argument `raise_on_error` can be used to raise an
|
||||||
|
exception on the first error (which may be useful for postmortem
|
||||||
|
debugging):
|
||||||
|
|
||||||
|
>>> doctest.testfile('test_doctest.txt', raise_on_error=True)
|
||||||
|
... # doctest: +ELLIPSIS
|
||||||
|
Traceback (most recent call last):
|
||||||
|
UnexpectedException: ...
|
||||||
|
>>> doctest.master = None # Reset master.
|
||||||
|
"""
|
||||||
|
|
||||||
# old_test1, ... used to live in doctest.py, but cluttered it. Note
|
# old_test1, ... used to live in doctest.py, but cluttered it. Note
|
||||||
# that these use the deprecated doctest.Tester, so should go away (or
|
# that these use the deprecated doctest.Tester, so should go away (or
|
||||||
# be rewritten) someday.
|
# be rewritten) someday.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue