Skip to content

File Testing

provide.testkit.file

File testing fixtures for the provide-io ecosystem.

Standard fixtures for file and directory operations that can be used across any project that depends on provide.foundation.

Functions

binary_file

binary_file() -> Generator[Path, None, None]

Create a temporary binary file for testing.

Yields:

Type Description
Path

Path to a binary file containing sample binary data.

Source code in provide/testkit/file/special_fixtures.py
@pytest.fixture
def binary_file() -> Generator[Path, None, None]:
    """
    Create a temporary binary file for testing.

    Yields:
        Path to a binary file containing sample binary data.
    """
    with foundation_temp_file(suffix=".bin", text=False, cleanup=False) as path:
        # Write some binary data
        path.write_bytes(
            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" + b"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6"
        )

    yield path
    safe_delete(path, missing_ok=True)

empty_directory

empty_directory() -> Generator[Path, None, None]

Create an empty temporary directory.

Yields:

Type Description
Path

Path to an empty directory.

Source code in provide/testkit/file/directory_fixtures.py
@pytest.fixture
def empty_directory() -> Generator[Path, None, None]:
    """
    Create an empty temporary directory.

    Yields:
        Path to an empty directory.
    """
    with foundation_temp_dir() as temp_dir:
        yield temp_dir

nested_directory_structure

nested_directory_structure() -> Generator[Path, None, None]

Create a deeply nested directory structure for testing.

Creates
  • level1/
    • level2/
      • level3/
        • deep_file.txt
    • file_l2.txt
  • file_l1.txt

Yields:

Type Description
Path

Path to the root of the structure.

Source code in provide/testkit/file/directory_fixtures.py
@pytest.fixture
def nested_directory_structure() -> Generator[Path, None, None]:
    """
    Create a deeply nested directory structure for testing.

    Creates:
        - level1/
            - level2/
                - level3/
                    - deep_file.txt
            - file_l2.txt
        - file_l1.txt

    Yields:
        Path to the root of the structure.
    """
    with foundation_temp_dir() as root:
        # Create nested structure
        deep_dir = root / "level1" / "level2" / "level3"
        deep_dir.mkdir(parents=True)

        # Add files at different levels
        (root / "file_l1.txt").write_text("Level 1 file")
        (root / "level1" / "file_l2.txt").write_text("Level 2 file")
        (deep_dir / "deep_file.txt").write_text("Deep file")

        yield root

readonly_file

readonly_file() -> Generator[Path, None, None]

Create a read-only file for permission testing.

Yields:

Type Description
Path

Path to a read-only file.

Source code in provide/testkit/file/special_fixtures.py
@pytest.fixture
def readonly_file() -> Generator[Path, None, None]:
    """
    Create a read-only file for permission testing.

    Yields:
        Path to a read-only file.
    """
    with foundation_temp_file(suffix=".txt", text=True, cleanup=False) as path:
        path.write_text("Read-only content")

    # Make file read-only
    if sys.platform == "win32":
        # Windows: Only read-only bit is meaningful
        path.chmod(stat.S_IREAD)
    else:
        # Unix: Full permission control
        path.chmod(0o444)

    yield path

    # Restore write permission for cleanup
    if sys.platform == "win32":
        path.chmod(stat.S_IWRITE | stat.S_IREAD)
    else:
        path.chmod(0o644)
    safe_delete(path, missing_ok=True)

temp_binary_file

temp_binary_file() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary binary files.

Returns:

Type Description
None

Function that creates binary files.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_binary_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary binary files.

    Returns:
        Function that creates binary files.
    """
    created_files: list[Path] = []

    def _make_binary(size: int = 1024, pattern: bytes | None = None, suffix: str = ".bin") -> Path:
        """
        Create a temporary binary file.

        Args:
            size: File size in bytes
            pattern: Optional byte pattern to repeat
            suffix: File suffix

        Returns:
            Path to created binary file
        """
        if pattern is None:
            # Create pseudo-random binary data
            content = bytes(random.randint(0, 255) for _ in range(size))
        else:
            # Repeat pattern to reach size
            repetitions = size // len(pattern) + 1
            content = (pattern * repetitions)[:size]

        with foundation_temp_file(suffix=suffix, text=False, cleanup=False) as path:
            path.write_bytes(content)

        created_files.append(path)
        return path

    yield _make_binary

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_csv_file

temp_csv_file() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary CSV files for testing.

Returns:

Type Description
None

Function that creates CSV files.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_csv_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary CSV files for testing.

    Returns:
        Function that creates CSV files.
    """
    created_files: list[Path] = []

    def _make_csv(
        headers: Sequence[str],
        rows: Sequence[Sequence[Any]],
        suffix: str = ".csv",
    ) -> Path:
        """
        Create a temporary CSV file.

        Args:
            headers: Column headers
            rows: Data rows
            suffix: File suffix

        Returns:
            Path to created CSV file
        """
        with (
            foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path,
            path.open("w", newline="") as f,
        ):
            writer = csv.writer(f)
            writer.writerow(headers)
            writer.writerows(rows)

        created_files.append(path)
        return path

    yield _make_csv

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_directory

