diff --git a/CHANGELOG b/CHANGELOG index 08481d8..26e6ee8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,9 @@ - fix issue34 - distributed testing with -p plugin now works correctly +- fix race condition in looponfail mode where a concurrent file removal + could cause a crash + 1.5 ------------------------- diff --git a/testing/test_looponfail.py b/testing/test_looponfail.py index 809ecb9..c65a564 100644 --- a/testing/test_looponfail.py +++ b/testing/test_looponfail.py @@ -14,11 +14,11 @@ class TestStatRecorder: changed = sd.check() assert changed - tmp.ensure("new.py") + p = tmp.ensure("new.py") changed = sd.check() assert changed - tmp.join("new.py").remove() + p.remove() changed = sd.check() assert changed @@ -36,6 +36,24 @@ class TestStatRecorder: changed = sd.check() assert changed + def test_filechange_deletion_race(self, tmpdir, monkeypatch): + tmp = tmpdir + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + p = tmp.ensure("new.py") + changed = sd.check() + assert changed + + p.remove() + # make check()'s visit() call return our just removed + # path as if we were in a race condition + monkeypatch.setattr(tmp, 'visit', lambda *args: [p]) + + changed = sd.check() + assert changed + def test_pycremoval(self, tmpdir): tmp = tmpdir hello = tmp.ensure("hello.py") diff --git a/xdist/looponfail.py b/xdist/looponfail.py index 9136bcd..4f4907f 100644 --- a/xdist/looponfail.py +++ b/xdist/looponfail.py @@ -201,14 +201,11 @@ class StatRecorder: newstat = {} for rootdir in self.rootdirlist: for path in rootdir.visit(self.fil, self.rec): - oldstat = statcache.get(path, None) - if oldstat is not None: - del statcache[path] + oldstat = statcache.pop(path, None) try: newstat[path] = curstat = path.stat() except py.error.ENOENT: if oldstat: - del statcache[path] changed = True else: if oldstat: