mirror of
https://github.com/python/cpython.git
synced 2025-08-28 12:45:07 +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
224
Lib/doctest.py
224
Lib/doctest.py
|
@ -200,6 +200,7 @@ __all__ = [
|
|||
'DebugRunner',
|
||||
# 6. Test Functions
|
||||
'testmod',
|
||||
'testfile',
|
||||
'run_docstring_examples',
|
||||
# 7. Tester
|
||||
'Tester',
|
||||
|
@ -478,6 +479,30 @@ class _OutputRedirectingPdb(pdb.Pdb):
|
|||
# Restore 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
|
||||
######################################################################
|
||||
|
@ -1881,6 +1906,7 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
|
|||
DONT_ACCEPT_BLANKLINE
|
||||
NORMALIZE_WHITESPACE
|
||||
ELLIPSIS
|
||||
IGNORE_EXCEPTION_DETAIL
|
||||
REPORT_UDIFF
|
||||
REPORT_CDIFF
|
||||
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
|
||||
set to doctest.is_private to skip over functions marked as private
|
||||
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
|
||||
class doctest.Tester, then merges the results into (or creates)
|
||||
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
|
||||
|
||||
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",
|
||||
compileflags=None, optionflags=0):
|
||||
"""
|
||||
|
@ -2311,52 +2450,59 @@ class DocFileCase(DocTestCase):
|
|||
% (self._dt_test.name, self._dt_test.filename, err)
|
||||
)
|
||||
|
||||
def DocFileTest(path, package=None, globs=None, **options):
|
||||
name = path.split('/')[-1]
|
||||
|
||||
# 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()
|
||||
|
||||
def DocFileTest(path, module_relative=True, package=None,
|
||||
globs=None, **options):
|
||||
if globs is None:
|
||||
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)
|
||||
|
||||
def DocFileSuite(*paths, **kw):
|
||||
"""Creates a suite of doctest files.
|
||||
|
||||
One or more text file paths are given as strings. These should
|
||||
use "/" characters to separate path segments. Paths are relative
|
||||
to the directory of the calling module, or relative to the package
|
||||
passed as a keyword argument.
|
||||
"""A unittest suite for one or more doctest files.
|
||||
|
||||
The path to each doctest file is given as a string; the
|
||||
interpretation of that string depends on the keyword argument
|
||||
"module_relative".
|
||||
|
||||
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
|
||||
The name of a Python package. Text-file paths will be
|
||||
interpreted relative to the directory containing this package.
|
||||
The package may be supplied as a package object or as a dotted
|
||||
package name.
|
||||
A Python package or the name of a Python package whose directory
|
||||
should be used as the base directory for module relative paths.
|
||||
If "package" is not 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.
|
||||
|
||||
setUp
|
||||
The name of a set-up function. This is called before running the
|
||||
|
@ -2375,14 +2521,14 @@ def DocFileSuite(*paths, **kw):
|
|||
|
||||
optionflags
|
||||
A set of doctest option flags expressed as an integer.
|
||||
|
||||
"""
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
# We do this here so that _normalize_module is called at the right
|
||||
# level. If it were called in DocFileTest, then this function
|
||||
# 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:
|
||||
suite.addTest(DocFileTest(path, **kw))
|
||||
|
|
|
@ -1829,8 +1829,9 @@ def test_DocFileSuite():
|
|||
... package=new.module('__main__'))
|
||||
>>> sys.argv = save_argv
|
||||
|
||||
Absolute paths may also be used; they should use the native
|
||||
path separator (*not* '/').
|
||||
By setting `module_relative=False`, os-specific paths may be
|
||||
used (including absolute paths and paths relative to the
|
||||
working directory):
|
||||
|
||||
>>> # Get the absolute path of the test package.
|
||||
>>> 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.
|
||||
>>> 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())
|
||||
<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:
|
||||
|
||||
>>> 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
|
||||
# that these use the deprecated doctest.Tester, so should go away (or
|
||||
# be rewritten) someday.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue