Merge lp:~mbp/python-fixtures/timeout into lp:~python-fixtures/python-fixtures/trunk

Proposed by Martin Pool
Status: Merged
Merged at revision: 43
Proposed branch: lp:~mbp/python-fixtures/timeout
Merge into: lp:~python-fixtures/python-fixtures/trunk
Diff against target: 234 lines (+162/-0)
7 files modified
NEWS (+2/-0)
README (+18/-0)
lib/fixtures/__init__.py (+4/-0)
lib/fixtures/_fixtures/__init__.py (+6/-0)
lib/fixtures/_fixtures/timeout.py (+64/-0)
lib/fixtures/tests/_fixtures/__init__.py (+1/-0)
lib/fixtures/tests/_fixtures/test_timeout.py (+67/-0)
To merge this branch: bzr merge lp:~mbp/python-fixtures/timeout
Reviewer Review Type Date Requested Status
python-fixtures committers Pending
Review via email: mp+83721@code.launchpad.net

Description of the change

This adds a new TestTimeout based on https://code.launchpad.net/~mbp/bzr/test-timeout/+merge/83559

There are two modes, because I suspect people will have different preferences for being relatively sure things will stop vs getting a clean error.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

We discussed on IRC.

Could you please
rename to 'Timeout', and change from talking about use in tests (e.g. 1 per test) to use in general (can only use one of these at a time because it builds on SIGALARM).

lp:~mbp/python-fixtures/timeout updated
44. By Martin Pool

Rename to just 'Timeout'; other review cleanups

45. By Martin Pool

Rename to just TimeoutException, and remove more connections to Timeout only being used in tests

Revision history for this message
Robert Collins (lifeless) wrote :

I've merged this. I noted some race conditions while doing so, you may want to apply them to bzrlib's version (or start using fixtures :P).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2011-11-22 09:26:54 +0000
+++ NEWS 2011-11-29 02:25:26 +0000
@@ -11,6 +11,8 @@
11* EnvironmentVariableFixture now upcalls via super().11* EnvironmentVariableFixture now upcalls via super().
12 (Jonathan Lange, #881120)12 (Jonathan Lange, #881120)
1313
14* New Timeout fixture. (Martin Pool)
15
140.3.7160.3.7
15~~~~~17~~~~~
1618
1719
=== modified file 'README'
--- README 2011-11-22 10:07:24 +0000
+++ README 2011-11-29 02:25:26 +0000
@@ -335,3 +335,21 @@
335335
336The created directory is stored in the ``path`` attribute of the fixture after336The created directory is stored in the ``path`` attribute of the fixture after
337setUp.337setUp.
338
339Timeout
340+++++++
341
342Aborts if the covered code takes more than a specified number of whole wall-clock
343seconds.
344
345There are two possibilities, controlled by the 'gentle' argument: when gentle,
346an exception will be raised and the test (or other covered code) will fail.
347When not gentle, the entire process will be terminated, which is less clean,
348but more likely to break hangs where no Python code is running.
349
350*Caution:* Only one timeout can be active at any time across all threads in a
351single process. Using more than one has undefined results. (This could be
352improved by chaining alarms.)
353
354*Note:* Currently supported only on Unix because it relies on the ``alarm``
355system call.
338356
=== modified file 'lib/fixtures/__init__.py'
--- lib/fixtures/__init__.py 2011-11-22 08:58:38 +0000
+++ lib/fixtures/__init__.py 2011-11-29 02:25:26 +0000
@@ -51,6 +51,8 @@
51 'PythonPathEntry',51 'PythonPathEntry',
52 'TempDir',52 'TempDir',
53 'TestWithFixtures',53 'TestWithFixtures',
54 'Timeout',
55 'TimeoutException',
54 ]56 ]
5557
5658
@@ -64,6 +66,8 @@
64 PythonPackage,66 PythonPackage,
65 PythonPathEntry,67 PythonPathEntry,
66 TempDir,68 TempDir,
69 Timeout,
70 TimeoutException,
67 )71 )
68from fixtures.testcase import TestWithFixtures72from fixtures.testcase import TestWithFixtures
6973
7074
=== modified file 'lib/fixtures/_fixtures/__init__.py'
--- lib/fixtures/_fixtures/__init__.py 2011-10-26 15:10:31 +0000
+++ lib/fixtures/_fixtures/__init__.py 2011-11-29 02:25:26 +0000
@@ -25,6 +25,8 @@
25 'PythonPackage',25 'PythonPackage',
26 'PythonPathEntry',26 'PythonPathEntry',
27 'TempDir',27 'TempDir',
28 'Timeout',
29 'TimeoutException',
28 ]30 ]
2931
3032
@@ -36,3 +38,7 @@
36from fixtures._fixtures.pythonpackage import PythonPackage38from fixtures._fixtures.pythonpackage import PythonPackage
37from fixtures._fixtures.pythonpath import PythonPathEntry39from fixtures._fixtures.pythonpath import PythonPathEntry
38from fixtures._fixtures.tempdir import TempDir40from fixtures._fixtures.tempdir import TempDir
41from fixtures._fixtures.timeout import (
42 Timeout,
43 TimeoutException,
44 )
3945
=== added file 'lib/fixtures/_fixtures/timeout.py'
--- lib/fixtures/_fixtures/timeout.py 1970-01-01 00:00:00 +0000
+++ lib/fixtures/_fixtures/timeout.py 2011-11-29 02:25:26 +0000
@@ -0,0 +1,64 @@
1# fixtures: Fixtures with cleanups for testing and convenience.
2#
3# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.net>
4#
5# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6# license at the users choice. A copy of both licenses are available in the
7# project source as Apache-2.0 and BSD. You may not use this file except in
8# compliance with one of these two licences.
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# license you chose for the specific language governing permissions and
14# limitations under that license.
15
16
17"""Timeout fixture."""
18
19
20import signal
21
22import fixtures
23
24__all__ = [
25 'Timeout',
26 'TimeoutException',
27 ]
28
29
30class TimeoutException(Exception):
31 """Timeout expired"""
32
33
34class Timeout(fixtures.Fixture):
35 """Fixture that aborts the contained code after a number of seconds.
36
37 The interrupt can be either gentle, in which case TimeoutException is
38 raised, or not gentle, in which case the process will typically be aborted
39 by SIGALRM.
40
41 Cautions:
42 * This has no effect on Windows.
43 * Only one Timeout can be used at any time per process.
44 """
45
46 def __init__(self, timeout_secs, gentle):
47 self.timeout_secs = timeout_secs
48 self.alarm_fn = getattr(signal, 'alarm', None)
49 self.gentle = gentle
50
51 def signal_handler(self, signum, frame):
52 raise TimeoutException()
53
54 def setUp(self):
55 super(Timeout, self).setUp()
56 if self.alarm_fn is None:
57 return # Can't run on Windows
58 self.alarm_fn(self.timeout_secs)
59 self.addCleanup(lambda: self.alarm_fn(0))
60 if self.gentle:
61 saved_handler = signal.signal(signal.SIGALRM, self.signal_handler)
62 self.addCleanup(lambda: signal.signal(
63 signal.SIGALRM, saved_handler))
64 # Otherwise, the SIGALRM will probably kill the process.
065
=== modified file 'lib/fixtures/tests/_fixtures/__init__.py'
--- lib/fixtures/tests/_fixtures/__init__.py 2011-10-27 11:58:24 +0000
+++ lib/fixtures/tests/_fixtures/__init__.py 2011-11-29 02:25:26 +0000
@@ -23,6 +23,7 @@
23 'pythonpackage',23 'pythonpackage',
24 'pythonpath',24 'pythonpath',
25 'tempdir',25 'tempdir',
26 'timeout',
26 ]27 ]
27 prefix = "fixtures.tests._fixtures.test_"28 prefix = "fixtures.tests._fixtures.test_"
28 test_mod_names = [prefix + test_module for test_module in test_modules]29 test_mod_names = [prefix + test_module for test_module in test_modules]
2930
=== added file 'lib/fixtures/tests/_fixtures/test_timeout.py'
--- lib/fixtures/tests/_fixtures/test_timeout.py 1970-01-01 00:00:00 +0000
+++ lib/fixtures/tests/_fixtures/test_timeout.py 2011-11-29 02:25:26 +0000
@@ -0,0 +1,67 @@
1# fixtures: Fixtures with cleanups for testing and convenience.
2#
3# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.net>
4#
5# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6# license at the users choice. A copy of both licenses are available in the
7# project source as Apache-2.0 and BSD. You may not use this file except in
8# compliance with one of these two licences.
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# license you chose for the specific language governing permissions and
14# limitations under that license.
15
16import os
17import signal
18import time
19
20import testtools
21from testtools.testcase import (
22 TestSkipped,
23 )
24
25import fixtures
26
27
28def sample_timeout_passes():
29 with fixtures.Timeout(100, gentle=True):
30 pass # Timeout shouldn't fire
31
32def sample_long_delay_with_gentle_timeout():
33 with fixtures.Timeout(1, gentle=True):
34 time.sleep(100) # Expected to be killed here.
35
36def sample_long_delay_with_harsh_timeout():
37 with fixtures.Timeout(1, gentle=False):
38 time.sleep(100) # Expected to be killed here.
39
40
41class TestTimeout(testtools.TestCase, fixtures.TestWithFixtures):
42
43 def requireUnix(self):
44 if getattr(signal, 'alarm', None) is None:
45 raise TestSkipped("no alarm() function")
46
47 def test_timeout_passes(self):
48 # This can pass even on Windows - the test is skipped.
49 sample_timeout_passes()
50
51 def test_timeout_gentle(self):
52 self.requireUnix()
53 self.assertRaises(
54 fixtures.TimeoutException,
55 sample_long_delay_with_gentle_timeout)
56
57 def test_timeout_harsh(self):
58 self.requireUnix()
59 # This will normally kill the whole process, which would be
60 # inconvenient. Let's hook the alarm here so we can observe it.
61 self.got_alarm = False
62 def sigalrm_handler(signum, frame):
63 self.got_alarm = True
64 old_handler = signal.signal(signal.SIGALRM, sigalrm_handler)
65 self.addCleanup(signal.signal, signal.SIGALRM, old_handler)
66 sample_long_delay_with_harsh_timeout()
67 self.assertTrue(self.got_alarm)

Subscribers

People subscribed via source and target branches