# HG changeset patch -- Bitbucket.org
# Project py-trunk
# URL http://bitbucket.org/hpk42/py-trunk/overview
# User holger krekel <hol...@merlinux.eu>
# Date 1272374143 -7200
# Node ID e368347f7d1342098d61d6abe8b54361e7859b4f
# Parent  e387b83b6267b3fd7e44b59b899299e528ce6733
(fixes issue85) correctly write non-ascii test output to junitxml files, refine 
some internal methods for it

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@ Changes between 1.2.1 and 1.2.2 (release
 ==================================================
 
 - new mechanism to allow plugins to register new hooks 
+- (issue85) fix junitxml plugin to handle tests with non-ascii output
 - fixes for handling of unicode exception values
 - added links to the new capturelog and coverage plugins 
 - (issue87) fix unboundlocal error in assertionold code 

--- a/testing/plugin/test_pytest_junitxml.py
+++ b/testing/plugin/test_pytest_junitxml.py
@@ -1,5 +1,6 @@
 
 from xml.dom import minidom
+import py
 
 def runandparse(testdir, *args):
     resultpath = testdir.tmpdir.join("junit.xml")
@@ -118,6 +119,19 @@ class TestPython:
         fnode = tnode.getElementsByTagName("skipped")[0]
         assert_attr(fnode, message="collection skipped")
 
+    def test_unicode(self, testdir):
+        value = 'hx\xc4\x85\xc4\x87\n'
+        testdir.makepyfile("""
+            def test_hello():
+                print (%r)
+                assert 0
+        """ % value)
+        result, dom = runandparse(testdir)
+        assert result.ret == 1
+        tnode = dom.getElementsByTagName("testcase")[0]
+        fnode = tnode.getElementsByTagName("failure")[0]
+        assert "hx" in fnode.toxml()
+
 class TestNonPython:
     def test_summing_simple(self, testdir):
         testdir.makeconftest("""

--- a/py/_plugin/pytest_junitxml.py
+++ b/py/_plugin/pytest_junitxml.py
@@ -43,6 +43,10 @@ class LogXML(object):
 
     def _closetestcase(self):
         self.test_logs.append("</testcase>")
+
+    def appendlog(self, fmt, *args):
+        args = tuple([py.xml.escape(arg) for arg in args])
+        self.test_logs.append(fmt % args)
          
     def append_pass(self, report):
         self.passed += 1
@@ -51,10 +55,9 @@ class LogXML(object):
 
     def append_failure(self, report):
         self._opentestcase(report)
-        s = py.xml.escape(str(report.longrepr))
         #msg = str(report.longrepr.reprtraceback.extraline)
-        self.test_logs.append(
-            '<failure message="test failure">%s</failure>' % (s))
+        self.appendlog('<failure message="test failure">%s</failure>', 
+            report.longrepr)
         self._closetestcase()
         self.failed += 1
 
@@ -69,33 +72,30 @@ class LogXML(object):
 
     def append_collect_failure(self, report):
         self._opentestcase_collectfailure(report)
-        s = py.xml.escape(str(report.longrepr))
         #msg = str(report.longrepr.reprtraceback.extraline)
-        self.test_logs.append(
-            '<failure message="collection failure">%s</failure>' % (s))
+        self.appendlog('<failure message="collection failure">%s</failure>', 
+            report.longrepr)
         self._closetestcase()
         self.errors += 1
 
     def append_collect_skipped(self, report):
         self._opentestcase_collectfailure(report)
-        s = py.xml.escape(str(report.longrepr))
         #msg = str(report.longrepr.reprtraceback.extraline)
-        self.test_logs.append(
-            '<skipped message="collection skipped">%s</skipped>' % (s))
+        self.appendlog('<skipped message="collection skipped">%s</skipped>',
+            report.longrepr)
         self._closetestcase()
         self.skipped += 1
 
     def append_error(self, report):
         self._opentestcase(report)
-        s = py.xml.escape(str(report.longrepr))
-        self.test_logs.append(
-            '<error message="test setup failure">%s</error>' % s)
+        self.appendlog('<error message="test setup failure">%s</error>', 
+            report.longrepr)
         self._closetestcase()
         self.errors += 1
 
     def append_skipped(self, report):
         self._opentestcase(report)
-        self.test_logs.append("<skipped/>")
+        self.appendlog("<skipped/>")
         self._closetestcase()
         self.skipped += 1
 
@@ -126,7 +126,7 @@ class LogXML(object):
 
     def pytest_internalerror(self, excrepr):
         self.errors += 1
-        data = py.xml.escape(str(excrepr))
+        data = py.xml.escape(excrepr)
         self.test_logs.append(
             '\n<testcase classname="pytest" name="internal">'
             '    <error message="internal error">'
@@ -136,7 +136,11 @@ class LogXML(object):
         self.suite_start_time = time.time()
 
     def pytest_sessionfinish(self, session, exitstatus, __multicall__):
-        logfile = open(self.logfile, 'w', 1) # line buffered
+        if py.std.sys.version_info[0] < 3:
+            logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8')
+        else:
+            logfile = open(self.logfile, 'w', encoding='utf-8')
+            
         suite_stop_time = time.time()
         suite_time_delta = suite_stop_time - self.suite_start_time
         numtests = self.passed + self.failed

--- a/py/_code/code.py
+++ b/py/_code/code.py
@@ -417,6 +417,12 @@ class ExceptionInfo(object):
         loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
         return str(loc)
 
+    def __unicode__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return unicode(loc)
+        
+
 class FormattedExcinfo(object):
     """ presenting information about failing Functions and Generators. """ 
     # for traceback entries 
@@ -579,11 +585,15 @@ class FormattedExcinfo(object):
 
 class TerminalRepr:
     def __str__(self):
+        s = self.__unicode__()
+        if sys.version_info[0] < 3:
+            s = s.encode('utf-8')
+        return s
+
+    def __unicode__(self):
         tw = py.io.TerminalWriter(stringio=True)
         self.toterminal(tw)
         s = tw.stringio.getvalue().strip()
-        if sys.version_info[0] < 3:
-            s = s.encode('utf-8')
         return s
 
     def __repr__(self):

--- a/testing/root/test_xmlgen.py
+++ b/testing/root/test_xmlgen.py
@@ -5,6 +5,25 @@ from py._xmlgen import unicode, html
 class ns(py.xml.Namespace): 
     pass 
 
+def test_escape():
+    uvalue = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8')
+    class A:
+        def __unicode__(self):
+            return uvalue
+        def __str__(self):
+            x = self.__unicode__()
+            if py.std.sys.version_info[0] < 3:
+                return x.encode('utf-8')
+            return x
+    y = py.xml.escape(uvalue)
+    assert y == uvalue 
+    x = py.xml.escape(A())
+    assert x == uvalue 
+    if py.std.sys.version_info[0] < 3:
+        assert isinstance(x, unicode)
+        assert isinstance(y, unicode)
+    
+
 def test_tag_with_text(): 
     x = ns.hello("world") 
     u = unicode(x) 

--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -199,3 +199,5 @@ def test_unicode_handling(testdir):
         raise Exception(value)
     excinfo = py.test.raises(Exception, f)
     s = str(excinfo)
+    if sys.version_info[0] < 3:
+        u = unicode(excinfo)

--- a/py/_xmlgen.py
+++ b/py/_xmlgen.py
@@ -238,6 +238,7 @@ class _escape:
 
     def __call__(self, ustring):
         """ xml-escape the given unicode string. """
+        ustring = unicode(ustring)
         return self.charef_rex.sub(self._replacer, ustring)
 
 escape = _escape()
_______________________________________________
py-svn mailing list
py-svn@codespeak.net
http://codespeak.net/mailman/listinfo/py-svn

Reply via email to