1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/a433de9ca4f0/ Changeset: a433de9ca4f0 User: gutworth Date: 2014-09-01 22:51:27 Summary: improvements to rewrite cache invalidation
- stat the source path before it is read. - Validate the source size in addition to mtime. Affected #: 3 files diff -r aed87f841a762133588d09f17f2ceb050d726569 -r a433de9ca4f04e4f4f433ef9e80a5d93790c2f4f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ NEXT ----------- +- Improve assertion rewriting cache invalidation precision. + - fixed issue561: adapt autouse fixture example for python3. - fixed issue453: assertion rewriting issue with __repr__ containing diff -r aed87f841a762133588d09f17f2ceb050d726569 -r a433de9ca4f04e4f4f433ef9e80a5d93790c2f4f _pytest/assertion/rewrite.py --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -134,12 +134,12 @@ co = _read_pyc(fn_pypath, pyc, state.trace) if co is None: state.trace("rewriting %r" % (fn,)) - co = _rewrite_test(state, fn_pypath) + source_stat, co = _rewrite_test(state, fn_pypath) if co is None: # Probably a SyntaxError in the test. return None if write: - _make_rewritten_pyc(state, fn_pypath, pyc, co) + _make_rewritten_pyc(state, source_stat, pyc, co) else: state.trace("found cached rewritten pyc for %r" % (fn,)) self.modules[name] = co, pyc @@ -192,13 +192,12 @@ pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider) -def _write_pyc(state, co, source_path, pyc): +def _write_pyc(state, co, source_stat, pyc): # Technically, we don't have to have the same pyc format as # (C)Python, since these "pycs" should never be seen by builtin # import. However, there's little reason deviate, and I hope # sometime to be able to use imp.load_compiled to load them. (See # the comment in load_module above.) - mtime = int(source_path.mtime()) try: fp = open(pyc, "wb") except IOError: @@ -210,7 +209,9 @@ return False try: fp.write(imp.get_magic()) - fp.write(struct.pack("<l", mtime)) + mtime = int(source_stat.mtime) + size = source_stat.size & 0xFFFFFFFF + fp.write(struct.pack("<ll", mtime, size)) marshal.dump(co, fp) finally: fp.close() @@ -225,9 +226,10 @@ def _rewrite_test(state, fn): """Try to read and rewrite *fn* and return the code object.""" try: + stat = fn.stat() source = fn.read("rb") except EnvironmentError: - return None + return None, None if ASCII_IS_DEFAULT_ENCODING: # ASCII is the default encoding in Python 2. Without a coding # declaration, Python 2 will complain about any bytes in the file @@ -246,14 +248,15 @@ cookie_re.match(source[0:end1]) is None and cookie_re.match(source[end1 + 1:end2]) is None): if hasattr(state, "_indecode"): - return None # encodings imported us again, we don't rewrite + # encodings imported us again, so don't rewrite. + return None, None state._indecode = True try: try: source.decode("ascii") except UnicodeDecodeError: # Let it fail in real import. - return None + return None, None finally: del state._indecode # On Python versions which are not 2.7 and less than or equal to 3.1, the @@ -265,7 +268,7 @@ except SyntaxError: # Let this pop up again in the real import. state.trace("failed to parse: %r" % (fn,)) - return None + return None, None rewrite_asserts(tree) try: co = compile(tree, fn.strpath, "exec") @@ -273,20 +276,20 @@ # It's possible that this error is from some bug in the # assertion rewriting, but I don't know of a fast way to tell. state.trace("failed to compile: %r" % (fn,)) - return None - return co + return None, None + return stat, co -def _make_rewritten_pyc(state, fn, pyc, co): +def _make_rewritten_pyc(state, source_stat, pyc, co): """Try to dump rewritten code to *pyc*.""" if sys.platform.startswith("win"): # Windows grants exclusive access to open files and doesn't have atomic # rename, so just write into the final file. - _write_pyc(state, co, fn, pyc) + _write_pyc(state, co, source_stat, pyc) else: # When not on windows, assume rename is atomic. Dump the code object # into a file specific to this process and atomically replace it. proc_pyc = pyc + "." + str(os.getpid()) - if _write_pyc(state, co, fn, proc_pyc): + if _write_pyc(state, co, source_stat, proc_pyc): os.rename(proc_pyc, pyc) def _read_pyc(source, pyc, trace=lambda x: None): @@ -301,13 +304,14 @@ with fp: try: mtime = int(source.mtime()) - data = fp.read(8) + size = source.size() + data = fp.read(12) except EnvironmentError as e: trace('_read_pyc(%s): EnvironmentError %s' % (source, e)) return None # Check for invalid or out of date pyc file. - if (len(data) != 8 or data[:4] != imp.get_magic() or - struct.unpack("<l", data[4:])[0] != mtime): + if (len(data) != 12 or data[:4] != imp.get_magic() or + struct.unpack("<ll", data[4:]) != (mtime, size)): trace('_read_pyc(%s): invalid or out of date pyc' % source) return None try: @@ -318,6 +322,7 @@ if not isinstance(co, types.CodeType): trace('_read_pyc(%s): not a code object' % source) return None + open("/tmp/goop", "wb").write(b"hi") return co diff -r aed87f841a762133588d09f17f2ceb050d726569 -r a433de9ca4f04e4f4f433ef9e80a5d93790c2f4f testing/test_assertrewrite.py --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -511,13 +511,13 @@ state = AssertionState(config, "rewrite") source_path = tmpdir.ensure("source.py") pycpath = tmpdir.join("pyc").strpath - assert _write_pyc(state, [1], source_path, pycpath) + assert _write_pyc(state, [1], source_path.stat(), pycpath) def open(*args): e = IOError() e.errno = 10 raise e monkeypatch.setattr(b, "open", open) - assert not _write_pyc(state, [1], source_path, pycpath) + assert not _write_pyc(state, [1], source_path.stat(), pycpath) def test_resources_provider_for_loader(self, testdir): """ Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit