From f54bad4564d6216cd658ce1d93421d8d40f7a719 Mon Sep 17 00:00:00 2001 From: Jim Fulton Date: Sat, 28 Aug 2004 14:57:56 +0000 Subject: - 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. --- Lib/doctest.py | 141 +++++++++++++++++++++++++++++++++++++++++------ Lib/test/test_doctest.py | 116 ++++++++++++++++++++++++++++++++++++-- 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()) + + + 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()) + + + 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 + + + + 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 + - + + + b + + + + + 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. -- cgit v0.12