<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12634] CalDAVTester/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12634">12634</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-02-10 11:57:08 -0800 (Mon, 10 Feb 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add &quot;observers&quot; to allow customization of the output from tests. Switch to cElementTree.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalDAVTestertrunkREADMEtxt">CalDAVTester/trunk/README.txt</a></li>
<li><a href="#CalDAVTestertrunksrccaldavtestpy">CalDAVTester/trunk/src/caldavtest.py</a></li>
<li><a href="#CalDAVTestertrunksrcmanagerpy">CalDAVTester/trunk/src/manager.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersaclItemspy">CalDAVTester/trunk/verifiers/aclItems.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersdataMatchpy">CalDAVTester/trunk/verifiers/dataMatch.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersmultistatusItemspy">CalDAVTester/trunk/verifiers/multistatusItems.py</a></li>
<li><a href="#CalDAVTestertrunkverifierspostFreeBusypy">CalDAVTester/trunk/verifiers/postFreeBusy.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersprepostconditionpy">CalDAVTester/trunk/verifiers/prepostcondition.py</a></li>
<li><a href="#CalDAVTestertrunkverifierspropfindItemspy">CalDAVTester/trunk/verifiers/propfindItems.py</a></li>
<li><a href="#CalDAVTestertrunkverifierspropfindValuespy">CalDAVTester/trunk/verifiers/propfindValues.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersxmlDataMatchpy">CalDAVTester/trunk/verifiers/xmlDataMatch.py</a></li>
<li><a href="#CalDAVTestertrunkverifiersxmlElementMatchpy">CalDAVTester/trunk/verifiers/xmlElementMatch.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalDAVTester/trunk/src/observers/</li>
<li><a href="#CalDAVTestertrunksrcobservers__init__py">CalDAVTester/trunk/src/observers/__init__.py</a></li>
<li><a href="#CalDAVTestertrunksrcobserversbasepy">CalDAVTester/trunk/src/observers/base.py</a></li>
<li><a href="#CalDAVTestertrunksrcobserversjsondumppy">CalDAVTester/trunk/src/observers/jsondump.py</a></li>
<li><a href="#CalDAVTestertrunksrcobserverslogpy">CalDAVTester/trunk/src/observers/log.py</a></li>
<li><a href="#CalDAVTestertrunksrcobserverstracepy">CalDAVTester/trunk/src/observers/trace.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalDAVTestertrunkREADMEtxt"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/README.txt (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/README.txt        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/README.txt        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -23,6 +23,7 @@
</span><span class="cx">         [--always-print-request] \
</span><span class="cx">         [--always-print-response] \
</span><span class="cx">         [--exclude filename] \
</span><ins>+        [--observer OBSERVER] \
</ins><span class="cx">         file1 file2 ...
</span><span class="cx"> 
</span><span class="cx">         -s : filename specifies the file to use for server information
</span><span class="lines">@@ -56,6 +57,13 @@
</span><span class="cx">         
</span><span class="cx">         --exclude FILE : when running with --all, exclude the file from the test run. 
</span><span class="cx"> 
</span><ins>+        --observer OBSEREVER : specify one or more times to change which classes are
+        used to process log and trace messages during a test. The OBSERVER name must
+        be the name of a module in the observers package. The default observer is the
+        &quot;log&quot; observer which produces an output similar to Python unit tests. The
+        &quot;trace&quot; observer produces an output similar to the original output format.
+        The &quot;jsondump&quot; observer prints a JSON representation of the test results.

</ins><span class="cx">         file1 file2 ...: a list of test files to execute tests from.
</span><span class="cx"> 
</span><span class="cx"> QUICKSTART
</span></span></pre></div>
<a id="CalDAVTestertrunksrccaldavtestpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/src/caldavtest.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/caldavtest.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/src/caldavtest.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -27,7 +27,7 @@
</span><span class="cx"> from src.request import stats
</span><span class="cx"> from src.testsuite import testsuite
</span><span class="cx"> from src.xmlUtils import nodeForPath, xmlPathSplit
</span><del>-from xml.etree.ElementTree import ElementTree, tostring
</del><ins>+from xml.etree.cElementTree import ElementTree, tostring
</ins><span class="cx"> import commands
</span><span class="cx"> import httplib
</span><span class="cx"> import os
</span><span class="lines">@@ -39,8 +39,6 @@
</span><span class="cx"> import urllib
</span><span class="cx"> import urlparse
</span><span class="cx"> 
</span><del>-STATUSTXT_WIDTH = 60
-
</del><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Patch the HTTPConnection.send to record full request details
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -96,33 +94,28 @@
</span><span class="cx"> 
</span><span class="cx">     def run(self):
</span><span class="cx">         if len(self.missingFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;----- Ignoring Tests from \&quot;%s\&quot;... -----&quot; % self.name, before=1)
-            self.manager.log(manager.LOG_HIGH, &quot;      Missing features: %s&quot; % (&quot;, &quot;.join(sorted(self.missingFeatures())),))
</del><ins>+            self.manager.testFile(self.name, &quot;Missing features: %s&quot; % (&quot;, &quot;.join(sorted(self.missingFeatures()),)), manager.RESULT_IGNORED)
</ins><span class="cx">             return 0, 0, 1
</span><span class="cx">         if len(self.excludedFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;----- Ignoring Tests from \&quot;%s\&quot;... -----&quot; % self.name, before=1)
-            self.manager.log(manager.LOG_HIGH, &quot;      Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(self.excludedFeatures())),))
</del><ins>+            self.manager.testFile(self.name, &quot;Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(self.excludedFeatures()),)), manager.RESULT_IGNORED)
</ins><span class="cx">             return 0, 0, 1
</span><span class="cx"> 
</span><span class="cx">         self.only = any([suite.only for suite in self.suites])
</span><span class="cx">         try:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;----- Running Tests from \&quot;%s\&quot;... -----&quot; % self.name, before=1)
-            result = self.dorequests(&quot;Executing Start Requests...&quot;, self.start_requests, False, True, label=&quot;%s | %s&quot; % (self.name, &quot;START_REQUESTS&quot;))
</del><ins>+            result = self.dorequests(&quot;Start Requests...&quot;, self.start_requests, False, True, label=&quot;%s | %s&quot; % (self.name, &quot;START_REQUESTS&quot;))
</ins><span class="cx">             if not result:
</span><del>-                self.manager.log(manager.LOG_ERROR, &quot;Start items failed - tests will not be run.&quot;)
-                ok = 0
-                failed = 1
-                ignored = 0
</del><ins>+                self.manager.testFile(self.name, &quot;Start items failed - tests will not be run.&quot;, manager.RESULT_ERROR)
+                ok, failed, ignored = (0, 1, 0,)
</ins><span class="cx">             else:
</span><span class="cx">                 ok, failed, ignored = self.run_tests(label=self.name)
</span><span class="cx">             self.doenddelete(&quot;Deleting Requests...&quot;, label=&quot;%s | %s&quot; % (self.name, &quot;END_DELETE&quot;))
</span><del>-            self.dorequests(&quot;Executing End Requests...&quot;, self.end_requests, False, label=&quot;%s | %s&quot; % (self.name, &quot;END_REQUESTS&quot;))
</del><ins>+            self.dorequests(&quot;End Requests...&quot;, self.end_requests, False, label=&quot;%s | %s&quot; % (self.name, &quot;END_REQUESTS&quot;))
</ins><span class="cx">             return ok, failed, ignored
</span><span class="cx">         except socket.error, msg:
</span><del>-            self.manager.log(manager.LOG_ERROR, &quot;SOCKET ERROR: %s&quot; % (msg,), before=2)
</del><ins>+            self.manager.testFile(self.name, &quot;SOCKET ERROR: %s&quot; % (msg,), manager.RESULT_ERROR)
</ins><span class="cx">             return 0, 1, 0
</span><span class="cx">         except Exception, e:
</span><del>-            self.manager.log(manager.LOG_ERROR, &quot;FATAL ERROR: %s&quot; % (e,), before=2)
</del><ins>+            self.manager.testFile(self.name, &quot;FATAL ERROR: %s&quot; % (e,), manager.RESULT_ERROR)
</ins><span class="cx">             return 0, 1, 0
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -130,71 +123,65 @@
</span><span class="cx">         ok = 0
</span><span class="cx">         failed = 0
</span><span class="cx">         ignored = 0
</span><ins>+        testfile = self.manager.testFile(self.name, self.description)
</ins><span class="cx">         for suite in self.suites:
</span><del>-            o, f, i = self.run_test_suite(suite, label=&quot;%s | %s&quot; % (label, suite.name))
</del><ins>+            o, f, i = self.run_test_suite(testfile, suite, label=&quot;%s | %s&quot; % (label, suite.name))
</ins><span class="cx">             ok += o
</span><span class="cx">             failed += f
</span><span class="cx">             ignored += i
</span><span class="cx">         return (ok, failed, ignored)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def run_test_suite(self, suite, label=&quot;&quot;):
-        descriptor = &quot;    Test Suite: %s&quot; % suite.name
-        descriptor += &quot; &quot; * max(1, STATUSTXT_WIDTH - len(descriptor))
-        self.manager.log(manager.LOG_HIGH, &quot;%s&quot; % (descriptor,), before=1, after=0)
</del><ins>+    def run_test_suite(self, testfile, suite, label=&quot;&quot;):
+        result_name = suite.name
</ins><span class="cx">         ok = 0
</span><span class="cx">         failed = 0
</span><span class="cx">         ignored = 0
</span><span class="cx">         postgresCount = None
</span><span class="cx">         if self.only and not suite.only or suite.ignore:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
</del><ins>+            self.manager.testSuite(testfile, result_name, &quot;    Deliberately ignored&quot;, manager.RESULT_IGNORED)
</ins><span class="cx">             ignored = len(suite.tests)
</span><span class="cx">         elif len(suite.missingFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            self.manager.log(manager.LOG_HIGH, &quot;      Missing features: %s&quot; % (&quot;, &quot;.join(sorted(suite.missingFeatures())),))
</del><ins>+            self.manager.testSuite(testfile, result_name, &quot;    Missing features: %s&quot; % (&quot;, &quot;.join(sorted(suite.missingFeatures())),), manager.RESULT_IGNORED)
</ins><span class="cx">             ignored = len(suite.tests)
</span><span class="cx">         elif len(suite.excludedFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            self.manager.log(manager.LOG_HIGH, &quot;      Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(suite.excludedFeatures())),))
</del><ins>+            self.manager.testSuite(testfile, result_name, &quot;    Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(suite.excludedFeatures())),), manager.RESULT_IGNORED)
</ins><span class="cx">             ignored = len(suite.tests)
</span><span class="cx">         else:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;&quot;)
</del><span class="cx">             postgresCount = self.postgresInit()
</span><span class="cx">             if self.manager.memUsage:
</span><span class="cx">                 start_usage = self.manager.getMemusage()
</span><span class="cx">             etags = {}
</span><span class="cx">             only_tests = any([test.only for test in suite.tests])
</span><ins>+            testsuite = self.manager.testSuite(testfile, result_name, &quot;&quot;)
</ins><span class="cx">             for test in suite.tests:
</span><del>-                result = self.run_test(test, etags, only_tests, label=&quot;%s | %s&quot; % (label, test.name))
</del><ins>+                result = self.run_test(testsuite, test, etags, only_tests, label=&quot;%s | %s&quot; % (label, test.name))
</ins><span class="cx">                 if result == &quot;t&quot;:
</span><span class="cx">                     ok += 1
</span><span class="cx">                 elif result == &quot;f&quot;:
</span><span class="cx">                     failed += 1
</span><span class="cx">                 else:
</span><span class="cx">                     ignored += 1
</span><ins>+
</ins><span class="cx">             if self.manager.memUsage:
</span><span class="cx">                 end_usage = self.manager.getMemusage()
</span><del>-                self.manager.log(manager.LOG_HIGH, &quot;Mem. Usage: RSS=%s%% VSZ=%s%%&quot; % (str(((end_usage[1] - start_usage[1]) * 100) / start_usage[1]), str(((end_usage[0] - start_usage[0]) * 100) / start_usage[0])))
-        self.manager.log(manager.LOG_HIGH, &quot;Suite Results: %d PASSED, %d FAILED, %d IGNORED&quot; % (ok, failed, ignored), before=1, indent=4)
</del><ins>+                self.manager.message(&quot;trace&quot;, &quot;    Mem. Usage: RSS=%s%% VSZ=%s%%&quot; % (str(((end_usage[1] - start_usage[1]) * 100) / start_usage[1]), str(((end_usage[0] - start_usage[0]) * 100) / start_usage[0])))
+
+        self.manager.message(&quot;trace&quot;, &quot;  Suite Results: %d PASSED, %d FAILED, %d IGNORED\n&quot; % (ok, failed, ignored))
</ins><span class="cx">         if postgresCount is not None:
</span><span class="cx">             self.postgresResult(postgresCount, indent=4)
</span><span class="cx">         return (ok, failed, ignored)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def run_test(self, test, etags, only, label=&quot;&quot;):
-        descriptor = &quot;        Test: %s&quot; % test.name
-        descriptor += &quot; &quot; * max(1, STATUSTXT_WIDTH - len(descriptor))
-        self.manager.log(manager.LOG_HIGH, &quot;%s&quot; % (descriptor,), before=1, after=0)
</del><ins>+    def run_test(self, testsuite, test, etags, only, label=&quot;&quot;):
</ins><span class="cx">         if test.ignore or only and not test.only:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
</del><ins>+            self.manager.testResult(testsuite, test.name, &quot;      Deliberately ignored&quot;, manager.RESULT_IGNORED)
</ins><span class="cx">             return &quot;i&quot;
</span><span class="cx">         elif len(test.missingFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            self.manager.log(manager.LOG_HIGH, &quot;      Missing features: %s&quot; % (&quot;, &quot;.join(sorted(test.missingFeatures())),))
</del><ins>+            self.manager.testResult(testsuite, test.name, &quot;      Missing features: %s&quot; % (&quot;, &quot;.join(sorted(test.missingFeatures())),), manager.RESULT_IGNORED)
</ins><span class="cx">             return &quot;i&quot;
</span><span class="cx">         elif len(test.excludedFeatures()) != 0:
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            self.manager.log(manager.LOG_HIGH, &quot;      Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(test.excludedFeatures())),))
</del><ins>+            self.manager.testResult(testsuite, test.name, &quot;      Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(test.excludedFeatures())),), manager.RESULT_IGNORED)
</ins><span class="cx">             return &quot;i&quot;
</span><span class="cx">         else:
</span><span class="cx">             result = True
</span><span class="lines">@@ -225,13 +212,12 @@
</span><span class="cx">                     if failed:
</span><span class="cx">                         break
</span><span class="cx"> 
</span><del>-            loglevel = [manager.LOG_ERROR, manager.LOG_HIGH][result]
-            self.manager.log(loglevel, [&quot;[FAILED]&quot;, &quot;[OK]&quot;][result])
</del><ins>+            self.manager.testResult(testsuite, test.name, resulttxt, manager.RESULT_OK if result else manager.RESULT_FAILED)
</ins><span class="cx">             if len(resulttxt) &gt; 0:
</span><del>-                self.manager.log(loglevel, resulttxt)
</del><ins>+                self.manager.message(&quot;trace&quot;, resulttxt)
</ins><span class="cx">             if result and test.stats:
</span><del>-                self.manager.log(manager.LOG_MEDIUM, &quot;Total Time: %.3f secs&quot; % (reqstats.totaltime,), indent=8)
-                self.manager.log(manager.LOG_MEDIUM, &quot;Average Time: %.3f secs&quot; % (reqstats.totaltime / reqstats.count,), indent=8)
</del><ins>+                self.manager.message(&quot;trace&quot;, &quot;    Total Time: %.3f secs&quot; % (reqstats.totaltime,), indent=8)
+                self.manager.message(&quot;trace&quot;, &quot;    Average Time: %.3f secs&quot; % (reqstats.totaltime / reqstats.count,), indent=8)
</ins><span class="cx">             self.postgresResult(postgresCount, indent=8)
</span><span class="cx">             return [&quot;f&quot;, &quot;t&quot;][result]
</span><span class="cx"> 
</span><span class="lines">@@ -239,17 +225,15 @@
</span><span class="cx">     def dorequests(self, description, list, doverify=True, forceverify=False, label=&quot;&quot;, count=1):
</span><span class="cx">         if len(list) == 0:
</span><span class="cx">             return True
</span><del>-        description += &quot; &quot; * max(1, STATUSTXT_WIDTH - len(description))
-        self.manager.log(manager.LOG_HIGH, description, before=1, after=0)
</del><ins>+        self.manager.message(&quot;trace&quot;, &quot;Start: &quot; + description)
</ins><span class="cx">         for req_count, req in enumerate(list):
</span><span class="cx">             result, resulttxt, _ignore_response, _ignore_respdata = self.dorequest(req, False, doverify, forceverify, label=&quot;%s | #%s&quot; % (label, req_count + 1), count=count)
</span><span class="cx">             if not result:
</span><span class="cx">                 resulttxt += &quot;\nFailure during multiple requests #%d out of %d, request=%s&quot; % (req_count + 1, len(list), str(req))
</span><span class="cx">                 break
</span><del>-        loglevel = [manager.LOG_ERROR, manager.LOG_HIGH][result]
-        self.manager.log(loglevel, [&quot;[FAILED]&quot;, &quot;[OK]&quot;][result])
</del><ins>+        self.manager.message(&quot;trace&quot;, &quot;{name:&lt;60}{value:&gt;10}&quot;.format(name=&quot;End: &quot; + description, value=[&quot;[FAILED]&quot;, &quot;[OK]&quot;][result]))
</ins><span class="cx">         if len(resulttxt) &gt; 0:
</span><del>-            self.manager.log(loglevel, resulttxt)
</del><ins>+            self.manager.message(&quot;trace&quot;, resulttxt)
</ins><span class="cx">         return result
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -466,8 +450,7 @@
</span><span class="cx">     def doenddelete(self, description, label=&quot;&quot;):
</span><span class="cx">         if len(self.end_deletes) == 0:
</span><span class="cx">             return True
</span><del>-        description += &quot; &quot; * max(1, STATUSTXT_WIDTH - len(description))
-        self.manager.log(manager.LOG_HIGH, description, before=1, after=0)
</del><ins>+        self.manager.message(&quot;trace&quot;, &quot;Start: &quot; + description)
</ins><span class="cx">         for deleter in self.end_deletes:
</span><span class="cx">             req = request(self.manager)
</span><span class="cx">             req.method = &quot;DELETE&quot;
</span><span class="lines">@@ -478,7 +461,7 @@
</span><span class="cx">             if len(deleter[2]):
</span><span class="cx">                 req.pswd = deleter[2]
</span><span class="cx">             self.dorequest(req, False, False, label=label)
</span><del>-        self.manager.log(manager.LOG_HIGH, &quot;[DONE]&quot;)
</del><ins>+        self.manager.message(&quot;trace&quot;, &quot;{name:&lt;60}{value:&gt;10}&quot;.format(name=&quot;End: &quot; + description, value=&quot;[DONE]&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def dorequest(self, req, details=False, doverify=True, forceverify=False, stats=None, etags=None, label=&quot;&quot;, count=1):
</span><span class="lines">@@ -492,12 +475,8 @@
</span><span class="cx">             return True, &quot;&quot;, None, None
</span><span class="cx"> 
</span><span class="cx">         if len(req.missingFeatures()) != 0:
</span><del>-            #self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            #self.manager.log(manager.LOG_HIGH, &quot;      Missing features: %s&quot; % (&quot;, &quot;.join(sorted(req.missingFeatures())),))
</del><span class="cx">             return True, &quot;&quot;, None, None
</span><span class="cx">         if len(req.excludedFeatures()) != 0:
</span><del>-            #self.manager.log(manager.LOG_HIGH, &quot;[IGNORED]&quot;)
-            #self.manager.log(manager.LOG_HIGH, &quot;      Excluded features: %s&quot; % (&quot;, &quot;.join(sorted(req.excludedFeatures())),))
</del><span class="cx">             return True, &quot;&quot;, None, None
</span><span class="cx"> 
</span><span class="cx">         # Special check for DELETEALL
</span><span class="lines">@@ -594,19 +573,16 @@
</span><span class="cx">             headers['User-Agent'] = label.encode(&quot;utf-8&quot;)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            #self.manager.log(manager.LOG_LOW, &quot;Sending request&quot;)
</del><span class="cx">             puri = list(urlparse.urlparse(uri))
</span><span class="cx">             puri[2] = urllib.quote(puri[2])
</span><span class="cx">             quri = urlparse.urlunparse(puri)
</span><span class="cx"> 
</span><span class="cx">             http.request(method, quri, data, headers)
</span><del>-            #self.manager.log(manager.LOG_LOW, &quot;Sent request&quot;)
</del><span class="cx"> 
</span><span class="cx">             response = http.getresponse()
</span><span class="cx"> 
</span><span class="cx">             respdata = None
</span><span class="cx">             respdata = response.read()
</span><del>-            #self.manager.log(manager.LOG_LOW, &quot;Read response&quot;)
</del><span class="cx"> 
</span><span class="cx">         finally:
</span><span class="cx">             http.close()
</span><span class="lines">@@ -983,4 +959,4 @@
</span><span class="cx">                 newCount = int(commands.getoutput(&quot;grep \&quot;LOG:  statement:\&quot; %s | wc -l&quot; % (self.manager.postgresLog,)))
</span><span class="cx">             else:
</span><span class="cx">                 newCount = 0
</span><del>-            self.manager.log(manager.LOG_HIGH, &quot;Postgres Statements: %d&quot; % (newCount - startCount,), indent=indent)
</del><ins>+            self.manager.message(&quot;trace&quot;, &quot;Postgres Statements: %d&quot; % (newCount - startCount,))
</ins></span></pre></div>
<a id="CalDAVTestertrunksrcmanagerpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/src/manager.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/manager.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/src/manager.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from src.serverinfo import serverinfo
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> from xml.parsers.expat import ExpatError
</span><span class="cx"> import getopt
</span><span class="cx"> import httplib
</span><span class="lines">@@ -40,20 +40,19 @@
</span><span class="cx">     Main class that runs test suites defined in an XML config file.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    LOG_NONE = 0
-    LOG_ERROR = 1
-    LOG_LOW = 2
-    LOG_MEDIUM = 3
-    LOG_HIGH = 4
</del><ins>+    RESULT_OK = 0
+    RESULT_FAILED = 1
+    RESULT_ERROR = 2
+    RESULT_IGNORED = 3
</ins><span class="cx"> 
</span><del>-    def __init__(self, text=True, level=LOG_HIGH):
</del><ins>+
+    def __init__(self, text=True):
</ins><span class="cx">         self.server_info = serverinfo()
</span><span class="cx">         self.tests = []
</span><span class="cx">         self.textMode = text
</span><span class="cx">         self.pid = 0
</span><span class="cx">         self.memUsage = None
</span><span class="cx">         self.randomSeed = None
</span><del>-        self.logLevel = level
</del><span class="cx">         self.logFile = None
</span><span class="cx">         self.digestCache = {}
</span><span class="cx">         self.postgresLog = &quot;&quot;
</span><span class="lines">@@ -61,27 +60,67 @@
</span><span class="cx">         self.print_response = False
</span><span class="cx">         self.print_request_response_on_error = False
</span><span class="cx"> 
</span><ins>+        self.results = []
+        self.totals = {
+            self.RESULT_OK: 0,
+            self.RESULT_FAILED: 0,
+            self.RESULT_ERROR: 0,
+            self.RESULT_IGNORED: 0
+        }
+        self.observers = []
</ins><span class="cx"> 
</span><del>-    def log(self, level, str, indent=0, indentStr=&quot; &quot;, after=1, before=0):
-        if self.textMode and level &lt;= self.logLevel:
-            if before:
-                self.logit(&quot;\n&quot; * before)
-            if indent:
-                self.logit(indentStr * indent)
-            self.logit(str)
-            if after:
-                self.logit(&quot;\n&quot; * after)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def logit(self, str):
</span><span class="cx">         if self.logFile:
</span><del>-            self.logFile.write(str)
-        print str,
</del><ins>+            self.logFile.write(str + &quot;\n&quot;)
+        print str
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def loadObserver(self, observer_name):
+        module = __import__(&quot;observers.&quot; + observer_name, globals(), locals(), [&quot;Observer&quot;, ])
+        cl = getattr(module, &quot;Observer&quot;)
+        self.observers.append(cl(self))
+
+
+    def message(self, message, *args, **kwargs):
+        map(lambda x: x.message(message, *args, **kwargs), self.observers)
+
+
+    def testFile(self, name, details, result=None):
+        self.results.append({
+            &quot;name&quot;: name,
+            &quot;details&quot;: details,
+            &quot;result&quot;: result,
+            &quot;tests&quot;: []
+        })
+        self.message(&quot;testFile&quot;, self.results[-1])
+        return self.results[-1][&quot;tests&quot;]
+
+
+    def testSuite(self, testfile, name, details, result=None):
+        testfile.append({
+            &quot;name&quot;: name,
+            &quot;details&quot;: details,
+            &quot;result&quot;: result,
+            &quot;tests&quot;: []
+        })
+        self.message(&quot;testSuite&quot;, testfile[-1])
+        return testfile[-1][&quot;tests&quot;]
+
+
+    def testResult(self, testsuite, name, details, result,):
+        testsuite.append({
+            &quot;name&quot;: name,
+            &quot;result&quot;: result,
+            &quot;details&quot;: details
+        })
+        self.totals[result] += 1
+        self.message(&quot;testResult&quot;, testsuite[-1])
+
+
</ins><span class="cx">     def readXML(self, serverfile, testfiles, ssl, all, moresubs={}):
</span><span class="cx"> 
</span><del>-        self.log(manager.LOG_HIGH, &quot;Reading Server Info from \&quot;%s\&quot;&quot; % serverfile, after=2)
</del><ins>+        self.message(&quot;trace&quot;, &quot;Reading Server Info from \&quot;{}\&quot;&quot;.format(serverfile))
</ins><span class="cx"> 
</span><span class="cx">         # Open and parse the server config file
</span><span class="cx">         try:
</span><span class="lines">@@ -118,7 +157,8 @@
</span><span class="cx"> 
</span><span class="cx">         self.server_info.addsubs(moresubs)
</span><span class="cx"> 
</span><del>-        for testfile in testfiles:
</del><ins>+        for ctr, testfile in enumerate(testfiles):
+            print &quot;\rTest File {} of {}&quot;.format(ctr + 1, len(testfiles)),
</ins><span class="cx">             # Open and parse the config file
</span><span class="cx">             try:
</span><span class="cx">                 tree = ElementTree(file=testfile)
</span><span class="lines">@@ -129,11 +169,11 @@
</span><span class="cx">             from src.caldavtest import caldavtest
</span><span class="cx">             caldavtest_node = tree.getroot()
</span><span class="cx">             if caldavtest_node.tag != src.xmlDefs.ELEMENT_CALDAVTEST:
</span><del>-                self.log(manager.LOG_HIGH, &quot;Ignoring file \&quot;%s\&quot; because it is not a test file&quot; % (testfile,), after=2)
</del><ins>+                self.message(&quot;trace&quot;, &quot;Ignoring file \&quot;{}\&quot; because it is not a test file&quot;.format(testfile))
</ins><span class="cx">                 continue
</span><span class="cx">             if not len(caldavtest_node):
</span><span class="cx">                 raise EX_INVALID_CONFIG_FILE
</span><del>-            self.log(manager.LOG_HIGH, &quot;Reading Test Details from \&quot;%s\&quot;&quot; % testfile, after=2)
</del><ins>+            self.message(&quot;Reading Test Details from \&quot;{}\&quot;&quot;.format(testfile))
</ins><span class="cx"> 
</span><span class="cx">             # parse all the config data
</span><span class="cx">             test = caldavtest(self, testfile)
</span><span class="lines">@@ -143,7 +183,9 @@
</span><span class="cx">             if not all or not test.ignore_all:
</span><span class="cx">                 self.tests.append(test)
</span><span class="cx"> 
</span><ins>+        print &quot;&quot;
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def readCommandLine(self):
</span><span class="cx">         sname = &quot;scripts/server/serverinfo.xml&quot;
</span><span class="cx">         dname = &quot;scripts/tests&quot;
</span><span class="lines">@@ -155,6 +197,8 @@
</span><span class="cx">         pidfile = &quot;../CalendarServer/logs/caldavd.pid&quot;
</span><span class="cx">         random_order = False
</span><span class="cx">         random_seed = str(random.randint(0, 1000000))
</span><ins>+        observer_names = []
+
</ins><span class="cx">         options, args = getopt.getopt(
</span><span class="cx">             sys.argv[1:],
</span><span class="cx">             &quot;s:mo:x:&quot;,
</span><span class="lines">@@ -163,6 +207,7 @@
</span><span class="cx">                 &quot;all&quot;,
</span><span class="cx">                 &quot;subdir=&quot;,
</span><span class="cx">                 &quot;exclude=&quot;,
</span><ins>+                &quot;observer=&quot;,
</ins><span class="cx">                 &quot;pid=&quot;,
</span><span class="cx">                 &quot;postgres-log=&quot;,
</span><span class="cx">                 &quot;random&quot;,
</span><span class="lines">@@ -193,6 +238,8 @@
</span><span class="cx">                 self.logFile = open(value, &quot;w&quot;)
</span><span class="cx">             elif option == &quot;--pid&quot;:
</span><span class="cx">                 pidfile = value
</span><ins>+            elif option == &quot;--observer&quot;:
+                observer_names.append(value)
</ins><span class="cx">             elif option == &quot;--postgres-log&quot;:
</span><span class="cx">                 self.postgresLog = value
</span><span class="cx">             elif option == &quot;--print-details-onfail&quot;:
</span><span class="lines">@@ -239,6 +286,9 @@
</span><span class="cx">             random.shuffle(fnames)
</span><span class="cx">             self.randomSeed = random_seed
</span><span class="cx"> 
</span><ins>+        # Load observers
+        map(lambda name: self.loadObserver(name), observer_names if observer_names else [&quot;log&quot;, ])
+
</ins><span class="cx">         self.readXML(sname, fnames, ssl, all)
</span><span class="cx"> 
</span><span class="cx">         if self.memUsage:
</span><span class="lines">@@ -251,8 +301,7 @@
</span><span class="cx"> 
</span><span class="cx">         startTime = time.time()
</span><span class="cx"> 
</span><del>-        if self.randomSeed is not None:
-            self.log(manager.LOG_LOW, &quot;Randomizing order using seed '%s'&quot; % (self.randomSeed,))
</del><ins>+        self.message(&quot;start&quot;)
</ins><span class="cx"> 
</span><span class="cx">         ok = 0
</span><span class="cx">         failed = 0
</span><span class="lines">@@ -270,8 +319,8 @@
</span><span class="cx"> 
</span><span class="cx">         endTime = time.time()
</span><span class="cx"> 
</span><del>-        self.log(manager.LOG_LOW, &quot;Overall Results: %d PASSED, %d FAILED, %d IGNORED&quot; % (ok, failed, ignored), before=2, indent=4)
-        self.log(manager.LOG_LOW, &quot;Total time: %.3f secs&quot; % (endTime - startTime,))
</del><ins>+        self.timeDiff = endTime - startTime
+        self.message(&quot;finish&quot;)
</ins><span class="cx"> 
</span><span class="cx">         if self.logFile is not None:
</span><span class="cx">             self.logFile.close()
</span></span></pre></div>
<a id="CalDAVTestertrunksrcobservers__init__py"></a>
<div class="addfile"><h4>Added: CalDAVTester/trunk/src/observers/__init__.py (0 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/observers/__init__.py                                (rev 0)
+++ CalDAVTester/trunk/src/observers/__init__.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalDAVTestertrunksrcobserversbasepy"></a>
<div class="addfile"><h4>Added: CalDAVTester/trunk/src/observers/base.py (0 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/observers/base.py                                (rev 0)
+++ CalDAVTester/trunk/src/observers/base.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -0,0 +1,48 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+class BaseResultsObserver(object):
+    &quot;&quot;&quot;
+    A base class for an observer that gets passed results of tests.
+
+    Supported messages:
+
+    trace - tracing tool activity
+    begin - beginning
+    load - loading a test
+    start - starting tests
+    testFile - add a test file
+    testSuite - add a test suite
+    testResult - add a test result
+    finish - tests completed
+    &quot;&quot;&quot;
+
+    def __init__(self, manager):
+        self.updateCalls()
+        self.manager = manager
+
+
+    def updateCalls(self):
+        self._calls = {}
+
+
+    def message(self, message, *args, **kwargs):
+
+        callit = self._calls.get(message)
+
+        if callit is not None:
+            callit(*args, **kwargs)
</ins></span></pre></div>
<a id="CalDAVTestertrunksrcobserversjsondumppy"></a>
<div class="addfile"><h4>Added: CalDAVTester/trunk/src/observers/jsondump.py (0 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/observers/jsondump.py                                (rev 0)
+++ CalDAVTester/trunk/src/observers/jsondump.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -0,0 +1,34 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from src.observers.base import BaseResultsObserver
+import json
+
+
+class Observer(BaseResultsObserver):
+    &quot;&quot;&quot;
+    A results observer that prints results to standard output.
+    &quot;&quot;&quot;
+
+    def updateCalls(self):
+        super(Observer, self).updateCalls()
+        self._calls.update({
+            &quot;finish&quot;: self.finish,
+        })
+
+
+    def finish(self):
+        print json.dumps(self.manager.results)
</ins></span></pre></div>
<a id="CalDAVTestertrunksrcobserverslogpy"></a>
<div class="addfile"><h4>Added: CalDAVTester/trunk/src/observers/log.py (0 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/observers/log.py                                (rev 0)
+++ CalDAVTester/trunk/src/observers/log.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -0,0 +1,107 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from src.manager import manager
+from src.observers.base import BaseResultsObserver
+
+
+class Observer(BaseResultsObserver):
+    &quot;&quot;&quot;
+    A results observer that prints results to standard output.
+    &quot;&quot;&quot;
+
+    RESULT_STRINGS = {
+        manager.RESULT_OK: &quot;[OK]&quot;,
+        manager.RESULT_FAILED: &quot;[FAILED]&quot;,
+        manager.RESULT_ERROR: &quot;[ERROR]&quot;,
+        manager.RESULT_IGNORED: &quot;[IGNORED]&quot;,
+    }
+
+    _print_details = False
+
+    def __init__(self, manager):
+        super(Observer, self).__init__(manager)
+        self.loggedFailures = []
+        self.currentFile = None
+        self.currentSuite = None
+
+
+    def updateCalls(self):
+        super(Observer, self).updateCalls()
+        self._calls.update({
+            &quot;start&quot;: self.start,
+            &quot;testFile&quot;: self.testFile,
+            &quot;testSuite&quot;: self.testSuite,
+            &quot;testResult&quot;: self.testResult,
+            &quot;finish&quot;: self.finish,
+        })
+
+
+    def start(self):
+        self.manager.logit(&quot;Starting tests&quot;)
+        if self.manager.randomSeed is not None:
+            self.manager.logit(&quot;Randomizing order using seed '{}'&quot;.format(self.randomSeed))
+
+
+    def testFile(self, result):
+        self.currentFile = result[&quot;name&quot;].replace(&quot;/&quot;, &quot;.&quot;)[:-4]
+        self.manager.logit(&quot;&quot;)
+        self._logResult(self.currentFile, result)
+
+
+    def testSuite(self, result):
+        self.currentSuite = result[&quot;name&quot;]
+        result_name = &quot;  Suite: &quot; + result[&quot;name&quot;]
+        self._logResult(result_name, result)
+
+
+    def testResult(self, result):
+        result_name = &quot;    Test: &quot; + result[&quot;name&quot;]
+        self._logResult(result_name, result)
+        if result[&quot;result&quot;] in (manager.RESULT_FAILED, manager.RESULT_ERROR):
+            failtxt = &quot;{}/{}/{}\n{}&quot;.format(self.currentFile, self.currentSuite, result[&quot;name&quot;], result[&quot;details&quot;])
+            self.loggedFailures.append(failtxt)
+
+
+    def _logResult(self, name, result):
+        if result[&quot;result&quot;] is not None:
+            result_value = self.RESULT_STRINGS[result[&quot;result&quot;]]
+            self.manager.logit(&quot;{name:&lt;60}{value:&gt;10}&quot;.format(name=name, value=result_value))
+        else:
+            self.manager.logit(&quot;{name:&lt;60}&quot;.format(name=name))
+        if self._print_details and result[&quot;details&quot;]:
+            self.manager.logit(result[&quot;details&quot;])
+
+
+    def finish(self):
+        self.manager.logit(&quot;&quot;)
+        if self.manager.totals[manager.RESULT_FAILED] + self.manager.totals[manager.RESULT_ERROR] != 0:
+            for failed in self.loggedFailures:
+                self.manager.logit(&quot;******&quot;)
+                self.manager.logit(failed)
+            overall = &quot;******\n\nFAILED (ok={}, ignored={}, failed={}, errors={}) Time = {:.3f} secs&quot;.format(
+                self.manager.totals[manager.RESULT_OK],
+                self.manager.totals[manager.RESULT_IGNORED],
+                self.manager.totals[manager.RESULT_FAILED],
+                self.manager.totals[manager.RESULT_ERROR],
+                self.manager.timeDiff
+            )
+        else:
+            overall = &quot;PASSED (ok={}, ignored={}) Time = {:.3f} secs&quot;.format(
+                self.manager.totals[manager.RESULT_OK],
+                self.manager.totals[manager.RESULT_IGNORED],
+                self.manager.timeDiff
+            )
+        self.manager.logit(overall)
</ins></span></pre></div>
<a id="CalDAVTestertrunksrcobserverstracepy"></a>
<div class="addfile"><h4>Added: CalDAVTester/trunk/src/observers/trace.py (0 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/src/observers/trace.py                                (rev 0)
+++ CalDAVTester/trunk/src/observers/trace.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -0,0 +1,35 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from src.observers.log import Observer as LogObserver
+
+
+class Observer(LogObserver):
+    &quot;&quot;&quot;
+    A results observer that prints results to standard output.
+    &quot;&quot;&quot;
+
+    _print_details = True
+
+    def updateCalls(self):
+        super(Observer, self).updateCalls()
+        self._calls.update({
+            &quot;trace&quot;: self.trace,
+        })
+
+
+    def trace(self, text):
+        self.manager.logit(text)
</ins></span></pre></div>
<a id="CalDAVTestertrunkverifiersaclItemspy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/aclItems.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/aclItems.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/aclItems.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><span class="cx"> are available for the currently authenticated user.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifiersdataMatchpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/dataMatch.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/dataMatch.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/dataMatch.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> Verifier that checks the response body for an exact match to data in a file.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree, tostring
</del><ins>+from xml.etree.cElementTree import ElementTree, tostring
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifiersmultistatusItemspy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/multistatusItems.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/multistatusItems.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/multistatusItems.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><span class="cx"> are returned with appropriate status codes.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifierspostFreeBusypy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/postFreeBusy.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/postFreeBusy.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/postFreeBusy.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><span class="cx"> 
</span><span class="cx"> from pycalendar.icalendar.calendar import Calendar
</span><span class="cx"> from pycalendar.exceptions import InvalidData
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> from xml.parsers.expat import ExpatError
</span><span class="cx"> import StringIO
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalDAVTestertrunkverifiersprepostconditionpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/prepostcondition.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/prepostcondition.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/prepostcondition.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -18,7 +18,7 @@
</span><span class="cx"> Verifier that checks the response for a pre/post-condition &lt;DAV:error&gt; result.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifierspropfindItemspy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/propfindItems.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/propfindItems.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/propfindItems.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><span class="cx"> are returned with appropriate status codes.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree, tostring
</del><ins>+from xml.etree.cElementTree import ElementTree, tostring
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifierspropfindValuespy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/propfindValues.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/propfindValues.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/propfindValues.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> Verifier that checks a propfind response for regex matches to property values.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from xml.etree.ElementTree import ElementTree, tostring
</del><ins>+from xml.etree.cElementTree import ElementTree, tostring
</ins><span class="cx"> from StringIO import StringIO
</span><span class="cx"> import re
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalDAVTestertrunkverifiersxmlDataMatchpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/xmlDataMatch.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/xmlDataMatch.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/xmlDataMatch.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from difflib import unified_diff
</span><del>-from xml.etree.ElementTree import ElementTree, tostring
</del><ins>+from xml.etree.cElementTree import ElementTree, tostring
</ins><span class="cx"> import StringIO
</span><span class="cx"> 
</span><span class="cx"> class Verifier(object):
</span></span></pre></div>
<a id="CalDAVTestertrunkverifiersxmlElementMatchpy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/verifiers/xmlElementMatch.py (12633 => 12634)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/verifiers/xmlElementMatch.py        2014-02-10 19:35:54 UTC (rev 12633)
+++ CalDAVTester/trunk/verifiers/xmlElementMatch.py        2014-02-10 19:57:08 UTC (rev 12634)
</span><span class="lines">@@ -20,7 +20,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from pycalendar.icalendar.calendar import Calendar
</span><del>-from xml.etree.ElementTree import ElementTree
</del><ins>+from xml.etree.cElementTree import ElementTree
</ins><span class="cx"> import json
</span><span class="cx"> import StringIO
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>