[Checkins] SVN: zc.async/trunk/s small changes, primarily for testing conveniences

Gary Poster gary at zope.com
Wed Jul 16 14:19:27 EDT 2008


Log message for revision 88420:
  small changes, primarily for testing conveniences

Changed:
  U   zc.async/trunk/setup.py
  U   zc.async/trunk/src/zc/async/CHANGES.txt
  U   zc.async/trunk/src/zc/async/configure.py
  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/job.py
  U   zc.async/trunk/src/zc/async/job.txt
  U   zc.async/trunk/src/zc/async/testing.py
  U   zc.async/trunk/src/zc/async/tips.txt

-=-
Modified: zc.async/trunk/setup.py
===================================================================
--- zc.async/trunk/setup.py	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/setup.py	2008-07-16 18:19:26 UTC (rev 88420)
@@ -71,7 +71,7 @@
 
 setup(
     name='zc.async',
-    version='1.3.0',
+    version='1.4.0a1',
     packages=find_packages('src'),
     package_dir={'':'src'},
     zip_safe=False,

Modified: zc.async/trunk/src/zc/async/CHANGES.txt
===================================================================
--- zc.async/trunk/src/zc/async/CHANGES.txt	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/CHANGES.txt	2008-07-16 18:19:26 UTC (rev 88420)
@@ -4,6 +4,15 @@
 - Mentioned in ftesting.txt that Zope 3 users should uses zope.app.testing
   3.4.2 or newer.  Also added a summary section at the beginning of that file.
 
+- Added logging of critical messages to __stdout__ for ``ftesting.setUp``.
+  This can help discovering problems in callback transactions.
+
+- Changed testing.wait_for_result and testing.wait_for_annotation to ignore
+  ReadConflictErrors, so they can be used more reliably in tests that use
+  MappingStorage, and other storages without MVCC.
+
+- Support <type 'builtin_function_or_method'> for adaptation to Job.
+
 1.3 (2008-07-04)
 ================
 

Modified: zc.async/trunk/src/zc/async/configure.py
===================================================================
--- zc.async/trunk/src/zc/async/configure.py	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/configure.py	2008-07-16 18:19:26 UTC (rev 88420)
@@ -48,6 +48,10 @@
         zc.async.job.Job,
         adapts=(zc.twist.METHOD_WRAPPER_TYPE,),
         provides=zc.async.interfaces.IJob)
+    zope.component.provideAdapter( # optional, rarely used
+        zc.async.job.Job,
+        adapts=(types.BuiltinFunctionType,),
+        provides=zc.async.interfaces.IJob)
 
     # UUID for this instance
     zope.component.provideUtility(

Modified: zc.async/trunk/src/zc/async/ftesting.py
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.py	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/ftesting.py	2008-07-16 18:19:26 UTC (rev 88420)
@@ -1,3 +1,4 @@
+import logging
 import sys
 import transaction
 import zope.component
@@ -13,7 +14,8 @@
     queue_installer=zc.async.subscribers.queue_installer,
     dispatcher_installer=zc.async.subscribers.ThreadedDispatcherInstaller(
         poll_interval=0.1),
-    agent_installer=zc.async.subscribers.agent_installer):
+    agent_installer=zc.async.subscribers.agent_installer,
+    log_file=sys.stdout, log_level=logging.CRITICAL):
     """Set up zc.async, as is needed for Zope 3 functional tests.
     """
     if connection is None:
@@ -29,10 +31,25 @@
     zc.async.testing.get_poll(dispatcher, count=0)
     assert "" in zc.async.testing.get_poll(dispatcher)
     assert dispatcher.activated is not None
+    if log_file is not None:
+        # 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;
+        # pass ``log_file=None`` to setUp.
+        logger = logging.getLogger('zc.async')
+        # stashing this on the dispatcher is a hack, but at least we're doing
+        # it on code from the same package.
+        dispatcher._debug_handler = logging.StreamHandler(log_file)
+        logger.setLevel(log_level)
+        logger.addHandler(dispatcher._debug_handler)
 
 
 def tearDown():
     dispatcher = zc.async.dispatcher.get()
+    if getattr(dispatcher, '_debug_handler', None) is not None:
+        logger = logging.getLogger('zc.async')
+        logger.removeHandler(dispatcher._debug_handler)
+        del dispatcher._debug_handler
     dispatcher.reactor.callFromThread(dispatcher.reactor.stop)
     dispatcher.thread.join(3)
     zc.async.dispatcher.clear()

Modified: zc.async/trunk/src/zc/async/ftesting.txt
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.txt	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/ftesting.txt	2008-07-16 18:19:26 UTC (rev 88420)
@@ -11,26 +11,30 @@
 
 - The Zope 3 tests use DemoStorage, which does not use MVCC.  This can lead
   to your tests having occasional ConflictErrors that will not occur in
-  production.  In common cases, you won't notice these because of Zope's
-  usual retry policy.
+  production. In common cases for ftests, you won't notice these because of
+  Zope's usual retry policy.  Unit or integration tests may show these
+  problems.
 
 - Set up the basic configuration in zcml or Python (see examples below), but
   you need to make sure that ftests do not use dispatchers started in the
-  application. Start up ftest dispatchers separately, using
-  ``zc.async.ftesting.setUp``, and then tear down after the tests are done with
-  ``zc.async.ftesting.tearDown``.
+  application. Start up ftest (or integration test) dispatchers separately,
+  using ``zc.async.ftesting.setUp``, and then tear down after the tests are
+  done with ``zc.async.ftesting.tearDown``.
 
   The ftest dispatcher polls every tenth of a second, so you shouldn't need to
   wait long for you job to get started in your tests.
 
-- General zc.async testing tools such as ``zc.async.dispatcher.get`` and
-  ``zc.async.testing.get_poll`` can still be useful for in-depth zc.async
-  tests.
+- General zc.async testing tools such as ``zc.async.dispatcher.get``,
+  ``zc.async.testing.get_poll`` and ``zc.async.testing.wait_for_result`` can
+  still be useful for in-depth zc.async tests.
 
 - If you don't want to dig into guts in your functional tests to use the tools
   described in the previous point, consider making a view to check on job
   status using a data structure like JSON, and looking at that in your tests.
 
+- The ``setUp`` code by default sends critical log messages to __stdout__ so it
+  can help diagnose why a callback might never complete.
+
 ----------
 Discussion
 ----------
@@ -164,6 +168,19 @@
 on a given job.  These are demonstrated in various doctests in this package,
 but should also be reasonably simple and self-explanatory.
 
+Callbacks will retry some errors forever, by default.  The logic is that
+callbacks are often the "cleanup" and must be run.  This can lead to confusion
+in debugging tests, though, because the retry warnings are sent to the log,
+and the log is not usually monitored in functional tests.
+
+``setUp`` tries to help with this by adding logging of ``CRITICAL`` log
+messages in the "zc.async" logger to stdout.
+
+    >>> import logging
+    >>> 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.

Modified: zc.async/trunk/src/zc/async/job.py
===================================================================
--- zc.async/trunk/src/zc/async/job.py	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/job.py	2008-07-16 18:19:26 UTC (rev 88420)
@@ -375,6 +375,10 @@
         elif isinstance(value, zc.twist.METHOD_WRAPPER_TYPE):
             self._callable_root = zc.twist.get_self(value)
             self._callable_name = value.__name__
+        elif (isinstance(value, types.BuiltinMethodType) and
+              getattr(value, '__self__', None) is not None):
+            self._callable_root = value.__self__
+            self._callable_name = value.__name__
         else:
             self._callable_root, self._callable_name = value, None
         if zc.async.interfaces.IJob.providedBy(self._callable_root):

Modified: zc.async/trunk/src/zc/async/job.txt
===================================================================
--- zc.async/trunk/src/zc/async/job.txt	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/job.txt	2008-07-16 18:19:26 UTC (rev 88420)
@@ -94,6 +94,8 @@
     >>> demo.counter
     1
 
+[#other_callables]_
+
 So our two calls so far have returned direct successes.  This one returns
 a failure, because the wrapped call raises an exception.
 
@@ -1653,6 +1655,17 @@
     ...
     AttributeError: can't set attribute
 
+.. [#other_callables]  Other callables also work.
+
+    >>> import time
+    >>> import types
+    >>> isinstance(time.time, types.BuiltinFunctionType)
+    True
+    >>> j = root['j'] = zc.async.job.Job(time.time)
+    >>> transaction.commit()
+    >>> time.time() <= j() <= time.time()
+    True
+
 .. [#no_live_frames] Failures have two particularly dangerous bits: the
     traceback and the stack.  We use the __getstate__ code on Failures
     to clean them up.  This makes the traceback (``tb``) None...

Modified: zc.async/trunk/src/zc/async/testing.py
===================================================================
--- zc.async/trunk/src/zc/async/testing.py	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/testing.py	2008-07-16 18:19:26 UTC (rev 88420)
@@ -11,6 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
+import ZODB.POSException
 import threading
 import bisect
 from time import sleep as time_sleep # this import style is intentional, so
@@ -201,8 +202,12 @@
 def wait_for_result(job, seconds=6):
     for i in range(seconds * 10):
         t = transaction.begin()
-        if job.status == zc.async.interfaces.COMPLETED:
-            return job.result
+        try:
+            if job.status == zc.async.interfaces.COMPLETED:
+                return job.result
+        except ZODB.POSException.ReadConflictError:
+            # storage does not have MVCC
+            pass
         time_sleep(0.1)
     else:
         assert False, 'job never completed'
@@ -211,8 +216,12 @@
 def wait_for_annotation(job, name):
     for i in range(60):
         t = transaction.begin()
-        if name in job.annotations:
-            return job.annotations[name]
+        try:
+            if name in job.annotations:
+                return job.annotations[name]
+        except ZODB.POSException.ReadConflictError:
+            # storage does not have MVCC
+            pass
         time_sleep(0.1)
     else:
         assert False, 'annotation never found'

Modified: zc.async/trunk/src/zc/async/tips.txt
===================================================================
--- zc.async/trunk/src/zc/async/tips.txt	2008-07-16 17:13:52 UTC (rev 88419)
+++ zc.async/trunk/src/zc/async/tips.txt	2008-07-16 18:19:26 UTC (rev 88420)
@@ -30,6 +30,10 @@
   or at all, with a storage that supports those features such as FileStorage.
   Notice that all of the tests in this package use FileStorage.
 
+* If you get a failure as a result and you didn't expect it, don't forget
+  the ``getTraceback`` and ``printTraceback`` methods on the failure.  The
+  whole point of the failure is to help you diagnose problems.
+
 * ``zc.async.dispatcher.get()`` will get you the dispatcher.  You can then check
   if it is ``activated`` and also use the other introspection and status
   methods.



More information about the Checkins mailing list