[Zope3-checkins] SVN: Zope3/trunk/src/zope/testing/doctest.py
Integrated doctest from Python CVS head
Jim Fulton
jim at zope.com
Mon Oct 25 17:48:26 EDT 2004
Log message for revision 28245:
Integrated doctest from Python CVS head
Changed:
U Zope3/trunk/src/zope/testing/doctest.py
-=-
Modified: Zope3/trunk/src/zope/testing/doctest.py
===================================================================
--- Zope3/trunk/src/zope/testing/doctest.py 2004-10-25 21:24:15 UTC (rev 28244)
+++ Zope3/trunk/src/zope/testing/doctest.py 2004-10-25 21:48:26 UTC (rev 28245)
@@ -8,13 +8,11 @@
r"""Module doctest -- a framework for running examples in docstrings.
-NORMAL USAGE
-
In simplest use, end each module M to be tested with:
def _test():
import doctest
- return doctest.testmod()
+ doctest.testmod()
if __name__ == "__main__":
_test()
@@ -40,133 +38,13 @@
it by passing "verbose=False". In either of those cases, sys.argv is not
examined by testmod.
-In any case, testmod returns a 2-tuple of ints (f, t), where f is the
-number of docstring examples that failed and t is the total number of
-docstring examples attempted.
-
There are a variety of other ways to run doctests, including integration
with the unittest framework, and support for running non-Python text
files containing doctests. There are also many ways to override parts
of doctest's default behaviors. See the Library Reference Manual for
details.
+"""
-
-WHICH DOCSTRINGS ARE EXAMINED?
-
-+ M.__doc__.
-
-+ f.__doc__ for all functions f in M.__dict__.values(), except those
- defined in other modules.
-
-+ C.__doc__ for all classes C in M.__dict__.values(), except those
- defined in other modules.
-
-+ If M.__test__ exists and "is true", it must be a dict, and
- each entry maps a (string) name to a function object, class object, or
- string. Function and class object docstrings found from M.__test__
- are searched, and strings are searched directly as if they were docstrings.
- In output, a key K in M.__test__ appears with name
- <name of M>.__test__.K
-
-Any classes found are recursively searched similarly, to test docstrings in
-their contained methods and nested classes.
-
-
-WHAT'S THE EXECUTION CONTEXT?
-
-By default, each time testmod finds a docstring to test, it uses a *copy*
-of M's globals (so that running tests on a module doesn't change the
-module's real globals, and so that one test in M can't leave behind crumbs
-that accidentally allow another test to work). This means examples can
-freely use any names defined at top-level in M. It also means that sloppy
-imports (see above) can cause examples in external docstrings to use
-globals inappropriate for them.
-
-You can force use of your own dict as the execution context by passing
-"globs=your_dict" to testmod instead. Presumably this would be a copy of
-M.__dict__ merged with the globals from other imported modules.
-
-
-WHAT ABOUT EXCEPTIONS?
-
-No problem, as long as the only output generated by the example is the
-traceback itself. For example:
-
- >>> [1, 2, 3].remove(42)
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- ValueError: list.remove(x): x not in list
- >>>
-
-Note that only the exception type and value are compared.
-
-
-SO WHAT DOES A DOCTEST EXAMPLE LOOK LIKE ALREADY!?
-
-Oh ya. It's easy! In most cases a copy-and-paste of an interactive
-console session works fine -- just make sure the leading whitespace is
-rigidly consistent (you can mix tabs and spaces if you're too lazy to do it
-right, but doctest is not in the business of guessing what you think a tab
-means).
-
- >>> # comments are ignored
- >>> x = 12
- >>> x
- 12
- >>> if x == 13:
- ... print "yes"
- ... else:
- ... print "no"
- ... print "NO"
- ... print "NO!!!"
- ...
- no
- NO
- NO!!!
- >>>
-
-Any expected output must immediately follow the final ">>>" or "..." line
-containing the code, and the expected output (if any) extends to the next
-">>>" or all-whitespace line. That's it.
-
-Bummers:
-
-+ Output to stdout is captured, but not output to stderr (exception
- tracebacks are captured via a different means).
-
-+ If you continue a line via backslashing in an interactive session,
- or for any other reason use a backslash, you should use a raw
- docstring, which will preserve your backslahses exactly as you type
- them:
-
- >>> def f(x):
- ... r'''Backslashes in a raw docstring: m\n'''
- >>> print f.__doc__
- Backslashes in a raw docstring: m\n
-
- Otherwise, the backslash will be interpreted as part of the string.
- E.g., the "\n" above would be interpreted as a newline character.
- Alternatively, you can double each backslash in the doctest version
- (and not use a raw string):
-
- >>> def f(x):
- ... '''Backslashes in a raw docstring: m\\n'''
- >>> print f.__doc__
- Backslashes in a raw docstring: m\n
-
-The starting column doesn't matter:
-
->>> assert "Easy!"
- >>> import math
- >>> math.floor(1.9)
- 1.0
-
-and as many leading whitespace characters are stripped from the expected
-output as appeared in the initial ">>>" line that triggered it.
-
-If you execute this very file, the examples above will be found and
-executed.
-"""
__docformat__ = 'reStructuredText en'
__all__ = [
@@ -176,10 +54,13 @@
'DONT_ACCEPT_BLANKLINE',
'NORMALIZE_WHITESPACE',
'ELLIPSIS',
+ 'IGNORE_EXCEPTION_DETAIL',
+ 'COMPARISON_FLAGS',
'REPORT_UDIFF',
'REPORT_CDIFF',
'REPORT_NDIFF',
'REPORT_ONLY_FIRST_FAILURE',
+ 'REPORTING_FLAGS',
# 1. Utility Functions
'is_private',
# 2. Example & DocTest
@@ -197,20 +78,18 @@
'DebugRunner',
# 6. Test Functions
'testmod',
+ 'testfile',
'run_docstring_examples',
# 7. Tester
'Tester',
# 8. Unittest Support
- 'DocTestCase',
'DocTestSuite',
- 'DocFileCase',
- 'DocFileTest',
'DocFileSuite',
+ 'set_unittest_reportflags',
# 9. Debugging Support
'script_from_examples',
'testsource',
'debug_src',
- 'debug_script',
'debug',
]
@@ -248,6 +127,7 @@
# +---------+
# Option constants.
+
OPTIONFLAGS_BY_NAME = {}
def register_optionflag(name):
flag = 1 << len(OPTIONFLAGS_BY_NAME)
@@ -258,11 +138,24 @@
DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
ELLIPSIS = register_optionflag('ELLIPSIS')
+IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
+
+COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
+ DONT_ACCEPT_BLANKLINE |
+ NORMALIZE_WHITESPACE |
+ ELLIPSIS |
+ IGNORE_EXCEPTION_DETAIL)
+
REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
+REPORTING_FLAGS = (REPORT_UDIFF |
+ REPORT_CDIFF |
+ REPORT_NDIFF |
+ REPORT_ONLY_FIRST_FAILURE)
+
# Special string markers for use in `want` strings:
BLANKLINE_MARKER = '<BLANKLINE>'
ELLIPSIS_MARKER = '...'
@@ -461,6 +354,31 @@
# Restore stdout.
sys.stdout = save_stdout
+# [XX] Normalize with respect to os.path.pardir?
+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
######################################################################
@@ -831,7 +749,7 @@
"""
def __init__(self, verbose=False, parser=DocTestParser(),
- recurse=True, _namefilter=None):
+ recurse=True, _namefilter=None, exclude_empty=True):
"""
Create a new doctest finder.
@@ -843,10 +761,14 @@
If the optional argument `recurse` is false, then `find` will
only examine the given object, and not any contained objects.
+
+ If the optional argument `exclude_empty` is false, then `find`
+ will include tests for objects with empty docstrings.
"""
self._parser = parser
self._verbose = verbose
self._recurse = recurse
+ self._exclude_empty = exclude_empty
# _namefilter is undocumented, and exists only for temporary backward-
# compatibility support of testmod's deprecated isprivate mess.
self._namefilter = _namefilter
@@ -1002,7 +924,7 @@
"must be strings, functions, methods, "
"classes, or modules: %r" %
(type(val),))
- valname = '%s.%s' % (name, valname)
+ valname = '%s.__test__.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines,
globs, seen)
@@ -1038,18 +960,21 @@
else:
try:
if obj.__doc__ is None:
- return None
- docstring = str(obj.__doc__)
+ docstring = ''
+ else:
+ docstring = obj.__doc__
+ if not isinstance(docstring, basestring):
+ docstring = str(docstring)
except (TypeError, AttributeError):
- return None
+ docstring = ''
+ # Find the docstring's location in the file.
+ lineno = self._find_lineno(obj, source_lines)
+
# Don't bother if the docstring is empty.
- if not docstring:
+ if self._exclude_empty and not docstring:
return None
- # Find the docstring's location in the file.
- lineno = self._find_lineno(obj, source_lines)
-
# Return a DocTest for this object.
if module is None:
filename = None
@@ -1279,6 +1204,10 @@
# to modify them).
original_optionflags = self.optionflags
+ SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
+
+ check = self._checker.check_output
+
# Process each example.
for examplenum, example in enumerate(test.examples):
@@ -1323,46 +1252,54 @@
got = self._fakeout.getvalue() # the actual output
self._fakeout.truncate(0)
+ outcome = FAILURE # guilty until proved innocent or insane
# If the example executed without raising any exceptions,
- # then verify its output and report its outcome.
+ # verify its output.
if exception is None:
- if self._checker.check_output(example.want, got,
- self.optionflags):
- if not quiet:
- self.report_success(out, test, example, got)
- else:
- if not quiet:
- self.report_failure(out, test, example, got)
- failures += 1
+ if check(example.want, got, self.optionflags):
+ outcome = SUCCESS
- # If the example raised an exception, then check if it was
- # expected.
+ # The example raised an exception: check if it was expected.
else:
exc_info = sys.exc_info()
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
+ if not quiet:
+ got += _exception_traceback(exc_info)
- # If `example.exc_msg` is None, then we weren't
- # expecting an exception.
+ # If `example.exc_msg` is None, then we weren't expecting
+ # an exception.
if example.exc_msg is None:
- if not quiet:
- self.report_unexpected_exception(out, test, example,
- exc_info)
- failures += 1
- # If `example.exc_msg` matches the actual exception
- # message (`exc_msg`), then the example succeeds.
- elif (self._checker.check_output(example.exc_msg, exc_msg,
- self.optionflags)):
- if not quiet:
- got += _exception_traceback(exc_info)
- self.report_success(out, test, example, got)
- # Otherwise, the example fails.
- else:
- if not quiet:
- got += _exception_traceback(exc_info)
- self.report_failure(out, test, example, got)
- failures += 1
+ outcome = BOOM
+ # We expected an exception: see whether it matches.
+ elif check(example.exc_msg, exc_msg, self.optionflags):
+ outcome = SUCCESS
+
+ # Another chance if they didn't care about the detail.
+ elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
+ m1 = re.match(r'[^:]*:', example.exc_msg)
+ m2 = re.match(r'[^:]*:', exc_msg)
+ if m1 and m2 and check(m1.group(0), m2.group(0),
+ self.optionflags):
+ outcome = SUCCESS
+
+ # Report the outcome.
+ if outcome is SUCCESS:
+ if not quiet:
+ self.report_success(out, test, example, got)
+ elif outcome is FAILURE:
+ if not quiet:
+ self.report_failure(out, test, example, got)
+ failures += 1
+ elif outcome is BOOM:
+ if not quiet:
+ self.report_unexpected_exception(out, test, example,
+ exc_info)
+ failures += 1
+ else:
+ assert False, ("unknown outcome", outcome)
+
# Restore the option flags (in case they were modified)
self.optionflags = original_optionflags
@@ -1502,6 +1439,20 @@
print "Test passed."
return totalf, totalt
+ #/////////////////////////////////////////////////////////////////
+ # Backward compatibility cruft to maintain doctest.master.
+ #/////////////////////////////////////////////////////////////////
+ def merge(self, other):
+ d = self._name2ft
+ for name, (f, t) in other._name2ft.items():
+ if name in d:
+ print "*** DocTestRunner.merge: '" + name + "' in both" \
+ " testers; summing outcomes."
+ f2, t2 = d[name]
+ f = f + f2
+ t = t + t2
+ d[name] = f, t
+
class OutputChecker:
"""
A class used to check the whether the actual output from a doctest
@@ -1781,11 +1732,16 @@
######################################################################
# These should be backwards compatible.
+# For backward compatibility, a global instance of a DocTestRunner
+# class, updated by testmod.
+master = None
+
def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
report=True, optionflags=0, extraglobs=None,
- raise_on_error=False):
+ raise_on_error=False, exclude_empty=False):
"""m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None
+ report=True, optionflags=0, extraglobs=None, raise_on_error=False,
+ exclude_empty=False
Test examples in docstrings in functions and classes reachable
from module m (or the current module if m is not supplied), starting
@@ -1828,6 +1784,7 @@
DONT_ACCEPT_BLANKLINE
NORMALIZE_WHITESPACE
ELLIPSIS
+ IGNORE_EXCEPTION_DETAIL
REPORT_UDIFF
REPORT_CDIFF
REPORT_NDIFF
@@ -1843,9 +1800,7 @@
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
@@ -1854,6 +1809,8 @@
displaying a summary. Invoke doctest.master.summarize(verbose)
when you're done fiddling.
"""
+ global master
+
if isprivate is not None:
warnings.warn("the isprivate argument is deprecated; "
"examine DocTestFinder.find() lists instead",
@@ -1875,7 +1832,7 @@
name = m.__name__
# Find, parse, and run all tests in the given module.
- finder = DocTestFinder(_namefilter=isprivate)
+ finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty)
if raise_on_error:
runner = DebugRunner(verbose=verbose, optionflags=optionflags)
@@ -1888,8 +1845,131 @@
if report:
runner.summarize()
+ if master is None:
+ master = runner
+ else:
+ master.merge(runner)
+
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, parser=DocTestParser()):
+ """
+ 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 test; by default
+ use the file's basename.
+
+ 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.
+
+ Optional keyword arg "parser" specifies a DocTestParser (or
+ subclass) that should be used to extract tests from the files.
+
+ 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.basename(filename)
+
+ # 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 = parser.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):
"""
@@ -1928,8 +2008,7 @@
DeprecationWarning, stacklevel=2)
if mod is None and globs is None:
raise TypeError("Tester.__init__: must specify mod or globs")
- from inspect import ismodule as _ismodule
- if mod is not None and not _ismodule(mod):
+ if mod is not None and not inspect.ismodule(mod):
raise TypeError("Tester.__init__: mod must be a module; %r" %
(mod,))
if globs is None:
@@ -1973,35 +2052,22 @@
import new
m = new.module(name)
m.__test__ = d
- return self.rundoc(m, name, module)
+ return self.rundoc(m, name)
def summarize(self, verbose=None):
return self.testrunner.summarize(verbose)
def merge(self, other):
- d = self.testrunner._name2ft
- for name, (f, t) in other.testrunner._name2ft.items():
- if name in d:
- print "*** Tester.merge: '" + name + "' in both" \
- " testers; summing outcomes."
- f2, t2 = d[name]
- f = f + f2
- t = t + t2
- d[name] = f, t
+ self.testrunner.merge(other.testrunner)
######################################################################
## 8. Unittest Support
######################################################################
_unittest_reportflags = 0
-valid_unittest_reportflags = (
- REPORT_CDIFF |
- REPORT_UDIFF |
- REPORT_NDIFF |
- REPORT_ONLY_FIRST_FAILURE
- )
+
def set_unittest_reportflags(flags):
- """Sets the unit test option flags
+ """Sets the unittest option flags.
The old flag is returned so that a runner could restore the old
value if it wished to:
@@ -2015,42 +2081,26 @@
>>> doctest._unittest_reportflags == (REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE)
True
-
+
Only reporting flags can be set:
>>> set_unittest_reportflags(ELLIPSIS)
Traceback (most recent call last):
...
- ValueError: ('Invalid flags passed', 8)
+ ValueError: ('Only reporting flags allowed', 8)
>>> set_unittest_reportflags(old) == (REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE)
True
-
"""
+ global _unittest_reportflags
- # extract the valid reporting flags:
- rflags = flags & valid_unittest_reportflags
-
- # Now remove these flags from the given flags
- nrflags = flags ^ rflags
-
- if nrflags:
- raise ValueError("Invalid flags passed", flags)
-
- global _unittest_reportflags
+ if (flags & REPORTING_FLAGS) != flags:
+ raise ValueError("Only reporting flags allowed", flags)
old = _unittest_reportflags
_unittest_reportflags = flags
return old
-
-class FakeModule:
- """Fake module created by tests
- """
-
- def __init__(self, dict, name):
- self.__dict__ = dict
- self.__name__ = name
class DocTestCase(unittest.TestCase):
@@ -2066,7 +2116,7 @@
def setUp(self):
test = self._dt_test
-
+
if self._dt_setUp is not None:
self._dt_setUp(test)
@@ -2083,12 +2133,12 @@
old = sys.stdout
new = StringIO()
optionflags = self._dt_optionflags
-
- if not (optionflags & valid_unittest_reportflags):
+
+ if not (optionflags & REPORTING_FLAGS):
# The option flags don't include any reporting flags,
# so add the default reporting flags
optionflags |= _unittest_reportflags
-
+
runner = DocTestRunner(optionflags=optionflags,
checker=self._dt_checker, verbose=False)
@@ -2216,20 +2266,14 @@
A number of options may be provided as keyword arguments:
- 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.
-
setUp
- The name of a set-up function. This is called before running the
+ A set-up function. This is called before running the
tests in each file. The setUp function will be passed a DocTest
object. The setUp function can access the test globals as the
globs attribute of the test passed.
tearDown
- The name of a tear-down function. This is called after running the
+ A tear-down function. This is called after running the
tests in each file. The tearDown function will be passed a DocTest
object. The tearDown function can access the test globals as the
globs attribute of the test passed.
@@ -2281,44 +2325,67 @@
% (self._dt_test.name, self._dt_test.filename, err)
)
-def DocFileTest(path, package=None, globs=None, **options):
- package = _normalize_module(package)
- name = path.split('/')[-1]
- dir = os.path.split(package.__file__)[0]
- path = os.path.join(dir, *(path.split('/')))
- doc = open(path).read()
-
+def DocFileTest(path, module_relative=True, package=None,
+ globs=None, parser=DocTestParser(), **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.basename(path)
+ doc = open(path).read()
+
+ # Convert it to a test, and wrap it in a DocFileCase.
+ test = parser.get_doctest(doc, globs, name, path, 0)
return DocFileCase(test, **options)
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
- 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.
+ 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
+ A set-up function. This is called before running the
tests in each file. The setUp function will be passed a DocTest
object. The setUp function can access the test globals as the
globs attribute of the test passed.
tearDown
- The name of a tear-down function. This is called after running the
+ A tear-down function. This is called after running the
tests in each file. The tearDown function will be passed a DocTest
object. The tearDown function can access the test globals as the
globs attribute of the test passed.
@@ -2327,15 +2394,19 @@
A dictionary containing initial global variables for the tests.
optionflags
- A set of doctest option flags expressed as an integer.
-
+ A set of doctest option flags expressed as an integer.
+
+ parser
+ A DocTestParser (or subclass) that should be used to extract
+ tests from the files.
"""
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))
More information about the Zope3-Checkins
mailing list