* 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
=====================================

View File

@ -17,6 +17,16 @@ class MultiCall:
self.kwargs = kwargs
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):
while self.methods:
currentmethod = self.methods.pop()

View File

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

View File

@ -139,6 +139,7 @@ class TerminalWriter(object):
Black=40, Red=41, Green=42, Yellow=43,
Blue=44, Purple=45, Cyan=46, White=47,
bold=1, light=2, blink=5, invert=7)
_encoding = "utf-8"
def __init__(self, file=None, stringio=False):
if file is None:
@ -194,58 +195,27 @@ class TerminalWriter(object):
def write(self, s, **kw):
if s:
s = str(s)
s = self._getbytestring(s)
if self.hasmarkup and kw:
s = self.markup(s, **kw)
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):
self.write(s, **kw)
self.write('\n')
class Win32ConsoleWriter(object):
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)
class Win32ConsoleWriter(TerminalWriter):
def write(self, s, **kw):
if s:
s = str(s)
s = self._getbytestring(s)
if self.hasmarkup:
handle = GetStdHandle(STD_OUTPUT_HANDLE)
@ -269,8 +239,8 @@ class Win32ConsoleWriter(object):
if self.hasmarkup:
SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
def line(self, s='', **kw):
self.write(s + '\n', **kw)
def line(self, s="", **kw):
self.write(s+"\n", **kw)
if sys.platform == 'win32':
TerminalWriter = Win32ConsoleWriter

View File

@ -37,6 +37,16 @@ class BaseTests:
assert len(l) == 1
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):
tw = self.getwriter()
tw.sep("-", fullwidth=60)
@ -85,6 +95,16 @@ class BaseTests:
l = self.getlines()
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):
def getwriter(self):
self.tw = py.io.TerminalWriter(stringio=True)

View File

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

View File

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

View File

@ -107,11 +107,24 @@ class CaptureManager:
def __init__(self):
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):
if method == "fd":
return py.io.StdCaptureFD()
return py.io.StdCaptureFD(
out=self._maketempfile(), err=self._maketempfile()
)
elif method == "sys":
return py.io.StdCapture()
return py.io.StdCapture(
out=self._makestringio(), err=self._makestringio()
)
else:
raise ValueError("unknown capturing method: %r" % method)
@ -252,3 +265,13 @@ class CaptureFuncarg:
def close(self):
self.capture.reset()
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:
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):
# for now, we run setup/teardown in the subprocess
# XXX optionally allow sharing of setup/teardown

View File

@ -1,5 +1,5 @@
import py, os, sys
from py.__.test.plugin.pytest_capture import CaptureManager
from py.__.test.plugin.pytest_capture import CaptureManager, ustream
class TestCaptureManager:
@ -54,6 +54,29 @@ class TestCaptureManager:
finally:
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):
p = testdir.makepyfile("""
print "collect %s failure" % 13

View File

@ -271,3 +271,13 @@ def test_functional_boxed(testdir):
"*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)