[Checkins] SVN: zc.async/trunk/src/zc/async/ Fix some testing-related bugs (see CHANGES).
Patrick Strawderman
patrick at zope.com
Tue Sep 29 10:07:30 EDT 2009
Log message for revision 104606:
Fix some testing-related bugs (see CHANGES).
Changed:
U zc.async/trunk/src/zc/async/CHANGES.txt
U zc.async/trunk/src/zc/async/ftesting.py
U zc.async/trunk/src/zc/async/ftesting.txt
U zc.async/trunk/src/zc/async/subscribers.py
U zc.async/trunk/src/zc/async/subscribers.txt
U zc.async/trunk/src/zc/async/testing.py
-=-
Modified: zc.async/trunk/src/zc/async/CHANGES.txt
===================================================================
--- zc.async/trunk/src/zc/async/CHANGES.txt 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/CHANGES.txt 2009-09-29 14:07:30 UTC (rev 104606)
@@ -2,14 +2,31 @@
Changes
=======
+1.5.3 (unreleased)
+==================
+
+- Made zc.async.subscribers.ThreadedDispatcherInstaller keep track of signal
+ handlers it installs in a module global "signal_handlers."
+
+- Made zc.async.ftesting.tearDown restore the signal handlers that were
+ replaced by ThreadedDispatcherInstaller.
+
+- Fix a bug in zc.async.ftesting.setUp and zc.async.testing.print_logs
+ which would result in the default argument for log_file becoming
+ "fixated" with an incorrect value across tests.
+
+- Make the ftesting.txt test exercise the 'zc.async' logger in
+ addition to 'zc.async.event'.
+
1.5.2 (2009-07-22)
==================
-- Fix a bug were zc.async.testing._datetime.now did not accept the same keyword
- arguments as datetime.datetime, added tests.
-- Fix a bug were zc.async.testing._datetime.astimezone did not accept the same
+- Fix a bug where zc.async.testing._datetime.now did not accept the same
keyword arguments as datetime.datetime, added tests.
+- Fix a bug where zc.async.testing._datetime.astimezone did not accept the same
+ keyword arguments as datetime.datetime, added tests.
+
1.5.1 (2008-10-13)
==================
@@ -120,8 +137,8 @@
1.4.2 (2009-07-17)
==================
-- Fix a bug were zc.async.testing._datetime.now did not accept the same keyword
- arguments as datetime.datetime, added tests.
+- Fix a bug where zc.async.testing._datetime.now did not accept the same
+ keyword arguments as datetime.datetime, added tests.
1.4.1 (2008-07-30)
==================
Modified: zc.async/trunk/src/zc/async/ftesting.py
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.py 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/ftesting.py 2009-09-29 14:07:30 UTC (rev 104606)
@@ -1,4 +1,5 @@
import logging
+import signal
import sys
import transaction
import zope.component
@@ -9,13 +10,20 @@
# helper functions convenient for Zope 3 functional tests
+# setUp's default parameter for log_file used to be sys.stdout.
+# This could cause problems in tests, as Python evaluates default
+# arguments once, but sys.stdout may change across tests. Passing
+# None for log_file is already used in order to signify no logging,
+# so we use a marker instead.
+_marker = object()
+
def setUp(
connection=None,
queue_installer=zc.async.subscribers.queue_installer,
dispatcher_installer=zc.async.subscribers.ThreadedDispatcherInstaller(
poll_interval=0.1),
agent_installer=zc.async.subscribers.agent_installer,
- log_file=sys.stdout, log_level=logging.CRITICAL):
+ log_file=_marker, log_level=logging.CRITICAL):
"""Set up zc.async, as is needed for Zope 3 functional tests.
"""
if connection is None:
@@ -32,6 +40,8 @@
assert "" in zc.async.testing.get_poll(dispatcher)
assert dispatcher.activated is not None
if log_file is not None:
+ if log_file is _marker:
+ log_file = sys.stdout
# this helps with debugging critical problems that happen in your
# zc.async calls. Of course, if your test
# intentionally generates CRITICAL log messages, you may not want this;
@@ -49,3 +59,14 @@
del dispatcher._debug_handler
zc.async.testing.tear_down_dispatcher(dispatcher)
zc.async.dispatcher.clear()
+ # Restore previous signal handlers
+ key = id(dispatcher)
+ sighandlers = zc.async.subscribers.signal_handlers.get(key)
+ if sighandlers:
+ for _signal, handlers in sighandlers.items():
+ prev, cur = handlers
+ # The previous signal handler is only restored if the currently
+ # registered handler is the one we originally installed.
+ if signal.getsignal(_signal) is cur:
+ signal.signal(_signal, prev)
+ del zc.async.subscribers.signal_handlers[key]
Modified: zc.async/trunk/src/zc/async/ftesting.txt
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.txt 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/ftesting.txt 2009-09-29 14:07:30 UTC (rev 104606)
@@ -181,10 +181,16 @@
messages in the "zc.async" logger to stdout.
>>> import logging
- >>> logging.getLogger('zc.async.event').critical('Foo!')
+ >>> logging.getLogger('zc.async').critical('Foo!')
Foo!
- >>> logging.getLogger('zc.async.event').error('Bar!')
+ >>> logging.getLogger('zc.async').error('Bar!')
+The zc.async.* logs are configured to work the same way.
+
+ >>> logging.getLogger('zc.async.event').critical('Foo!')
+ Foo!
+ >>> logging.getLogger('zc.async.event').error('Bar!')
+
Once you have finished your tests, make sure to shut down your dispatcher, or
the testing framework will complain about an unstopped daemon thread.
zc.async.ftesting.tearDown will do the trick.
@@ -222,7 +228,7 @@
>>> zc.async.ftesting.tearDown() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
- TearDownDispatcherError:
+ TearDownDispatcherError:
Job in pool 'main' failed to stop:
<zc.async.job.Job (oid ..., db ...) ``zc.async.doctest_test.bad_job()``>
>>> zc.async.testing.wait_for_result(job)
@@ -239,7 +245,7 @@
>>> zc.async.ftesting.tearDown() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
- TearDownDispatcherError:
+ TearDownDispatcherError:
Dispatcher (..., ...) failed to stop.
Let's restore the original reactor.stop method and call tearDown again, which
Modified: zc.async/trunk/src/zc/async/subscribers.py
===================================================================
--- zc.async/trunk/src/zc/async/subscribers.py 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/subscribers.py 2009-09-29 14:07:30 UTC (rev 104606)
@@ -76,6 +76,7 @@
queue_installer = QueueInstaller()
multidb_queue_installer = QueueInstaller(db_name='async')
+signal_handlers = {} # id(dispatcher) -> signal -> (prev handler, curr handler)
class ThreadedDispatcherInstaller(object):
def __init__(self,
@@ -125,11 +126,20 @@
reactor.callFromThread(reactor.stop)
raise SystemExit()
- signal.signal(signal.SIGINT, sigint_handler)
- signal.signal(signal.SIGTERM, handler)
+ # We keep a record of the current signal handler and the one we're
+ # installing so that our handler can later be uninstalled and the old
+ # one reinstated (for instance, by zc.async.ftesting.tearDown).
+ key = id(dispatcher)
+ handlers = signal_handlers[key] = {}
+
+ handlers[signal.SIGINT] = (
+ signal.signal(signal.SIGINT, sigint_handler), sigint_handler,)
+ handlers[signal.SIGTERM] = (signal.signal(signal.SIGTERM, handler),
+ handler,)
# Catch Ctrl-Break in windows
if getattr(signal, "SIGBREAK", None) is not None:
- signal.signal(signal.SIGBREAK, handler)
+ handlers[signal.SIGBREAK] = (
+ signal.signal(signal.SIGBREAK, handler), handler,)
threaded_dispatcher_installer = ThreadedDispatcherInstaller()
Modified: zc.async/trunk/src/zc/async/subscribers.txt
===================================================================
--- zc.async/trunk/src/zc/async/subscribers.txt 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/subscribers.txt 2009-09-29 14:07:30 UTC (rev 104606)
@@ -221,6 +221,72 @@
>>> bool(da.activated)
False
+The dispatcher installer keeps track of the signal handlers it has
+registered, as well as the signal handlers that were replaced. This makes
+it more convenient to restore the old signal handlers if needed.
+
+References to the handlers are stored in the module global signal_handlers,
+which is keyed by id(dispatcher).
+
+ >>> signal_handlers = zc.async.subscribers.signal_handlers[id(dispatcher)]
+
+The value is a mapping of signal to a tuple containing the old and new
+signal handlers.
+
+ >>> signal.SIGINT in signal_handlers
+ True
+ >>> signal.SIGTERM in signal_handlers
+ True
+ >>> has_sigbreak = hasattr(signal, "SIGBREAK")
+ >>> not has_sigbreak or signal.SIGBREAK in signal_handlers
+ True
+
+zc.async.ftesting.tearDown will restore the old signal handlers, unless
+the currently registered handler is different than the one that was
+registered by the installer.
+
+Let's register a newer SIGTERM handler to exercise this.
+
+ >>> old_sigterm_handler = signal.getsignal(signal.SIGTERM)
+ >>> def new_sigterm_handler(*args):
+ ... old_sigterm_handler(*args)
+ >>> ignored = signal.signal(signal.SIGTERM, new_sigterm_handler)
+
+ >>> import zc.async.ftesting
+ >>> zc.async.ftesting.tearDown()
+
+The signal_handlers mapping has been cleared.
+
+ >>> zc.async.subscribers.signal_handlers
+ {}
+
+The old signal handlers have been restored.
+
+ >>> prev, curr = signal_handlers[signal.SIGINT]
+ >>> signal.getsignal(signal.SIGINT) is prev
+ True
+ >>> if has_sigbreak:
+ ... prev, curr = signal_handlers[signal.SIGBREAK]
+ ... signal.getsignal(signal.SIGBREAK) is prev
+ ... else:
+ ... True
+ True
+
+...except for the SIGTERM handler, since we registered a newer one.
+
+ >>> prev, curr = signal_handlers[signal.SIGTERM]
+ >>> old_sigterm_handler is curr
+ True
+ >>> signal.getsignal(signal.SIGTERM) is prev
+ False
+ >>> signal.getsignal(signal.SIGTERM) is new_sigterm_handler
+ True
+ >>> ignore = signal.signal(signal.SIGTERM, prev) # restore original handler
+
+
+
+
+
.. ......... ..
.. Footnotes ..
.. ......... ..
Modified: zc.async/trunk/src/zc/async/testing.py
===================================================================
--- zc.async/trunk/src/zc/async/testing.py 2009-09-29 12:02:59 UTC (rev 104605)
+++ zc.async/trunk/src/zc/async/testing.py 2009-09-29 14:07:30 UTC (rev 104606)
@@ -303,7 +303,8 @@
problems = '\n' + '\n'.join(problems)
raise TearDownDispatcherError(problems)
-def print_logs(log_file=sys.stdout, log_level=logging.CRITICAL):
+def print_logs(log_file=None, log_level=logging.CRITICAL):
+ log_file = log_file or sys.stdout
# really more of a debugging tool
logger = logging.getLogger('zc.async')
# stashing this on the dispatcher is a hack, but at least we're doing
More information about the checkins
mailing list