[Checkins] SVN: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt should have kept the test
Godefroid Chapelle
gotcha at bubblenet.be
Mon Oct 11 08:10:17 EDT 2010
Log message for revision 117449:
should have kept the test
Changed:
A Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt
-=-
Copied: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt (from rev 117448, lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt)
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt (rev 0)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt 2010-10-11 12:10:16 UTC (rev 117449)
@@ -0,0 +1,259 @@
+==================================
+Task Service Browser Management UI
+==================================
+
+Let's start a browser:
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
+ >>> browser.handleErrors = False
+
+Now we add a task service:
+
+ >>> browser.open('http://localhost/manage')
+ >>> browser.getLink('Remote Task Service').click()
+ >>> browser.getControl(name='new_value').value = 'tasks'
+ >>> browser.getControl('Apply').click()
+
+Now let's have a look at the job's table:
+
+ >>> browser.getLink('tasks').click()
+
+You can see the available tasks:
+
+ >>> 'Available Tasks' in browser.contents
+ True
+
+By default there is an "echo" task:
+
+ >>> '<div>echo</div>' in browser.contents
+ True
+
+Below you see a table of all the jobs. Initially we have no jobs, so let's add
+one via XML-RPC:
+
+ >>> print http(r"""
+ ... POST /tasks/ HTTP/1.0
+ ... Authorization: Basic mgr:mgrpw
+ ... Content-Type: text/xml
+ ...
+ ... <?xml version='1.0'?>
+ ... <methodCall>
+ ... <methodName>add</methodName>
+ ... <params>
+ ... <value><string>echo</string></value>
+ ... <value><struct>
+ ... <key><string>foo</string></key>
+ ... <value><string>bar</string></value>
+ ... </struct></value>
+ ... </params>
+ ... </methodCall>
+ ... """)
+ HTTP/1.0 200 Ok
+ ...
+
+If we now refresh the screen, we will see the new job:
+
+ >>> browser.reload()
+ >>> print browser.contents
+ <!DOCTYPE ...
+ <tbody>
+ <tr class="odd">
+ <td class="">
+ <input type="checkbox" name="jobs:list" value="1506179619">
+ </td>
+ <td class="tableId">
+ 1506179619
+ </td>
+ <td class="tableTask">
+ echo
+ </td>
+ <td class="tableStatus">
+ <span class="status-queued">queued</span>
+ </td>
+ <td class="tableDetail">
+ No input detail available
+ </td>
+ <td class="tableCreated">
+ ...
+ </td>
+ <td class="tableStart">
+ [not set]
+ </td>
+ <td class="tableEnd">
+ [not set]
+ </td>
+ </tr>
+ </tbody>
+ ...
+
+It is possible to provide custom views for the details. Note the name of the
+view "echo_detail", it consists of the task name and "_detail". This allows us
+to use different detail views on the same job classes. if no such view is
+found a view with name 'detail' is searched.
+
+ >>> from zope import interface
+ >>> from zope.publisher.interfaces.browser import IBrowserView
+ >>> class EchoDetailView(object):
+ ... interface.implements(IBrowserView)
+ ... def __init__(self, context, request):
+ ... self.context = context
+ ... self.request = request
+ ... def __call__(self):
+ ... return u'echo: foo=%s'% self.context.input['foo']
+ >>> from lovely.remotetask.interfaces import IJob
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+ >>> from zope import component
+ >>> component.provideAdapter(EchoDetailView,
+ ... (IJob, IDefaultBrowserLayer),
+ ... name='echo_detail')
+ >>> browser.reload()
+ >>> print browser.contents
+ <!DOCTYPE
+ ...
+ <td class="tableDetail">
+ echo: foo=bar
+ ...
+
+You can cancel scheduled jobs:
+
+ >>> browser.getControl('Cancel', index=0).click()
+ >>> 'No jobs were selected.' in browser.contents
+ True
+
+ >>> browser.getControl(name='jobs:list').getControl(
+ ... value='1506179619').click()
+ >>> browser.getControl('Cancel', index=0).click()
+ >>> 'Jobs were successfully cancelled.' in browser.contents
+ True
+
+It is also possible cancel all jobs::
+
+ >>> browser.getControl('Cancel all', index=0).click()
+ >>> 'All jobs cancelled' in browser.contents
+ True
+
+You can also clean attic jobs:
+
+ >>> browser.getControl('Remove all').click()
+ >>> 'Cleaned 1 Jobs' in browser.contents
+ True
+
+
+Thread Exception Reporting
+--------------------------
+
+If a job raises an exception the task service repeats the job 3 times. On
+every exception a traceback is written to the log.
+
+We modify the python logger to get the log output.
+
+ >>> import logging
+ >>> logger = logging.getLogger("lovely.remotetask")
+ >>> logger.setLevel(logging.ERROR)
+ >>> import StringIO
+ >>> io = StringIO.StringIO()
+ >>> ch = logging.StreamHandler(io)
+ >>> ch.setLevel(logging.DEBUG)
+ >>> logger.addHandler(ch)
+
+ >>> from time import sleep
+ >>> from zope import component
+ >>> from lovely.remotetask.interfaces import ITaskService
+ >>> service = getRootFolder()['tasks']
+
+We add a job for a task which raises a ZeroDivisionError every time it is
+called.
+
+ >>> jobid = service.add(u'exception')
+ >>> service.getStatus(jobid)
+ 'queued'
+ >>> import transaction
+ >>> transaction.commit()
+ >>> service.startProcessing()
+ >>> transaction.commit()
+
+ >>> import time
+ >>> time.sleep(1.5)
+
+
+Note that the processing thread is daemonic, that way it won't keep the process
+alive unnecessarily.
+
+ >>> import threading
+ >>> for thread in threading.enumerate():
+ ... if thread.getName().startswith('remotetasks.'):
+ ... print thread.isDaemon()
+ True
+
+ >>> service.stopProcessing()
+ >>> transaction.commit()
+
+
+We got log entries with the tracebacks of the division error.
+
+ >>> logvalue = io.getvalue()
+ >>> print logvalue
+ Caught a generic exception, preventing thread from crashing
+ integer division or modulo by zero
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: integer division or modulo by zero
+ <BLANKLINE>
+
+We had 3 retries, but every error is reported twice, once by the processor and
+once from by the task service.
+
+ >>> logvalue.count('ZeroDivisionError')
+ 6
+
+The job status is set to 'error'.
+
+ >>> service.getStatus(jobid)
+ 'error'
+
+We do the same again to see if the same thing happens again. This test is
+necessary to see if the internal runCount in the task service is reset.
+
+ >>> io.seek(0)
+ >>> jobid = service.add(u'exception')
+ >>> service.getStatus(jobid)
+ 'queued'
+ >>> import transaction
+ >>> transaction.commit()
+ >>> service.startProcessing()
+ >>> transaction.commit()
+ >>> sleep(1.5)
+ >>> service.stopProcessing()
+ >>> transaction.commit()
+
+We got log entries with the tracebacks of the division error.
+
+ >>> logvalue = io.getvalue()
+ >>> print logvalue
+ Caught a generic exception, preventing thread from crashing
+ integer division or modulo by zero
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: integer division or modulo by zero
+ <BLANKLINE>
+
+We had 3 retries, but every error is reported twice, once by the processor and
+once from by the task service.
+
+ >>> logvalue.count('ZeroDivisionError')
+ 6
+
+The job status is set to 'error'.
+
+ >>> service.getStatus(jobid)
+ 'error'
+
+
+Clenaup
+-------
+
+Allow the threads to exit:
+
+ >>> sleep(0.2)
More information about the checkins
mailing list