* fix capturing and unicode printing in tests

* introduce "_encoding" to py/io/terminalwriter writing
* beautify a few __repr__ for better internal debugging

--HG--
branch : 1.0.x
This commit is contained in:
holger krekel 2009-08-06 14:34:19 +02:00
parent 91597f4100
commit 8fcdac9dd6
11 changed files with 133 additions and 53 deletions

View File

@ -1,3 +1,10 @@
Changes between 1.0.0 and 1.0.1
=====================================
* various unicode fixes: capturing and prints of unicode strings now
work within tests, they are encoded as "utf8" by default, terminalwriting
was adapted and somewhat unified between windows and linux
Changes between 1.0.0b9 and 1.0.0 Changes between 1.0.0b9 and 1.0.0
===================================== =====================================

View File

@ -17,6 +17,16 @@ class MultiCall:
self.kwargs = kwargs self.kwargs = kwargs
self.results = [] self.results = []
def __repr__(self):
args = []
if self.args:
args.append("posargs=%r" %(self.args,))
kw = self.kwargs
args.append(", ".join(["%s=%r" % x for x in self.kwargs.items()]))
args = " ".join(args)
status = "results: %r, rmethods: %r" % (self.results, self.methods)
return "<MultiCall %s %s>" %(args, status)
def execute(self, firstresult=False): def execute(self, firstresult=False):
while self.methods: while self.methods:
currentmethod = self.methods.pop() currentmethod = self.methods.pop()

View File

@ -76,7 +76,7 @@ class StdCaptureFD(Capture):
os.close(fd) os.close(fd)
if out: if out:
tmpfile = None tmpfile = None
if isinstance(out, file): if hasattr(out, 'write'):
tmpfile = out tmpfile = out
self.out = py.io.FDCapture(1, tmpfile=tmpfile) self.out = py.io.FDCapture(1, tmpfile=tmpfile)
if patchsys: if patchsys:
@ -84,7 +84,7 @@ class StdCaptureFD(Capture):
if err: if err:
if mixed and out: if mixed and out:
tmpfile = self.out.tmpfile tmpfile = self.out.tmpfile
elif isinstance(err, file): elif hasattr(err, 'write'):
tmpfile = err tmpfile = err
else: else:
tmpfile = None tmpfile = None

View File

