diff options
author | Martin Pool <mbp@canonical.com> | 2011-11-29 12:56:44 +1100 |
---|---|---|
committer | Martin Pool <mbp@canonical.com> | 2011-11-29 12:56:44 +1100 |
commit | 3e502a29cc859f5c6b74827f5f321dfb5a193e3c (patch) | |
tree | 9dad8e9431ba8987f4dd8e0edf1456933d40fb17 | |
parent | 5c1a5db9a6e06ff1fa2f816e99b606d3622fc789 (diff) | |
download | fixtures-3e502a29cc859f5c6b74827f5f321dfb5a193e3c.tar.gz |
Add TestTimeout fixture
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | lib/fixtures/__init__.py | 2 | ||||
-rw-r--r-- | lib/fixtures/_fixtures/__init__.py | 2 | ||||
-rw-r--r-- | lib/fixtures/_fixtures/timeout.py | 45 | ||||
-rw-r--r-- | lib/fixtures/tests/_fixtures/__init__.py | 1 | ||||
-rw-r--r-- | lib/fixtures/tests/_fixtures/test_timeout.py | 76 |
7 files changed, 142 insertions, 0 deletions
@@ -11,6 +11,8 @@ CHANGES: * EnvironmentVariableFixture now upcalls via super(). (Jonathan Lange, #881120) +* New TimeoutFixture. (Martin Pool) + 0.3.7 ~~~~~ @@ -335,3 +335,17 @@ Create a temporary directory and clean it up later. The created directory is stored in the ``path`` attribute of the fixture after setUp. + +TimeoutFixture +++++++++++++++ + +Interrupts tests if they take more than a specified number of whole wall-clock +seconds. + +There are two possibilities, controlled by the 'gentle' argument: when gentle, +an exception will be raised and the test will fail. When not gentle, the +entire test process will be terminated, which is less clean, but more likely to +break hangs where no Python code is running. You can only use one in any given +test. + +Currently supported only on Unix because it relies on the ``alarm`` system call. diff --git a/lib/fixtures/__init__.py b/lib/fixtures/__init__.py index 1d1948b..9e54aef 100644 --- a/lib/fixtures/__init__.py +++ b/lib/fixtures/__init__.py @@ -51,6 +51,7 @@ __all__ = [ 'PythonPathEntry', 'TempDir', 'TestWithFixtures', + 'TestTimeout', ] @@ -64,6 +65,7 @@ from fixtures._fixtures import ( PythonPackage, PythonPathEntry, TempDir, + TestTimeout, ) from fixtures.testcase import TestWithFixtures diff --git a/lib/fixtures/_fixtures/__init__.py b/lib/fixtures/_fixtures/__init__.py index 082c936..20285ef 100644 --- a/lib/fixtures/_fixtures/__init__.py +++ b/lib/fixtures/_fixtures/__init__.py @@ -25,6 +25,7 @@ __all__ = [ 'PythonPackage', 'PythonPathEntry', 'TempDir', + 'TestTimeout', ] @@ -36,3 +37,4 @@ from fixtures._fixtures.packagepath import PackagePathEntry from fixtures._fixtures.pythonpackage import PythonPackage from fixtures._fixtures.pythonpath import PythonPathEntry from fixtures._fixtures.tempdir import TempDir +from fixtures._fixtures.timeout import TestTimeout diff --git a/lib/fixtures/_fixtures/timeout.py b/lib/fixtures/_fixtures/timeout.py new file mode 100644 index 0000000..b1d133b --- /dev/null +++ b/lib/fixtures/_fixtures/timeout.py @@ -0,0 +1,45 @@ +# fixtures: Fixtures with cleanups for testing and convenience. +# +# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.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 signal + +import fixtures + + +class TestTimeoutException(Exception): + """Test timed out""" + + +class TestTimeout(fixtures.Fixture): + + def __init__(self, timeout_secs, gentle): + self.timeout_secs = timeout_secs + self.alarm_fn = getattr(signal, 'alarm', None) + self.gentle = gentle + + def signal_handler(self): + raise TestTimeoutException() + + def setUp(self): + super(TestTimeout, self).setUp() + if self.alarm_fn is None: + return # Can't run on Windows + self.alarm_fn(self.timeout_secs) + self.addCleanup(lambda: self.alarm_fn(0)) + if self.gentle: + saved_handler = signal.signal(signal.SIGALRM, self.signal_handler) + self.addCleanup(lambda: signal.signal(signal.SIGALRM, saved_handler)) + # Otherwise, the SIGALRM will probably kill the process. diff --git a/lib/fixtures/tests/_fixtures/__init__.py b/lib/fixtures/tests/_fixtures/__init__.py index b4d4be0..8e817a0 100644 --- a/lib/fixtures/tests/_fixtures/__init__.py +++ b/lib/fixtures/tests/_fixtures/__init__.py @@ -23,6 +23,7 @@ def load_tests(loader, standard_tests, pattern): 'pythonpackage', 'pythonpath', 'tempdir', + 'timeout', ] prefix = "fixtures.tests._fixtures.test_" test_mod_names = [prefix + test_module for test_module in test_modules] diff --git a/lib/fixtures/tests/_fixtures/test_timeout.py b/lib/fixtures/tests/_fixtures/test_timeout.py new file mode 100644 index 0000000..870f3bc --- /dev/null +++ b/lib/fixtures/tests/_fixtures/test_timeout.py @@ -0,0 +1,76 @@ +# fixtures: Fixtures with cleanups for testing and convenience. +# +# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.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 os +import signal + +import testtools +from testtools.testcase import ( + TestSkipped, + ) + +import fixtures +from fixtures import EnvironmentVariableFixture, TestWithFixtures + + +class ExampleTests(testtools.TestCase, TestWithFixtures): + """These are not intended to pass: they are sample data for the real tests""" + + def sample_timeout_passes(self): + self.useFixture(fixtures.TestTimeout(100, gentle=True)) + pass # Timeout shouldn't fire + + def sample_long_delay_with_timeout(self): + self.useFixture(fixtures.TestTimeout(2, gentle=True)) + time.sleep(100) # Expected to be killed here. + + def sample_long_delay_with_harsh_timeout(self): + self.useFixture(fixtures.TestTimeout(2, gentle=False)) + time.sleep(100) # Expected to be killed here. + + + +class TestTimeoutFixture(testtools.TestCase, TestWithFixtures): + + def requireUnix(self): + if getattr(signal, 'alarm', None) is None: + raise TestSkipped("no alarm() function") + + def test_timeout_passes(self): + # This can pass even on Windows - the test is skipped. + test = ExampleTests('sample_timeout_passes') + result = test.run() + self.assertTrue(result.wasSuccessful()) + + def test_timeout_gentle(self): + self.requireUnix() + test = ExampleTests('sample_long_delay_with_timeout') + result = test.run() + self.assertFalse(result.wasSuccessful()) + + def test_timeout_harsh(self): + self.requireUnix() + test = ExampleTests('sample_long_delay_with_harsh_timeout') + # This will normally kill the whole process, which would be + # inconvenient. Let's hook the alarm here so we can observe it. + got_alarm = False + def sigalrm_handler(): + got_alarm = True + old_handler = signal.signal(signal.SIGALRM, sigalrm_handler) + self.addCleanup(signal.signal, signal.SIGALRM, old_handler) + import pdb;pdb.set_trace() + result = test.run() + self.assertFalse(result.wasSuccessful()) + self.assertTrue(got_alarm) |