Commit 24dfd92a authored by Jim Fulton's avatar Jim Fulton

- setUp and tearDown functions are now passed the test object

- Added a set_unittest_reportflags to set default reporting flags used
  when running doctests under unittest control.
parent 7336748e
...@@ -179,6 +179,7 @@ __all__ = [ ...@@ -179,6 +179,7 @@ __all__ = [
'REPORT_UDIFF', 'REPORT_UDIFF',
'REPORT_CDIFF', 'REPORT_CDIFF',
'REPORT_NDIFF', 'REPORT_NDIFF',
'REPORT_ONLY_FIRST_FAILURE',
# 1. Utility Functions # 1. Utility Functions
'is_private', 'is_private',
# 2. Example & DocTest # 2. Example & DocTest
...@@ -1991,6 +1992,65 @@ class Tester: ...@@ -1991,6 +1992,65 @@ class Tester:
## 8. Unittest Support ## 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): class DocTestCase(unittest.TestCase):
def __init__(self, test, optionflags=0, setUp=None, tearDown=None, def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
...@@ -2004,23 +2064,37 @@ class DocTestCase(unittest.TestCase): ...@@ -2004,23 +2064,37 @@ class DocTestCase(unittest.TestCase):
self._dt_tearDown = tearDown self._dt_tearDown = tearDown
def setUp(self): def setUp(self):
test = self._dt_test
if self._dt_setUp is not None: if self._dt_setUp is not None:
self._dt_setUp() self._dt_setUp(test)
def tearDown(self): def tearDown(self):
test = self._dt_test
if self._dt_tearDown is not None: if self._dt_tearDown is not None:
self._dt_tearDown() self._dt_tearDown(test)
test.globs.clear()
def runTest(self): def runTest(self):
test = self._dt_test test = self._dt_test
old = sys.stdout old = sys.stdout
new = StringIO() 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) checker=self._dt_checker, verbose=False)
try: try:
runner.DIVIDER = "-"*70 runner.DIVIDER = "-"*70
failures, tries = runner.run(test, out=new.write) failures, tries = runner.run(
test, out=new.write, clear_globs=False)
finally: finally:
sys.stdout = old sys.stdout = old
...@@ -2105,9 +2179,11 @@ class DocTestCase(unittest.TestCase): ...@@ -2105,9 +2179,11 @@ class DocTestCase(unittest.TestCase):
""" """
self.setUp()
runner = DebugRunner(optionflags=self._dt_optionflags, runner = DebugRunner(optionflags=self._dt_optionflags,
checker=self._dt_checker, verbose=False) checker=self._dt_checker, verbose=False)
runner.run(self._dt_test) runner.run(self._dt_test)
self.tearDown()
def id(self): def id(self):
return self._dt_test.name return self._dt_test.name
...@@ -2121,10 +2197,8 @@ class DocTestCase(unittest.TestCase): ...@@ -2121,10 +2197,8 @@ class DocTestCase(unittest.TestCase):
def shortDescription(self): def shortDescription(self):
return "Doctest: " + self._dt_test.name return "Doctest: " + self._dt_test.name
def DocTestSuite(module=None, globs=None, extraglobs=None, def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
optionflags=0, test_finder=None, **options):
setUp=lambda: None, tearDown=lambda: None,
checker=None):
""" """
Convert doctest tests for a module to a unittest test suite. Convert doctest tests for a module to a unittest test suite.
...@@ -2138,6 +2212,32 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, ...@@ -2138,6 +2212,32 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
can be either a module or a module name. can be either a module or a module name.
If no argument is given, the calling module is used. 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: if test_finder is None:
...@@ -2147,7 +2247,9 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, ...@@ -2147,7 +2247,9 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
tests = test_finder.find(module, globs=globs, extraglobs=extraglobs) tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
if globs is None: if globs is None:
globs = module.__dict__ 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") raise ValueError(module, "has no tests")
tests.sort() tests.sort()
...@@ -2160,8 +2262,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, ...@@ -2160,8 +2262,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None,
if filename[-4:] in (".pyc", ".pyo"): if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1] filename = filename[:-1]
test.filename = filename test.filename = filename
suite.addTest(DocTestCase(test, optionflags, setUp, tearDown, suite.addTest(DocTestCase(test, **options))
checker))
return suite return suite
...@@ -2179,9 +2280,7 @@ class DocFileCase(DocTestCase): ...@@ -2179,9 +2280,7 @@ 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, def DocFileTest(path, package=None, globs=None, **options):
setUp=None, tearDown=None,
optionflags=0):
package = _normalize_module(package) package = _normalize_module(package)
name = path.split('/')[-1] name = path.split('/')[-1]
dir = os.path.split(package.__file__)[0] dir = os.path.split(package.__file__)[0]
...@@ -2193,7 +2292,7 @@ def DocFileTest(path, package=None, globs=None, ...@@ -2193,7 +2292,7 @@ def DocFileTest(path, package=None, globs=None,
test = DocTestParser().get_doctest(doc, globs, name, path, 0) test = DocTestParser().get_doctest(doc, globs, name, path, 0)
return DocFileCase(test, optionflags, setUp, tearDown) return DocFileCase(test, **options)
def DocFileSuite(*paths, **kw): def DocFileSuite(*paths, **kw):
"""Creates a suite of doctest files. """Creates a suite of doctest files.
...@@ -2213,14 +2312,22 @@ def DocFileSuite(*paths, **kw): ...@@ -2213,14 +2312,22 @@ def DocFileSuite(*paths, **kw):
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
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 tearDown
The name of a tear-down function. This is called after running the 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 globs
A dictionary containing initial global variables for the tests. A dictionary containing initial global variables for the tests.
optionflags
A set of doctest option flags expressed as an integer.
""" """
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
...@@ -1653,11 +1653,11 @@ def test_DocTestSuite(): ...@@ -1653,11 +1653,11 @@ def test_DocTestSuite():
You can supply setUp and tearDown functions: You can supply setUp and tearDown functions:
>>> def setUp(): >>> def setUp(t):
... import test.test_doctest ... import test.test_doctest
... test.test_doctest.sillySetup = True ... test.test_doctest.sillySetup = True
>>> def tearDown(): >>> def tearDown(t):
... import test.test_doctest ... import test.test_doctest
... del test.test_doctest.sillySetup ... del test.test_doctest.sillySetup
...@@ -1676,6 +1676,21 @@ def test_DocTestSuite(): ...@@ -1676,6 +1676,21 @@ def test_DocTestSuite():
... ...
AttributeError: 'module' object has no attribute 'sillySetup' 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 Finally, you can provide an alternate test finder. Here we'll
use a custom test_finder to to run just the test named bar. use a custom test_finder to to run just the test named bar.
However, the test in the module docstring, and the two tests However, the test in the module docstring, and the two tests
...@@ -1744,11 +1759,11 @@ def test_DocFileSuite(): ...@@ -1744,11 +1759,11 @@ def test_DocFileSuite():
You can supply setUp and teatDoen functions: You can supply setUp and teatDoen functions:
>>> def setUp(): >>> def setUp(t):
... import test.test_doctest ... import test.test_doctest
... test.test_doctest.sillySetup = True ... test.test_doctest.sillySetup = True
>>> def tearDown(): >>> def tearDown(t):
... import test.test_doctest ... import test.test_doctest
... del test.test_doctest.sillySetup ... del test.test_doctest.sillySetup
...@@ -1768,6 +1783,21 @@ def test_DocFileSuite(): ...@@ -1768,6 +1783,21 @@ def test_DocFileSuite():
... ...
AttributeError: 'module' object has no attribute 'sillySetup' 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(): def test_trailing_space_in_test():
...@@ -1779,6 +1809,82 @@ def test_trailing_space_in_test(): ...@@ -1779,6 +1809,82 @@ def test_trailing_space_in_test():
foo \n 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 # 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.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment