summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorEdward Loper <edloper@gradient.cis.upenn.edu>2004-09-19 17:19:33 (GMT)
committerEdward Loper <edloper@gradient.cis.upenn.edu>2004-09-19 17:19:33 (GMT)
commit052d0cd291ca0f447fe7c8bf5ad30cbb1ad8de8f (patch)
treec0fa5c54ea09988176a0bda38677ae513c7cbdfd /Lib
parent1be1a79ff996cda1c5f780d2409406d6ba88b36d (diff)
downloadcpython-052d0cd291ca0f447fe7c8bf5ad30cbb1ad8de8f.zip
cpython-052d0cd291ca0f447fe7c8bf5ad30cbb1ad8de8f.tar.gz
cpython-052d0cd291ca0f447fe7c8bf5ad30cbb1ad8de8f.tar.bz2
- 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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/doctest.py220
-rw-r--r--Lib/test/test_doctest.py135
2 files changed, 315 insertions, 40 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index 3414f4a..a34b0a1 100644
--- a/Lib/doctest.py
+++ b/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]
+def DocFileTest(path, module_relative=True, package=None,
+ globs=None, **options):
+ if globs is None:
+ globs = {}
- # 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):
+ 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)
- 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__)")
+ path = _module_relative_path(package, path)
- doc = open(path).read()
+ # Find the file and read it.
+ name = os.path.split(path)[-1]
- if globs is None:
- globs = {}
+ 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))
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 8c96b21..219540a 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -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.