Skip to content

Commit 45c4a8f

Browse files
committed
Use atomicrewrites only on Windows
Fixes #6147
1 parent ab10165 commit 45c4a8f

File tree

4 files changed

+74
-32
lines changed

4 files changed

+74
-32
lines changed

changelog/6148.improvement.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``python-atomicwrites`` is only used on Windows, fixing a performance regression with assertion rewriting on Unix.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"packaging",
88
"attrs>=17.4.0", # should match oldattrs tox env.
99
"more-itertools>=4.0.0",
10-
"atomicwrites>=1.0",
10+
'atomicwrites>=1.0;sys_platform=="win32"',
1111
'pathlib2>=2.2.0;python_version<"3.6"',
1212
'colorama;sys_platform=="win32"',
1313
"pluggy>=0.12,<1.0",

src/_pytest/assertion/rewrite.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
from typing import Set
2121
from typing import Tuple
2222

23-
import atomicwrites
24-
2523
from _pytest._io.saferepr import saferepr
2624
from _pytest._version import version
2725
from _pytest.assertion import util
@@ -255,26 +253,59 @@ def get_data(self, pathname):
255253
return f.read()
256254

257255

258-
def _write_pyc(state, co, source_stat, pyc):
256+
def _write_pyc_fp(fp, source_stat, co):
259257
# Technically, we don't have to have the same pyc format as
260258
# (C)Python, since these "pycs" should never be seen by builtin
261259
# import. However, there's little reason deviate.
262-
try:
263-
with atomicwrites.atomic_write(fspath(pyc), mode="wb", overwrite=True) as fp:
264-
fp.write(importlib.util.MAGIC_NUMBER)
265-
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
266-
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
267-
size = source_stat.st_size & 0xFFFFFFFF
268-
# "<LL" stands for 2 unsigned longs, little-ending
269-
fp.write(struct.pack("<LL", mtime, size))
270-
fp.write(marshal.dumps(co))
271-
except EnvironmentError as e:
272-
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
273-
# we ignore any failure to write the cache file
274-
# there are many reasons, permission-denied, pycache dir being a
275-
# file etc.
276-
return False
277-
return True
260+
fp.write(importlib.util.MAGIC_NUMBER)
261+
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
262+
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
263+
size = source_stat.st_size & 0xFFFFFFFF
264+
# "<LL" stands for 2 unsigned longs, little-ending
265+
fp.write(struct.pack("<LL", mtime, size))
266+
fp.write(marshal.dumps(co))
267+
268+
269+
if sys.platform == "win32":
270+
from atomicwrites import atomic_write
271+
272+
def _write_pyc(state, co, source_stat, pyc):
273+
try:
274+
with atomic_write(fspath(pyc), mode="wb", overwrite=True) as fp:
275+
_write_pyc_fp(fp, source_stat, co)
276+
except EnvironmentError as e:
277+
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
278+
# we ignore any failure to write the cache file
279+
# there are many reasons, permission-denied, pycache dir being a
280+
# file etc.
281+
return False
282+
return True
283+
284+
285+
else:
286+
287+
def _write_pyc(state, co, source_stat, pyc):
288+
proc_pyc = "{}.{}".format(pyc, os.getpid())
289+
try:
290+
fp = open(proc_pyc, "wb")
291+
except EnvironmentError as e:
292+
state.trace(
293+
"error writing pyc file at {}: errno={}".format(proc_pyc, e.errno)
294+
)
295+
return False
296+
297+
try:
298+
_write_pyc_fp(fp, source_stat, co)
299+
os.rename(proc_pyc, fspath(pyc))
300+
except BaseException as e:
301+
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
302+
# we ignore any failure to write the cache file
303+
# there are many reasons, permission-denied, pycache dir being a
304+
# file etc.
305+
return False
306+
finally:
307+
fp.close()
308+
return True
278309

279310

280311
def _rewrite_test(fn, config):

testing/test_assertrewrite.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -959,24 +959,34 @@ def test_meta_path():
959959
def test_write_pyc(self, testdir, tmpdir, monkeypatch):
960960
from _pytest.assertion.rewrite import _write_pyc
961961
from _pytest.assertion import AssertionState
962-
import atomicwrites
963-
from contextlib import contextmanager
964962

965963
config = testdir.parseconfig([])
966964
state = AssertionState(config, "rewrite")
967-
source_path = tmpdir.ensure("source.py")
965+
source_path = str(tmpdir.ensure("source.py"))
968966
pycpath = tmpdir.join("pyc").strpath
969-
assert _write_pyc(state, [1], os.stat(source_path.strpath), pycpath)
967+
assert _write_pyc(state, [1], os.stat(source_path), pycpath)
970968

971-
@contextmanager
972-
def atomic_write_failed(fn, mode="r", overwrite=False):
973-
e = IOError()
974-
e.errno = 10
975-
raise e
976-
yield
969+
if sys.platform == "win32":
970+
from contextlib import contextmanager
977971

978-
monkeypatch.setattr(atomicwrites, "atomic_write", atomic_write_failed)
979-
assert not _write_pyc(state, [1], source_path.stat(), pycpath)
972+
@contextmanager
973+
def atomic_write_failed(fn, mode="r", overwrite=False):
974+
e = IOError()
975+
e.errno = 10
976+
raise e
977+
yield
978+
979+
monkeypatch.setattr(
980+
_pytest.assertion.rewrite, "atomic_write", atomic_write_failed
981+
)
982+
else:
983+
984+
def raise_ioerror(*args):
985+
raise IOError()
986+
987+
monkeypatch.setattr("os.rename", raise_ioerror)
988+
989+
assert not _write_pyc(state, [1], os.stat(source_path), pycpath)
980990

981991
def test_resources_provider_for_loader(self, testdir):
982992
"""

0 commit comments

Comments
 (0)