Create the pytest cache directory atomically with supporting files. Creates a temporary directory with README.md, .gitignore, and CACHEDIR.TAG, then atomically renames it to the target location. If another process wins the race, the temporary directory is cleaned up.
(target: Path)
| 56 | |
| 57 | |
| 58 | def _make_cachedir(target: Path) -> None: |
| 59 | """Create the pytest cache directory atomically with supporting files. |
| 60 | |
| 61 | Creates a temporary directory with README.md, .gitignore, and CACHEDIR.TAG, |
| 62 | then atomically renames it to the target location. If another process wins |
| 63 | the race, the temporary directory is cleaned up. |
| 64 | """ |
| 65 | target.parent.mkdir(parents=True, exist_ok=True) |
| 66 | path = Path(tempfile.mkdtemp(prefix="pytest-cache-files-", dir=target.parent)) |
| 67 | try: |
| 68 | # Reset permissions to the default, see #12308. |
| 69 | # Note: there's no way to get the current umask atomically, eek. |
| 70 | umask = os.umask(0o022) |
| 71 | os.umask(umask) |
| 72 | path.chmod(0o777 - umask) |
| 73 | |
| 74 | for name, content in CACHEDIR_FILES.items(): |
| 75 | path.joinpath(name).write_bytes(content) |
| 76 | |
| 77 | path.rename(target) |
| 78 | except OSError as e: |
| 79 | # If 2 concurrent pytests both race to the rename, the loser |
| 80 | # gets "Directory not empty" from the rename. In this case, |
| 81 | # everything is handled so just continue after cleanup. |
| 82 | # On Windows, the error is a FileExistsError which translates to EEXIST. |
| 83 | if e.errno not in (errno.ENOTEMPTY, errno.EEXIST): |
| 84 | raise |
| 85 | finally: |
| 86 | shutil.rmtree(path, ignore_errors=True) |
| 87 | |
| 88 | |
| 89 | @final |