simplify reset/stop_capturing and fix capturing wrt to capturing simple os.write() calls

This commit is contained in:
holger krekel 2014-03-28 07:11:25 +01:00
parent e18c3ed494
commit a8f4f49a82
2 changed files with 103 additions and 98 deletions

View File

@ -6,7 +6,7 @@ from __future__ import with_statement
import sys
import os
import tempfile
from tempfile import TemporaryFile
import contextlib
import py
@ -101,12 +101,6 @@ def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
def maketmpfile():
f = py.std.tempfile.TemporaryFile()
newf = dupfile(f, encoding="UTF-8")
f.close()
return newf
class CaptureManager:
def __init__(self, defaultmethod=None):
self._method2capture = {}
@ -137,7 +131,7 @@ class CaptureManager:
def reset_capturings(self):
for cap in self._method2capture.values():
cap.pop_outerr_to_orig()
cap.reset()
cap.stop_capturing()
self._method2capture.clear()
def resumecapture_item(self, item):
@ -274,15 +268,16 @@ def pytest_funcarg__capfd(request):
class CaptureFixture:
def __init__(self, captureclass):
self._capture = StdCaptureBase(out=True, err=True, in_=False,
Capture=captureclass)
self.captureclass = captureclass
def _start(self):
self._capture = StdCaptureBase(out=True, err=True, in_=False,
Capture=self.captureclass)
self._capture.start_capturing()
def _finalize(self):
if hasattr(self, '_capture'):
outerr = self._outerr = self._capture.reset()
outerr = self._outerr = self._capture.stop_capturing()
del self._capture
return outerr
@ -355,21 +350,6 @@ class StdCaptureBase(object):
if err:
self.err = Capture(2)
def reset(self):
""" reset sys.stdout/stderr and return captured output as strings. """
if hasattr(self, '_reset'):
raise ValueError("was already reset")
self._reset = True
outfile, errfile = self.stop_capturing()
out, err = "", ""
if outfile and not outfile.closed:
out = outfile.read()
outfile.close()
if errfile and errfile != outfile and not errfile.closed:
err = errfile.read()
errfile.close()
return out, err
def start_capturing(self):
if self.in_:
self.in_.start()
@ -378,17 +358,6 @@ class StdCaptureBase(object):
if self.err:
self.err.start()
def stop_capturing(self):
""" return (outfile, errfile) and stop capturing. """
outfile = errfile = None
if self.out:
outfile = self.out.done()
if self.err:
errfile = self.err.done()
if self.in_:
self.in_.done()
return outfile, errfile
def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr()
@ -397,25 +366,27 @@ class StdCaptureBase(object):
if err:
self.err.writeorg(err)
def stop_capturing(self):
""" stop capturing and reset capturing streams """
if hasattr(self, '_reset'):
raise ValueError("was already stopped")
self._reset = True
if self.out:
self.out.done()
if self.err:
self.err.done()
if self.in_:
self.in_.done()
def readouterr(self):
""" return snapshot unicode value of stdout/stderr capturings. """
return self._readsnapshot('out'), self._readsnapshot('err')
def _readsnapshot(self, name):
try:
f = getattr(self, name).tmpfile
except AttributeError:
return ''
if f.tell() == 0:
return ''
f.seek(0)
res = f.read()
enc = getattr(f, "encoding", None)
if enc and isinstance(res, bytes):
res = py.builtin._totext(res, enc, "replace")
f.truncate(0)
f.seek(0)
return res
cap = getattr(self, name, None)
if cap is None:
return ""
return cap.snap()
class FDCapture:
@ -433,11 +404,16 @@ class FDCapture:
if targetfd == 0:
tmpfile = open(os.devnull, "r")
else:
tmpfile = maketmpfile()
f = TemporaryFile()
with f:
tmpfile = dupfile(f, encoding="UTF-8")
self.tmpfile = tmpfile
if targetfd in patchsysdict:
self._oldsys = getattr(sys, patchsysdict[targetfd])
def __repr__(self):
return "<FDCapture %s oldfd=%s>" % (self.targetfd, self._savefd)
def start(self):
""" Start capturing on targetfd using memorized tmpfile. """
try:
@ -450,16 +426,26 @@ class FDCapture:
subst = self.tmpfile if targetfd != 0 else DontReadFromInput()
setattr(sys, patchsysdict[targetfd], subst)
def snap(self):
f = self.tmpfile
f.seek(0)
res = f.read()
if res:
enc = getattr(f, "encoding", None)
if enc and isinstance(res, bytes):
res = py.builtin._totext(res, enc, "replace")
f.truncate(0)
f.seek(0)
return res
def done(self):
""" stop capturing, restore streams, return original capture file,
seeked to position zero. """
os.dup2(self._savefd, self.targetfd)
os.close(self._savefd)
if self.targetfd != 0:
self.tmpfile.seek(0)
if hasattr(self, '_oldsys'):
setattr(sys, patchsysdict[self.targetfd], self._oldsys)
return self.tmpfile
self.tmpfile.close()
def writeorg(self, data):
""" write a string to the original file descriptor
@ -482,18 +468,22 @@ class SysCapture:
def start(self):
setattr(sys, self.name, self.tmpfile)
def snap(self):
f = self.tmpfile
res = f.getvalue()
f.truncate(0)
f.seek(0)
return res
def done(self):
setattr(sys, self.name, self._old)
if self.name != "stdin":
self.tmpfile.seek(0)
return self.tmpfile
self.tmpfile.close()
def writeorg(self, data):
self._old.write(data)
self._old.flush()
class DontReadFromInput:
"""Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured

