[Yt-svn] yt: Adding xunit plugin from nose, modified not to use nose, as ...

hg at spacepope.org hg at spacepope.org
Sat Sep 11 01:12:23 PDT 2010


hg Repository: yt
details:   yt/rev/07a945f94009
changeset: 3395:07a945f94009
user:      Matthew Turk <matthewturk at gmail.com>
date:
Sat Sep 11 01:12:12 2010 -0700
description:
Adding xunit plugin from nose, modified not to use nose, as well as removing
check_for_setup.  -n to enzo_test will now output nosetests.xml.

diffstat:

 check_for_setup.py                    |    4 -
 yt/utilities/answer_testing/runner.py |   24 +++-
 yt/utilities/answer_testing/xunit.py  |  222 +++++++++++++++++++++++++++++++++++++
 3 files changed, 242 insertions(+), 8 deletions(-)

diffs (truncated from 306 to 300 lines):

diff -r eab5391f1f10 -r 07a945f94009 check_for_setup.py
--- a/check_for_setup.py	Wed Sep 08 19:19:17 2010 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-import os
-
-for dirpath, dirnames, filenames in os.walk("yt"):
-    if "setup.py" not in filenames: print dirpath
diff -r eab5391f1f10 -r 07a945f94009 yt/utilities/answer_testing/runner.py
--- a/yt/utilities/answer_testing/runner.py	Wed Sep 08 19:19:17 2010 -0700
+++ b/yt/utilities/answer_testing/runner.py	Sat Sep 11 01:12:12 2010 -0700
@@ -2,6 +2,7 @@
 import os, shelve, cPickle, sys
 from yt.config import ytcfg; ytcfg["yt","serialize"] = "False"
 import yt.utilities.cmdln as cmdln
+from .xunit import Xunit
 
 from output_tests import test_registry, MultipleOutputTest, \
                          RegressionTestException
@@ -73,26 +74,34 @@
             test_instance = test(self.io_log)
             self._run(test_instance)
 
+    watcher = None
     def _run(self, test):
+        if self.watcher is not None:
+            self.watcher.start()
         print self.id, "Running", test.name,
         test.setup()
         test.run()
         self.plot_list[test.name] = test.plot()
         self.results[test.name] = test.result
-        success = self._compare(test)
+        success, msg = self._compare(test)
         if success == True: print "SUCCEEDED"
         else: print "FAILED"
         self.passed_tests[test.name] = success
+        if self.watcher is not None:
+            if success == True:
+                self.watcher.addSuccess(test.name)
+            else:
+                self.watcher.addFailure(test.name, msg)
 
     def _compare(self, test):
         if self.old_results is None:
-            return True
+            return (True, "New Test")
         old_result = self.old_results[test.name]
         try:
             test.compare(old_result)
         except RegressionTestException, exc:
-            return str(exc)
-        return True
+            return (False, str(exc))
+        return (True, "Pass")
 
     def run_tests_from_file(self, filename):
         for line in open(filename):
@@ -125,6 +134,9 @@
     @cmdln.option("-o", "--output", action="store",
                   help="output results to file",
                   dest="outputfile", default=None)
