Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ Yannick Péroux
Yao Xiao
Yoav Caspi
Yuliang Shao
Yury V. Zaytsev
Yusuke Kadowaki
Yutian Li
Yuval Shimon
Expand Down
1 change: 1 addition & 0 deletions changelog/15174.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a race as concurrent pytest sessions of the same user would fail at completion with ``FileNotFoundError`` while removing a dead ``pytest-current`` symlink.
2 changes: 1 addition & 1 deletion src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def cleanup_dead_symlinks(root: Path) -> None:
for left_dir in root.iterdir():
if left_dir.is_symlink():
if not left_dir.resolve().exists():
left_dir.unlink()
left_dir.unlink(missing_ok=True)


def cleanup_numbered_dir(
Expand Down
16 changes: 16 additions & 0 deletions testing/test_tmpdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,22 @@ def test_cleanup_ignores_symlink(self, tmp_path):
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
self._do_cleanup(tmp_path)

def test_cleanup_dead_symlink_removed_concurrently(
self, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
the_symlink = tmp_path / (self.PREFIX + "current")
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
resolve = Path.resolve

def resolve_after_concurrent_unlink(path: Path, strict: bool = False) -> Path:
assert path == the_symlink
path.unlink() # another session removes the dead symlink first
return resolve(path, strict)

monkeypatch.setattr(Path, "resolve", resolve_after_concurrent_unlink)
pathlib.cleanup_dead_symlinks(tmp_path)
assert not the_symlink.is_symlink()

def test_removal_accepts_lock(self, tmp_path):
dir = make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
create_cleanup_lock(dir)
Expand Down
Loading