399 lines
15 KiB
Python
399 lines
15 KiB
Python
|
# fixtures: Fixtures with cleanups for testing and convenience.
|
||
|
#
|
||
|
# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
|
||
|
#
|
||
|
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
|
||
|
# license at the users choice. A copy of both licenses are available in the
|
||
|
# project source as Apache-2.0 and BSD. You may not use this file except in
|
||
|
# compliance with one of these two licences.
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
# license you chose for the specific language governing permissions and
|
||
|
# limitations under that license.
|
||
|
|
||
|
import types
|
||
|
|
||
|
import testtools
|
||
|
from testtools.content import text_content
|
||
|
from testtools.testcase import skipIf
|
||
|
|
||
|
import fixtures
|
||
|
from fixtures.fixture import gather_details
|
||
|
from fixtures.tests.helpers import LoggingFixture
|
||
|
|
||
|
|
||
|
require_gather_details = skipIf(gather_details is None,
|
||
|
"gather_details() is not available.")
|
||
|
|
||
|
|
||
|
# Note: the cleanup related tests are strictly speaking redundant, IFF they are
|
||
|
# replaced with contract tests for correct use of CallMany.
|
||
|
class TestFixture(testtools.TestCase):
|
||
|
|
||
|
def test_resetCallsSetUpCleanUp(self):
|
||
|
calls = []
|
||
|
class FixtureWithSetupOnly(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(FixtureWithSetupOnly, self).setUp()
|
||
|
calls.append('setUp')
|
||
|
self.addCleanup(calls.append, 'cleanUp')
|
||
|
fixture = FixtureWithSetupOnly()
|
||
|
fixture.setUp()
|
||
|
fixture.reset()
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual(['setUp', 'cleanUp', 'setUp', 'cleanUp'], calls)
|
||
|
|
||
|
def test_reset_raises_if_cleanup_raises(self):
|
||
|
class FixtureWithSetupOnly(fixtures.Fixture):
|
||
|
def do_raise(self):
|
||
|
raise Exception('foo')
|
||
|
def setUp(self):
|
||
|
super(FixtureWithSetupOnly, self).setUp()
|
||
|
self.addCleanup(self.do_raise)
|
||
|
fixture = FixtureWithSetupOnly()
|
||
|
fixture.setUp()
|
||
|
exc = self.assertRaises(Exception, fixture.reset)
|
||
|
self.assertEqual(('foo',), exc.args)
|
||
|
|
||
|
def test_cleanUp_raise_first_false_callscleanups_returns_exceptions(self):
|
||
|
calls = []
|
||
|
def raise_exception1():
|
||
|
calls.append('1')
|
||
|
raise Exception('woo')
|
||
|
def raise_exception2():
|
||
|
calls.append('2')
|
||
|
raise Exception('woo')
|
||
|
class FixtureWithException(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(FixtureWithException, self).setUp()
|
||
|
self.addCleanup(raise_exception2)
|
||
|
self.addCleanup(raise_exception1)
|
||
|
fixture = FixtureWithException()
|
||
|
fixture.setUp()
|
||
|
exceptions = fixture.cleanUp(raise_first=False)
|
||
|
self.assertEqual(['1', '2'], calls)
|
||
|
# There should be two exceptions
|
||
|
self.assertEqual(2, len(exceptions))
|
||
|
# They should be a sys.exc_info tuple.
|
||
|
self.assertEqual(3, len(exceptions[0]))
|
||
|
type, value, tb = exceptions[0]
|
||
|
self.assertEqual(Exception, type)
|
||
|
self.assertIsInstance(value, Exception)
|
||
|
self.assertEqual(('woo',), value.args)
|
||
|
self.assertIsInstance(tb, types.TracebackType)
|
||
|
|
||
|
def test_exit_propagates_exceptions(self):
|
||
|
fixture = fixtures.Fixture()
|
||
|
fixture.__enter__()
|
||
|
self.assertEqual(False, fixture.__exit__(None, None, None))
|
||
|
|
||
|
def test_exit_runs_all_raises_first_exception(self):
|
||
|
calls = []
|
||
|
def raise_exception1():
|
||
|
calls.append('1')
|
||
|
raise Exception('woo')
|
||
|
def raise_exception2():
|
||
|
calls.append('2')
|
||
|
raise Exception('hoo')
|
||
|
class FixtureWithException(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(FixtureWithException, self).setUp()
|
||
|
self.addCleanup(raise_exception2)
|
||
|
self.addCleanup(raise_exception1)
|
||
|
fixture = FixtureWithException()
|
||
|
fixture.__enter__()
|
||
|
exc = self.assertRaises(Exception, fixture.__exit__, None, None, None)
|
||
|
self.assertEqual(('woo',), exc.args[0][1].args)
|
||
|
self.assertEqual(('hoo',), exc.args[1][1].args)
|
||
|
self.assertEqual(['1', '2'], calls)
|
||
|
|
||
|
def test_useFixture(self):
|
||
|
parent = LoggingFixture('-outer')
|
||
|
nested = LoggingFixture('-inner', calls=parent.calls)
|
||
|
parent.setUp()
|
||
|
parent.useFixture(nested)
|
||
|
parent.cleanUp()
|
||
|
self.assertEqual(
|
||
|
['setUp-outer', 'setUp-inner', 'cleanUp-inner', 'cleanUp-outer'],
|
||
|
parent.calls)
|
||
|
|
||
|
@require_gather_details
|
||
|
def test_useFixture_details_captured_from_setUp(self):
|
||
|
# Details added during fixture set-up are gathered even if setUp()
|
||
|
# fails with an unknown exception.
|
||
|
class SomethingBroke(Exception): pass
|
||
|
class BrokenFixture(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(BrokenFixture, self).setUp()
|
||
|
self.addDetail('content', text_content("foobar"))
|
||
|
raise SomethingBroke()
|
||
|
broken_fixture = BrokenFixture()
|
||
|
class SimpleFixture(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(SimpleFixture, self).setUp()
|
||
|
self.useFixture(broken_fixture)
|
||
|
simple_fixture = SimpleFixture()
|
||
|
self.assertRaises(SomethingBroke, simple_fixture.setUp)
|
||
|
self.assertEqual(
|
||
|
{"content": text_content("foobar")},
|
||
|
broken_fixture.getDetails())
|
||
|
self.assertEqual(
|
||
|
{"content": text_content("foobar")},
|
||
|
simple_fixture.getDetails())
|
||
|
|
||
|
@require_gather_details
|
||
|
def test_useFixture_details_captured_from_setUp_MultipleExceptions(self):
|
||
|
# Details added during fixture set-up are gathered even if setUp()
|
||
|
# fails with (cleanly - with MultipleExceptions / SetupError).
|
||
|
class SomethingBroke(Exception): pass
|
||
|
class BrokenFixture(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.addDetail('content', text_content("foobar"))
|
||
|
raise SomethingBroke()
|
||
|
class SimpleFixture(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.useFixture(BrokenFixture())
|
||
|
simple = SimpleFixture()
|
||
|
e = self.assertRaises(fixtures.MultipleExceptions, simple.setUp)
|
||
|
self.assertEqual(
|
||
|
{"content": text_content("foobar")},
|
||
|
e.args[-1][1].args[0])
|
||
|
|
||
|
def test_getDetails(self):
|
||
|
fixture = fixtures.Fixture()
|
||
|
with fixture:
|
||
|
self.assertEqual({}, fixture.getDetails())
|
||
|
|
||
|
def test_details_from_child_fixtures_are_returned(self):
|
||
|
parent = fixtures.Fixture()
|
||
|
with parent:
|
||
|
child = fixtures.Fixture()
|
||
|
parent.useFixture(child)
|
||
|
# Note that we add the detail *after* using the fixture: the parent
|
||
|
# has to query just-in-time.
|
||
|
child.addDetail('foo', 'content')
|
||
|
self.assertEqual({'foo': 'content'}, parent.getDetails())
|
||
|
# And dropping it from the child drops it from the parent.
|
||
|
del child._details['foo']
|
||
|
self.assertEqual({}, parent.getDetails())
|
||
|
# After cleanup the child details are still gone.
|
||
|
child.addDetail('foo', 'content')
|
||
|
self.assertRaises(TypeError, parent.getDetails)
|
||
|
|
||
|
def test_duplicate_details_are_disambiguated(self):
|
||
|
parent = fixtures.Fixture()
|
||
|
with parent:
|
||
|
parent.addDetail('foo', 'parent-content')
|
||
|
child = fixtures.Fixture()
|
||
|
parent.useFixture(child)
|
||
|
# Note that we add the detail *after* using the fixture: the parent
|
||
|
# has to query just-in-time.
|
||
|
child.addDetail('foo', 'child-content')
|
||
|
self.assertEqual({'foo': 'parent-content',
|
||
|
'foo-1': 'child-content',}, parent.getDetails())
|
||
|
|
||
|
def test_addDetail(self):
|
||
|
fixture = fixtures.Fixture()
|
||
|
with fixture:
|
||
|
fixture.addDetail('foo', 'content')
|
||
|
self.assertEqual({'foo': 'content'}, fixture.getDetails())
|
||
|
del fixture._details['foo']
|
||
|
self.assertEqual({}, fixture.getDetails())
|
||
|
fixture.addDetail('foo', 'content')
|
||
|
# Cleanup clears the details too.
|
||
|
self.assertRaises(TypeError, fixture.getDetails)
|
||
|
|
||
|
def test_setUp_subclassed(self):
|
||
|
# Even though its no longer recommended, we need to be sure that
|
||
|
# overriding setUp and calling super().setUp still works.
|
||
|
class Subclass(fixtures.Fixture):
|
||
|
def setUp(self):
|
||
|
super(Subclass, self).setUp()
|
||
|
self.fred = 1
|
||
|
self.addCleanup(setattr, self, 'fred', 2)
|
||
|
with Subclass() as f:
|
||
|
self.assertEqual(1, f.fred)
|
||
|
self.assertEqual(2, f.fred)
|
||
|
|
||
|
def test__setUp(self):
|
||
|
# _setUp is called, and cleanups can be registered by it.
|
||
|
class Subclass(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.fred = 1
|
||
|
self.addCleanup(setattr, self, 'fred', 2)
|
||
|
with Subclass() as f:
|
||
|
self.assertEqual(1, f.fred)
|
||
|
self.assertEqual(2, f.fred)
|
||
|
|
||
|
def test__setUp_fails(self):
|
||
|
# when _setUp fails, the fixture is left ready-to-setUp, and any
|
||
|
# details added during _setUp are captured.
|
||
|
class Subclass(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.addDetail('log', text_content('stuff'))
|
||
|
1/0
|
||
|
f = Subclass()
|
||
|
e = self.assertRaises(fixtures.MultipleExceptions, f.setUp)
|
||
|
self.assertRaises(TypeError, f.cleanUp)
|
||
|
self.assertIsInstance(e.args[0][1], ZeroDivisionError)
|
||
|
self.assertIsInstance(e.args[1][1], fixtures.SetupError)
|
||
|
self.assertEqual('stuff', e.args[1][1].args[0]['log'].as_text())
|
||
|
|
||
|
def test__setUp_fails_cleanUp_fails(self):
|
||
|
# when _setUp fails, cleanups are called, and their failure is captured
|
||
|
# into the MultipleExceptions instance.
|
||
|
class Subclass(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.addDetail('log', text_content('stuff'))
|
||
|
self.addCleanup(lambda: 1/0)
|
||
|
raise Exception('fred')
|
||
|
f = Subclass()
|
||
|
e = self.assertRaises(fixtures.MultipleExceptions, f.setUp)
|
||
|
self.assertRaises(TypeError, f.cleanUp)
|
||
|
self.assertEqual(Exception, e.args[0][0])
|
||
|
self.assertEqual(ZeroDivisionError, e.args[1][0])
|
||
|
self.assertEqual(fixtures.SetupError, e.args[2][0])
|
||
|
self.assertEqual('stuff', e.args[2][1].args[0]['log'].as_text())
|
||
|
|
||
|
def test_setup_failures_with_base_exception(self):
|
||
|
# when _setUp fails with a BaseException (or subclass thereof) that
|
||
|
# exception is propagated as is, but we still call cleanups etc.
|
||
|
class MyBase(BaseException):pass
|
||
|
log = []
|
||
|
class Subclass(fixtures.Fixture):
|
||
|
def _setUp(self):
|
||
|
self.addDetail('log', text_content('stuff'))
|
||
|
self.addCleanup(log.append, 'cleaned')
|
||
|
raise MyBase('fred')
|
||
|
f = Subclass()
|
||
|
self.assertRaises(MyBase, f.setUp)
|
||
|
self.assertRaises(TypeError, f.cleanUp)
|
||
|
self.assertEqual(['cleaned'], log)
|
||
|
|
||
|
|
||
|
class TestFunctionFixture(testtools.TestCase):
|
||
|
|
||
|
def test_setup_only(self):
|
||
|
fixture = fixtures.FunctionFixture(lambda: 42)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(42, fixture.fn_result)
|
||
|
fixture.cleanUp()
|
||
|
self.assertFalse(hasattr(fixture, 'fn_result'))
|
||
|
|
||
|
def test_cleanup(self):
|
||
|
results = []
|
||
|
fixture = fixtures.FunctionFixture(lambda: 84, results.append)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(84, fixture.fn_result)
|
||
|
self.assertEqual([], results)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual([84], results)
|
||
|
|
||
|
def test_reset(self):
|
||
|
results = []
|
||
|
expected = [21, 7]
|
||
|
def setUp():
|
||
|
return expected.pop(0)
|
||
|
def reset(result):
|
||
|
results.append(('reset', result))
|
||
|
return expected.pop(0)
|
||
|
fixture = fixtures.FunctionFixture(setUp, results.append, reset)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual([], results)
|
||
|
fixture.reset()
|
||
|
self.assertEqual([('reset', 21)], results)
|
||
|
self.assertEqual(7, fixture.fn_result)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual([('reset', 21), 7], results)
|
||
|
|
||
|
|
||
|
class TestMethodFixture(testtools.TestCase):
|
||
|
|
||
|
def test_no_setup_cleanup(self):
|
||
|
class Stub:
|
||
|
pass
|
||
|
fixture = fixtures.MethodFixture(Stub())
|
||
|
fixture.setUp()
|
||
|
fixture.reset()
|
||
|
self.assertIsInstance(fixture.obj, Stub)
|
||
|
fixture.cleanUp()
|
||
|
|
||
|
def test_setup_only(self):
|
||
|
class Stub:
|
||
|
def setUp(self):
|
||
|
self.value = 42
|
||
|
fixture = fixtures.MethodFixture(Stub())
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(42, fixture.obj.value)
|
||
|
self.assertIsInstance(fixture.obj, Stub)
|
||
|
fixture.cleanUp()
|
||
|
|
||
|
def test_cleanup_only(self):
|
||
|
class Stub:
|
||
|
value = None
|
||
|
def tearDown(self):
|
||
|
self.value = 42
|
||
|
fixture = fixtures.MethodFixture(Stub())
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(None, fixture.obj.value)
|
||
|
self.assertIsInstance(fixture.obj, Stub)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual(42, fixture.obj.value)
|
||
|
|
||
|
def test_cleanup(self):
|
||
|
class Stub:
|
||
|
def setUp(self):
|
||
|
self.value = 42
|
||
|
def tearDown(self):
|
||
|
self.value = 84
|
||
|
fixture = fixtures.MethodFixture(Stub())
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(42, fixture.obj.value)
|
||
|
self.assertIsInstance(fixture.obj, Stub)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual(84, fixture.obj.value)
|
||
|
|
||
|
def test_custom_setUp(self):
|
||
|
class Stub:
|
||
|
def mysetup(self):
|
||
|
self.value = 42
|
||
|
obj = Stub()
|
||
|
fixture = fixtures.MethodFixture(obj, setup=obj.mysetup)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(42, fixture.obj.value)
|
||
|
self.assertEqual(obj, fixture.obj)
|
||
|
fixture.cleanUp()
|
||
|
|
||
|
def test_custom_cleanUp(self):
|
||
|
class Stub:
|
||
|
value = 42
|
||
|
def mycleanup(self):
|
||
|
self.value = None
|
||
|
obj = Stub()
|
||
|
fixture = fixtures.MethodFixture(obj, cleanup=obj.mycleanup)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(42, fixture.obj.value)
|
||
|
self.assertEqual(obj, fixture.obj)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual(None, fixture.obj.value)
|
||
|
|
||
|
def test_reset(self):
|
||
|
class Stub:
|
||
|
def setUp(self):
|
||
|
self.value = 42
|
||
|
def tearDown(self):
|
||
|
self.value = 84
|
||
|
def reset(self):
|
||
|
self.value = 126
|
||
|
obj = Stub()
|
||
|
fixture = fixtures.MethodFixture(obj, reset=obj.reset)
|
||
|
fixture.setUp()
|
||
|
self.assertEqual(obj, fixture.obj)
|
||
|
self.assertEqual(42, obj.value)
|
||
|
fixture.reset()
|
||
|
self.assertEqual(126, obj.value)
|
||
|
fixture.cleanUp()
|
||
|
self.assertEqual(84, obj.value)
|