Skip to content

fix: safely handle restrictive directory cleanup and close scandir iterator (fixes #15169) - #15171

Open
saifmodan2006 wants to merge 2 commits into
pytest-dev:mainfrom
saifmodan2006:fix/rm-rf-mode-0-dir
Open

saifmodan2006 wants to merge 2 commits into
pytest-dev:mainfrom
saifmodan2006:fix/rm-rf-mode-0-dir

Conversation

@saifmodan2006

Copy link
Copy Markdown

Summary

Fixes #15169.

When test suites create directories with restrictive modes such as 0o000 (for example, tests exercising non-browseable or permission-denied directories), pytest's subsequent cleanup of numbered directories (cleanup_numbered_dir -> rm_rf(garbage)) could fail with OSError: [Errno 39] Directory not empty or leak unclosed scandir iterators resulting in PytestUnraisableExceptionWarning. When running test suites with -W error, this warning would turn into an unhandled exception during session finish, causing clean test runs to fail.

Changes Made

  1. src/_pytest/pathlib.py:

    • on_rm_rf_error:
      • Handle IsADirectoryError and (func in (os.unlink, os.remove) and p.is_dir()): when directory traversal was unable to identify a directory (e.g. restrictive permissions on POSIX), chmod the entry and recursively remove it with rm_rf(p).
      • Handle ENOTEMPTY / EEXIST when func is os.rmdir: if removing a parent directory fails because child entries were skipped due to restrictive permissions, grant _chmod_rwx across the directory tree and retry removal.
    • rm_rf:
      • Call _chmod_rwx(str(path)) at the start of rm_rf so the target directory has read/write/exec permissions before shutil.rmtree traversal begins.
    • find_prefixed:
      • Wrap os.scandir(root) with a with os.scandir(root) as it: context manager to ensure the ScandirIterator is properly finalized and closed, preventing ResourceWarning: unclosed scandir iterator on Python 3.14+.
    • Imported ENOTEMPTY and EEXIST from errno.
  2. testing/test_tmpdir.py:

    • Added test_on_rm_rf_error_is_a_directory_error: verifies IsADirectoryError is handled cleanly by on_rm_rf_error without warnings.
    • Added test_on_rm_rf_error_directory_not_empty: verifies ENOTEMPTY on os.rmdir triggers recursive chmod and cleanup without warnings.
    • Added test_find_prefixed_closes_scandir_iterator: verifies find_prefixed closes the scandir iterator without emitting ResourceWarning.

Testing

  • Ran full testing/test_tmpdir.py: 53 passed, 9 skipped.
  • Ran full testing/test_pathlib.py: 113 passed.
  • All new regression tests pass cleanly.

@0xamlab

0xamlab commented Oct 13, 2026

Copy link
Copy Markdown

Verified this on macOS with Python 3.14.6. The three new regression tests in testing/test_tmpdir.py pass in 1.06s (3 passed). On main, the same scenarios fail: on_rm_rf_error(os.unlink, subdir, IsADirectoryError()) returns False, leaves the directory, and emits a PytestWarning; the ENOTEMPTY path behaves the same. The fix at src/_pytest/pathlib.py:128 routes directory-unlink errors into rm_rf, and src/_pytest/pathlib.py:137 chmods and re-tries when os.rmdir hits ENOTEMPTY/EEXIST. find_prefixed at src/_pytest/pathlib.py:243 now closes the scandir iterator with a context manager.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mode 0o000 directory causes following test suites with -W error to fail

2 participants