Files
TheRock/build_tools/tests/os_util_test.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

123 lines
4.1 KiB
Python

#!/usr/bin/env python
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
import platform
import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
from pathlib import Path
from _therock_utils.os_util import rmtree_with_retry
IS_WINDOWS = platform.system() == "Windows"
# Script that holds a file open WITHOUT FILE_SHARE_DELETE, preventing deletion.
# This simulates how antivirus scanners, search indexers, or loaded DLLs hold
# files on Windows.
_HOLD_FILE_SCRIPT = textwrap.dedent(
"""\
import ctypes
import sys
import time
from ctypes import wintypes
CreateFileW = ctypes.windll.kernel32.CreateFileW
CreateFileW.restype = wintypes.HANDLE
CloseHandle = ctypes.windll.kernel32.CloseHandle
GENERIC_READ = 0x80000000
FILE_SHARE_READ = 0x00000001
# No FILE_SHARE_DELETE - prevents deletion while handle is open
OPEN_EXISTING = 3
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
path = sys.argv[1]
hold_seconds = float(sys.argv[2])
handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, None, OPEN_EXISTING, 0, None)
if handle == INVALID_HANDLE_VALUE:
sys.exit(f"CreateFileW failed for {path}")
# Signal readiness
print("READY", flush=True)
time.sleep(hold_seconds)
CloseHandle(handle)
"""
)
def _start_holder(file_path: Path, hold_seconds: float) -> subprocess.Popen:
"""Start a subprocess that holds *file_path* open without FILE_SHARE_DELETE."""
holder = subprocess.Popen(
[sys.executable, "-c", _HOLD_FILE_SCRIPT, str(file_path), str(hold_seconds)],
stdout=subprocess.PIPE,
text=True,
)
line = holder.stdout.readline().strip()
assert line == "READY", f"holder subprocess failed to start: {line!r}"
return holder
class RmtreeWithRetryTest(unittest.TestCase):
def test_removes_directory(self):
"""Basic case: removes a directory with no locks."""
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "subdir"
target.mkdir()
(target / "file.txt").write_text("hello")
rmtree_with_retry(target)
self.assertFalse(target.exists())
def test_raises_on_nonexistent(self):
"""Raises FileNotFoundError for a path that doesn't exist."""
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "does_not_exist"
with self.assertRaises(FileNotFoundError):
rmtree_with_retry(target)
@unittest.skipUnless(IS_WINDOWS, "Windows file locking behavior")
def test_retries_on_locked_file(self):
"""rmtree_with_retry succeeds after a transient file lock is released."""
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "subdir"
target.mkdir()
locked_file = target / "locked.txt"
locked_file.write_text("data")
holder = _start_holder(locked_file, hold_seconds=1)
try:
# Bare shutil.rmtree should fail while the file is locked.
with self.assertRaises(PermissionError):
shutil.rmtree(target)
# rmtree_with_retry should succeed after the subprocess releases.
rmtree_with_retry(target, max_attempts=10, retry_delay_seconds=0.2)
self.assertFalse(target.exists())
finally:
holder.wait()
@unittest.skipUnless(IS_WINDOWS, "Windows file locking behavior")
def test_gives_up_after_max_attempts(self):
"""Raises PermissionError if the lock outlasts the retry window."""
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "subdir"
target.mkdir()
locked_file = target / "locked.txt"
locked_file.write_text("data")
holder = _start_holder(locked_file, hold_seconds=10)
try:
with self.assertRaises(PermissionError):
rmtree_with_retry(target, max_attempts=2, retry_delay_seconds=0.1)
finally:
holder.terminate()
holder.wait()
if __name__ == "__main__":
unittest.main()