Commits

Anonymous committed 383020c Merge

Merged stable

  • Participants
  • Parent commits 26fc5b4, 0d43392

Comments (0)

Files changed (3)

   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.
+- The xunit plugin now tags skipped tests with a <skipped> testcase tag, and
+  prevents the XML from containing invalid control characters.
 
 0.11.1
 

File nose/plugins/xunit.py

 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 cdata.replace(']]>', ']]>]]&gt;<![CDATA[')
+    return xml_safe(cdata).replace(']]>', ']]>]]&gt;<![CDATA[')
 
 def nice_classname(obj):
     """Returns a nice name for class object or class instance.
     exc = exc_info[1]
     if exc is None:
         # str exception
-        return exc_info[0]
-
-    try:
-        return str(exc)
-    except UnicodeEncodeError:
+        result = exc_info[0]
+    else:
         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]
+            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(Plugin):
     """This plugin provides test results in the standard XUnit XML format."""
 
     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(str(attr))
+        return saxutils.quoteattr(attr)
 
     def options(self, parser, env):
         """Sets additional command line options."""

File unit_tests/test_xunit.py

 import optparse
 import unittest
 from nose.tools import eq_
-from nose.plugins.xunit import Xunit
+from nose.plugins.xunit import Xunit, escape_cdata
 from nose.exc import SkipTest
 from nose.config import Config
 
         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._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')
 
+    def test_control_characters(self):
+        eq_(self.x._quoteattr('foo\n\b\f\r'), '"foo&#10;??&#13;"')
+        eq_(escape_cdata('foo\n\b\f\r'), 'foo\n??\r')
+
 class TestOptions(unittest.TestCase):
 
     def test_defaults(self):