diff --git a/CHANGELOG b/CHANGELOG index de14bcf5f..0fb396b19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,14 +5,14 @@ Changes between 1.2.1 and 1.2.2 (release pending) - (issue85) fix junitxml plugin to handle tests with non-ascii output - fix some python3 compatibility issues (thanks Benjamin Peterson) - fixes for making the jython/win32 combination work -- fixes for handling of unicode exception values -- added links to the new capturelog and coverage plugins +- fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing - expose test outcome related exceptions as py.test.skip.Exception, py.test.raises.Exception etc., useful mostly for plugins doing special outcome interpreteration/tweaking - ship distribute_setup.py version 0.6.10 +- added links to the new capturelog and coverage plugins Changes between 1.2.1 and 1.2.0 diff --git a/py/_builtin.py b/py/_builtin.py index 3508c9bb9..d5c96285d 100644 --- a/py/_builtin.py +++ b/py/_builtin.py @@ -93,7 +93,7 @@ if sys.version_info >= (3, 0): # some backward compatibility helpers _basestring = str - def _totext(obj, encoding): + def _totext(obj, encoding=None): if isinstance(obj, bytes): obj = obj.decode(encoding) elif not isinstance(obj, str): diff --git a/py/_code/code.py b/py/_code/code.py index 55ff38981..367c449bb 100644 --- a/py/_code/code.py +++ b/py/_code/code.py @@ -3,7 +3,7 @@ import sys, os.path builtin_repr = repr -repr = py.builtin._tryimport('repr', 'reprlib') +reprlib = py.builtin._tryimport('repr', 'reprlib') class Code(object): """ wrapper around Python code objects """ @@ -510,7 +510,7 @@ class FormattedExcinfo(object): lines.append("__builtins__ = ") else: # This formatting could all be handled by the - # _repr() function, which is only repr.Repr in + # _repr() function, which is only reprlib.Repr in # disguise, so is very configurable. str_repr = self._saferepr(value) #if len(str_repr) < 70 or not isinstance(value, @@ -591,14 +591,23 @@ class TerminalRepr: return s def __unicode__(self): - tw = py.io.TerminalWriter(stringio=True) + l = [] + tw = py.io.TerminalWriter(l.append) self.toterminal(tw) - s = tw.stringio.getvalue().strip() - return s + l = map(unicode_or_repr, l) + return "".join(l).strip() def __repr__(self): return "<%s instance at %0x>" %(self.__class__, id(self)) +def unicode_or_repr(obj): + try: + return py.builtin._totext(obj) + except KeyboardInterrupt: + raise + except Exception: + return "" % safe_repr(obj) + class ReprExceptionInfo(TerminalRepr): def __init__(self, reprtraceback, reprcrash): self.reprtraceback = reprtraceback @@ -709,17 +718,17 @@ class ReprFuncArgs(TerminalRepr): -class SafeRepr(repr.Repr): +class SafeRepr(reprlib.Repr): """ subclass of repr.Repr that limits the resulting size of repr() and includes information on exceptions raised during the call. """ def __init__(self, *args, **kwargs): - repr.Repr.__init__(self, *args, **kwargs) + reprlib.Repr.__init__(self, *args, **kwargs) self.maxstring = 240 # 3 * 80 chars self.maxother = 160 # 2 * 80 chars def repr(self, x): - return self._callhelper(repr.Repr.repr, self, x) + return self._callhelper(reprlib.Repr.repr, self, x) def repr_instance(self, x, level): return self._callhelper(builtin_repr, x) diff --git a/py/_io/terminalwriter.py b/py/_io/terminalwriter.py index 4996800f6..7b93b896f 100644 --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -145,15 +145,17 @@ class TerminalWriter(object): # XXX deprecate stringio argument def __init__(self, file=None, stringio=False, encoding=None): - self.encoding = encoding if file is None: if stringio: self.stringio = file = py.io.TextIO() else: file = py.std.sys.stdout + if hasattr(file, 'encoding'): + encoding = file.encoding elif hasattr(file, '__call__'): file = WriteFile(file, encoding=encoding) + self.encoding = encoding self._file = file self.fullwidth = get_terminal_width() self.hasmarkup = should_do_markup(file) @@ -200,18 +202,22 @@ class TerminalWriter(object): def write(self, s, **kw): if s: - s = self._getbytestring(s) - if self.hasmarkup and kw: - s = self.markup(s, **kw) + if not isinstance(self._file, WriteFile): + s = self._getbytestring(s) + if self.hasmarkup and kw: + s = self.markup(s, **kw) self._file.write(s) self._file.flush() def _getbytestring(self, s): # XXX review this and the whole logic - if self.encoding and sys.version_info < (3,0) and isinstance(s, unicode): + if self.encoding and sys.version_info[0] < 3 and isinstance(s, unicode): return s.encode(self.encoding) elif not isinstance(s, str): - return str(s) + try: + return str(s) + except UnicodeEncodeError: + return "" % type(s).__name__ return s def line(self, s='', **kw): diff --git a/py/_plugin/pytest_junitxml.py b/py/_plugin/pytest_junitxml.py index 674c80637..c2673ce6c 100644 --- a/py/_plugin/pytest_junitxml.py +++ b/py/_plugin/pytest_junitxml.py @@ -144,6 +144,7 @@ class LogXML(object): suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed + logfile.write('') logfile.write(' 2, l - assert len(l[1]) > 2, l + assert len(l[0]) >= 2, l + assert len(l[1]) >= 2, l def test_attr_fullwidth(self, tw): tw.sep("-", "hello", fullwidth=70) diff --git a/testing/plugin/test_pytest_capture.py b/testing/plugin/test_pytest_capture.py index d8870badb..39a4fc0fd 100644 --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -393,7 +393,8 @@ def test_setup_failure_does_not_kill_capturing(testdir): def test_fdfuncarg_skips_on_no_osdup(testdir): testdir.makepyfile(""" import os - del os.dup + if hasattr(os, 'dup'): + del os.dup def test_hello(capfd): pass """) diff --git a/testing/plugin/test_pytest_junitxml.py b/testing/plugin/test_pytest_junitxml.py index 8e5e8a57f..5c0ce082f 100644 --- a/testing/plugin/test_pytest_junitxml.py +++ b/testing/plugin/test_pytest_junitxml.py @@ -1,6 +1,6 @@ from xml.dom import minidom -import py +import py, sys def runandparse(testdir, *args): resultpath = testdir.tmpdir.join("junit.xml") @@ -104,7 +104,7 @@ class TestPython: name="test_collect_error") fnode = tnode.getElementsByTagName("failure")[0] assert_attr(fnode, message="collection failure") - assert "invalid syntax" in fnode.toxml() + assert "SyntaxError" in fnode.toxml() def test_collect_skipped(self, testdir): testdir.makepyfile("import py ; py.test.skip('xyz')") @@ -130,7 +130,8 @@ class TestPython: assert result.ret == 1 tnode = dom.getElementsByTagName("testcase")[0] fnode = tnode.getElementsByTagName("failure")[0] - assert "hx" in fnode.toxml() + if not sys.platform.startswith("java"): + assert "hx" in fnode.toxml() class TestNonPython: def test_summing_simple(self, testdir): diff --git a/testing/plugin/test_pytest_resultlog.py b/testing/plugin/test_pytest_resultlog.py index 0ce1f46c1..48cf8ffaf 100644 --- a/testing/plugin/test_pytest_resultlog.py +++ b/testing/plugin/test_pytest_resultlog.py @@ -142,7 +142,7 @@ class TestWithFunctionIntegration: entry_lines = entry.splitlines() assert entry_lines[0].startswith('! ') - assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc + assert os.path.basename(__file__)[:-9] in entry_lines[0] #.pyc/class assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry diff --git a/testing/plugin/test_pytest_terminal.py b/testing/plugin/test_pytest_terminal.py index 65ed0f5f0..36e45f988 100644 --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -97,7 +97,7 @@ class TestTerminal: excinfo = py.test.raises(ValueError, "raise ValueError('hello')") rep.pytest_internalerror(excinfo.getrepr()) linecomp.assert_contains_lines([ - "INTERNALERROR> *raise ValueError*" + "INTERNALERROR> *ValueError*hello*" ]) def test_writeline(self, testdir, linecomp):