View File

@ -103,7 +103,7 @@ class TestCaptureManager:
assert not out and not err
capman.reset_capturings()
finally:
capouter.reset()
capouter.stop_capturing()
@needsosdup
def test_juggle_capturings(self, testdir):
@ -127,7 +127,7 @@ class TestCaptureManager:
finally:
capman.reset_capturings()
finally:
capouter.reset()
capouter.stop_capturing()
@pytest.mark.parametrize("method", ['fd', 'sys'])
@ -696,17 +696,15 @@ class TestFDCapture:
cap = capture.FDCapture(fd)
data = tobytes("hello")
os.write(fd, data)
f = cap.done()
s = f.read()
f.close()
s = cap.snap()
cap.done()
assert not s
cap = capture.FDCapture(fd)
cap.start()
os.write(fd, data)
f = cap.done()
s = f.read()
s = cap.snap()
cap.done()
assert s == "hello"
f.close()
def test_simple_many(self, tmpfile):
for i in range(10):
@ -720,16 +718,15 @@ class TestFDCapture:
def test_simple_fail_second_start(self, tmpfile):
fd = tmpfile.fileno()
cap = capture.FDCapture(fd)
f = cap.done()
cap.done()
pytest.raises(ValueError, cap.start)
f.close()
def test_stderr(self):
cap = capture.FDCapture(2)
cap.start()
print_("hello", file=sys.stderr)
f = cap.done()
s = f.read()
s = cap.snap()
cap.done()
assert s == "hello\n"
def test_stdin(self, tmpfile):
@ -752,8 +749,8 @@ class TestFDCapture:
cap.writeorg(data2)
finally:
tmpfile.close()
f = cap.done()
scap = f.read()
scap = cap.snap()
cap.done()
assert scap == totext(data1)
stmp = open(tmpfile.name, 'rb').read()
assert stmp == data2
@ -769,17 +766,17 @@ class TestStdCapture:
cap = self.getcapture()
sys.stdout.write("hello")
sys.stderr.write("world")
outfile, errfile = cap.stop_capturing()
s = outfile.read()
assert s == "hello"
s = errfile.read()
assert s == "world"
out, err = cap.readouterr()
cap.stop_capturing()
assert out == "hello"
assert err == "world"
def test_capturing_reset_simple(self):
cap = self.getcapture()
print("hello world")
sys.stderr.write("hello error\n")
out, err = cap.reset()
out, err = cap.readouterr()
cap.stop_capturing()
assert out == "hello world\n"
assert err == "hello error\n"
@ -792,8 +789,9 @@ class TestStdCapture:
assert out == "hello world\n"
assert err == "hello error\n"
sys.stderr.write("error2")
out, err = cap.readouterr()
finally:
out, err = cap.reset()
cap.stop_capturing()
assert err == "error2"
def test_capturing_readouterr_unicode(self):
@ -802,7 +800,7 @@ class TestStdCapture:
print ("hx\xc4\x85\xc4\x87")
out, err = cap.readouterr()
finally:
cap.reset()
cap.stop_capturing()
assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
@pytest.mark.skipif('sys.version_info >= (3,)',
@ -813,13 +811,14 @@ class TestStdCapture:
print('\xa6')
out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
cap.reset()
cap.stop_capturing()
def test_reset_twice_error(self):
cap = self.getcapture()
print ("hello")
out, err = cap.reset()
pytest.raises(ValueError, cap.reset)
out, err = cap.readouterr()
cap.stop_capturing()
pytest.raises(ValueError, cap.stop_capturing)
assert out == "hello\n"
assert not err
@ -833,7 +832,8 @@ class TestStdCapture:
sys.stderr = capture.TextIO()
print ("not seen")
sys.stderr.write("not seen\n")
out, err = cap.reset()
out, err = cap.readouterr()
cap.stop_capturing()
assert out == "hello"
assert err == "world"
assert sys.stdout == oldout
@ -844,8 +844,10 @@ class TestStdCapture:
print ("cap1")
cap2 = self.getcapture()
print ("cap2")
out2, err2 = cap2.reset()
out1, err1 = cap1.reset()
out2, err2 = cap2.readouterr()
out1, err1 = cap1.readouterr()
cap2.stop_capturing()
cap1.stop_capturing()
assert out1 == "cap1\n"
assert out2 == "cap2\n"
@ -853,7 +855,8 @@ class TestStdCapture:
cap = self.getcapture(out=True, err=False)
sys.stdout.write("hello")
sys.stderr.write("world")
out, err = cap.reset()
out, err = cap.readouterr()
cap.stop_capturing()
assert out == "hello"
assert not err
@ -861,7 +864,8 @@ class TestStdCapture:
cap = self.getcapture(out=False, err=True)
sys.stdout.write("hello")
sys.stderr.write("world")
out, err = cap.reset()
out, err = cap.readouterr()
cap.stop_capturing()
assert err == "world"
assert not out
@ -869,7 +873,7 @@ class TestStdCapture:
old = sys.stdin
cap = self.getcapture(in_=True)
newstdin = sys.stdin
out, err = cap.reset()
cap.stop_capturing()
assert newstdin != sys.stdin
assert sys.stdin is old
@ -879,7 +883,7 @@ class TestStdCapture:
print ("XXX mechanisms")
cap = self.getcapture()
pytest.raises(IOError, "sys.stdin.read()")
out, err = cap.reset()
cap.stop_capturing()
class TestStdCaptureFD(TestStdCapture):
@ -890,6 +894,20 @@ class TestStdCaptureFD(TestStdCapture):
cap.start_capturing()
return cap
def test_simple_only_fd(self, testdir):
testdir.makepyfile("""
import os
def test_x():
os.write(1, "hello\\n".encode("ascii"))
assert 0
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
*test_x*
*assert 0*
*Captured stdout*
""")
def test_intermingling(self):
cap = self.getcapture()
oswritebytes(1, "1")
@ -900,7 +918,8 @@ class TestStdCaptureFD(TestStdCapture):
sys.stderr.write("b")
sys.stderr.flush()
oswritebytes(2, "c")
out, err = cap.reset()
out, err = cap.readouterr()
cap.stop_capturing()
assert out == "123"
assert err == "abc"
@ -908,7 +927,7 @@ class TestStdCaptureFD(TestStdCapture):
with lsof_check():
for i in range(10):
cap = StdCaptureFD()
cap.reset()
cap.stop_capturing()
@ -943,10 +962,6 @@ class TestStdCaptureFDinvalidFD:
def test_capture_not_started_but_reset():
capsys = StdCapture()
capsys.stop_capturing()
capsys.stop_capturing()
capsys.reset()
@needsosdup
@ -960,7 +975,7 @@ def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
capfile = cap.err.tmpfile
cap.readouterr()
finally:
cap.reset()
cap.stop_capturing()
capfile2 = cap.err.tmpfile
assert capfile2 == capfile