From 724639305fc7972ffbb6fe08c51e93657c8ab6bf Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 26 Jun 2025 17:35:18 -0400 Subject: [PATCH 1/3] fix: add logging for file inclusion Signed-off-by: Henry Schreiner --- docs/about/changelog.md | 1 + .../build/_file_processor.py | 19 +- tests/test_file_processor.py | 381 ++++++++++++++++++ 3 files changed, 400 insertions(+), 1 deletion(-) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index d6af9ac94..18ac3d4cd 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -9,6 +9,7 @@ Fixes: - Improve `.gitignore` iteration speed by @silversquirl in #1103 - Warn on 3.13.4 on Windows by @henryiii in #1104 +- Add logging explaining why a file is included/excluded by @henryiii in #1110 Documentation: diff --git a/src/scikit_build_core/build/_file_processor.py b/src/scikit_build_core/build/_file_processor.py index 8419c0110..60a18fe05 100644 --- a/src/scikit_build_core/build/_file_processor.py +++ b/src/scikit_build_core/build/_file_processor.py @@ -7,7 +7,8 @@ import pathspec -from scikit_build_core.format import pyproject_format +from .._logging import logger +from ..format import pyproject_format if TYPE_CHECKING: from collections.abc import Generator, Sequence @@ -72,19 +73,31 @@ def each_unignored_file( for p in all_paths: # Always include something included if include_spec.match_file(p): + logger.info("Including {} because it is explicitly included.", p) yield p continue # Always exclude something excluded if user_exclude_spec.match_file(p): + logger.info( + "Excluding {} because it is explicitly excluded by the user.", p + ) continue # Ignore from global ignore if global_exclude_spec.match_file(p): + logger.info( + "Excluding {} because it is explicitly excluded by the global ignore.", + p, + ) continue # Ignore built-in patterns if builtin_exclude_spec.match_file(p): + logger.info( + "Excluding {} because it is excluded by the built-in ignore patterns.", + p, + ) continue # Check relative ignores (Python 3.9's is_relative_to workaround) @@ -93,6 +106,10 @@ def each_unignored_file( for np, nex in nested_excludes.items() if dirpath == np or np in dirpath.parents ): + logger.info( + "Excluding {} because it is explicitly included by nested ignore.", + p, + ) continue yield p diff --git a/tests/test_file_processor.py b/tests/test_file_processor.py index 52647fbc9..726d681ab 100644 --- a/tests/test_file_processor.py +++ b/tests/test_file_processor.py @@ -2,11 +2,66 @@ import sys from pathlib import Path +from typing import TYPE_CHECKING import pytest from scikit_build_core.build._file_processor import each_unignored_file +if TYPE_CHECKING: + from collections.abc import Generator + + +def _mk_files(tmp_path: Path, files: str) -> Generator[Path, None, None]: + """ + Create a set of files in the given temporary path based on the provided string. + The string should contain file names + """ + for line in files.splitlines(): + file_contents = line.strip() + if file_contents: + file_name, _, contents = file_contents.partition(":") + file_path = tmp_path / file_name.strip() + # Create parent directories if needed + if not file_path.parent.is_dir(): + file_path.parent.mkdir(parents=True, exist_ok=True) + file_path.write_text(contents.strip() or "content") + yield file_path.relative_to(tmp_path) + + +def _setup_test_filesystem(tmp_path: Path) -> set[Path]: + """ + Set up a test filesystem with various files and directories for testing. + """ + + return set( + _mk_files( + tmp_path, + """ + README.md + setup.py + pyproject.toml + src/__init__.py + src/main.py + src/utils.py + tests/test_main.py + tests/test_utils.py + tests/tmp.py + docs/index.md + docs/api.rst + temp.tmp: temporary file + debug.log: log file + cache.db: cache file + local_ignored_file.txt + __pycache__/test.pyc + .git/config + .gitignore: *tmp* + .git/info/exclude: local_ignored_file.txt + nested_dir/not_ignored.txt: not ignored file + """, + ) + ) + @pytest.mark.skipif( sys.implementation.name == "pypy" and sys.platform.startswith("win"), @@ -74,3 +129,329 @@ def test_dot_git_is_a_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N git.write_text("gitdir: ../../.git/modules/foo") # If this throws an exception, the test will fail assert list(each_unignored_file(Path())) == [] + + +def test_include_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test that include patterns work correctly and override excludes. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Test including only Python files + result = {str(s) for s in each_unignored_file(Path(), include=["*.py"])} + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + "tests/tmp.py", + } + assert result == expected + + # Test including specific files + result = {str(s) for s in each_unignored_file(Path(), include=["tests/tmp.py"])} + assert result == expected | {"tests/tmp.py"} + + # Test including with wildcards + result = {str(s) for s in each_unignored_file(Path(), include=["tests/*"])} + expected = expected | {"tests/tmp.py"} + assert result == expected + + +def test_exclude_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test that exclude patterns work correctly. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Test excluding specific file types + result = {str(s) for s in each_unignored_file(Path(), exclude=["*.tmp", "*.log"])} + expected = { + ".gitignore", + "README.md", + "cache.db", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + } + assert result == expected + + # Test excluding directories + result = {str(s) for s in each_unignored_file(Path(), exclude=["tests/"])} + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + } + assert result == expected + + # Test excluding with wildcards + result = {str(s) for s in each_unignored_file(Path(), exclude=["*.py"])} + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + } + assert result == expected + + +def test_include_overrides_exclude( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Test that include patterns override exclude patterns. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Exclude all files but include specific ones + result = { + str(s) + for s in each_unignored_file( + Path(), include=["src/main.py", "tests/test_main.py"], exclude=["*"] + ) + } + expected = {"src/main.py", "tests/test_main.py"} + assert result == expected + + # Exclude everything but include a file from inside a directory + result = { + str(s) + for s in each_unignored_file( + Path(), include=["tests/test_main.py"], exclude=["*"] + ) + } + expected = {"tests/test_main.py"} + assert result == expected + + +def test_gitignore_interaction(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test interaction between include/exclude and gitignore files. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Create .gitignore that excludes some files + gitignore = tmp_path / ".gitignore" + gitignore.write_text("*.tmp\n*.log\n/cache.db\n") + + # Test that gitignore is respected by default + result = {str(s) for s in each_unignored_file(Path())} + expected = { + ".gitignore", + "README.md", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + "tests/tmp.py", + "docs/index.md", + "docs/api.rst", + "nested_dir/not_ignored.txt", + } + assert result == expected + + # Test that include can override gitignore + result = {str(s) for s in each_unignored_file(Path(), include=["*.tmp"])} + assert result == expected | {"temp.tmp"} + + +def test_nested_gitignore(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test handling of nested .gitignore files. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Create nested .gitignore in src directory + src_gitignore = tmp_path / "src" / ".gitignore" + src_gitignore.write_text("utils.py\n") + + # Test that nested gitignore is respected + result = {str(s) for s in each_unignored_file(Path())} + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/.gitignore", + "src/__init__.py", + "src/main.py", + "tests/test_main.py", + "tests/test_utils.py", + } + assert result == expected + + # Test that include can override nested gitignore + result = {str(s) for s in each_unignored_file(Path(), include=["src/utils.py"])} + assert result == expected | {"src/utils.py"} + + +def test_build_dir_exclusion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test that build_dir parameter correctly excludes build directories. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Create build directory + build_dir = tmp_path / "build" + build_dir.mkdir() + build_file = build_dir / "output.so" + build_file.write_text("compiled output") + + # Test that build directory is excluded when specified + result = {str(s) for s in each_unignored_file(Path(), build_dir="build")} + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + } + assert result == expected + assert str(build_file.relative_to(tmp_path)) not in result + + # Test that include can override build_dir exclusion + result = { + str(s) + for s in each_unignored_file(Path(), include=["build/*"], build_dir="build") + } + assert result == expected | {"build/output.so"} + + +def test_complex_combinations(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test complex combinations of include, exclude, gitignore, and build_dir. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Set up complex scenario + gitignore = tmp_path / ".gitignore" + gitignore.write_text("*.tmp\ndebug.log\n") + + build_dir = tmp_path / "_build" + build_dir.mkdir() + build_file = build_dir / "lib.so" + build_file.write_text("build output") + + # Complex pattern: exclude tests, include specific test, respect gitignore, exclude build + result = { + str(s) + for s in each_unignored_file( + Path(), + include=[ + "tests/test_main.py", + "*.tmp", + ], # Include specific test and override gitignore for .tmp + exclude=["*"], # Exclude tests dir and rst files + build_dir="_build", + ) + } + + expected = {"tests/test_main.py", "temp.tmp"} # Only these should match + assert result == expected + + +def test_empty_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test behavior with empty directory. + """ + monkeypatch.chdir(tmp_path) + + result = list(each_unignored_file(Path())) + assert result == [] + + result = list(each_unignored_file(Path(), include=["*.py"])) + assert result == [] + + result = list(each_unignored_file(Path(), exclude=["*.py"])) + assert result == [] + + +def test_nonexistent_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test behavior with patterns that don't match any files. + """ + monkeypatch.chdir(tmp_path) + _setup_test_filesystem(tmp_path) + + # Include pattern that matches nothing + include_result = list( + each_unignored_file(Path(), exclude=["*"], include=["*.nonexistent"]) + ) + assert include_result == [] + + # Exclude pattern that matches nothing + exclude_result = { + str(s) for s in each_unignored_file(Path(), exclude=["*.nonexistent"]) + } + expected = { + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + } + assert exclude_result == expected From 6560a865f46cfb093b6eb454733a554549f3899e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 26 Jun 2025 17:51:48 -0400 Subject: [PATCH 2/3] fix: minor issues noticed by CoPilot Signed-off-by: Henry Schreiner --- docs/about/changelog.md | 3 ++- src/scikit_build_core/build/_file_processor.py | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 18ac3d4cd..c1ace4a95 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -9,7 +9,8 @@ Fixes: - Improve `.gitignore` iteration speed by @silversquirl in #1103 - Warn on 3.13.4 on Windows by @henryiii in #1104 -- Add logging explaining why a file is included/excluded by @henryiii in #1110 +- Add debug logging explaining why a file is included/excluded by @henryiii in + #1110 Documentation: diff --git a/src/scikit_build_core/build/_file_processor.py b/src/scikit_build_core/build/_file_processor.py index 60a18fe05..17d25bf47 100644 --- a/src/scikit_build_core/build/_file_processor.py +++ b/src/scikit_build_core/build/_file_processor.py @@ -73,20 +73,20 @@ def each_unignored_file( for p in all_paths: # Always include something included if include_spec.match_file(p): - logger.info("Including {} because it is explicitly included.", p) + logger.debug("Including {} because it is explicitly included.", p) yield p continue # Always exclude something excluded if user_exclude_spec.match_file(p): - logger.info( + logger.debug( "Excluding {} because it is explicitly excluded by the user.", p ) continue # Ignore from global ignore if global_exclude_spec.match_file(p): - logger.info( + logger.debug( "Excluding {} because it is explicitly excluded by the global ignore.", p, ) @@ -94,7 +94,7 @@ def each_unignored_file( # Ignore built-in patterns if builtin_exclude_spec.match_file(p): - logger.info( + logger.debug( "Excluding {} because it is excluded by the built-in ignore patterns.", p, ) @@ -106,8 +106,8 @@ def each_unignored_file( for np, nex in nested_excludes.items() if dirpath == np or np in dirpath.parents ): - logger.info( - "Excluding {} because it is explicitly included by nested ignore.", + logger.debug( + "Excluding {} because it is explicitly excluded by nested ignore.", p, ) continue From d376ca3fd256d8be650a81e123f3c46a97e03d90 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 26 Jun 2025 18:25:18 -0400 Subject: [PATCH 3/3] fix: use Path instead of str for compare Signed-off-by: Henry Schreiner --- tests/test_file_processor.py | 304 ++++++++++++++++++----------------- 1 file changed, 160 insertions(+), 144 deletions(-) diff --git a/tests/test_file_processor.py b/tests/test_file_processor.py index 726d681ab..48701c6a2 100644 --- a/tests/test_file_processor.py +++ b/tests/test_file_processor.py @@ -139,33 +139,36 @@ def test_include_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> No _setup_test_filesystem(tmp_path) # Test including only Python files - result = {str(s) for s in each_unignored_file(Path(), include=["*.py"])} + result = set(each_unignored_file(Path(), include=["*.py"])) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", - "tests/test_main.py", - "tests/test_utils.py", - "tests/tmp.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + "tests/tmp.py", + ] } assert result == expected # Test including specific files - result = {str(s) for s in each_unignored_file(Path(), include=["tests/tmp.py"])} - assert result == expected | {"tests/tmp.py"} + result = set(each_unignored_file(Path(), include=["tests/tmp.py"])) + assert result == expected | {Path("tests/tmp.py")} # Test including with wildcards - result = {str(s) for s in each_unignored_file(Path(), include=["tests/*"])} - expected = expected | {"tests/tmp.py"} + result = set(each_unignored_file(Path(), include=["tests/*"])) + expected = expected | {Path("tests/tmp.py")} assert result == expected @@ -177,53 +180,62 @@ def test_exclude_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> No _setup_test_filesystem(tmp_path) # Test excluding specific file types - result = {str(s) for s in each_unignored_file(Path(), exclude=["*.tmp", "*.log"])} + result = set(each_unignored_file(Path(), exclude=["*.tmp", "*.log"])) expected = { - ".gitignore", - "README.md", - "cache.db", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", - "tests/test_main.py", - "tests/test_utils.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + ] } assert result == expected # Test excluding directories - result = {str(s) for s in each_unignored_file(Path(), exclude=["tests/"])} + result = set(each_unignored_file(Path(), exclude=["tests/"])) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + ] } assert result == expected # Test excluding with wildcards - result = {str(s) for s in each_unignored_file(Path(), exclude=["*.py"])} + result = set(each_unignored_file(Path(), exclude=["*.py"])) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + ] } assert result == expected @@ -238,23 +250,19 @@ def test_include_overrides_exclude( _setup_test_filesystem(tmp_path) # Exclude all files but include specific ones - result = { - str(s) - for s in each_unignored_file( + result = set( + each_unignored_file( Path(), include=["src/main.py", "tests/test_main.py"], exclude=["*"] ) - } - expected = {"src/main.py", "tests/test_main.py"} + ) + expected = {Path(s) for s in ["src/main.py", "tests/test_main.py"]} assert result == expected # Exclude everything but include a file from inside a directory - result = { - str(s) - for s in each_unignored_file( - Path(), include=["tests/test_main.py"], exclude=["*"] - ) - } - expected = {"tests/test_main.py"} + result = set( + each_unignored_file(Path(), include=["tests/test_main.py"], exclude=["*"]) + ) + expected = {Path(s) for s in ["tests/test_main.py"]} assert result == expected @@ -270,27 +278,30 @@ def test_gitignore_interaction(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) gitignore.write_text("*.tmp\n*.log\n/cache.db\n") # Test that gitignore is respected by default - result = {str(s) for s in each_unignored_file(Path())} + result = set(each_unignored_file(Path())) expected = { - ".gitignore", - "README.md", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", - "tests/test_main.py", - "tests/test_utils.py", - "tests/tmp.py", - "docs/index.md", - "docs/api.rst", - "nested_dir/not_ignored.txt", + Path(s) + for s in [ + ".gitignore", + "README.md", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + "tests/tmp.py", + "docs/index.md", + "docs/api.rst", + "nested_dir/not_ignored.txt", + ] } assert result == expected # Test that include can override gitignore - result = {str(s) for s in each_unignored_file(Path(), include=["*.tmp"])} - assert result == expected | {"temp.tmp"} + result = set(each_unignored_file(Path(), include=["*.tmp"])) + assert result == expected | {Path("temp.tmp")} def test_nested_gitignore(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -305,28 +316,31 @@ def test_nested_gitignore(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> No src_gitignore.write_text("utils.py\n") # Test that nested gitignore is respected - result = {str(s) for s in each_unignored_file(Path())} + result = set(each_unignored_file(Path())) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/.gitignore", - "src/__init__.py", - "src/main.py", - "tests/test_main.py", - "tests/test_utils.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/.gitignore", + "src/__init__.py", + "src/main.py", + "tests/test_main.py", + "tests/test_utils.py", + ] } assert result == expected # Test that include can override nested gitignore - result = {str(s) for s in each_unignored_file(Path(), include=["src/utils.py"])} - assert result == expected | {"src/utils.py"} + result = set(each_unignored_file(Path(), include=["src/utils.py"])) + assert result == expected | {Path("src/utils.py")} def test_build_dir_exclusion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -343,32 +357,32 @@ def test_build_dir_exclusion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> build_file.write_text("compiled output") # Test that build directory is excluded when specified - result = {str(s) for s in each_unignored_file(Path(), build_dir="build")} + result = set(each_unignored_file(Path(), build_dir="build")) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", - "tests/test_main.py", - "tests/test_utils.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + ] } assert result == expected - assert str(build_file.relative_to(tmp_path)) not in result + assert build_file.relative_to(tmp_path) not in result # Test that include can override build_dir exclusion - result = { - str(s) - for s in each_unignored_file(Path(), include=["build/*"], build_dir="build") - } - assert result == expected | {"build/output.so"} + result = set(each_unignored_file(Path(), include=["build/*"], build_dir="build")) + assert result == expected | {Path("build/output.so")} def test_complex_combinations(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -388,9 +402,8 @@ def test_complex_combinations(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) - build_file.write_text("build output") # Complex pattern: exclude tests, include specific test, respect gitignore, exclude build - result = { - str(s) - for s in each_unignored_file( + result = set( + each_unignored_file( Path(), include=[ "tests/test_main.py", @@ -399,9 +412,11 @@ def test_complex_combinations(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) - exclude=["*"], # Exclude tests dir and rst files build_dir="_build", ) - } + ) - expected = {"tests/test_main.py", "temp.tmp"} # Only these should match + expected = { + Path(s) for s in ["tests/test_main.py", "temp.tmp"] + } # Only these should match assert result == expected @@ -435,23 +450,24 @@ def test_nonexistent_patterns(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) - assert include_result == [] # Exclude pattern that matches nothing - exclude_result = { - str(s) for s in each_unignored_file(Path(), exclude=["*.nonexistent"]) - } + exclude_result = set(each_unignored_file(Path(), exclude=["*.nonexistent"])) expected = { - ".gitignore", - "README.md", - "cache.db", - "debug.log", - "docs/api.rst", - "docs/index.md", - "nested_dir/not_ignored.txt", - "pyproject.toml", - "setup.py", - "src/__init__.py", - "src/main.py", - "src/utils.py", - "tests/test_main.py", - "tests/test_utils.py", + Path(s) + for s in [ + ".gitignore", + "README.md", + "cache.db", + "debug.log", + "docs/api.rst", + "docs/index.md", + "nested_dir/not_ignored.txt", + "pyproject.toml", + "setup.py", + "src/__init__.py", + "src/main.py", + "src/utils.py", + "tests/test_main.py", + "tests/test_utils.py", + ] } assert exclude_result == expected