summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/doctest.py141
-rw-r--r--Lib/test/test_doctest.py116
2 files changed, 235 insertions, 22 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index c27dd05..67907a5 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -179,6 +179,7 @@ __all__ = [
'REPORT_UDIFF',
'REPORT_CDIFF',
'REPORT_NDIFF',
+ 'REPORT_ONLY_FIRST_FAILURE',
# 1. Utility Functions
'is_private',
# 2. Example & DocTest
@@ -1991,6 +1992,65 @@ class Tester:
## 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
+
+ The old flag is returned so that a runner could restore the old
+ value if it wished to:
+
+ >>> old = _unittest_reportflags
+ >>> set_unittest_reportflags(REPORT_NDIFF |
+ ... REPORT_ONLY_FIRST_FAILURE) == old
+ True
+
+ >>> import doctest
+ >>> 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)
+
+ >>> set_unittest_reportflags(old) == (REPORT_NDIFF |
+ ... REPORT_ONLY_FIRST_FAILURE)
+ True
+
+ """
+
+ # 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
+ 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):
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
@@ -2004,23 +2064,37 @@ class DocTestCase(unittest.TestCase):
self._dt_tearDown = tearDown
def setUp(self):
+ test = self._dt_test
+
if self._dt_setUp is not None:
- self._dt_setUp()
+ self._dt_setUp(test)
def tearDown(self):
+ test = self._dt_test
+
if self._dt_tearDown is not None:
- self._dt_tearDown()
+ self._dt_tearDown(test)
+
+ test.globs.clear()
def runTest(self):
test = self._dt_test
old = sys.stdout
new = StringIO()
- runner = DocTestRunner(optionflags=self._dt_optionflags,
+ optionflags = self._dt_optionflags
+
+ if not (optionflags & valid_unittest_reportflags):
+ # 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)
try:
runner.DIVIDER = "-"*70
- failures, tries = runner.run(test, out=new.write)
+ failures, tries = runner.run(
+ test, out=new.write, clear_globs=False)
finally:
sys.stdout = old
@@ -2105,9 +2179,11 @@ class DocTestCase(unittest.TestCase):
"""
+ self.setUp()
runner = DebugRunner(optionflags=self._dt_optionflags,
checker=self._dt_checker, verbose=False)
runner.run(self._dt_test)
+ self.tearDown()
def id(self):
return self._dt_test.name
@@ -2121,10 +2197,8 @@ class DocTestCase(unittest.TestCase):
def shortDescription(self):
return "Doctest: " + self._dt_test.name
-def DocTestSuite(module=None, globs=None, extraglobs=None,
- optionflags=0, test_finder=None,
- setUp=lambda: None, tearDown=lambda: None,
- checker=None):
+def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
+ **options):
"""
Convert doctest tests for a module to a unittest test suite.
@@ -2138,6 +2212,32 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
can be either a module or a module name.
If no argument is given, the calling module is used.
+
+ 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
+ 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
+ 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.
+
+ globs
+ A dictionary containing initial global variables for the tests.
+
+ optionflags
+ A set of doctest option flags expressed as an integer.
"""
if test_finder is None:
@@ -2147,7 +2247,9 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
if globs is None:
globs = module.__dict__
- if not tests: # [XX] why do we want to do this?
+ if not tests:
+ # Why do we want to do this? Because it reveals a bug that might
+ # otherwise be hidden.
raise ValueError(module, "has no tests")
tests.sort()
@@ -2160,8 +2262,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
test.filename = filename
- suite.addTest(DocTestCase(test, optionflags, setUp, tearDown,
- checker))
+ suite.addTest(DocTestCase(test, **options))
return suite
@@ -2179,9 +2280,7 @@ class DocFileCase(DocTestCase):
% (self._dt_test.name, self._dt_test.filename, err)
)
-def DocFileTest(path, package=None, globs=None,
- setUp=None, tearDown=None,
- optionflags=0):
+def DocFileTest(path, package=None, globs=None, **options):
package = _normalize_module(package)
name = path.split('/')[-1]
dir = os.path.split(package.__file__)[0]
@@ -2193,7 +2292,7 @@ def DocFileTest(path, package=None, globs=None,
test = DocTestParser().get_doctest(doc, globs, name, path, 0)
- return DocFileCase(test, optionflags, setUp, tearDown)
+ return DocFileCase(test, **options)
def DocFileSuite(*paths, **kw):
"""Creates a suite of doctest files.
@@ -2213,14 +2312,22 @@ def DocFileSuite(*paths, **kw):
setUp
The name of a set-up function. This is called before running the
- tests in each file.
+ 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
- tests in each file.
+ 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.
globs
A dictionary containing initial global variables for the tests.
+
+ optionflags
+ A set of doctest option flags expressed as an integer.
+
"""
suite = unittest.TestSuite()
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 2436644..53798ab 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -1653,11 +1653,11 @@ def test_DocTestSuite():
You can supply setUp and tearDown functions:
- >>> def setUp():
+ >>> def setUp(t):
... import test.test_doctest
... test.test_doctest.sillySetup = True
- >>> def tearDown():
+ >>> def tearDown(t):
... import test.test_doctest
... del test.test_doctest.sillySetup
@@ -1676,6 +1676,21 @@ def test_DocTestSuite():
...
AttributeError: 'module' object has no attribute 'sillySetup'
+ The setUp and tearDown funtions are passed test objects. Here
+ we'll use the setUp function to supply the missing variable y:
+
+ >>> def setUp(test):
+ ... test.globs['y'] = 1
+
+ >>> suite = doctest.DocTestSuite('test.sample_doctest', setUp=setUp)
+ >>> suite.run(unittest.TestResult())
+ <unittest.TestResult run=9 errors=0 failures=3>
+
+ Here, we didn't need to use a tearDown function because we
+ modified the test globals, which are a copy of the
+ sample_doctest module dictionary. The test globals are
+ automatically cleared for us after a test.
+
Finally, you can provide an alternate test finder. Here we'll
use a custom test_finder to to run just the test named bar.
However, the test in the module docstring, and the two tests
@@ -1744,11 +1759,11 @@ def test_DocFileSuite():
You can supply setUp and teatDoen functions:
- >>> def setUp():
+ >>> def setUp(t):
... import test.test_doctest
... test.test_doctest.sillySetup = True
- >>> def tearDown():
+ >>> def tearDown(t):
... import test.test_doctest
... del test.test_doctest.sillySetup
@@ -1768,7 +1783,22 @@ def test_DocFileSuite():
...
AttributeError: 'module' object has no attribute 'sillySetup'
- """
+ The setUp and tearDown funtions are passed test objects.
+ Here, we'll use a setUp function to set the favorite color in
+ test_doctest.txt:
+
+ >>> def setUp(test):
+ ... test.globs['favorite_color'] = 'blue'
+
+ >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp)
+ >>> suite.run(unittest.TestResult())
+ <unittest.TestResult run=1 errors=0 failures=0>
+
+ Here, we didn't need to use a tearDown function because we
+ modified the test globals. The test globals are
+ automatically cleared for us after a test.
+
+ """
def test_trailing_space_in_test():
"""
@@ -1779,6 +1809,82 @@ def test_trailing_space_in_test():
foo \n
"""
+
+def test_unittest_reportflags():
+ """Default unittest reporting flags can be set to control reporting
+
+ Here, we'll set the REPORT_ONLY_FIRST_FAILURE option so we see
+ only the first failure of each test. First, we'll look at the
+ output without the flag. The file test_doctest.txt file has two
+ tests. They both fail if blank lines are disabled:
+
+ >>> suite = doctest.DocFileSuite('test_doctest.txt',
+ ... optionflags=doctest.DONT_ACCEPT_BLANKLINE)
+ >>> import unittest
+ >>> result = suite.run(unittest.TestResult())
+ >>> print result.failures[0][1] # doctest: +ELLIPSIS
+ Traceback ...
+ Failed example:
+ favorite_color
+ ...
+ Failed example:
+ if 1:
+ ...
+
+ Note that we see both failures displayed.
+
+ >>> old = doctest.set_unittest_reportflags(
+ ... doctest.REPORT_ONLY_FIRST_FAILURE)
+
+ Now, when we run the test:
+
+ >>> result = suite.run(unittest.TestResult())
+ >>> print result.failures[0][1] # doctest: +ELLIPSIS
+ Traceback ...
+ Failed example:
+ favorite_color
+ Exception raised:
+ ...
+ NameError: name 'favorite_color' is not defined
+ <BLANKLINE>
+ <BLANKLINE>
+
+ We get only the first failure.
+
+ If we give any reporting options when we set up the tests,
+ however:
+
+ >>> suite = doctest.DocFileSuite('test_doctest.txt',
+ ... optionflags=doctest.DONT_ACCEPT_BLANKLINE | doctest.REPORT_NDIFF)
+
+ Then the default eporting options are ignored:
+
+ >>> result = suite.run(unittest.TestResult())
+ >>> print result.failures[0][1] # doctest: +ELLIPSIS
+ Traceback ...
+ Failed example:
+ favorite_color
+ ...
+ Failed example:
+ if 1:
+ print 'a'
+ print
+ print 'b'
+ Differences (ndiff with -expected +actual):
+ a
+ - <BLANKLINE>
+ +
+ b
+ <BLANKLINE>
+ <BLANKLINE>
+
+
+ Test runners can restore the formatting flags after they run:
+
+ >>> ignored = doctest.set_unittest_reportflags(old)
+
+ """
+
# 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.