[Zope3-checkins] SVN: zope.testing/branches/3.5/ Fix LP#253959: in
some invoking running the test runner from inside a
Marius Gedminas
marius at pov.lt
Fri Aug 1 13:16:11 EDT 2008
Log message for revision 89164:
Fix LP#253959: in some invoking running the test runner from inside a
test case could cause problems with layers.
Changed:
U zope.testing/branches/3.5/README.txt
A zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/
A zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/innertests.py
A zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/outertests.py
A zope.testing/branches/3.5/src/zope/testing/testrunner-reentrancy.txt
U zope.testing/branches/3.5/src/zope/testing/testrunner.py
-=-
Modified: zope.testing/branches/3.5/README.txt
===================================================================
--- zope.testing/branches/3.5/README.txt 2008-08-01 16:54:08 UTC (rev 89163)
+++ zope.testing/branches/3.5/README.txt 2008-08-01 17:16:10 UTC (rev 89164)
@@ -67,7 +67,8 @@
- Launchpad #242851: committed the last missing testing part
of the patch
-- Launchpad #253959: added extra diagnostics.
+- Launchpad #253959: in some invoking running the test runner from inside a
+ test case could cause problems with layers.
3.5.3 (2008/07/08)
Added: zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/innertests.py
===================================================================
--- zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/innertests.py (rev 0)
+++ zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/innertests.py 2008-08-01 17:16:10 UTC (rev 89164)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Sample test layers
+
+$Id$
+"""
+
+import unittest
+
+
+class InnerTest(unittest.TestCase):
+
+ def test(self):
+ pass
+
+
+def test_suite():
+ return unittest.makeSuite(InnerTest)
+
Property changes on: zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/innertests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/outertests.py
===================================================================
--- zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/outertests.py (rev 0)
+++ zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/outertests.py 2008-08-01 17:16:10 UTC (rev 89164)
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Sample test layers
+
+$Id$
+"""
+
+import os
+import unittest
+
+
+class TestLayer:
+
+ __bases__ = ()
+
+ def __init__(self, module, name):
+ self.__module__ = module
+ self.__name__ = name
+
+
+class UnitTest(unittest.TestCase):
+
+ def test(self):
+ from zope.testing import testrunner
+ this_directory = os.path.dirname(__file__)
+ defaults = [
+ '--path', this_directory,
+ '--tests-pattern', '^innertests$',
+ ]
+ print "-- inner test run starts --"
+ testrunner.run(defaults)
+ print "-- inner test run ends --"
+
+
+class LayeredTest(unittest.TestCase):
+
+ layer = TestLayer(__name__, 'HardToAccessTestLayer')
+
+ def test(self):
+ pass
+
+
+def test_suite():
+ return unittest.TestSuite([unittest.makeSuite(UnitTest),
+ unittest.makeSuite(LayeredTest)])
+
Property changes on: zope.testing/branches/3.5/src/zope/testing/testrunner-ex-r/outertests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zope.testing/branches/3.5/src/zope/testing/testrunner-reentrancy.txt
===================================================================
--- zope.testing/branches/3.5/src/zope/testing/testrunner-reentrancy.txt (rev 0)
+++ zope.testing/branches/3.5/src/zope/testing/testrunner-reentrancy.txt 2008-08-01 17:16:10 UTC (rev 89164)
@@ -0,0 +1,40 @@
+Using the testrunner recursively
+================================
+
+It's pretty obvious that the tests that are run by the testrunner can
+themselves invoke the testrunner. That's how the testrunner is tested,
+after all!
+
+However there's a corner case that used to break. We have a set of tests
+split into unit tests (no layer) and layered tests (where the layer cannot be
+accessed directly by importing the module and taking the attribute named by
+layer.__name__ in that module). The first test in the unit test layer
+imports zope.testing.testrunner and runs it recursively. It should work:
+
+ >>> import os.path, sys
+ >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex-r')
+ >>> defaults = [
+ ... '--path', directory_with_tests,
+ ... '--tests-pattern', '^outertests$',
+ ... ]
+
+ >>> sys.argv = []
+ >>> from zope.testing import testrunner
+ >>> testrunner.run(defaults)
+ Running unit tests:
+ -- inner test run starts --
+ Running unit tests:
+ Ran 1 tests with 0 failures and 0 errors in N.NNN seconds.
+ -- inner test run ends --
+ Ran 1 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running outertests.HardToAccessTestLayer tests:
+ Set up outertests.HardToAccessTestLayer in N.NNN seconds.
+ Ran 1 tests with 0 failures and 0 errors in N.NNN seconds.
+ Tearing down left over layers:
+ Tear down outertests.HardToAccessTestLayer in N.NNN seconds.
+ Total: 2 tests, 0 failures, 0 errors in N.NNN seconds.
+ False
+
+https://bugs.launchpad.net/zope.testing/+bug/253959 was caused by the
+inner test runner clearing the global layer name cache that was needed
+by the outer test runner.
Property changes on: zope.testing/branches/3.5/src/zope/testing/testrunner-reentrancy.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: zope.testing/branches/3.5/src/zope/testing/testrunner.py
===================================================================
--- zope.testing/branches/3.5/src/zope/testing/testrunner.py 2008-08-01 16:54:08 UTC (rev 89163)
+++ zope.testing/branches/3.5/src/zope/testing/testrunner.py 2008-08-01 17:16:10 UTC (rev 89164)
@@ -972,174 +972,180 @@
"""
global _layer_name_cache
- _layer_name_cache = {} # Reset to enforce test isolation
+ old_layer_name_cache = _layer_name_cache
+ try:
+ _layer_name_cache = {} # Reset to enforce test isolation
- output = options.output
+ output = options.output
- if options.resume_layer:
- original_stderr = sys.stderr
- sys.stderr = sys.stdout
- elif options.verbose:
- if options.all:
- msg = "Running tests at all levels"
- else:
- msg = "Running tests at level %d" % options.at_level
- output.info(msg)
+ if options.resume_layer:
+ original_stderr = sys.stderr
+ sys.stderr = sys.stdout
+ elif options.verbose:
+ if options.all:
+ msg = "Running tests at all levels"
+ else:
+ msg = "Running tests at level %d" % options.at_level
+ output.info(msg)
- old_threshold = gc.get_threshold()
- if options.gc:
- if len(options.gc) > 3:
- output.error("Too many --gc options")
- sys.exit(1)
- if options.gc[0]:
- output.info("Cyclic garbage collection threshold set to: %s" %
- repr(tuple(options.gc)))
- else:
- output.info("Cyclic garbage collection is disabled.")
+ old_threshold = gc.get_threshold()
+ if options.gc:
+ if len(options.gc) > 3:
+ output.error("Too many --gc options")
+ sys.exit(1)
+ if options.gc[0]:
+ output.info("Cyclic garbage collection threshold set to: %s" %
+ repr(tuple(options.gc)))
+ else:
+ output.info("Cyclic garbage collection is disabled.")
- gc.set_threshold(*options.gc)
+ gc.set_threshold(*options.gc)
- old_flags = gc.get_debug()
- if options.gc_option:
- new_flags = 0
- for op in options.gc_option:
- new_flags |= getattr(gc, op)
- gc.set_debug(new_flags)
+ old_flags = gc.get_debug()
+ if options.gc_option:
+ new_flags = 0
+ for op in options.gc_option:
+ new_flags |= getattr(gc, op)
+ gc.set_debug(new_flags)
- old_reporting_flags = doctest.set_unittest_reportflags(0)
- reporting_flags = 0
- if options.ndiff:
- reporting_flags = doctest.REPORT_NDIFF
- if options.udiff:
+ old_reporting_flags = doctest.set_unittest_reportflags(0)
+ reporting_flags = 0
+ if options.ndiff:
+ reporting_flags = doctest.REPORT_NDIFF
+ if options.udiff:
+ if reporting_flags:
+ output.error("Can only give one of --ndiff, --udiff, or --cdiff")
+ sys.exit(1)
+ reporting_flags = doctest.REPORT_UDIFF
+ if options.cdiff:
+ if reporting_flags:
+ output.error("Can only give one of --ndiff, --udiff, or --cdiff")
+ sys.exit(1)
+ reporting_flags = doctest.REPORT_CDIFF
+ if options.report_only_first_failure:
+ reporting_flags |= doctest.REPORT_ONLY_FIRST_FAILURE
+
if reporting_flags:
- output.error("Can only give one of --ndiff, --udiff, or --cdiff")
- sys.exit(1)
- reporting_flags = doctest.REPORT_UDIFF
- if options.cdiff:
- if reporting_flags:
- output.error("Can only give one of --ndiff, --udiff, or --cdiff")
- sys.exit(1)
- reporting_flags = doctest.REPORT_CDIFF
- if options.report_only_first_failure:
- reporting_flags |= doctest.REPORT_ONLY_FIRST_FAILURE
+ doctest.set_unittest_reportflags(reporting_flags)
+ else:
+ doctest.set_unittest_reportflags(old_reporting_flags)
- if reporting_flags:
- doctest.set_unittest_reportflags(reporting_flags)
- else:
- doctest.set_unittest_reportflags(old_reporting_flags)
+ # Add directories to the path
+ for path in options.path:
+ if path not in sys.path:
+ sys.path.append(path)
- # Add directories to the path
- for path in options.path:
- if path not in sys.path:
- sys.path.append(path)
+ remove_stale_bytecode(options)
- remove_stale_bytecode(options)
+ tests_by_layer_name = find_tests(options, found_suites)
- tests_by_layer_name = find_tests(options, found_suites)
+ ran = 0
+ failures = []
+ errors = []
+ nlayers = 0
+ import_errors = tests_by_layer_name.pop(None, None)
- ran = 0
- failures = []
- errors = []
- nlayers = 0
- import_errors = tests_by_layer_name.pop(None, None)
+ output.import_errors(import_errors)
- output.import_errors(import_errors)
+ if 'unit' in tests_by_layer_name:
+ tests = tests_by_layer_name.pop('unit')
+ if (not options.non_unit) and not options.resume_layer:
+ if options.layer:
+ should_run = False
+ for pat in options.layer:
+ if pat('unit'):
+ should_run = True
+ break
+ else:
+ should_run = True
- if 'unit' in tests_by_layer_name:
- tests = tests_by_layer_name.pop('unit')
- if (not options.non_unit) and not options.resume_layer:
- if options.layer:
- should_run = False
- for pat in options.layer:
- if pat('unit'):
- should_run = True
- break
- else:
- should_run = True
+ if should_run:
+ if options.list_tests:
+ output.list_of_tests(tests, 'unit')
+ else:
+ output.info("Running unit tests:")
+ nlayers += 1
+ ran += run_tests(options, tests, 'unit', failures, errors)
- if should_run:
- if options.list_tests:
- output.list_of_tests(tests, 'unit')
- else:
- output.info("Running unit tests:")
- nlayers += 1
- ran += run_tests(options, tests, 'unit', failures, errors)
+ setup_layers = {}
- setup_layers = {}
+ layers_to_run = list(ordered_layers(tests_by_layer_name))
+ if options.resume_layer is not None:
+ layers_to_run = [
+ (layer_name, layer, tests)
+ for (layer_name, layer, tests) in layers_to_run
+ if layer_name == options.resume_layer
+ ]
+ elif options.layer:
+ layers_to_run = [
+ (layer_name, layer, tests)
+ for (layer_name, layer, tests) in layers_to_run
+ if filter(None, [pat(layer_name) for pat in options.layer])
+ ]
- layers_to_run = list(ordered_layers(tests_by_layer_name))
- if options.resume_layer is not None:
- layers_to_run = [
- (layer_name, layer, tests)
- for (layer_name, layer, tests) in layers_to_run
- if layer_name == options.resume_layer
- ]
- elif options.layer:
- layers_to_run = [
- (layer_name, layer, tests)
- for (layer_name, layer, tests) in layers_to_run
- if filter(None, [pat(layer_name) for pat in options.layer])
- ]
+ if options.list_tests:
+ for layer_name, layer, tests in layers_to_run:
+ output.list_of_tests(tests, layer_name)
+ return True
- if options.list_tests:
+ start_time = time.time()
+
for layer_name, layer, tests in layers_to_run:
- output.list_of_tests(tests, layer_name)
- return True
+ nlayers += 1
+ try:
+ ran += run_layer(options, layer_name, layer, tests,
+ setup_layers, failures, errors)
+ except CanNotTearDown:
+ setup_layers = None
+ if not options.resume_layer:
+ ran += resume_tests(options, layer_name, layers_to_run,
+ failures, errors)
+ break
- start_time = time.time()
+ if setup_layers:
+ if options.resume_layer == None:
+ output.info("Tearing down left over layers:")
+ tear_down_unneeded(options, (), setup_layers, True)
- for layer_name, layer, tests in layers_to_run:
- nlayers += 1
- try:
- ran += run_layer(options, layer_name, layer, tests,
- setup_layers, failures, errors)
- except CanNotTearDown:
- setup_layers = None
- if not options.resume_layer:
- ran += resume_tests(options, layer_name, layers_to_run,
- failures, errors)
- break
+ total_time = time.time() - start_time
- if setup_layers:
- if options.resume_layer == None:
- output.info("Tearing down left over layers:")
- tear_down_unneeded(options, (), setup_layers, True)
+ if options.resume_layer:
+ sys.stdout.close()
+ # Communicate with the parent. The protocol is obvious:
+ print >> original_stderr, ran, len(failures), len(errors)
+ for test, exc_info in failures:
+ print >> original_stderr, ' '.join(str(test).strip().split('\n'))
+ for test, exc_info in errors:
+ print >> original_stderr, ' '.join(str(test).strip().split('\n'))
- total_time = time.time() - start_time
+ else:
+ if options.verbose:
+ output.tests_with_errors(errors)
+ output.tests_with_failures(failures)
- if options.resume_layer:
- sys.stdout.close()
- # Communicate with the parent. The protocol is obvious:
- print >> original_stderr, ran, len(failures), len(errors)
- for test, exc_info in failures:
- print >> original_stderr, ' '.join(str(test).strip().split('\n'))
- for test, exc_info in errors:
- print >> original_stderr, ' '.join(str(test).strip().split('\n'))
+ if nlayers != 1:
+ output.totals(ran, len(failures), len(errors), total_time)
- else:
- if options.verbose:
- output.tests_with_errors(errors)
- output.tests_with_failures(failures)
+ output.modules_with_import_problems(import_errors)
- if nlayers != 1:
- output.totals(ran, len(failures), len(errors), total_time)
+ doctest.set_unittest_reportflags(old_reporting_flags)
- output.modules_with_import_problems(import_errors)
+ if options.gc_option:
+ gc.set_debug(old_flags)
- doctest.set_unittest_reportflags(old_reporting_flags)
+ if options.gc:
+ gc.set_threshold(*old_threshold)
- if options.gc_option:
- gc.set_debug(old_flags)
+ return not bool(import_errors or failures or errors)
+ finally:
+ # restore old value to prevent cleaning the global cache when one of
+ # the tests decides to invoke testrunner.run() inside itself.
+ _layer_name_cache = old_layer_name_cache
- if options.gc:
- gc.set_threshold(*old_threshold)
- return not bool(import_errors or failures or errors)
-
-
def run_tests(options, tests, name, failures, errors):
repeat = options.repeat or 1
repeat_range = iter(range(repeat))
@@ -2707,6 +2713,7 @@
'testrunner-gc.txt',
'testrunner-knit.txt',
'testrunner-package-normalization.txt',
+ 'testrunner-reentrancy.txt',
setUp=setUp, tearDown=tearDown,
optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE,
checker=checker),
More information about the Zope3-Checkins
mailing list