testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #00768
[Merge] lp:~jml/testtools/less-stack into lp:testtools
Jonathan Lange has proposed merging lp:~jml/testtools/less-stack into lp:testtools with lp:~jml/testtools/all-match-615108 as a prerequisite.
Requested reviews:
testtools developers (testtools-dev)
Related bugs:
Bug #788974 in testtools: "exception stack depth not restricted to relevant modules"
https://bugs.launchpad.net/testtools/+bug/788974
For more details, see:
https://code.launchpad.net/~jml/testtools/less-stack/+merge/68543
Hide irrelevant levels of stack when displaying test errors and failures.
Uses the __unittest attribute which is supported by Python 2.4, 2.5, 2.6, 2.7, 3.0, 3.1 and 3.2. I checked. unittest will hide any levels of the stack that have the attribute existing as a global.
We hide the modules testtools.matchers, testtools.runtest and testtools.testcase. These values were picked by looking at actual tracebacks.
In the bug, Robert raised concerns about how this might make it harder to develop testtools itself. To address these concerns, I've made sure that the __unittest attributes have been deleted while the tests are running. We do it by wrapping the suite returned by testtools.tests.test_suite().
To make this happen, I've added FixtureSuite & safe_hasattr. Since they are more generally useful, I've documented and exported them.
Another change has been mixed into this, which is to get rid of the * import in testtools.tests.helpers.
This branch depends both on lp:~jml/testtools/all-match-615108 and lp:~jml/testtools/after-preprocessing-spelling-813460.
I have to say, hiding testtools's levels of stack makes a genuine & positive difference when developing my own project.
--
https://code.launchpad.net/~jml/testtools/less-stack/+merge/68543
Your team testtools developers is requested to review the proposed merge of lp:~jml/testtools/less-stack into lp:testtools.
=== modified file 'NEWS'
--- NEWS 2011-07-20 13:08:20 +0000
+++ NEWS 2011-07-20 13:08:20 +0000
@@ -4,6 +4,15 @@
NEXT
~~~~
+Changes
+-------
+
+* ``AfterPreproccessing`` renamed to ``AfterPreproccesing``, which is a more
+ correct spelling. Old name preserved for backwards compatibility, but is
+ now deprecated. Please stop using it.
+ (Jonathan Lange, #813460)
+
+
Improvements
------------
@@ -16,6 +25,12 @@
* Correctly display non-ASCII unicode output on terminals that claim to have a
unicode encoding. (Martin [gz], #804122)
+* ``FixtureSuite`` added, allows test suites to run with a given fixture.
+ (Jonathan Lange)
+
+* Hide testtools's own stack frames when displaying tracebacks, making it
+ easier for test authors to focus on their errors. (Jonathan Lange, #788974)
+
* Less boilerplate displayed in test failures and errors.
(Jonathan Lange, #660852)
@@ -34,6 +49,8 @@
* ``GreaterThan``. (Christian Kampka)
+* New helper, ``safe_hasattr`` added. (Jonathan Lange)
+
0.9.11
~~~~~~
=== modified file 'doc/for-framework-folk.rst'
--- doc/for-framework-folk.rst 2010-12-22 20:36:40 +0000
+++ doc/for-framework-folk.rst 2011-07-20 13:08:20 +0000
@@ -182,5 +182,13 @@
objects with a run(result), runs them all in threads using the
ThreadsafeForwardingResult to coalesce their activity.
+FixtureSuite
+------------
+
+A test suite that sets up a fixture_ before running any tests, and then tears
+it down after all of the tests are run. The fixture is *not* made available to
+any of the tests.
+
.. _`testtools API docs`: http://mumak.net/testtools/apidocs/
.. _unittest: http://docs.python.org/library/unittest.html
+.. _fixture: http://pypi.python.org/pypi/fixtures
=== modified file 'doc/for-test-authors.rst'
--- doc/for-test-authors.rst 2011-07-20 13:08:20 +0000
+++ doc/for-test-authors.rst 2011-07-20 13:08:20 +0000
@@ -485,7 +485,7 @@
def HasFileContent(content):
def _read(path):
return open(path).read()
- return AfterPreproccessing(_read, Equals(content))
+ return AfterPreprocessing(_read, Equals(content))
self.assertThat('/tmp/foo.txt', PathHasFileContent("Hello world!"))
@@ -756,7 +756,6 @@
arbitrary-color-name: {{{blue}}}
Traceback (most recent call last):
- ...
File "exampletest.py", line 8, in test_thingy
1 / 0 # Gratuitous error!
ZeroDivisionError: integer division or modulo by zero
@@ -1145,6 +1144,14 @@
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
+Safe attribute testing
+----------------------
+
+``hasattr`` is broken_ on many versions of Python. testtools provides
+``safe_hasattr``, which can be used to safely test whether an object has a
+particular attribute.
+
+
.. _testrepository: https://launchpad.net/testrepository
.. _Trial: http://twistedmatrix.com/documents/current/core/howto/testing.html
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
@@ -1159,3 +1166,4 @@
.. _`testtools API docs`: http://mumak.net/testtools/apidocs/
.. _Distutils: http://docs.python.org/library/distutils.html
.. _`setup configuration`: http://docs.python.org/distutils/configfile.html
+.. _broken: http://chipaca.com/post/3210673069/hasattr-17-less-harmful
=== modified file 'testtools/__init__.py'
--- testtools/__init__.py 2011-06-12 00:58:39 +0000
+++ testtools/__init__.py 2011-07-20 13:08:20 +0000
@@ -8,12 +8,14 @@
'ErrorHolder',
'ExpectedException',
'ExtendedToOriginalDecorator',
+ 'FixtureSuite',
'iterate_tests',
'MultipleExceptions',
'MultiTestResult',
'PlaceHolder',
'run_test_with',
'TestCase',
+ 'TestCommand',
'TestResult',
'TextTestResult',
'RunTest',
@@ -59,6 +61,7 @@
)
from testtools.testsuite import (
ConcurrentTestSuite,
+ FixtureSuite,
iterate_tests,
)
from testtools.distutilscmd import (
=== modified file 'testtools/helpers.py'
--- testtools/helpers.py 2011-06-30 15:51:58 +0000
+++ testtools/helpers.py 2011-07-20 13:08:20 +0000
@@ -1,6 +1,7 @@
# Copyright (c) 2010-2011 testtools developers. See LICENSE for details.
__all__ = [
+ 'safe_hasattr',
'try_import',
'try_imports',
]
@@ -60,7 +61,7 @@
:param module_names: A sequence of module names to try to import.
:param alternative: The value to return if no module can be imported.
If unspecified, we raise an ImportError.
- :param error_callback: If None, called with the ImportError for *each*
+ :param error_callback: If None, called with the ImportError for *each*
module that fails to load.
:raises ImportError: If none of the modules can be imported and no
alternative value was specified.
@@ -74,3 +75,14 @@
raise ImportError(
"Could not import any of: %s" % ', '.join(module_names))
return alternative
+
+
+def safe_hasattr(obj, attr):
+ """Does 'obj' have an attribute 'attr'?
+
+ Use this rather than built-in hasattr, as the built-in swallows exceptions
+ in some versions of Python and behaves unpredictably with respect to
+ properties.
+ """
+ marker = object()
+ return getattr(obj, attr, marker) is not marker
=== modified file 'testtools/matchers.py'
--- testtools/matchers.py 2011-07-20 13:08:20 +0000
+++ testtools/matchers.py 2011-07-20 13:08:20 +0000
@@ -12,7 +12,7 @@
__metaclass__ = type
__all__ = [
- 'AfterPreproccessing',
+ 'AfterPreprocessing',
'AllMatch',
'Annotate',
'DocTestMatches',
@@ -812,7 +812,7 @@
).match(not_matched[:common_length])
-class AfterPreproccessing(object):
+class AfterPreprocessing(object):
"""Matches if the value matches after passing through a function.
This can be used to aid in creating trivial matchers as functions, for
@@ -821,7 +821,7 @@
def PathHasFileContent(content):
def _read(path):
return open(path).read()
- return AfterPreproccessing(_read, Equals(content))
+ return AfterPreprocessing(_read, Equals(content))
"""
def __init__(self, preprocessor, matcher):
@@ -834,7 +834,7 @@
return str(self.preprocessor)
def __str__(self):
- return "AfterPreproccessing(%s, %s)" % (
+ return "AfterPreprocessing(%s, %s)" % (
self._str_preprocessor(), self.matcher)
def match(self, value):
@@ -843,6 +843,10 @@
"after %s" % self._str_preprocessor(),
self.matcher).match(value)
+# This is the old, deprecated. spelling of the name, kept for backwards
+# compatibility.
+AfterPreproccessing = AfterPreprocessing
+
class AllMatch(object):
"""Matches if all provided values match the given matcher."""
@@ -861,3 +865,8 @@
mismatches.append(mismatch)
if mismatches:
return MismatchesAll(mismatches)
+
+
+# Signal that this is part of the testing framework, and that code from this
+# should not normally appear in tracebacks.
+__unittest = True
=== modified file 'testtools/runtest.py'
--- testtools/runtest.py 2011-03-22 15:17:10 +0000
+++ testtools/runtest.py 2011-07-20 13:08:20 +0000
@@ -198,3 +198,8 @@
self._exceptions.append(e)
return self.exception_caught
raise e
+
+
+# Signal that this is part of the testing framework, and that code from this
+# should not normally appear in tracebacks.
+__unittest = True
=== modified file 'testtools/testcase.py'
--- testtools/testcase.py 2011-06-20 11:57:32 +0000
+++ testtools/testcase.py 2011-07-20 13:08:20 +0000
@@ -772,3 +772,8 @@
raise AssertionError('"%s" does not match "%s".' %
(str(exc_value), self.value_re))
return True
+
+
+# Signal that this is part of the testing framework, and that code from this
+# should not normally appear in tracebacks.
+__unittest = True
=== modified file 'testtools/tests/__init__.py'
--- testtools/tests/__init__.py 2011-07-11 10:30:08 +0000
+++ testtools/tests/__init__.py 2011-07-20 13:08:20 +0000
@@ -2,7 +2,8 @@
# See README for copyright and licensing details.
-import unittest
+from testtools.tests.helpers import StackHidingFixture
+from testtools.testsuite import FixtureSuite
def test_suite():
@@ -41,4 +42,4 @@
test_testsuite,
]
suites = map(lambda x: x.test_suite(), modules)
- return unittest.TestSuite(suites)
+ return FixtureSuite(StackHidingFixture(False), suites)
=== modified file 'testtools/tests/helpers.py'
--- testtools/tests/helpers.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/helpers.py 2011-07-20 13:08:20 +0000
@@ -1,15 +1,21 @@
-# Copyright (c) 2008 testtools developers. See LICENSE for details.
+# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
"""Helpers for tests."""
-import sys
-
-__metaclass__ = type
__all__ = [
'LoggingResult',
]
+import sys
+
+from fixtures import Fixture
+
from testtools import TestResult
+from testtools.helpers import (
+ safe_hasattr,
+ try_import,
+ )
+from testtools import runtest
# GZ 2010-08-12: Don't do this, pointlessly creates an exc_info cycle
@@ -67,6 +73,36 @@
self._events.append(('time', a_datetime))
super(LoggingResult, self).time(a_datetime)
-# Note, the following three classes are different to LoggingResult by
-# being fully defined exact matches rather than supersets.
-from testtools.testresult.doubles import *
+
+def is_stack_hidden():
+ return safe_hasattr(runtest, '__unittest')
+
+
+def hide_testtools_stack(should_hide=True):
+ modules = [
+ 'testtools.matchers',
+ 'testtools.runtest',
+ 'testtools.testcase',
+ ]
+ for module_name in modules:
+ module = try_import(module_name)
+ if should_hide:
+ setattr(module, '__unittest', True)
+ else:
+ try:
+ delattr(module, '__unittest')
+ except AttributeError:
+ # Attribute already doesn't exist. Our work here is done.
+ pass
+
+
+class StackHidingFixture(Fixture):
+
+ def __init__(self, should_hide):
+ super(StackHidingFixture, self).__init__()
+ self._should_hide = should_hide
+
+ def setUp(self):
+ super(StackHidingFixture, self).setUp()
+ self.addCleanup(hide_testtools_stack, is_stack_hidden())
+ hide_testtools_stack(self._should_hide)
=== modified file 'testtools/tests/test_deferredruntest.py'
--- testtools/tests/test_deferredruntest.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/test_deferredruntest.py 2011-07-20 13:08:20 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 testtools developers. See LICENSE for details.
+# Copyright (c) 2010-2011 testtools developers. See LICENSE for details.
"""Tests for the DeferredRunTest single test execution logic."""
@@ -13,7 +13,6 @@
text_content,
)
from testtools.helpers import try_import
-from testtools.tests.helpers import ExtendedTestResult
from testtools.matchers import (
Equals,
KeysEqual,
@@ -21,6 +20,7 @@
Raises,
)
from testtools.runtest import RunTest
+from testtools.testresult.doubles import ExtendedTestResult
from testtools.tests.test_spinner import NeedsTwistedTestCase
assert_fails_with = try_import('testtools.deferredruntest.assert_fails_with')
=== modified file 'testtools/tests/test_fixturesupport.py'
--- testtools/tests/test_fixturesupport.py 2011-05-25 15:03:35 +0000
+++ testtools/tests/test_fixturesupport.py 2011-07-20 13:08:20 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 testtools developers. See LICENSE for details.
+# Copyright (c) 2010-2011 testtools developers. See LICENSE for details.
import unittest
@@ -8,7 +8,7 @@
content_type,
)
from testtools.helpers import try_import
-from testtools.tests.helpers import (
+from testtools.testresult.doubles import (
ExtendedTestResult,
)
=== modified file 'testtools/tests/test_helpers.py'
--- testtools/tests/test_helpers.py 2011-06-30 15:51:58 +0000
+++ testtools/tests/test_helpers.py 2011-07-20 13:08:20 +0000
@@ -6,10 +6,18 @@
try_imports,
)
from testtools.matchers import (
+ AllMatch,
+ AfterPreprocessing,
Equals,
Is,
Not,
)
+from testtools.tests.helpers import (
+ hide_testtools_stack,
+ is_stack_hidden,
+ safe_hasattr,
+ StackHidingFixture,
+ )
def check_error_callback(test, function, arg, expected_error_count,
@@ -39,6 +47,37 @@
test.assertEquals(len(cb_calls), expected_error_count)
+class TestSafeHasattr(TestCase):
+
+ def test_attribute_not_there(self):
+ class Foo(object):
+ pass
+ self.assertEqual(False, safe_hasattr(Foo(), 'anything'))
+
+ def test_attribute_there(self):
+ class Foo(object):
+ pass
+ foo = Foo()
+ foo.attribute = None
+ self.assertEqual(True, safe_hasattr(foo, 'attribute'))
+
+ def test_property_there(self):
+ class Foo(object):
+ @property
+ def attribute(self):
+ return None
+ foo = Foo()
+ self.assertEqual(True, safe_hasattr(foo, 'attribute'))
+
+ def test_property_raises(self):
+ class Foo(object):
+ @property
+ def attribute(self):
+ 1/0
+ foo = Foo()
+ self.assertRaises(ZeroDivisionError, safe_hasattr, foo, 'attribute')
+
+
class TestTryImport(TestCase):
def test_doesnt_exist(self):
@@ -154,6 +193,53 @@
0, True)
+import testtools.matchers
+import testtools.runtest
+import testtools.testcase
+
+
+def StackHidden(is_hidden):
+ return AllMatch(
+ AfterPreprocessing(
+ lambda module: safe_hasattr(module, '__unittest'),
+ Equals(is_hidden)))
+
+
+class TestStackHiding(TestCase):
+
+ modules = [
+ testtools.matchers,
+ testtools.runtest,
+ testtools.testcase,
+ ]
+
+ def setUp(self):
+ super(TestStackHiding, self).setUp()
+ self.addCleanup(hide_testtools_stack, is_stack_hidden())
+
+ def test_shown_during_testtools_testsuite(self):
+ self.assertThat(self.modules, StackHidden(False))
+
+ def test_is_stack_hidden_consistent_true(self):
+ hide_testtools_stack(True)
+ self.assertEqual(True, is_stack_hidden())
+
+ def test_is_stack_hidden_consistent_false(self):
+ hide_testtools_stack(False)
+ self.assertEqual(False, is_stack_hidden())
+
+ def test_show_stack(self):
+ hide_testtools_stack(False)
+ self.assertThat(self.modules, StackHidden(False))
+
+ def test_fixture(self):
+ current_state = is_stack_hidden()
+ fixture = StackHidingFixture(not current_state)
+ with fixture:
+ self.assertThat(self.modules, StackHidden(not current_state))
+ self.assertThat(self.modules, StackHidden(current_state))
+
+
def test_suite():
from unittest import TestLoader
return TestLoader().loadTestsFromName(__name__)
=== modified file 'testtools/tests/test_matchers.py'
--- testtools/tests/test_matchers.py 2011-07-20 13:08:20 +0000
+++ testtools/tests/test_matchers.py 2011-07-20 13:08:20 +0000
@@ -14,7 +14,7 @@
StringIO,
)
from testtools.matchers import (
- AfterPreproccessing,
+ AfterPreprocessing,
AllMatch,
Annotate,
AnnotatedMismatch,
@@ -699,24 +699,24 @@
re.S))
-class TestAfterPreproccessing(TestCase, TestMatchersInterface):
+class TestAfterPreprocessing(TestCase, TestMatchersInterface):
def parity(x):
return x % 2
- matches_matcher = AfterPreproccessing(parity, Equals(1))
+ matches_matcher = AfterPreprocessing(parity, Equals(1))
matches_matches = [3, 5]
matches_mismatches = [2]
str_examples = [
- ("AfterPreproccessing(<function parity>, Equals(1))",
- AfterPreproccessing(parity, Equals(1))),
+ ("AfterPreprocessing(<function parity>, Equals(1))",
+ AfterPreprocessing(parity, Equals(1))),
]
describe_examples = [
("1 != 0: after <function parity>",
2,
- AfterPreproccessing(parity, Equals(1))),
+ AfterPreprocessing(parity, Equals(1))),
]
=== modified file 'testtools/tests/test_runtest.py'
--- testtools/tests/test_runtest.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/test_runtest.py 2011-07-20 13:08:20 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2009-2010 testtools developers. See LICENSE for details.
+# Copyright (c) 2009-2011 testtools developers. See LICENSE for details.
"""Tests for the RunTest single test execution logic."""
@@ -10,7 +10,7 @@
TestResult,
)
from testtools.matchers import MatchesException, Is, Raises
-from testtools.tests.helpers import ExtendedTestResult
+from testtools.testresult.doubles import ExtendedTestResult
class TestRunTest(TestCase):
=== modified file 'testtools/tests/test_testcase.py'
--- testtools/tests/test_testcase.py 2011-07-11 10:30:08 +0000
+++ testtools/tests/test_testcase.py 2011-07-20 13:08:20 +0000
@@ -23,12 +23,14 @@
MatchesException,
Raises,
)
+from testtools.testresult.doubles import (
+ Python26TestResult,
+ Python27TestResult,
+ ExtendedTestResult,
+ )
from testtools.tests.helpers import (
an_exc_info,
LoggingResult,
- Python26TestResult,
- Python27TestResult,
- ExtendedTestResult,
)
try:
exec('from __future__ import with_statement')
=== modified file 'testtools/tests/test_testresult.py'
--- testtools/tests/test_testresult.py 2011-07-01 10:50:54 +0000
+++ testtools/tests/test_testresult.py 2011-07-20 13:08:20 +0000
@@ -44,11 +44,14 @@
Raises,
)
from testtools.tests.helpers import (
+ an_exc_info,
LoggingResult,
+ StackHidingFixture,
+ )
+from testtools.testresult.doubles import (
Python26TestResult,
Python27TestResult,
ExtendedTestResult,
- an_exc_info
)
from testtools.testresult.real import (
_details_to_str,
@@ -369,7 +372,10 @@
result.time(now)
self.assertEqual(now, result._now())
- def test_traceback_formatting(self):
+ def test_traceback_formatting_without_stack_hidden(self):
+ # During the testtools test run, we show our levels of the stack,
+ # because we want to be able to use our test suite to debug our own
+ # code.
result = self.makeResult()
test = make_erroring_test()
test.run(result)
@@ -386,6 +392,20 @@
'ZeroDivisionError: ...\n',
doctest.ELLIPSIS))
+ def test_traceback_formatting_with_stack_hidden(self):
+ result = self.makeResult()
+ test = make_erroring_test()
+ with StackHidingFixture(True):
+ test.run(result)
+ self.assertThat(
+ result.errors[0][1],
+ DocTestMatches(
+ 'Traceback (most recent call last):\n'
+ ' File "testtools/tests/test_testresult.py", line ..., in error\n'
+ ' 1/0\n'
+ 'ZeroDivisionError: ...\n',
+ doctest.ELLIPSIS))
+
class TestMultiTestResult(TestCase):
"""Tests for 'MultiTestResult'."""
@@ -575,21 +595,18 @@
DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS))
def test_stopTestRun_shows_details(self):
- self.result.startTestRun()
- make_erroring_test().run(self.result)
- make_unexpectedly_successful_test().run(self.result)
- make_failing_test().run(self.result)
- self.reset_output()
- self.result.stopTestRun()
+ with StackHidingFixture(True):
+ self.result.startTestRun()
+ make_erroring_test().run(self.result)
+ make_unexpectedly_successful_test().run(self.result)
+ make_failing_test().run(self.result)
+ self.reset_output()
+ self.result.stopTestRun()
self.assertThat(self.getvalue(),
DocTestMatches("""...======================================================================
ERROR: testtools.tests.test_testresult.Test.error
----------------------------------------------------------------------
Traceback (most recent call last):
- File "...testtools...runtest.py", line ..., in _run_user...
- return fn(*args, **kwargs)
- File "...testtools...testcase.py", line ..., in _run_test_method
- return self._get_test_method()()
File "...testtools...tests...test_testresult.py", line ..., in error
1/0
ZeroDivisionError:... divi... by zero...
@@ -597,10 +614,6 @@
FAIL: testtools.tests.test_testresult.Test.failed
----------------------------------------------------------------------
Traceback (most recent call last):
- File "...testtools...runtest.py", line ..., in _run_user...
- return fn(*args, **kwargs)
- File "...testtools...testcase.py", line ..., in _run_test_method
- return self._get_test_method()()
File "...testtools...tests...test_testresult.py", line ..., in failed
self.fail("yo!")
AssertionError: yo!
=== modified file 'testtools/tests/test_testsuite.py'
--- testtools/tests/test_testsuite.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/test_testsuite.py 2011-07-20 13:08:20 +0000
@@ -1,20 +1,19 @@
-# Copyright (c) 2009 testtools developers. See LICENSE for details.
+# Copyright (c) 2009-2011 testtools developers. See LICENSE for details.
"""Test ConcurrentTestSuite and related things."""
__metaclass__ = type
-import datetime
import unittest
+from fixtures import FunctionFixture
+
from testtools import (
ConcurrentTestSuite,
iterate_tests,
TestCase,
)
-from testtools.matchers import (
- Equals,
- )
+from testtools.testsuite import FixtureSuite
from testtools.tests.helpers import LoggingResult
@@ -48,6 +47,23 @@
return tests[0], tests[1]
+class TestFixtureSuite(TestCase):
+
+ def test_fixture_suite(self):
+ log = []
+ class Sample(TestCase):
+ def test_one(self):
+ log.append(1)
+ def test_two(self):
+ log.append(2)
+ fixture = FunctionFixture(
+ lambda: log.append('setUp'),
+ lambda fixture: log.append('tearDown'))
+ suite = FixtureSuite(fixture, [Sample('test_one'), Sample('test_two')])
+ suite.run(LoggingResult([]))
+ self.assertEqual(['setUp', 1, 2, 'tearDown'], log)
+
+
def test_suite():
from unittest import TestLoader
return TestLoader().loadTestsFromName(__name__)
=== modified file 'testtools/testsuite.py'
--- testtools/testsuite.py 2011-01-22 17:56:00 +0000
+++ testtools/testsuite.py 2011-07-20 13:08:20 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2009 testtools developers. See LICENSE for details.
+# Copyright (c) 2009-2011 testtools developers. See LICENSE for details.
"""Test suites and related things."""
@@ -8,10 +8,10 @@
'iterate_tests',
]
-try:
- from Queue import Queue
-except ImportError:
- from queue import Queue
+from testtools.helpers import try_imports
+
+Queue = try_imports(['Queue.Queue', 'queue.Queue'])
+
import threading
import unittest
@@ -85,3 +85,14 @@
test.run(process_result)
finally:
queue.put(test)
+
+
+class FixtureSuite(unittest.TestSuite):
+
+ def __init__(self, fixture, tests):
+ super(FixtureSuite, self).__init__(tests)
+ self._fixture = fixture
+
+ def run(self, result):
+ with self._fixture:
+ super(FixtureSuite, self).run(result)
Follow ups