temp_directory() -> Generator[Path, None, None]

Create a temporary directory that's cleaned up after test.

Yields:

Type Description
Path

Path to the temporary directory.

Source code in provide/testkit/file/directory_fixtures.py
@pytest.fixture
def temp_directory() -> Generator[Path, None, None]:
    """
    Create a temporary directory that's cleaned up after test.

    Yields:
        Path to the temporary directory.
    """
    with foundation_temp_dir() as temp_dir:
        yield temp_dir

temp_executable_file

temp_executable_file() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary executable files for testing.

Returns:

Type Description
None

Function that creates executable files.

Source code in provide/testkit/file/special_fixtures.py
@pytest.fixture
def temp_executable_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary executable files for testing.

    Returns:
        Function that creates executable files.
    """
    created_files: list[Path] = []

    def _make_executable(content: str | None = None, suffix: str | None = None) -> Path:
        """
        Create a temporary executable file.

        Args:
            content: Script content (platform-specific default if None)
            suffix: File suffix (platform-specific default if None)

        Returns:
            Path to created executable file
        """
        # Platform-specific defaults
        if content is None:
            content = "@echo off\necho test\n" if sys.platform == "win32" else "#!/bin/sh\necho 'test'\n"
        if suffix is None:
            suffix = ".bat" if sys.platform == "win32" else ".sh"

        with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
            path.write_text(content)

        # Make executable (Unix only - Windows uses file extension)
        if sys.platform != "win32":
            current = path.stat().st_mode
            path.chmod(current | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)

        created_files.append(path)
        return path

    yield _make_executable

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_file

temp_file() -> Generator[Callable[..., Path], None, None]

Create a temporary file factory with optional content.

Returns:

Type Description
None

A function that creates temporary files with specified content and suffix.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create a temporary file factory with optional content.

    Returns:
        A function that creates temporary files with specified content and suffix.
    """
    created_files: list[Path] = []

    def _make_temp_file(content: str = "test content", suffix: str = ".txt") -> Path:
        """
        Create a temporary file.

        Args:
            content: Content to write to the file
            suffix: File suffix/extension

        Returns:
            Path to the created temporary file
        """
        with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
            path.write_text(content)
            created_files.append(path)
            return path

    yield _make_temp_file

    # Cleanup all created files
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_file_with_content

temp_file_with_content() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary files with specific content.

Returns:

Type Description
None

Function that creates files with content.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_file_with_content() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary files with specific content.

    Returns:
        Function that creates files with content.
    """
    created_files: list[Path] = []

    def _make_file(content: str | bytes, suffix: str = ".txt", encoding: str = "utf-8") -> Path:
        """
        Create a temporary file with content.

        Args:
            content: Content to write
            suffix: File suffix
            encoding: Text encoding (for str content)

        Returns:
            Path to created file
        """
        # Use Foundation's temp_file
        with foundation_temp_file(suffix=suffix, text=not isinstance(content, bytes), cleanup=False) as path:
            if isinstance(content, bytes):
                path.write_bytes(content)
            else:
                path.write_text(content, encoding=encoding)

        created_files.append(path)
        return path

    yield _make_file

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_json_file

temp_json_file() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary JSON files for testing.

Returns:

Type Description
None

Function that creates JSON files.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_json_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary JSON files for testing.

    Returns:
        Function that creates JSON files.
    """
    created_files: list[Path] = []

    def _make_json(data: dict[str, Any] | list[Any], suffix: str = ".json", indent: int = 2) -> Path:
        """
        Create a temporary JSON file.

        Args:
            data: JSON data to write
            suffix: File suffix
            indent: JSON indentation

        Returns:
            Path to created JSON file
        """
        with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path, path.open("w") as f:
            json.dump(data, f, indent=indent)

        created_files.append(path)
        return path

    yield _make_json

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)