@ -139,6 +139,7 @@ class TerminalWriter(object):
Black=40, Red=41, Green=42, Yellow=43, Black=40, Red=41, Green=42, Yellow=43,
Blue=44, Purple=45, Cyan=46, White=47, Blue=44, Purple=45, Cyan=46, White=47,
bold=1, light=2, blink=5, invert=7) bold=1, light=2, blink=5, invert=7)
_encoding = "utf-8"
def __init__(self, file=None, stringio=False): def __init__(self, file=None, stringio=False):
if file is None: if file is None:
@ -194,58 +195,27 @@ class TerminalWriter(object):
def write(self, s, **kw): def write(self, s, **kw):
if s: if s:
s = str(s) s = self._getbytestring(s)
if self.hasmarkup and kw: if self.hasmarkup and kw:
s = self.markup(s, **kw) s = self.markup(s, **kw)
self._file.write(s) self._file.write(s)
self._file.flush() self._file.flush()
def _getbytestring(self, s):
if isinstance(s, unicode):
return s.encode(self._encoding)
elif not isinstance(s, str):
return str(s)
return s
def line(self, s='', **kw): def line(self, s='', **kw):
self.write(s, **kw) self.write(s, **kw)
self.write('\n') self.write('\n')
class Win32ConsoleWriter(object): class Win32ConsoleWriter(TerminalWriter):
def __init__(self, file=None, stringio=False):
if file is None:
if stringio:
self.stringio = file = py.std.cStringIO.StringIO()
else:
file = py.std.sys.stdout
elif callable(file):
file = WriteFile(file)
self._file = file
self.fullwidth = get_terminal_width()
self.hasmarkup = should_do_markup(file)
def sep(self, sepchar, title=None, fullwidth=None, **kw):
if fullwidth is None:
fullwidth = self.fullwidth
# the goal is to have the line be as long as possible
# under the condition that len(line) <= fullwidth
if title is not None:
# we want 2 + 2*len(fill) + len(title) <= fullwidth
# i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth
# 2*len(sepchar)*N <= fullwidth - len(title) - 2
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
N = (fullwidth - len(title) - 2) // (2*len(sepchar))
fill = sepchar * N
line = "%s %s %s" % (fill, title, fill)
else:
# we want len(sepchar)*N <= fullwidth
# i.e. N <= fullwidth // len(sepchar)
line = sepchar * (fullwidth // len(sepchar))
# in some situations there is room for an extra sepchar at the right,
# in particular if we consider that with a sepchar like "_ " the
# trailing space is not important at the end of the line
if len(line) + len(sepchar.rstrip()) <= fullwidth:
line += sepchar.rstrip()
self.line(line, **kw)
def write(self, s, **kw): def write(self, s, **kw):
if s: if s:
s = str(s) s = self._getbytestring(s)
if self.hasmarkup: if self.hasmarkup:
handle = GetStdHandle(STD_OUTPUT_HANDLE) handle = GetStdHandle(STD_OUTPUT_HANDLE)
@ -269,8 +239,8 @@ class Win32ConsoleWriter(object):
if self.hasmarkup: if self.hasmarkup:
SetConsoleTextAttribute(handle, FOREGROUND_WHITE) SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
def line(self, s='', **kw): def line(self, s="", **kw):
self.write(s + '\n', **kw) self.write(s+"\n", **kw)
if sys.platform == 'win32': if sys.platform == 'win32':
TerminalWriter = Win32ConsoleWriter TerminalWriter = Win32ConsoleWriter

View File

@ -37,6 +37,16 @@ class BaseTests:
assert len(l) == 1 assert len(l) == 1
assert l[0] == "hello\n" assert l[0] == "hello\n"
def test_line_unicode(self):
tw = self.getwriter()
for encoding in 'utf8', 'latin1':
tw._encoding = encoding
msg = unicode('b\u00f6y', 'utf8')
tw.line(msg)
l = self.getlines()
assert not isinstance(l[0], unicode)
assert unicode(l[0], encoding) == msg + "\n"
def test_sep_no_title(self): def test_sep_no_title(self):
tw = self.getwriter() tw = self.getwriter()
tw.sep("-", fullwidth=60) tw.sep("-", fullwidth=60)
@ -85,6 +95,16 @@ class BaseTests:
l = self.getlines() l = self.getlines()
assert len(l[0]) == len(l[1]) assert len(l[0]) == len(l[1])
class TestTmpfile(BaseTests):
def getwriter(self):
self.path = py.test.config.ensuretemp("terminalwriter").ensure("tmpfile")
self.tw = py.io.TerminalWriter(self.path.open('w+'))
return self.tw
def getlines(self):
io = self.tw._file
io.flush()
return self.path.open('r').readlines()
class TestStringIO(BaseTests): class TestStringIO(BaseTests):
def getwriter(self): def getwriter(self):
self.tw = py.io.TerminalWriter(stringio=True) self.tw = py.io.TerminalWriter(stringio=True)

View File

@ -10,6 +10,7 @@ class TestMultiCall:
def test_uses_copy_of_methods(self): def test_uses_copy_of_methods(self):
l = [lambda: 42] l = [lambda: 42]
mc = MultiCall(l) mc = MultiCall(l)
repr(mc)
l[:] = [] l[:] = []
res = mc.execute() res = mc.execute()
return res == 42 return res == 42
@ -33,16 +34,27 @@ class TestMultiCall:
p1 = P1() p1 = P1()
p2 = P2() p2 = P2()
multicall = MultiCall([p1.m, p2.m], 23) multicall = MultiCall([p1.m, p2.m], 23)
assert "23" in repr(multicall)
reslist = multicall.execute() reslist = multicall.execute()
assert len(reslist) == 2 assert len(reslist) == 2
# ensure reversed order # ensure reversed order
assert reslist == [23, 17] assert reslist == [23, 17]
def test_keyword_args(self):
def f(x):
return x + 1
multicall = MultiCall([f], x=23)
assert "x=23" in repr(multicall)
reslist = multicall.execute()
assert reslist == [24]
assert "24" in repr(multicall)
def test_optionalcallarg(self): def test_optionalcallarg(self):
class P1: class P1:
def m(self, x): def m(self, x):
return x return x
call = MultiCall([P1().m], 23) call = MultiCall([P1().m], 23)
assert "23" in repr(call)
assert call.execute() == [23] assert call.execute() == [23]
assert call.execute(firstresult=True) == 23 assert call.execute(firstresult=True) == 23

View File

@ -145,11 +145,9 @@ class SlaveNode(object):
if call.excinfo: if call.excinfo:
# likely it is not collectable here because of # likely it is not collectable here because of
# platform/import-dependency induced skips # platform/import-dependency induced skips
# XXX somewhat ugly shortcuts - also makes a collection # we fake a setup-error report with the obtained exception
# failure into an ItemTestReport - this might confuse # and do not care about capturing or non-runner hooks
# pytest_runtest_logreport hooks
rep = self.runner.pytest_runtest_makereport(item=item, call=call) rep = self.runner.pytest_runtest_makereport(item=item, call=call)
self.pytest_runtest_logreport(rep) self.pytest_runtest_logreport(rep)
return return
item.config.hook.pytest_runtest_protocol(item=item) item.config.hook.pytest_runtest_protocol(item=item)

View File

@ -107,11 +107,24 @@ class CaptureManager:
def __init__(self): def __init__(self):
self._method2capture = {} self._method2capture = {}
def _maketempfile(self):
f = py.std.tempfile.TemporaryFile()
newf = py.io.dupfile(f)
f.close()
return ustream(newf)
def _makestringio(self):
return py.std.StringIO.StringIO()
def _startcapture(self, method): def _startcapture(self, method):
if method == "fd": if method == "fd":
return py.io.StdCaptureFD() return py.io.StdCaptureFD(
out=self._maketempfile(), err=self._maketempfile()
)
elif method == "sys": elif method == "sys":
return py.io.StdCapture() return py.io.StdCapture(
out=self._makestringio(), err=self._makestringio()
)
else: else:
raise ValueError("unknown capturing method: %r" % method) raise ValueError("unknown capturing method: %r" % method)
@ -252,3 +265,13 @@ class CaptureFuncarg:
def close(self): def close(self):
self.capture.reset() self.capture.reset()
del self.capture del self.capture
def ustream(f):
import codecs
encoding = getattr(f, 'encoding', None) or "UTF-8"
reader = codecs.getreader(encoding)
writer = codecs.getwriter(encoding)
srw = codecs.StreamReaderWriter(f, reader, writer)
srw.encoding = encoding
return srw

View File

@ -108,6 +108,13 @@ class CallInfo:
except: except:
self.excinfo = py.code.ExceptionInfo() self.excinfo = py.code.ExceptionInfo()
def __repr__(self):
if self.excinfo:
status = "exception: %s" % str(self.excinfo.value)
else:
status = "result: %r" % (self.result,)
return "<CallInfo when=%r %s>" % (self.when, status)
def forked_run_report(item): def forked_run_report(item):
# for now, we run setup/teardown in the subprocess # for now, we run setup/teardown in the subprocess
# XXX optionally allow sharing of setup/teardown # XXX optionally allow sharing of setup/teardown

View File

@ -1,5 +1,5 @@
import py, os, sys import py, os, sys
from py.__.test.plugin.pytest_capture import CaptureManager from py.__.test.plugin.pytest_capture import CaptureManager, ustream
class TestCaptureManager: class TestCaptureManager:
@ -54,6 +54,29 @@ class TestCaptureManager:
finally: finally:
capouter.reset() capouter.reset()
@py.test.mark.multi(method=['fd', 'sys'])
def test_capturing_unicode(testdir, method):
testdir.makepyfile("""
# taken from issue 227 from nosests
def test_unicode():
import sys
print sys.stdout
print u'b\\u00f6y'
""")
result = testdir.runpytest("--capture=%s" % method)
result.stdout.fnmatch_lines([
"*1 passed*"
])
def test_ustream_helper(testdir):
p = testdir.makepyfile("hello")
f = p.open('w')
#f.encoding = "utf8"
x = ustream(f)
x.write(u'b\\00f6y')
x.close()
def test_collect_capturing(testdir): def test_collect_capturing(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
print "collect %s failure" % 13 print "collect %s failure" % 13

View File

@ -271,3 +271,13 @@ def test_functional_boxed(testdir):
"*1 failed*" "*1 failed*"
]) ])
def test_callinfo():
ci = runner.CallInfo(lambda: 0, '123')
assert ci.when == "123"
assert ci.result == 0
assert "result" in repr(ci)
ci = runner.CallInfo(lambda: 0/0, '123')
assert ci.when == "123"
assert not hasattr(ci, 'result')
assert ci.excinfo
assert "exc" in repr(ci)