diff --git a/CHANGELOG b/CHANGELOG
index 7f1232366..778a8aa55 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@ Changes between 1.2.1 and 1.2.2 (release pending)
==================================================
- 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
diff --git a/py/_code/code.py b/py/_code/code.py
index 7cfec853b..55ff38981 100644
--- 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):
diff --git a/py/_plugin/pytest_junitxml.py b/py/_plugin/pytest_junitxml.py
index cdad69881..674c80637 100644
--- 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("")
+
+ 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(
- '%s' % (s))
+ self.appendlog('%s',
+ 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(
- '%s' % (s))
+ self.appendlog('%s',
+ 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(
- '%s' % (s))
+ self.appendlog('%s',
+ 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(
- '%s' % s)
+ self.appendlog('%s',
+ report.longrepr)
self._closetestcase()
self.errors += 1
def append_skipped(self, report):
self._opentestcase(report)
- self.test_logs.append("")
+ self.appendlog("")
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'
' '
@@ -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
diff --git a/py/_xmlgen.py b/py/_xmlgen.py
index 2d1f63350..854fbccae 100644
--- 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()
diff --git a/testing/code/test_code.py b/testing/code/test_code.py
index 583fc7852..add389762 100644
--- 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)
diff --git a/testing/plugin/test_pytest_junitxml.py b/testing/plugin/test_pytest_junitxml.py
index 65597c535..8e5e8a57f 100644
--- 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("""
diff --git a/testing/root/test_xmlgen.py b/testing/root/test_xmlgen.py
index dc076332b..e566e07e4 100644
--- 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)