Prevent null-characters from appearing in junitxml's output

The Jenkins XML parser does not deal with null-characters inside the
XML.  This replaces any null character with nothing in the XML output,
which makes no visual difference.
This commit is contained in:
Floris Bruynooghe 2011-04-16 00:09:25 +01:00
parent 60ff2e8529
commit 1c1918eb22
2 changed files with 111 additions and 1 deletions

View File

@ -5,8 +5,42 @@ Based on initial code from Ross Lawley.
import py import py
import os import os
import re
import sys
import time import time
# Python 2.X and 3.X compatibility
try:
unichr(65)
except NameError:
unichr = chr
try:
unicode('A')
except NameError:
unicode = str
try:
long(1)
except NameError:
long = int
# We need to get the subset of the invalid unicode ranges according to
# XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
# | [#x10000-#x10FFFF]
_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x19),
(0xD800, 0xDFFF), (0xFDD0, 0xFFFF)]
_illegal_ranges = [unicode("%s-%s") % (unichr(low), unichr(high))
for (low, high) in _illegal_unichrs
if low < sys.maxunicode]
illegal_xml_re = re.compile(unicode('[%s]') %
unicode('').join(_illegal_ranges))
del _illegal_unichrs
del _illegal_ranges
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("terminal reporting") group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', action="store", dest="xmlpath", group.addoption('--junitxml', action="store", dest="xmlpath",
@ -28,6 +62,7 @@ def pytest_unconfigure(config):
del config._xml del config._xml
config.pluginmanager.unregister(xml) config.pluginmanager.unregister(xml)
class LogXML(object): class LogXML(object):
def __init__(self, logfile, prefix): def __init__(self, logfile, prefix):
self.logfile = logfile self.logfile = logfile
@ -55,7 +90,14 @@ class LogXML(object):
self.test_logs.append("</testcase>") self.test_logs.append("</testcase>")
def appendlog(self, fmt, *args): def appendlog(self, fmt, *args):
args = tuple([py.xml.escape(arg) for arg in args]) def repl(matchobj):
i = ord(matchobj.group())
if i <= 0xFF:
return unicode('#x%02X') % i
else:
return unicode('#x%04X') % i
args = tuple([illegal_xml_re.sub(repl, py.xml.escape(arg))
for arg in args])
self.test_logs.append(fmt % args) self.test_logs.append(fmt % args)
def append_pass(self, report): def append_pass(self, report):

View File

@ -283,3 +283,71 @@ class TestNonPython:
assert_attr(fnode, message="test failure") assert_attr(fnode, message="test failure")
assert "custom item runtest failed" in fnode.toxml() assert "custom item runtest failed" in fnode.toxml()
def test_nullbyte(testdir):
# A null byte can not occur in XML (see section 2.2 of the spec)
testdir.makepyfile("""
import sys
def test_print_nullbyte():
sys.stdout.write('Here the null -->' + chr(0) + '<--')
sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
assert False
""")
xmlf = testdir.tmpdir.join('junit.xml')
result = testdir.runpytest('--junitxml=%s' % xmlf)
text = xmlf.read()
assert '\x00' not in text
assert '#x00' in text
def test_nullbyte_replace(testdir):
# Check if the null byte gets replaced
testdir.makepyfile("""
import sys
def test_print_nullbyte():
sys.stdout.write('Here the null -->' + chr(0) + '<--')
sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
assert False
""")
xmlf = testdir.tmpdir.join('junit.xml')
result = testdir.runpytest('--junitxml=%s' % xmlf)
text = xmlf.read()
assert '#x0' in text
def test_invalid_xml_escape(testdir):
# Test some more invalid xml chars, the full range should be
# tested really but let's just thest the edges of the ranges
# intead.
# XXX This only tests low unicode character points for now as
# there are some issues with the testing infrastructure for
# the higher ones.
# XXX Testing 0xD (\r) is tricky as it overwrites the just written
# line in the output, so we skip it too.
global unichr
try:
unichr(65)
except NameError:
unichr = chr
u = py.builtin._totext
invalid = (0x1, 0xB, 0xC, 0xE, 0x19,)
# 0xD800, 0xDFFF, 0xFFFE, 0x0FFFF) #, 0x110000)
valid = (0x9, 0xA, 0x20,) # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF)
all = invalid + valid
prints = [u(" sys.stdout.write('''0x%X-->%s<--''')") % (i, unichr(i))
for i in all]
testdir.makepyfile(u("# -*- coding: UTF-8 -*-"),
u("import sys"),
u("def test_print_bytes():"),
u("\n").join(prints),
u(" assert False"))
xmlf = testdir.tmpdir.join('junit.xml')
result = testdir.runpytest('--junitxml=%s' % xmlf)
text = xmlf.read()
for i in invalid:
if i <= 0xFF:
assert '#x%02X' % i in text
else:
assert '#x%04X' % i in text
for i in valid:
assert chr(i) in text