[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