[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