temp_named_file

temp_named_file() -> (
    Generator[Callable[..., Path], None, None]
)

Create a named temporary file factory.

Returns:

Type Description
None

Function that creates named temporary files.

Source code in provide/testkit/file/content_fixtures.py
@pytest.fixture
def temp_named_file() -> Generator[Callable[..., Path], None, None]:
    """
    Create a named temporary file factory.

    Returns:
        Function that creates named temporary files.
    """
    created_files: list[Path] = []

    def _make_named_file(
        content: str | bytes | None = None,
        suffix: str = "",
        prefix: str = "tmp",
        dir: Path | str | None = None,
        mode: str = "w+b",
        delete: bool = False,
    ) -> Path:
        """
        Create a named temporary file.

        Args:
            content: Optional content to write
            suffix: File suffix
            prefix: File prefix
            dir: Directory for the file
            mode: File mode
            delete: Whether to delete on close

        Returns:
            Path to the created file
        """
        if isinstance(dir, Path):
            dir = str(dir)

        # Use Foundation's temp_file with cleanup=False since we manage cleanup
        with foundation_temp_file(
            suffix=suffix, prefix=prefix, dir=dir, text="b" not in mode, cleanup=False
        ) as path:
            if content is not None:
                if isinstance(content, str):
                    if "b" in mode:
                        path.write_bytes(content.encode())
                    else:
                        path.write_text(content)
                else:
                    path.write_bytes(content)

        if not delete:
            created_files.append(path)

        return path

    yield _make_named_file

    # Cleanup
    for path in created_files:
        safe_delete(path, missing_ok=True)
temp_symlink() -> (
    Generator[Callable[..., Path], None, None]
)

Create temporary symbolic links for testing.

Returns:

Type Description
None

Function that creates symbolic links.

Source code in provide/testkit/file/special_fixtures.py
@pytest.fixture
def temp_symlink() -> Generator[Callable[..., Path], None, None]:
    """
    Create temporary symbolic links for testing.

    Returns:
        Function that creates symbolic links.
    """
    created_links: list[Path] = []

    def _make_symlink(target: Path | str, link_name: Path | str | None = None) -> Path:
        """
        Create a temporary symbolic link.

        Args:
            target: Target path for the symlink
            link_name: Optional link name (auto-generated if None)

        Returns:
            Path to created symlink

        Raises:
            pytest.skip: On Windows if symlinks require admin/developer mode
        """
        target = Path(target)

        if link_name is None:
            with foundation_temp_file(cleanup=True) as temp_path:
                link_name = Path(str(temp_path) + "_link")
        else:
            link_name = Path(link_name)

        # On Windows, symlink creation requires special permissions
        if sys.platform == "win32":
            try:
                link_name.symlink_to(target)
            except OSError as e:
                pytest.skip(f"Symlink creation requires admin rights or Developer Mode on Windows: {e}")
        else:
            link_name.symlink_to(target)

        created_links.append(link_name)

        return link_name

    yield _make_symlink

    # Cleanup
    for link in created_links:
        safe_delete(link, missing_ok=True)

test_files_structure

test_files_structure() -> (
    Generator[tuple[Path, Path], None, None]
)

Create standard test file structure with files and subdirectories.

Creates
  • source/
    • file1.txt (contains "Content 1")
    • file2.txt (contains "Content 2")
    • subdir/
      • file3.txt (contains "Content 3")

Yields:

Type Description
tuple[Path, Path]

Tuple of (temp_path, source_path)

Source code in provide/testkit/file/directory_fixtures.py
@pytest.fixture
def test_files_structure() -> Generator[tuple[Path, Path], None, None]:
    """
    Create standard test file structure with files and subdirectories.

    Creates:
        - source/
            - file1.txt (contains "Content 1")
            - file2.txt (contains "Content 2")
            - subdir/
                - file3.txt (contains "Content 3")

    Yields:
        Tuple of (temp_path, source_path)
    """
    with foundation_temp_dir() as path:
        source = path / "source"
        source.mkdir()

        # Create test files
        (source / "file1.txt").write_text("Content 1")
        (source / "file2.txt").write_text("Content 2")

        # Create subdirectory with files
        subdir = source / "subdir"
        subdir.mkdir()
        (subdir / "file3.txt").write_text("Content 3")

        yield path, source