+    @cmdln.option("-n", "--nose", action="store_true",
+                  help="run through nose with xUnit testing",
+                  dest="run_nose", default=False)
     def do_compare(self, subcmd, opts, reference, comparison, *test_modules):
         """
         ${cmd_name}: Compare a reference dataset against a new dataset.  The
@@ -139,7 +151,11 @@
             print "Loading module %s" % (fn)
             __import__(fn)
         test_runner = RegressionTestRunner(comparison, reference)
+        if opts.run_nose:
+            test_runner.watcher = Xunit()
         results = test_runner.run_all_tests()
+        if opts.run_nose:
+            test_runner.watcher.report()
         if opts.outputfile is not None:
             f = open(str(opts.outputfile), "w")
             for testname, success in sorted(results.items()):
diff -r eab5391f1f10 -r 07a945f94009 yt/utilities/answer_testing/xunit.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yt/utilities/answer_testing/xunit.py	Sat Sep 11 01:12:12 2010 -0700
@@ -0,0 +1,222 @@
+"""
+This code was modified from the nose plugins/xunit.py module, licensed under
+the GNU Lesser General Public License and copyrighted by Jason Pellerin.
+
+For more information, see the nose homepage at:
+
+    http://python-nose.googlecode.com/
+
+--
+This plugin provides test results in the standard XUnit XML format.
+
+It was designed for the `Hudson`_ continuous build system but will
+probably work for anything else that understands an XUnit-formatted XML
+representation of test results.
+
+Add this shell command to your builder ::
+
+    nosetests --with-xunit
+
+And by default a file named nosetests.xml will be written to the
+working directory.
+
+In a Hudson builder, tick the box named "Publish JUnit test result report"
+under the Post-build Actions and enter this value for Test report XMLs::
+
+    **/nosetests.xml
+
+If you need to change the name or location of the file, you can set the
+``--xunit-file`` option.
+
+Here is an abbreviated version of what an XML test report might look like::
+
+    <?xml version="1.0" encoding="UTF-8"?>
+    <testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">
+        <testcase classname="path_to_test_suite.TestSomething"
+                  name="test_it" time="0">
+            <error type="exceptions.TypeError" message="oops, wrong type">
+            Traceback (most recent call last):
+            ...
+            TypeError: oops, wrong type
+            </error>
+        </testcase>
+    </testsuite>
+
+.. _Hudson: https://hudson.dev.java.net/
+
+"""
+
+import doctest
+import os
+import traceback
+import re
+import inspect
+from time import time
+from xml.sax import saxutils
+
+# Invalid XML characters, control characters 0-31 sans \t, \n and \r
+CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]")
+
+def xml_safe(value):
+    """Replaces invalid XML characters with '?'."""
+    return CONTROL_CHARACTERS.sub('?', value)
+
+def escape_cdata(cdata):
+    """Escape a string for an XML CDATA section."""
+    return xml_safe(cdata).replace(']]>', ']]>]]><![CDATA[')
+
+def nice_classname(obj):
+    """Returns a nice name for class object or class instance.
+
+        >>> nice_classname(Exception()) # doctest: +ELLIPSIS
+        '...Exception'
+        >>> nice_classname(Exception)
+        'exceptions.Exception'
+
+    """
+    if inspect.isclass(obj):
+        cls_name = obj.__name__
+    else:
+        cls_name = obj.__class__.__name__
+    mod = inspect.getmodule(obj)
+    if mod:
+        name = mod.__name__
+        # jython
+        if name.startswith('org.python.core.'):
+            name = name[len('org.python.core.'):]
+        return "%s.%s" % (name, cls_name)
+    else:
+        return cls_name
+
+def exc_message(exc_info):
+    """Return the exception's message."""
+    exc = exc_info[1]
+    if exc is None:
+        # str exception
+        result = exc_info[0]
+    else:
+        try:
+            result = str(exc)
+        except UnicodeEncodeError:
+            try:
+                result = unicode(exc)
+            except UnicodeError:
+                # Fallback to args as neither str nor
+                # unicode(Exception(u'\xe6')) work in Python < 2.6
+                result = exc.args[0]
+    return xml_safe(result)
+
+class Xunit(object):
+    """This plugin provides test results in the standard XUnit XML format."""
+    name = 'xunit'
+    xunit_file = "nosetests.xml"
+    score = 2000
+    encoding = 'UTF-8'
+    error_report_file = None
+
+    def _timeTaken(self):
+        if hasattr(self, '_timer'):
+            taken = time() - self._timer
+        else:
+            # test died before it ran (probably error in setup())
+            # or success/failure added before test started probably 
+            # due to custom TestResult munging
+            taken = 0.0
+        return taken
+
+    def _quoteattr(self, attr):
+        """Escape an XML attribute. Value can be unicode."""
+        attr = xml_safe(attr)
+        if isinstance(attr, unicode):
+            attr = attr.encode(self.encoding)
+        return saxutils.quoteattr(attr)
+
+    def __init__(self):
+        """Configures the xunit plugin."""
+        self.stats = {'errors': 0,
+                      'failures': 0,
+                      'passes': 0,
+                      'skipped': 0
+                      }
+        self.errorlist = []
+        self.error_report_file = open(self.xunit_file, 'w')
+
+    def report(self):
+        """Writes an Xunit-formatted XML file
+
+        The file includes a report of test errors and failures.
+
+        """
+        self.stats['encoding'] = self.encoding
+        self.stats['total'] = (self.stats['errors'] + self.stats['failures']
+                               + self.stats['passes'] + self.stats['skipped'])
+        self.error_report_file.write(
+            '<?xml version="1.0" encoding="%(encoding)s"?>'
+            '<testsuite name="nosetests" tests="%(total)d" '
+            'errors="%(errors)d" failures="%(failures)d" '
+            'skip="%(skipped)d">' % self.stats)
+        self.error_report_file.write(''.join(self.errorlist))
+        self.error_report_file.write('</testsuite>')
+        self.error_report_file.close()
+
+    def addError(self, test, err, capt=None):
+        """Add error output to Xunit report.
+        """
+        taken = self._timeTaken()
+
+        if issubclass(err[0], SkipTest):
+            type = 'skipped'
+            self.stats['skipped'] += 1
+        else:
+            type = 'error'
+            self.stats['errors'] += 1
+        tb = ''.join(traceback.format_exception(*err))
+        id = test.id()
+        self.errorlist.append(
+            '<testcase classname=%(cls)s name=%(name)s time="%(taken)d">'
+            '<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
+            '</%(type)s></testcase>' %
+            {'cls': self._quoteattr('.'.join(id.split('.')[:-1])),
+             'name': self._quoteattr(id.split('.')[-1]),
+             'taken': taken,
+             'type': type,
+             'errtype': self._quoteattr(nice_classname(err[0])),
+             'message': self._quoteattr(exc_message(err)),
+             'tb': escape_cdata(tb),
+             })
+
+    def addFailure(self, test, err, capt=None, tb_info=None):
+        """Add failure output to Xunit report.
+        """
+        taken = self._timeTaken()
+        tb = ''.join(traceback.format_exception(*err))
+        self.stats['failures'] += 1
+        id = test
+        self.errorlist.append(
+            '<testcase classname=%(cls)s name=%(name)s time="%(taken)d">'
+            '<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
+            '</failure></testcase>' %
+            {'cls': self._quoteattr('.'.join(id.split('.')[:-1])),
+             'name': self._quoteattr(id.split('.')[-1]),
+             'taken': taken,
+             'errtype': self._quoteattr(nice_classname(err[0])),
+             'message': self._quoteattr(exc_message(err)),
+             'tb': escape_cdata(tb),
+             })
+
+    def addSuccess(self, test, capt=None):
+        """Add success output to Xunit report.
+        """
+        taken = self._timeTaken()
+        self.stats['passes'] += 1
+        id = test
+        self.errorlist.append(
+            '<testcase classname=%(cls)s name=%(name)s '
+            'time="%(taken)d" />' %
+            {'cls': self._quoteattr('.'.join(id.split('.')[:-1])),



More information about the yt-svn mailing list