# fixtures: Fixtures with cleanups for testing and convenience. # # Copyright (c) 2010, Robert Collins # # 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)