Commits

jpellerin  committed 41b14a3 Merge

Merged stable

  • Participants
  • Parent commits 0cd5b37, c583df1

Comments (0)

Files changed (7)

 - Fixed bugs in nose's test suite that caused spurious failures on Windows.
 - Fixed bug in twisted tools: delayed calls were not shut down on
   reactor stop. Thanks to abbeyj for the patch (#278).
-- Fixed bug where root log handlers were not cleared. For example, this was 
+- Fixed bug where root log handlers were not cleared. For example, this was
   emitting unwanted messages when testing Google App Engine websites.
+- Fixed bug in test names output by xunit plugin. Thanks to Philip
+  Jenvey for the bug report and patch (#280).
+- Fixed bug in profile plugin that caused stats to fail to print under Python
+  2.5 and later. Thanks to djs at n-cube dot org for the bug report (#285).
+- The xunit plugin now tags skipped tests with a <skipped> testcase tag.
 
 0.11.1
 

File functional_tests/doc_tests/test_xunit_plugin/test_skips.rst

 FAILED (SKIP=1, errors=1, failures=1)
 
 >>> open(outfile, 'r').read() # doctest: +ELLIPSIS
-'<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="4" errors="1" failures="1" skip="1"><testcase classname="test_skip" name="test_skip.test_ok" time="..." /><testcase classname="test_skip" name="test_skip.test_err" time="..."><error type="exceptions.Exception">.../error></testcase><testcase classname="test_skip" name="test_skip.test_fail" time="..."><failure type="exceptions.AssertionError">...</failure></testcase></testsuite>'
+'<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="4" errors="1" failures="1" skip="1"><testcase classname="test_skip" name="test_ok" time="..." /><testcase classname="test_skip" name="test_err" time="..."><error type="exceptions.Exception" message="oh no">...</error></testcase><testcase classname="test_skip" name="test_fail" time="..."><failure type="exceptions.AssertionError" message="bye">...</failure></testcase><testcase classname="test_skip" name="test_skip" time="..."><skipped type="nose.plugins.skip.SkipTest" message="not me">...</skipped></testcase></testsuite>'

File functional_tests/support/xunit/test_xunit_as_suite.py

+# -*- coding: utf-8 -*-
 import sys
 from nose.exc import SkipTest
 import unittest
     def test_error(self):
         raise TypeError("oops, wrong type")
     
+    def test_non_ascii_error(self):
+        raise Exception(u"日本")
+    
     def test_output(self):
         sys.stdout.write("test-generated output\n")
 

File functional_tests/test_xunit.py

+# -*- coding: utf-8 -*-
 import os
 import unittest
 from nose.plugins.xunit import Xunit
         print result
         
         assert '<?xml version="1.0" encoding="UTF-8"?>' in result
-        assert '<testsuite name="nosetests" tests="5" errors="1" failures="1" skip="1">' in result
-        assert '<testcase classname="test_xunit_as_suite.TestForXunit" name="test_xunit_as_suite.TestForXunit.test_error" time="0">' in result
+        assert '<testsuite name="nosetests" tests="6" errors="2" failures="1" skip="1">' in result
+        assert '<testcase classname="test_xunit_as_suite.TestForXunit" name="test_error" time="0">' in result
+        assert '<error type="exceptions.Exception" message="日本">' in result
         assert '</testcase>' in result
         assert '</testsuite>' in result
 

File nose/plugins/prof.py

         # 2.5 has completely different stream handling from 2.4 and earlier.
         # Before 2.5, stats objects have no stream attribute; in 2.5 and later
         # a reference sys.stdout is stored before we can tweak it.
-        compat_25 = hasattr(stats, 'stream')
+        compat_25 = hasattr(prof_stats, 'stream')
         if compat_25:
             tmp = prof_stats.stream
-            stats.stream = stream
+            prof_stats.stream = stream
         else:
             tmp = sys.stdout
             sys.stdout = stream
                 prof_stats.print_stats()
         finally:
             if compat_25:
-                stats.stream = tmp
+                prof_stats.stream = tmp
             else:
                 sys.stdout = tmp
 

File nose/plugins/xunit.py

     <?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="path_to_test_suite.TestSomething.test_it" time="0">
-            <error type="exceptions.TypeError">
+                  name="test_it" time="0">
+            <error type="exceptions.TypeError" message="oops, wrong type">
             Traceback (most recent call last):
             ...
             TypeError: oops, wrong type
 
 """
 
+import doctest
 import os
 import traceback
 import re
 from nose.plugins.base import Plugin
 from nose.exc import SkipTest
 from time import time
-import doctest
+from xml.sax import saxutils
 
-def xmlsafe(s, encoding="utf-8"):
-    """Used internally to escape XML."""
-    if isinstance(s, unicode):
-        s = s.encode(encoding)
-    s = str(s)
-    for src, rep in [('&', '&amp;', ),
-                     ('<', '&lt;', ),
-                     ('>', '&gt;', ),
-                     ('"', '&quot;', ),
-                     ("'", '&#39;', ),
-                     ]:
-        s = s.replace(src, rep)
-    return s
+def escape_cdata(cdata):
+    """Escape a string for an XML CDATA section."""
+    return cdata.replace(']]>', ']]>]]&gt;<![CDATA[')
 
 def nice_classname(obj):
     """Returns a nice name for class object or class instance.
     else:
         return cls_name
 
+def exc_message(exc_info):
+    """Return the exception's message."""
+    exc = exc_info[1]
+    if exc is None:
+        # str exception
+        return exc_info[0]
+
+    try:
+        return str(exc)
+    except UnicodeEncodeError:
+        try:
+            return unicode(exc)
+        except UnicodeError:
+            # Fallback to args as neither str nor
+            # unicode(Exception(u'\xe6')) work in Python < 2.6
+            return exc.args[0]
+
 class Xunit(Plugin):
     """This plugin provides test results in the standard XUnit XML format."""
     name = 'xunit'
             taken = 0.0
         return taken
 
-    def _xmlsafe(self, s):
-        return xmlsafe(s, encoding=self.encoding)
+    def _quoteattr(self, attr):
+        """Escape an XML attribute. Value can be unicode."""
+        if isinstance(attr, unicode):
+            attr = attr.encode(self.encoding)
+        return saxutils.quoteattr(str(attr))
 
     def options(self, parser, env):
         """Sets additional command line options."""
         taken = self._timeTaken()
 
         if issubclass(err[0], SkipTest):
-            self.stats['skipped'] +=1
-            return
+            type = 'skipped'
+            self.stats['skipped'] += 1
+        else:
+            type = 'error'
+            self.stats['errors'] += 1
         tb = ''.join(traceback.format_exception(*err))
-        self.stats['errors'] += 1
         id = test.id()
         self.errorlist.append(
-            '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
-            '<error type="%(errtype)s">%(tb)s</error></testcase>' %
-            {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
-             'name': self._xmlsafe(id),
-             'errtype': self._xmlsafe(nice_classname(err[0])),
-             'tb': self._xmlsafe(tb),
+            '<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):
         self.stats['failures'] += 1
         id = test.id()
         self.errorlist.append(
-            '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
-            '<failure type="%(errtype)s">%(tb)s</failure></testcase>' %
-            {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
-             'name': self._xmlsafe(id),
-             'errtype': self._xmlsafe(nice_classname(err[0])),
-             'tb': self._xmlsafe(tb),
+            '<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):
         self.stats['passes'] += 1
         id = test.id()
         self.errorlist.append(
-            '<testcase classname="%(cls)s" name="%(name)s" '
+            '<testcase classname=%(cls)s name=%(name)s '
             'time="%(taken)d" />' %
-            {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
-             'name': self._xmlsafe(id),
+            {'cls': self._quoteattr('.'.join(id.split('.')[:-1])),
+             'name': self._quoteattr(id.split('.')[-1]),
              'taken': taken,
              })

File unit_tests/test_xunit.py

 from nose.exc import SkipTest
 from nose.config import Config
 
-def mktest():    
+def mktest():
     class TC(unittest.TestCase):
         def runTest(self):
             pass
 mktest.__test__ = False
 
 class TestEscaping(unittest.TestCase):
-    
+
     def setUp(self):
         self.x = Xunit()
-    
+
     def test_all(self):
-        eq_(self.x._xmlsafe(
+        eq_(self.x._quoteattr(
             '''<baz src="http://foo?f=1&b=2" quote="inix hubris 'maximus'?" />'''),
-            ('&lt;baz src=&quot;http://foo?f=1&amp;b=2&quot; '
-                'quote=&quot;inix hubris &#39;maximus&#39;?&quot; /&gt;'))
+            ('"&lt;baz src=&quot;http://foo?f=1&amp;b=2&quot; '
+                'quote=&quot;inix hubris \'maximus\'?&quot; /&gt;"'))
 
-    
     def test_unicode_is_utf8_by_default(self):
-        eq_(self.x._xmlsafe(u'Ivan Krsti\u0107'),
-            'Ivan Krsti\xc4\x87')
+        eq_(self.x._quoteattr(u'Ivan Krsti\u0107'),
+            '"Ivan Krsti\xc4\x87"')
 
-    
+
     def test_unicode_custom_utf16_madness(self):
         self.x.encoding = 'utf-16'
-        utf16 = self.x._xmlsafe(u'Ivan Krsti\u0107')
-        
+        utf16 = self.x._quoteattr(u'Ivan Krsti\u0107')[1:-1]
+
         # to avoid big/little endian bytes, assert that we can put it back:
         eq_(utf16.decode('utf16'), u'Ivan Krsti\u0107')
 
 class TestOptions(unittest.TestCase):
-    
+
     def test_defaults(self):
         parser = optparse.OptionParser()
         x = Xunit()
         x.add_options(parser, env={})
         (options, args) = parser.parse_args([])
         eq_(options.xunit_file, "nosetests.xml")
-    
+
     def test_file_from_environ(self):
         parser = optparse.OptionParser()
         x = Xunit()
         x.add_options(parser, env={'NOSE_XUNIT_FILE': "kangaroo.xml"})
         (options, args) = parser.parse_args([])
         eq_(options.xunit_file, "kangaroo.xml")
-    
+
     def test_file_from_opt(self):
         parser = optparse.OptionParser()
         x = Xunit()
         x.add_options(parser, env={})
         (options, args) = parser.parse_args(["--xunit-file=blagojevich.xml"])
         eq_(options.xunit_file, "blagojevich.xml")
-    
+
 class TestXMLOutputWithXML(unittest.TestCase):
-    
+
     def setUp(self):
         self.xmlfile = os.path.abspath(
             os.path.join(os.path.dirname(__file__), 
             "--xunit-file=%s" % self.xmlfile
         ])
         self.x.configure(options, Config())
-        
+
         try:
             import xml.etree.ElementTree
         except ImportError:
             self.ET = False
         else:
             self.ET = xml.etree.ElementTree
-    
+
     def tearDown(self):
         os.unlink(self.xmlfile)
-    
+
     def get_xml_report(self):
         class DummyStream:
             pass
         
             tc = tree.find("testcase")
             eq_(tc.attrib['classname'], "test_xunit.TC")
-            eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+            eq_(tc.attrib['name'], "runTest")
             assert int(tc.attrib['time']) >= 0
         
             err = tc.find("failure")
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert '<testsuite name="nosetests" tests="1" errors="0" failures="1" skip="0">' in result
-            assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+            assert '<testcase classname="test_xunit.TC" name="runTest"' in result
             assert '<failure type="exceptions.AssertionError">' in result
             assert 'AssertionError: one is not &#39;equal&#39; to two' in result
             assert 'AssertionError(&quot;one is not &#39;equal&#39; to two&quot;)' in result
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert ('<testcase classname="test_xunit.TC" '
-                    'name="test_xunit.TC.runTest" time="0">') in result
+                    'name="runTest" time="0">') in result
     
     def test_addError(self):
         test = mktest()
         
             tc = tree.find("testcase")
             eq_(tc.attrib['classname'], "test_xunit.TC")
-            eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+            eq_(tc.attrib['name'], "runTest")
             assert int(tc.attrib['time']) >= 0
         
             err = tc.find("error")
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert '<testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">' in result
-            assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+            assert '<testcase classname="test_xunit.TC" name="runTest"' in result
             assert '<error type="exceptions.RuntimeError">' in result
             assert 'RuntimeError: some error happened' in result
             assert '</error></testcase></testsuite>' in result
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert ('<testcase classname="test_xunit.TC" '
-                    'name="test_xunit.TC.runTest" time="0">') in result
+                    'name="runTest" time="0">') in result
         
     def test_addSuccess(self):
         test = mktest()
         
             tc = tree.find("testcase")
             eq_(tc.attrib['classname'], "test_xunit.TC")
-            eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+            eq_(tc.attrib['name'], "runTest")
             assert int(tc.attrib['time']) >= 0
         else:
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert '<testsuite name="nosetests" tests="1" errors="0" failures="0" skip="0">' in result
-            assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+            assert '<testcase classname="test_xunit.TC" name="runTest"' in result
             assert '</testsuite>' in result
     
     def test_addSuccess_early(self):
             # this is a dumb test for 2.4-
             assert '<?xml version="1.0" encoding="UTF-8"?>' in result
             assert ('<testcase classname="test_xunit.TC" '
-                    'name="test_xunit.TC.runTest" time="0" />') in result
+                    'name="runTest" time="0" />') in result