merge PR192, streamline a bit.

This commit is contained in:
holger krekel 2014-08-07 10:42:23 +02:00
commit d16fdb378c
3 changed files with 38 additions and 8 deletions

View File

@ -3,6 +3,10 @@ NEXT
- No longer show line numbers in the --verbose output, the output is now - No longer show line numbers in the --verbose output, the output is now
purely the nodeid. The line number is still shown in failure reports. purely the nodeid. The line number is still shown in failure reports.
Thanks Floris Bruynooghe.
- fix issue437 where assertion rewriting could cause pytest-xdist slaves
to collect different tests. Thanks Bruno Oliveira.
- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled. - fix issue547 capsys/capfd also work when output capturing ("-s") is disabled.

View File

@ -131,7 +131,7 @@ class AssertionRewritingHook(object):
pyc = os.path.join(cache_dir, cache_name) pyc = os.path.join(cache_dir, cache_name)
# Notice that even if we're in a read-only directory, I'm going # Notice that even if we're in a read-only directory, I'm going
# to check for a cached pyc. This may not be optimal... # to check for a cached pyc. This may not be optimal...
co = _read_pyc(fn_pypath, pyc) co = _read_pyc(fn_pypath, pyc, state.trace)
if co is None: if co is None:
state.trace("rewriting %r" % (fn,)) state.trace("rewriting %r" % (fn,))
co = _rewrite_test(state, fn_pypath) co = _rewrite_test(state, fn_pypath)
@ -289,7 +289,7 @@ def _make_rewritten_pyc(state, fn, pyc, co):
if _write_pyc(state, co, fn, proc_pyc): if _write_pyc(state, co, fn, proc_pyc):
os.rename(proc_pyc, pyc) os.rename(proc_pyc, pyc)
def _read_pyc(source, pyc): def _read_pyc(source, pyc, trace=lambda x: None):
"""Possibly read a pytest pyc containing rewritten code. """Possibly read a pytest pyc containing rewritten code.
Return rewritten code if successful or None if not. Return rewritten code if successful or None if not.
@ -298,23 +298,27 @@ def _read_pyc(source, pyc):
fp = open(pyc, "rb") fp = open(pyc, "rb")
except IOError: except IOError:
return None return None
try: with fp:
try: try:
mtime = int(source.mtime()) mtime = int(source.mtime())
data = fp.read(8) data = fp.read(8)
except EnvironmentError: except EnvironmentError as e:
trace('_read_pyc(%s): EnvironmentError %s' % (source, e))
return None return None
# Check for invalid or out of date pyc file. # Check for invalid or out of date pyc file.
if (len(data) != 8 or data[:4] != imp.get_magic() or if (len(data) != 8 or data[:4] != imp.get_magic() or
struct.unpack("<l", data[4:])[0] != mtime): struct.unpack("<l", data[4:])[0] != mtime):
trace('_read_pyc(%s): invalid or out of date pyc' % source)
return None return None
try:
co = marshal.load(fp) co = marshal.load(fp)
except Exception as e:
trace('_read_pyc(%s): marshal.load error %s' % (source, e))
return None
if not isinstance(co, types.CodeType): if not isinstance(co, types.CodeType):
# That's interesting.... trace('_read_pyc(%s): not a code object' % source)
return None return None
return co return co
finally:
fp.close()
def rewrite_asserts(mod): def rewrite_asserts(mod):

View File

@ -539,3 +539,25 @@ class TestAssertionRewriteHookDetails(object):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'* 1 passed*', '* 1 passed*',
]) ])
def test_read_pyc(self, tmpdir):
"""
Ensure that the `_read_pyc` can properly deal with corrupted pyc files.
In those circumstances it should just give up instead of generating
an exception that is propagated to the caller.
"""
import py_compile
from _pytest.assertion.rewrite import _read_pyc
source = tmpdir.join('source.py')
pyc = source + 'c'
source.write('def test(): pass')
py_compile.compile(str(source), str(pyc))
contents = pyc.read(mode='rb')
strip_bytes = 20 # header is around 8 bytes, strip a little more
assert len(contents) > strip_bytes
pyc.write(contents[:strip_bytes], mode='wb')
assert _read_pyc(source, str(pyc)) is None # no error