* 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:
parent
91597f4100
commit
8fcdac9dd6
|
@ -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
|
||||
=====================================
|
||||
|
||||
|
|
10
py/_com.py
10
py/_com.py
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue