From 4e6be63f1363f599b5ad9a68ddff7c95e1a1d9a2 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 18:45:56 +0700 Subject: [PATCH 1/9] Introduce a common package fixture Signed-off-by: Cristian Le --- tests/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index db9eda95..a4ad1e5d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,6 +199,18 @@ def process_package( monkeypatch.chdir(package_dir) +@pytest.fixture +def package( + request: pytest.FixtureRequest, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> PackageInfo: + pkg_name = request.param + assert isinstance(pkg_name, str) + package = PackageInfo(pkg_name) + assert (DIR / "packages" / package.name).exists() + process_package(package, tmp_path, monkeypatch) + return package + + @pytest.fixture def package_simple_pyproject_ext( tmp_path: Path, monkeypatch: pytest.MonkeyPatch From 23c5443bcb92f1bde135e752cea02ffe4c47a075 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 18:51:10 +0700 Subject: [PATCH 2/9] Consolidate package fixtures Signed-off-by: Cristian Le --- tests/conftest.py | 132 ----------------------------- tests/test_broken_fallback.py | 9 +- tests/test_dynamic_metadata.py | 27 ++++-- tests/test_editable.py | 12 +-- tests/test_hatchling.py | 6 +- tests/test_prepare_metadata.py | 3 +- tests/test_pyproject_extra_dirs.py | 6 +- tests/test_pyproject_pep517.py | 17 ++-- tests/test_pyproject_pep518.py | 6 +- tests/test_pyproject_pep660.py | 6 +- tests/test_pyproject_purelib.py | 3 +- tests/test_setuptools_pep517.py | 15 ++-- tests/test_setuptools_pep518.py | 6 +- 13 files changed, 77 insertions(+), 171 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a4ad1e5d..96033c73 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -226,138 +226,6 @@ def package_simple_pyproject_ext( return package -@pytest.fixture -def package_simple_pyproject_script_with_flags( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo( - "simple_pyproject_script_with_flags", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_simple_pyproject_source_dir( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo( - "simple_pyproject_source_dir", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_simple_setuptools_ext( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo("simple_setuptools_ext") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_toml_setuptools_ext( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo("toml_setuptools_ext") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_mixed_setuptools( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo("mixed_setuptools") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_filepath_pure( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo("filepath_pure") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_dynamic_metadata( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo("dynamic_metadata") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_hatchling(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo: - package = PackageInfo("hatchling") - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_simplest_c(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo: - package = PackageInfo( - "simplest_c", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def navigate_editable(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo: - package = PackageInfo( - "navigate_editable", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def broken_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo: - package = PackageInfo( - "broken_fallback", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_sdist_config( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo( - "sdist_config", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_simple_purelib_package( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> PackageInfo: - package = PackageInfo( - "simple_purelib_package", - ) - process_package(package, tmp_path, monkeypatch) - return package - - -@pytest.fixture -def package_pep639_pure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo: - package = PackageInfo( - "pep639_pure", - ) - process_package(package, tmp_path, monkeypatch) - return package - - def which_mock(name: str) -> str | None: if name in {"ninja", "ninja-build", "cmake3", "samu", "gmake", "make"}: return None diff --git a/tests/test_broken_fallback.py b/tests/test_broken_fallback.py index 3730037c..c7cebeb7 100644 --- a/tests/test_broken_fallback.py +++ b/tests/test_broken_fallback.py @@ -11,7 +11,8 @@ @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("broken_fallback") +@pytest.mark.parametrize("package", {"broken_fallback"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.parametrize("broken_define", ["BROKEN_CMAKE", "BROKEN_CODE"]) def test_broken_code( broken_define: str, capfd: pytest.CaptureFixture[str], tmp_path: Path @@ -39,7 +40,8 @@ def test_broken_code( assert "CMake build failed" in out -@pytest.mark.usefixtures("broken_fallback") +@pytest.mark.parametrize("package", {"broken_fallback"}, indirect=True) +@pytest.mark.usefixtures("package") def test_fail_setting( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ): @@ -54,7 +56,8 @@ def test_fail_setting( assert "fail setting was enabled" in err -@pytest.mark.usefixtures("broken_fallback") +@pytest.mark.parametrize("package", {"broken_fallback"}, indirect=True) +@pytest.mark.usefixtures("package") def test_fail_setting_msg( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ): diff --git a/tests/test_dynamic_metadata.py b/tests/test_dynamic_metadata.py index 9038edc5..60fdfd0f 100644 --- a/tests/test_dynamic_metadata.py +++ b/tests/test_dynamic_metadata.py @@ -99,7 +99,8 @@ def mock_entry_points(monkeypatch): monkeypatch.setattr(importlib, "import_module", special_loader) -@pytest.mark.usefixtures("mock_entry_points", "package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("mock_entry_points", "package") def test_dynamic_metadata(): with Path("pyproject.toml").open("rb") as ft: pyproject = tomllib.load(ft) @@ -115,7 +116,8 @@ def test_dynamic_metadata(): assert metadata.readme == pyproject_metadata.Readme("Some text", None, "text/x-rst") -@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_plugin_metadata(): reason_msg = ( "install hatch-fancy-pypi-readme and setuptools-scm to test the " @@ -157,7 +159,8 @@ def test_plugin_metadata(): assert metadata.optional_dependencies == {"dev": [Requirement("fancy==0.1.0")]} -@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_faulty_metadata(): reason_msg = "install hatch-fancy-pypi-readme to test the dynamic metadata plugins" pytest.importorskip("hatch_fancy_pypi_readme", reason=reason_msg) @@ -173,7 +176,8 @@ def test_faulty_metadata(): get_standard_metadata(pyproject, settings) -@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_local_plugin_metadata(): with Path("local_pyproject.toml").open("rb") as ft: pyproject = tomllib.load(ft) @@ -186,7 +190,8 @@ def test_local_plugin_metadata(): assert metadata.version == Version("3.2.1") -@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_warn_metadata(): with Path("warn_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) @@ -199,7 +204,8 @@ def test_warn_metadata(): get_standard_metadata(pyproject, settings) -@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_fail_experimental_metadata(): with Path("warn_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) @@ -213,7 +219,8 @@ def test_fail_experimental_metadata(): assert value == 7 -@pytest.mark.usefixtures("mock_entry_points", "package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("mock_entry_points", "package") def test_dual_metadata(): with Path("dual_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) @@ -239,7 +246,8 @@ def test_dual_metadata(): @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("mock_entry_points", "package_dynamic_metadata") +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("mock_entry_points", "package") def test_pep517_wheel(virtualenv, tmp_path: Path) -> None: dist = tmp_path / "dist" out = build_wheel(str(dist)) @@ -354,8 +362,9 @@ def test_regex_remove( assert version == ("1.2.3dev1" if dev else "1.2.3") -@pytest.mark.usefixtures("package_dynamic_metadata") @pytest.mark.parametrize("override", [None, "env", "sdist"]) +@pytest.mark.parametrize("package", {"dynamic_metadata"}, indirect=True) +@pytest.mark.usefixtures("package") def test_build_requires_field(override, monkeypatch) -> None: shutil.copy("build_requires_project.toml", "pyproject.toml") diff --git a/tests/test_editable.py b/tests/test_editable.py index 6d4a1c18..c32852e1 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -14,7 +14,7 @@ @pytest.mark.integration @pytest.mark.parametrize("isolate", [True, False], ids=["isolated", "notisolated"]) @pytest.mark.parametrize( - "package", + "py_pkg", [ pytest.param( True, @@ -24,17 +24,18 @@ pytest.param(False, id="datafolder"), ], ) -@pytest.mark.usefixtures("navigate_editable") +@pytest.mark.parametrize("package", {"navigate_editable"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.xfail( sys.version_info[:2] == (3, 9), reason="Python 3.9 not supported yet" ) -def test_navigate_editable(isolated, isolate, package): +def test_navigate_editable(isolated, isolate, py_pkg): isolate_args = ["--no-build-isolation"] if not isolate else [] isolated.install("pip>=23") if not isolate: isolated.install("scikit-build-core") - if package: + if py_pkg: init_py = Path("python/shared_pkg/data/__init__.py") init_py.touch() @@ -114,7 +115,8 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.usefixtures("package_simplest_c") +@pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.usefixtures("package") def test_install_dir(isolated): isolated.install("pip>=23") isolated.install("scikit-build-core") diff --git a/tests/test_hatchling.py b/tests/test_hatchling.py index d4615f65..a1ee65c5 100644 --- a/tests/test_hatchling.py +++ b/tests/test_hatchling.py @@ -10,7 +10,8 @@ @pytest.mark.network @pytest.mark.integration -@pytest.mark.usefixtures("package_hatchling") +@pytest.mark.parametrize("package", {"hatchling"}, indirect=True) +@pytest.mark.usefixtures("package") def test_hatchling_sdist(isolated, tmp_path: Path) -> None: dist = tmp_path / "dist" isolated.install("build[virtualenv]") @@ -34,7 +35,8 @@ def test_hatchling_sdist(isolated, tmp_path: Path) -> None: @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.usefixtures("package_hatchling") +@pytest.mark.parametrize("package", {"hatchling"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.parametrize( "build_args", [(), ("--wheel",)], ids=["sdist_to_wheel", "wheel_directly"] ) diff --git a/tests/test_prepare_metadata.py b/tests/test_prepare_metadata.py index 1c6c7379..4bf57beb 100644 --- a/tests/test_prepare_metadata.py +++ b/tests/test_prepare_metadata.py @@ -15,7 +15,8 @@ from scikit_build_core.settings.skbuild_model import ScikitBuildSettings -@pytest.mark.usefixtures("package_simplest_c") +@pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.parametrize("editable", [True, False], ids=["editable", "wheel"]) def test_prepare_metadata_for_build(fp, editable): # Old versions of packaging call mac_ver via subprocess diff --git a/tests/test_pyproject_extra_dirs.py b/tests/test_pyproject_extra_dirs.py index 2c93ea28..d7be0658 100644 --- a/tests/test_pyproject_extra_dirs.py +++ b/tests/test_pyproject_extra_dirs.py @@ -11,7 +11,8 @@ @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_filepath_pure") +@pytest.mark.parametrize("package", {"filepath_pure"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_wheel_extra_dirs(monkeypatch, tmp_path: Path): monkeypatch.setenv("SKBUILD_CMAKE_DEFINE", "SOME_DEFINE3=baz;SOME_DEFINE4=baz") monkeypatch.setenv("SKBUILD_CMAKE_ARGS", "-DSOME_ARGS1=baz") @@ -50,7 +51,8 @@ def test_pep517_wheel_extra_dirs(monkeypatch, tmp_path: Path): assert scripts == {"in_scripts.py"} -@pytest.mark.usefixtures("package_filepath_pure") +@pytest.mark.parametrize("package", {"filepath_pure"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_wheel_too_old_core(monkeypatch): monkeypatch.setenv("SKBUILD_CMAKE_DEFINE", "SOME_DEFINE3=baz;SOME_DEFINE4=baz") monkeypatch.setenv("SKBUILD_CMAKE_ARGS", "-DSOME_ARGS1=baz") diff --git a/tests/test_pyproject_pep517.py b/tests/test_pyproject_pep517.py index c7b730c9..ec648ea3 100644 --- a/tests/test_pyproject_pep517.py +++ b/tests/test_pyproject_pep517.py @@ -166,7 +166,10 @@ def each_unignored_file_ordered(*args, **kwargs): @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_simple_pyproject_script_with_flags") +@pytest.mark.parametrize( + "package", {"simple_pyproject_script_with_flags"}, indirect=True +) +@pytest.mark.usefixtures("package") @pytest.mark.parametrize( ("env_var", "setting"), [ @@ -248,7 +251,8 @@ def test_pep517_wheel(virtualenv, tmp_path: Path): @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_simple_pyproject_source_dir") +@pytest.mark.parametrize("package", {"simple_pyproject_source_dir"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_wheel_source_dir(virtualenv, tmp_path: Path): dist = tmp_path / "dist" out = build_wheel(str(dist), config_settings={"skbuild.wheel.build-tag": "1foo"}) @@ -367,7 +371,8 @@ def test_prepare_metdata_for_build_wheel_by_hand(tmp_path): assert len(metadata) == len(answer) -@pytest.mark.usefixtures("package_pep639_pure") +@pytest.mark.parametrize("package", {"pep639_pure"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep639_license_files_metadata(): metadata = build.util.project_wheel_metadata(str(Path.cwd()), isolated=False) answer = { @@ -384,7 +389,8 @@ def test_pep639_license_files_metadata(): assert len(metadata) == sum(len(v) for v in answer.values()) -@pytest.mark.usefixtures("package_pep639_pure") +@pytest.mark.parametrize("package", {"pep639_pure"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep639_license_files_sdist(tmp_path: Path): expected_metadata = ( inspect.cleandoc( @@ -424,7 +430,8 @@ def test_pep639_license_files_sdist(tmp_path: Path): assert pkg_info_contents == expected_metadata -@pytest.mark.usefixtures("package_pep639_pure") +@pytest.mark.parametrize("package", {"pep639_pure"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep639_license_files_wheel(tmp_path: Path): dist = tmp_path / "dist" out = build_wheel(str(dist), {}) diff --git a/tests/test_pyproject_pep518.py b/tests/test_pyproject_pep518.py index 91d551fe..328476f4 100644 --- a/tests/test_pyproject_pep518.py +++ b/tests/test_pyproject_pep518.py @@ -69,7 +69,8 @@ def test_pep518_sdist(isolated, package_simple_pyproject_ext, tmp_path: Path): @pytest.mark.network @pytest.mark.configure @pytest.mark.integration -@pytest.mark.usefixtures("package_sdist_config") +@pytest.mark.parametrize("package", {"sdist_config"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep518_sdist_with_cmake_config(isolated, cleanup_overwrite, tmp_path: Path): cleanup_overwrite.write_text("set(MY_VERSION fiddlesticks)") @@ -114,7 +115,8 @@ def test_pep518_sdist_with_cmake_config(isolated, cleanup_overwrite, tmp_path: P @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.usefixtures("package_sdist_config") +@pytest.mark.parametrize("package", {"sdist_config"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.parametrize( "build_args", [(), ("--wheel",)], ids=["sdist_to_wheel", "wheel_directly"] ) diff --git a/tests/test_pyproject_pep660.py b/tests/test_pyproject_pep660.py index 110bd680..eb6303e1 100644 --- a/tests/test_pyproject_pep660.py +++ b/tests/test_pyproject_pep660.py @@ -22,7 +22,8 @@ def editable_mode(request: pytest.FixtureRequest) -> str: strict=False, reason="No idea why this fails on Cygwin", ) -@pytest.mark.usefixtures("package_simplest_c") +@pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep660_wheel(editable_mode: str, tmp_path: Path): dist = tmp_path / "dist" out = build_editable(str(dist), {"editable.mode": editable_mode}) @@ -54,7 +55,8 @@ def test_pep660_wheel(editable_mode: str, tmp_path: Path): @pytest.mark.configure @pytest.mark.integration @pytest.mark.parametrize("isolate", [True, False], ids=["isolated", "not_isolated"]) -@pytest.mark.usefixtures("package_simplest_c") +@pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep660_pip_isolated(isolated, isolate, editable_mode: str): isolate_args = ["--no-build-isolation"] if not isolate else [] isolated.install("pip>=23") diff --git a/tests/test_pyproject_purelib.py b/tests/test_pyproject_purelib.py index 710a1542..b0e5fa62 100644 --- a/tests/test_pyproject_purelib.py +++ b/tests/test_pyproject_purelib.py @@ -7,7 +7,8 @@ @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_simple_purelib_package") +@pytest.mark.parametrize("package", {"simple_purelib_package"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_wheel(virtualenv, tmp_path: Path): dist = tmp_path / "dist" out = build_wheel(str(dist), {}) diff --git a/tests/test_setuptools_pep517.py b/tests/test_setuptools_pep517.py index b4eb4447..8d425c62 100644 --- a/tests/test_setuptools_pep517.py +++ b/tests/test_setuptools_pep517.py @@ -13,7 +13,8 @@ setuptools_version = Version(importlib.metadata.version("setuptools")) -@pytest.mark.usefixtures("package_simple_setuptools_ext") +@pytest.mark.parametrize("package", {"simple_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_sdist(tmp_path: Path): correct_metadata = textwrap.dedent( """\ @@ -68,7 +69,8 @@ def test_pep517_sdist(tmp_path: Path): @pytest.mark.compile @pytest.mark.configure @pytest.mark.broken_on_urct -@pytest.mark.usefixtures("package_simple_setuptools_ext") +@pytest.mark.parametrize("package", {"simple_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep517_wheel(virtualenv, tmp_path: Path): dist = tmp_path / "dist" out = build_wheel(str(dist)) @@ -97,7 +99,8 @@ def test_pep517_wheel(virtualenv, tmp_path: Path): assert add.strip() == "3" -@pytest.mark.usefixtures("package_toml_setuptools_ext") +@pytest.mark.parametrize("package", {"toml_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.skipif( setuptools_version < Version("61.0"), reason="Requires setuptools 61+" ) @@ -148,7 +151,8 @@ def test_toml_sdist(tmp_path: Path): @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_toml_setuptools_ext") +@pytest.mark.parametrize("package", {"toml_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") @pytest.mark.skipif( setuptools_version < Version("61.0"), reason="Requires setuptools 61+" ) @@ -182,7 +186,8 @@ def test_toml_wheel(virtualenv, tmp_path: Path): @pytest.mark.compile @pytest.mark.configure -@pytest.mark.usefixtures("package_mixed_setuptools") +@pytest.mark.parametrize("package", {"mixed_setuptools"}, indirect=True) +@pytest.mark.usefixtures("package") def test_mixed_wheel(virtualenv, tmp_path: Path): dist = tmp_path / "dist" out = build_wheel(str(dist)) diff --git a/tests/test_setuptools_pep518.py b/tests/test_setuptools_pep518.py index cf1d066e..2f070566 100644 --- a/tests/test_setuptools_pep518.py +++ b/tests/test_setuptools_pep518.py @@ -17,7 +17,8 @@ reason="Cygwin fails here with ld errors", strict=False, ) -@pytest.mark.usefixtures("package_simple_setuptools_ext") +@pytest.mark.parametrize("package", {"simple_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep518_wheel(isolated, tmp_path: Path): dist = tmp_path / "dist" isolated.install("build[virtualenv]") @@ -55,7 +56,8 @@ def test_pep518_wheel(isolated, tmp_path: Path): reason="Cygwin fails here with ld errors", strict=False, ) -@pytest.mark.usefixtures("package_simple_setuptools_ext") +@pytest.mark.parametrize("package", {"simple_setuptools_ext"}, indirect=True) +@pytest.mark.usefixtures("package") def test_pep518_pip(isolated): isolated.install("-v", ".") From d0b865a7fd8771f32328affc0879fa2ae0d167f0 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 19:08:29 +0700 Subject: [PATCH 3/9] Add a helper for no-build-isolation Signed-off-by: Cristian Le --- tests/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 96033c73..acf9ef80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -153,6 +153,21 @@ def install(self, *args: str, isolated: bool = True) -> None: isolated_flags = "" if isolated else ["--no-build-isolation"] self.module("pip", "install", *isolated_flags, *args) + def prepare_no_build_isolation(self) -> None: + if not self.wheelhouse: + msg = "Wheelhouse was not setup." + raise ValueError(msg) + + ninja = [ + "ninja" for f in self.wheelhouse.iterdir() if f.name.startswith("ninja-") + ] + cmake = [ + "cmake" for f in self.wheelhouse.iterdir() if f.name.startswith("cmake-") + ] + + self.install("pip>23") + self.install("scikit-build-core", *ninja, *cmake) + @pytest.fixture def isolated(tmp_path: Path, pep518_wheelhouse: Path) -> VEnv: From 8948319b2eb451b12958f39660f02ee831c43fb5 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 19:28:34 +0700 Subject: [PATCH 4/9] Introduce editable and insolation fixtures Signed-off-by: Cristian Le --- tests/conftest.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index acf9ef80..c10b65d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -241,6 +241,55 @@ def package_simple_pyproject_ext( return package +@dataclasses.dataclass(frozen=True) +class Isolate: + state: bool + flags: list[str] + + +@pytest.fixture(params=[True, False], ids=["isolated", "not_isolated"]) +def isolate(request: pytest.FixtureRequest, isolated: VEnv) -> Isolate: + isolate_request = request.param + assert isinstance(isolate_request, bool) + if not isolate_request: + isolated.prepare_no_build_isolation() + flags = [] + if not isolate_request: + flags.append("--no-build-isolation") + return Isolate( + state=isolate_request, + flags=flags, + ) + + +@dataclasses.dataclass(frozen=True) +class Editable: + mode: str | None + config_settings: list[str] + + @property + def flags(self) -> list[str]: + if not self.mode: + return self.config_settings + return [*self.config_settings, "-e"] + + +@pytest.fixture(params=[pytest.param(None, id="not_editable"), "redirect", "inplace"]) +def editable(request: pytest.FixtureRequest) -> Editable: + editable_mode = request.param + assert editable_mode is None or isinstance(editable_mode, str) + config_settings = [] + if editable_mode: + config_settings.append(f"--config-settings=editable.mode={editable_mode}") + if editable_mode != "inplace": + build_dir = "build/{wheel_tag}" + config_settings.append(f"--config-settings=build-dir={build_dir}") + return Editable( + mode=editable_mode, + config_settings=config_settings, + ) + + def which_mock(name: str) -> str | None: if name in {"ninja", "ninja-build", "cmake3", "samu", "gmake", "make"}: return None From 6971028761d13c675a211e0cf90f710ce802f38a Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 20:07:38 +0700 Subject: [PATCH 5/9] Consolidate isolate and editable_mode fixture usages Signed-off-by: Cristian Le --- tests/test_editable.py | 147 ++++++++------------------------- tests/test_pyproject_pep660.py | 36 +++----- 2 files changed, 47 insertions(+), 136 deletions(-) diff --git a/tests/test_editable.py b/tests/test_editable.py index c32852e1..54e0e7f1 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -6,13 +6,12 @@ from pathlib import Path import pytest -from conftest import PackageInfo, VEnv, process_package +from conftest import PackageInfo, process_package @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.parametrize("isolate", [True, False], ids=["isolated", "notisolated"]) @pytest.mark.parametrize( "py_pkg", [ @@ -30,17 +29,12 @@ sys.version_info[:2] == (3, 9), reason="Python 3.9 not supported yet" ) def test_navigate_editable(isolated, isolate, py_pkg): - isolate_args = ["--no-build-isolation"] if not isolate else [] - isolated.install("pip>=23") - if not isolate: - isolated.install("scikit-build-core") - if py_pkg: init_py = Path("python/shared_pkg/data/__init__.py") init_py.touch() isolated.install( - "-v", "--config-settings=build-dir=build/{wheel_tag}", *isolate_args, "-e", "." + "-v", "--config-settings=build-dir=build/{wheel_tag}", *isolate.flags, "-e", "." ) value = isolated.execute("import shared_pkg; shared_pkg.call_c_method()") @@ -59,17 +53,9 @@ def test_navigate_editable(isolated, isolate, py_pkg): @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.parametrize( - ("editable", "editable_mode"), [(False, ""), (True, "redirect"), (True, "inplace")] -) -def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): - editable_flag = ["-e"] if editable else [] - - config_mode_flags = [] - if editable: - config_mode_flags.append(f"--config-settings=editable.mode={editable_mode}") - if editable_mode != "inplace": - config_mode_flags.append("--config-settings=build-dir=build/{wheel_tag}") +@pytest.mark.parametrize("isolate", {False}, indirect=True) +def test_cython_pxd(monkeypatch, tmp_path, editable, isolated, isolate): + isolated.install("cython") package1 = PackageInfo( "cython_pxd_editable/pkg1", @@ -78,21 +64,10 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): tmp_path1.mkdir() process_package(package1, tmp_path1, monkeypatch) - ninja = [ - "ninja" for f in isolated.wheelhouse.iterdir() if f.name.startswith("ninja-") - ] - cmake = [ - "cmake" for f in isolated.wheelhouse.iterdir() if f.name.startswith("cmake-") - ] - - isolated.install("pip>23") - isolated.install("cython", "scikit-build-core", *ninja, *cmake) - isolated.install( "-v", - *config_mode_flags, - "--no-build-isolation", - *editable_flag, + *isolate.flags, + *editable.flags, ".", ) @@ -105,9 +80,8 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): isolated.install( "-v", - *config_mode_flags, - "--no-build-isolation", - *editable_flag, + *isolate.flags, + *editable.flags, ".", ) @@ -116,11 +90,9 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): @pytest.mark.configure @pytest.mark.integration @pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.parametrize("isolate", {False}, indirect=True) @pytest.mark.usefixtures("package") -def test_install_dir(isolated): - isolated.install("pip>=23") - isolated.install("scikit-build-core") - +def test_install_dir(isolated, isolate): settings_overrides = { "build-dir": "build/{wheel_tag}", "wheel.install-dir": "other_pkg", @@ -141,7 +113,7 @@ def test_install_dir(isolated): isolated.install( "-v", *[f"--config-settings={k}={v}" for k, v in settings_overrides.items()], - "--no-build-isolation", + *isolate.flags, "-e", ".", ) @@ -163,67 +135,25 @@ def test_install_dir(isolated): assert not failed_c_module.exists() -def _setup_package_for_editable_layout_tests( - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, - editable: bool, - editable_mode: str, - isolated: VEnv, -) -> None: - editable_flag = ["-e"] if editable else [] - - config_mode_flags = [] - if editable: - config_mode_flags.append(f"--config-settings=editable.mode={editable_mode}") - if editable_mode != "inplace": - config_mode_flags.append("--config-settings=build-dir=build/{wheel_tag}") - - # Use a context so that we only change into the directory up until the point where - # we run the editable install. We do not want to be in that directory when importing - # to avoid importing the source directory instead of the installed package. - with monkeypatch.context() as m: - package = PackageInfo("importlib_editable") - process_package(package, tmp_path, m) - - assert isolated.wheelhouse - - ninja = [ - "ninja" - for f in isolated.wheelhouse.iterdir() - if f.name.startswith("ninja-") - ] - cmake = [ - "cmake" - for f in isolated.wheelhouse.iterdir() - if f.name.startswith("cmake-") - ] - - isolated.install("pip>23") - isolated.install("scikit-build-core", *ninja, *cmake) - - isolated.install( - "-v", - *config_mode_flags, - "--no-build-isolation", - *editable_flag, - ".", - ) - - @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.parametrize( - ("editable", "editable_mode"), [(False, ""), (True, "redirect"), (True, "inplace")] -) -def test_direct_import(monkeypatch, tmp_path, editable, editable_mode, isolated): +@pytest.mark.parametrize("package", {"importlib_editable"}, indirect=True) +@pytest.mark.usefixtures("package") +def test_direct_import(editable, isolated, monkeypatch, tmp_path): # TODO: Investigate these failures - if platform.system() == "Windows" and editable_mode == "inplace": + if platform.system() == "Windows" and editable.mode == "inplace": pytest.xfail("Windows fails to import the top-level extension module") - _setup_package_for_editable_layout_tests( - monkeypatch, tmp_path, editable, editable_mode, isolated + isolated.install( + "-v", + *editable.flags, + ".", ) + + # Exit the package path before execution + # TODO: Use src-layout instead + monkeypatch.chdir(tmp_path) isolated.execute("import pkg") isolated.execute("import pmod") isolated.execute("import emod") @@ -232,32 +162,27 @@ def test_direct_import(monkeypatch, tmp_path, editable, editable_mode, isolated) @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.parametrize( - ("editable", "editable_mode"), - [ - (False, ""), - pytest.param( - True, - "redirect", - marks=pytest.mark.xfail, - ), - (True, "inplace"), - ], -) -def test_importlib_resources(monkeypatch, tmp_path, editable, editable_mode, isolated): +@pytest.mark.parametrize("package", {"importlib_editable"}, indirect=True) +@pytest.mark.usefixtures("package") +def test_importlib_resources(editable, isolated, monkeypatch, tmp_path): if sys.version_info < (3, 9): pytest.skip("importlib.resources.files is introduced in Python 3.9") # TODO: Investigate these failures - if editable_mode == "redirect": + if editable.mode == "redirect": pytest.xfail("Redirect mode is at navigating importlib.resources.files") - if platform.system() == "Windows" and editable_mode == "inplace": + if platform.system() == "Windows" and editable.mode == "inplace": pytest.xfail("Windows fails to import the top-level extension module") - _setup_package_for_editable_layout_tests( - monkeypatch, tmp_path, editable, editable_mode, isolated + isolated.install( + "-v", + *editable.flags, + ".", ) + # Exit the package path before execution + # TODO: Use src-layout instead + monkeypatch.chdir(tmp_path) isolated.execute( textwrap.dedent( """ diff --git a/tests/test_pyproject_pep660.py b/tests/test_pyproject_pep660.py index eb6303e1..f083c860 100644 --- a/tests/test_pyproject_pep660.py +++ b/tests/test_pyproject_pep660.py @@ -1,6 +1,5 @@ import sys import sysconfig -import typing import zipfile from pathlib import Path @@ -9,11 +8,6 @@ from scikit_build_core.build import build_editable -@pytest.fixture(params=["redirect", "inplace"]) -def editable_mode(request: pytest.FixtureRequest) -> str: - return typing.cast("str", request.param) - - # TODO: figure out why gmake is reporting no rule to make simple_pure.cpp @pytest.mark.compile @pytest.mark.configure @@ -23,10 +17,11 @@ def editable_mode(request: pytest.FixtureRequest) -> str: reason="No idea why this fails on Cygwin", ) @pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.parametrize("editable", ["redirect", "inplace"], indirect=True) @pytest.mark.usefixtures("package") -def test_pep660_wheel(editable_mode: str, tmp_path: Path): +def test_pep660_wheel(editable, tmp_path: Path): dist = tmp_path / "dist" - out = build_editable(str(dist), {"editable.mode": editable_mode}) + out = build_editable(str(dist), {"editable.mode": editable.mode}) (wheel,) = dist.glob("simplest-0.0.1-*.whl") assert wheel == dist / out @@ -36,9 +31,9 @@ def test_pep660_wheel(editable_mode: str, tmp_path: Path): with zf.open("simplest-0.0.1.dist-info/METADATA") as f: metadata = f.read().decode("utf-8") - assert len(file_names) == 4 if editable_mode == "redirect" else 2 + assert len(file_names) == 4 if editable.mode == "redirect" else 2 assert "simplest-0.0.1.dist-info" in file_names - if editable_mode == "redirect": + if editable.mode == "redirect": assert "simplest" in file_names assert "_simplest_editable.py" in file_names else: @@ -54,23 +49,14 @@ def test_pep660_wheel(editable_mode: str, tmp_path: Path): @pytest.mark.compile @pytest.mark.configure @pytest.mark.integration -@pytest.mark.parametrize("isolate", [True, False], ids=["isolated", "not_isolated"]) @pytest.mark.parametrize("package", {"simplest_c"}, indirect=True) +@pytest.mark.parametrize("editable", ["redirect", "inplace"], indirect=True) @pytest.mark.usefixtures("package") -def test_pep660_pip_isolated(isolated, isolate, editable_mode: str): - isolate_args = ["--no-build-isolation"] if not isolate else [] - isolated.install("pip>=23") - if not isolate: - isolated.install("scikit-build-core") - - build_dir = "" if editable_mode == "inplace" else "build/{wheel_tag}" - +def test_pep660_pip_isolated(isolated, isolate, editable): isolated.install( "-v", - f"--config-settings=build-dir={build_dir}", - f"--config-settings=editable.mode={editable_mode}", - *isolate_args, - "-e", + *isolate.flags, + *editable.flags, ".", ) @@ -87,7 +73,7 @@ def test_pep660_pip_isolated(isolated, isolate, editable_mode: str): assert any(x.samefile(python_source) for x in locations) cmake_install = isolated.platlib.joinpath("simplest").resolve() - if editable_mode == "redirect": + if editable.mode == "redirect": # Second path is from the CMake install assert any(x.samefile(cmake_install) for x in locations) @@ -106,7 +92,7 @@ def test_pep660_pip_isolated(isolated, isolate, editable_mode: str): else: ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") - module_source = python_source if editable_mode == "inplace" else cmake_install + module_source = python_source if editable.mode == "inplace" else cmake_install module_file = module_source / f"_module{ext_suffix}" # Windows FindPython may produce the wrong extension From d06aa173d21ba85be2c0ddb12f58a9756adf6bf4 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 20:21:01 +0700 Subject: [PATCH 6/9] Move importlib_edtiable to src-layout Signed-off-by: Cristian Le --- tests/packages/importlib_editable/pyproject.toml | 1 + .../importlib_editable/{ => src}/CMakeLists.txt | 0 tests/packages/importlib_editable/{ => src}/emod.c | 0 tests/packages/importlib_editable/{ => src}/emod.pyi | 0 .../importlib_editable/{ => src}/pkg/CMakeLists.txt | 0 .../importlib_editable/{ => src}/pkg/__init__.py | 0 .../packages/importlib_editable/{ => src}/pkg/emod_a.c | 0 .../importlib_editable/{ => src}/pkg/emod_a.pyi | 0 .../importlib_editable/{ => src}/pkg/pmod_a.py | 0 .../{ => src}/pkg/sub_a/CMakeLists.txt | 0 .../importlib_editable/{ => src}/pkg/sub_a/__init__.py | 0 .../importlib_editable/{ => src}/pkg/sub_a/emod_b.c | 0 .../importlib_editable/{ => src}/pkg/sub_a/emod_b.pyi | 0 .../importlib_editable/{ => src}/pkg/sub_a/pmod_b.py | 0 .../{ => src}/pkg/sub_b/CMakeLists.txt | 0 .../importlib_editable/{ => src}/pkg/sub_b/__init__.py | 0 .../importlib_editable/{ => src}/pkg/sub_b/emod_c.c | 0 .../importlib_editable/{ => src}/pkg/sub_b/emod_c.pyi | 0 .../importlib_editable/{ => src}/pkg/sub_b/pmod_c.py | 0 .../{ => src}/pkg/sub_b/sub_c/CMakeLists.txt | 0 .../{ => src}/pkg/sub_b/sub_c/__init__.py | 0 .../{ => src}/pkg/sub_b/sub_c/emod_d.c | 0 .../{ => src}/pkg/sub_b/sub_c/emod_d.pyi | 0 .../{ => src}/pkg/sub_b/sub_c/pmod_d.py | 0 .../{ => src}/pkg/sub_b/sub_d/CMakeLists.txt | 0 .../{ => src}/pkg/sub_b/sub_d/__init__.py | 0 .../{ => src}/pkg/sub_b/sub_d/emod_e.c | 0 .../{ => src}/pkg/sub_b/sub_d/emod_e.pyi | 0 .../{ => src}/pkg/sub_b/sub_d/pmod_e.py | 0 tests/packages/importlib_editable/{ => src}/pmod.py | 0 tests/test_editable.py | 10 ++-------- 31 files changed, 3 insertions(+), 8 deletions(-) rename tests/packages/importlib_editable/{ => src}/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/emod.c (100%) rename tests/packages/importlib_editable/{ => src}/emod.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/pkg/__init__.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/emod_a.c (100%) rename tests/packages/importlib_editable/{ => src}/pkg/emod_a.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/pmod_a.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_a/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_a/__init__.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_a/emod_b.c (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_a/emod_b.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_a/pmod_b.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/__init__.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/emod_c.c (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/emod_c.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/pmod_c.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_c/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_c/__init__.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_c/emod_d.c (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_c/emod_d.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_c/pmod_d.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_d/CMakeLists.txt (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_d/__init__.py (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_d/emod_e.c (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_d/emod_e.pyi (100%) rename tests/packages/importlib_editable/{ => src}/pkg/sub_b/sub_d/pmod_e.py (100%) rename tests/packages/importlib_editable/{ => src}/pmod.py (100%) diff --git a/tests/packages/importlib_editable/pyproject.toml b/tests/packages/importlib_editable/pyproject.toml index dae7eb5a..b5884f93 100644 --- a/tests/packages/importlib_editable/pyproject.toml +++ b/tests/packages/importlib_editable/pyproject.toml @@ -7,4 +7,5 @@ name = "pkg" version = "0.0.1" [tool.scikit-build] +cmake.source-dir = "src" build-dir = "build/{wheel_tag}" diff --git a/tests/packages/importlib_editable/CMakeLists.txt b/tests/packages/importlib_editable/src/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/CMakeLists.txt rename to tests/packages/importlib_editable/src/CMakeLists.txt diff --git a/tests/packages/importlib_editable/emod.c b/tests/packages/importlib_editable/src/emod.c similarity index 100% rename from tests/packages/importlib_editable/emod.c rename to tests/packages/importlib_editable/src/emod.c diff --git a/tests/packages/importlib_editable/emod.pyi b/tests/packages/importlib_editable/src/emod.pyi similarity index 100% rename from tests/packages/importlib_editable/emod.pyi rename to tests/packages/importlib_editable/src/emod.pyi diff --git a/tests/packages/importlib_editable/pkg/CMakeLists.txt b/tests/packages/importlib_editable/src/pkg/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/pkg/CMakeLists.txt rename to tests/packages/importlib_editable/src/pkg/CMakeLists.txt diff --git a/tests/packages/importlib_editable/pkg/__init__.py b/tests/packages/importlib_editable/src/pkg/__init__.py similarity index 100% rename from tests/packages/importlib_editable/pkg/__init__.py rename to tests/packages/importlib_editable/src/pkg/__init__.py diff --git a/tests/packages/importlib_editable/pkg/emod_a.c b/tests/packages/importlib_editable/src/pkg/emod_a.c similarity index 100% rename from tests/packages/importlib_editable/pkg/emod_a.c rename to tests/packages/importlib_editable/src/pkg/emod_a.c diff --git a/tests/packages/importlib_editable/pkg/emod_a.pyi b/tests/packages/importlib_editable/src/pkg/emod_a.pyi similarity index 100% rename from tests/packages/importlib_editable/pkg/emod_a.pyi rename to tests/packages/importlib_editable/src/pkg/emod_a.pyi diff --git a/tests/packages/importlib_editable/pkg/pmod_a.py b/tests/packages/importlib_editable/src/pkg/pmod_a.py similarity index 100% rename from tests/packages/importlib_editable/pkg/pmod_a.py rename to tests/packages/importlib_editable/src/pkg/pmod_a.py diff --git a/tests/packages/importlib_editable/pkg/sub_a/CMakeLists.txt b/tests/packages/importlib_editable/src/pkg/sub_a/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_a/CMakeLists.txt rename to tests/packages/importlib_editable/src/pkg/sub_a/CMakeLists.txt diff --git a/tests/packages/importlib_editable/pkg/sub_a/__init__.py b/tests/packages/importlib_editable/src/pkg/sub_a/__init__.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_a/__init__.py rename to tests/packages/importlib_editable/src/pkg/sub_a/__init__.py diff --git a/tests/packages/importlib_editable/pkg/sub_a/emod_b.c b/tests/packages/importlib_editable/src/pkg/sub_a/emod_b.c similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_a/emod_b.c rename to tests/packages/importlib_editable/src/pkg/sub_a/emod_b.c diff --git a/tests/packages/importlib_editable/pkg/sub_a/emod_b.pyi b/tests/packages/importlib_editable/src/pkg/sub_a/emod_b.pyi similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_a/emod_b.pyi rename to tests/packages/importlib_editable/src/pkg/sub_a/emod_b.pyi diff --git a/tests/packages/importlib_editable/pkg/sub_a/pmod_b.py b/tests/packages/importlib_editable/src/pkg/sub_a/pmod_b.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_a/pmod_b.py rename to tests/packages/importlib_editable/src/pkg/sub_a/pmod_b.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/CMakeLists.txt b/tests/packages/importlib_editable/src/pkg/sub_b/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/CMakeLists.txt rename to tests/packages/importlib_editable/src/pkg/sub_b/CMakeLists.txt diff --git a/tests/packages/importlib_editable/pkg/sub_b/__init__.py b/tests/packages/importlib_editable/src/pkg/sub_b/__init__.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/__init__.py rename to tests/packages/importlib_editable/src/pkg/sub_b/__init__.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/emod_c.c b/tests/packages/importlib_editable/src/pkg/sub_b/emod_c.c similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/emod_c.c rename to tests/packages/importlib_editable/src/pkg/sub_b/emod_c.c diff --git a/tests/packages/importlib_editable/pkg/sub_b/emod_c.pyi b/tests/packages/importlib_editable/src/pkg/sub_b/emod_c.pyi similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/emod_c.pyi rename to tests/packages/importlib_editable/src/pkg/sub_b/emod_c.pyi diff --git a/tests/packages/importlib_editable/pkg/sub_b/pmod_c.py b/tests/packages/importlib_editable/src/pkg/sub_b/pmod_c.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/pmod_c.py rename to tests/packages/importlib_editable/src/pkg/sub_b/pmod_c.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_c/CMakeLists.txt b/tests/packages/importlib_editable/src/pkg/sub_b/sub_c/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_c/CMakeLists.txt rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_c/CMakeLists.txt diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_c/__init__.py b/tests/packages/importlib_editable/src/pkg/sub_b/sub_c/__init__.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_c/__init__.py rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_c/__init__.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_c/emod_d.c b/tests/packages/importlib_editable/src/pkg/sub_b/sub_c/emod_d.c similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_c/emod_d.c rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_c/emod_d.c diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_c/emod_d.pyi b/tests/packages/importlib_editable/src/pkg/sub_b/sub_c/emod_d.pyi similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_c/emod_d.pyi rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_c/emod_d.pyi diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_c/pmod_d.py b/tests/packages/importlib_editable/src/pkg/sub_b/sub_c/pmod_d.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_c/pmod_d.py rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_c/pmod_d.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_d/CMakeLists.txt b/tests/packages/importlib_editable/src/pkg/sub_b/sub_d/CMakeLists.txt similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_d/CMakeLists.txt rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_d/CMakeLists.txt diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_d/__init__.py b/tests/packages/importlib_editable/src/pkg/sub_b/sub_d/__init__.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_d/__init__.py rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_d/__init__.py diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_d/emod_e.c b/tests/packages/importlib_editable/src/pkg/sub_b/sub_d/emod_e.c similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_d/emod_e.c rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_d/emod_e.c diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_d/emod_e.pyi b/tests/packages/importlib_editable/src/pkg/sub_b/sub_d/emod_e.pyi similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_d/emod_e.pyi rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_d/emod_e.pyi diff --git a/tests/packages/importlib_editable/pkg/sub_b/sub_d/pmod_e.py b/tests/packages/importlib_editable/src/pkg/sub_b/sub_d/pmod_e.py similarity index 100% rename from tests/packages/importlib_editable/pkg/sub_b/sub_d/pmod_e.py rename to tests/packages/importlib_editable/src/pkg/sub_b/sub_d/pmod_e.py diff --git a/tests/packages/importlib_editable/pmod.py b/tests/packages/importlib_editable/src/pmod.py similarity index 100% rename from tests/packages/importlib_editable/pmod.py rename to tests/packages/importlib_editable/src/pmod.py diff --git a/tests/test_editable.py b/tests/test_editable.py index 54e0e7f1..b6cee1c2 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -140,7 +140,7 @@ def test_install_dir(isolated, isolate): @pytest.mark.integration @pytest.mark.parametrize("package", {"importlib_editable"}, indirect=True) @pytest.mark.usefixtures("package") -def test_direct_import(editable, isolated, monkeypatch, tmp_path): +def test_direct_import(editable, isolated): # TODO: Investigate these failures if platform.system() == "Windows" and editable.mode == "inplace": pytest.xfail("Windows fails to import the top-level extension module") @@ -151,9 +151,6 @@ def test_direct_import(editable, isolated, monkeypatch, tmp_path): ".", ) - # Exit the package path before execution - # TODO: Use src-layout instead - monkeypatch.chdir(tmp_path) isolated.execute("import pkg") isolated.execute("import pmod") isolated.execute("import emod") @@ -164,7 +161,7 @@ def test_direct_import(editable, isolated, monkeypatch, tmp_path): @pytest.mark.integration @pytest.mark.parametrize("package", {"importlib_editable"}, indirect=True) @pytest.mark.usefixtures("package") -def test_importlib_resources(editable, isolated, monkeypatch, tmp_path): +def test_importlib_resources(editable, isolated): if sys.version_info < (3, 9): pytest.skip("importlib.resources.files is introduced in Python 3.9") @@ -180,9 +177,6 @@ def test_importlib_resources(editable, isolated, monkeypatch, tmp_path): ".", ) - # Exit the package path before execution - # TODO: Use src-layout instead - monkeypatch.chdir(tmp_path) isolated.execute( textwrap.dedent( """ From ccde78a6a896f33c7250cadc3b62db9522bdeb6b Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Sun, 14 Dec 2025 20:32:02 +0700 Subject: [PATCH 7/9] Add another fixture to consolidate multi-package installs Signed-off-by: Cristian Le --- tests/conftest.py | 24 +++++++++++++++++++++-- tests/test_editable.py | 43 ++++++++++++++---------------------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c10b65d5..29effd22 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ import subprocess import sys import sysconfig +from collections.abc import Iterable from importlib import metadata from pathlib import Path from typing import Any, Literal, overload @@ -207,9 +208,12 @@ def source_date_epoch(self) -> str: def process_package( - package: PackageInfo, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + package: PackageInfo, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + pkg_path: str = "pkg", ) -> None: - package_dir = tmp_path / "pkg" + package_dir = tmp_path / pkg_path shutil.copytree(DIR / "packages" / package.name, package_dir) monkeypatch.chdir(package_dir) @@ -226,6 +230,22 @@ def package( return package +@pytest.fixture +def multiple_packages( + request: pytest.FixtureRequest, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> list[PackageInfo]: + package_names = request.param + assert isinstance(package_names, Iterable) + packages = [] + for pkg_name in package_names: + pkg = PackageInfo(pkg_name) + assert (DIR / "packages" / pkg.name).exists() + process_package(pkg, tmp_path, monkeypatch, pkg_path=pkg.name) + packages.append(pkg) + monkeypatch.chdir(tmp_path) + return packages + + @pytest.fixture def package_simple_pyproject_ext( tmp_path: Path, monkeypatch: pytest.MonkeyPatch diff --git a/tests/test_editable.py b/tests/test_editable.py index b6cee1c2..c71b28a2 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -6,7 +6,6 @@ from pathlib import Path import pytest -from conftest import PackageInfo, process_package @pytest.mark.compile @@ -54,36 +53,22 @@ def test_navigate_editable(isolated, isolate, py_pkg): @pytest.mark.configure @pytest.mark.integration @pytest.mark.parametrize("isolate", {False}, indirect=True) -def test_cython_pxd(monkeypatch, tmp_path, editable, isolated, isolate): +@pytest.mark.parametrize( + "multiple_packages", + [["cython_pxd_editable/pkg1", "cython_pxd_editable/pkg2"]], + indirect=True, +) +def test_cython_pxd(multiple_packages, editable, isolated, isolate): isolated.install("cython") - package1 = PackageInfo( - "cython_pxd_editable/pkg1", - ) - tmp_path1 = tmp_path / "pkg1" - tmp_path1.mkdir() - process_package(package1, tmp_path1, monkeypatch) - - isolated.install( - "-v", - *isolate.flags, - *editable.flags, - ".", - ) - - package2 = PackageInfo( - "cython_pxd_editable/pkg2", - ) - tmp_path2 = tmp_path / "pkg2" - tmp_path2.mkdir() - process_package(package2, tmp_path2, monkeypatch) - - isolated.install( - "-v", - *isolate.flags, - *editable.flags, - ".", - ) + # install the packages in order with one dependent on the other + for package in multiple_packages: + isolated.install( + "-v", + *isolate.flags, + *editable.flags, + package.name, + ) @pytest.mark.compile From c094c95dc907a36dde2d6e7b8367de42d98ee576 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Mon, 15 Dec 2025 11:57:05 +0700 Subject: [PATCH 8/9] Try to make the test paths more compact Signed-off-by: Cristian Le --- tests/conftest.py | 35 ++++++++++++++++++++--------------- tests/test_editable.py | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 29effd22..79035d80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -185,6 +185,7 @@ def virtualenv(tmp_path: Path) -> VEnv: @dataclasses.dataclass(frozen=True) class PackageInfo: name: str + workdir: Path sdist_hash38: str | None = None sdist_hash39: str | None = None sdist_dated_hash39: str | None = None @@ -209,55 +210,59 @@ def source_date_epoch(self) -> str: def process_package( package: PackageInfo, - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, - pkg_path: str = "pkg", ) -> None: - package_dir = tmp_path / pkg_path - shutil.copytree(DIR / "packages" / package.name, package_dir) - monkeypatch.chdir(package_dir) + pkg_src = DIR / "packages" / package.name + assert pkg_src.exists() + shutil.copytree(pkg_src, package.workdir, dirs_exist_ok=True) + monkeypatch.chdir(package.workdir) @pytest.fixture def package( - request: pytest.FixtureRequest, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + request: pytest.FixtureRequest, + tmp_path_factory: pytest.TempPathFactory, + monkeypatch: pytest.MonkeyPatch, ) -> PackageInfo: pkg_name = request.param assert isinstance(pkg_name, str) - package = PackageInfo(pkg_name) + package = PackageInfo(pkg_name, tmp_path_factory.mktemp("pkg")) assert (DIR / "packages" / package.name).exists() - process_package(package, tmp_path, monkeypatch) + process_package(package, monkeypatch) return package @pytest.fixture def multiple_packages( - request: pytest.FixtureRequest, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + request: pytest.FixtureRequest, + tmp_path_factory: pytest.TempPathFactory, + monkeypatch: pytest.MonkeyPatch, ) -> list[PackageInfo]: package_names = request.param assert isinstance(package_names, Iterable) packages = [] for pkg_name in package_names: - pkg = PackageInfo(pkg_name) - assert (DIR / "packages" / pkg.name).exists() - process_package(pkg, tmp_path, monkeypatch, pkg_path=pkg.name) + pkg = PackageInfo(pkg_name, tmp_path_factory.mktemp("pkg")) + process_package(pkg, monkeypatch) packages.append(pkg) - monkeypatch.chdir(tmp_path) + monkeypatch.chdir(tmp_path_factory.getbasetemp()) return packages @pytest.fixture def package_simple_pyproject_ext( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch + tmp_path_factory: pytest.TempPathFactory, + monkeypatch: pytest.MonkeyPatch, ) -> PackageInfo: package = PackageInfo( "simple_pyproject_ext", + tmp_path_factory.mktemp("pkg"), "71b4e95854ef8d04886758d24d18fe55ebe63648310acf58c7423387cca73508", "ed930179fbf5adc2e71a64a6f9686c61fdcce477c85bc94dd51598641be886a7", "0178462b64b4eb9c41ae70eb413a9cc111c340e431b240af1b218fe81b0c2ecb", "de79895a9d5c2112257715214ab419d3635e841716655e8a55390e5d52445819", ) - process_package(package, tmp_path, monkeypatch) + process_package(package, monkeypatch) return package diff --git a/tests/test_editable.py b/tests/test_editable.py index c71b28a2..04249def 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -67,7 +67,7 @@ def test_cython_pxd(multiple_packages, editable, isolated, isolate): "-v", *isolate.flags, *editable.flags, - package.name, + str(package.workdir), ) From 92f0fd3bdcdf058e8936e5910e6bc8b986d0ae53 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 16 Dec 2025 14:20:18 +0700 Subject: [PATCH 9/9] Use Literal for editable mode Signed-off-by: Cristian Le --- tests/conftest.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 79035d80..a560816e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,11 @@ else: import tomllib +if sys.version_info < (3, 10): + from typing_extensions import TypeGuard +else: + from typing import TypeGuard + import pytest from packaging.requirements import Requirement @@ -287,9 +292,13 @@ def isolate(request: pytest.FixtureRequest, isolated: VEnv) -> Isolate: ) +def is_editable_mode(maybe_mode: str) -> TypeGuard[Literal["redirect", "inplace"]]: + return maybe_mode in {"redirect", "inplace"} + + @dataclasses.dataclass(frozen=True) class Editable: - mode: str | None + mode: Literal["redirect", "inplace"] | None config_settings: list[str] @property @@ -302,7 +311,7 @@ def flags(self) -> list[str]: @pytest.fixture(params=[pytest.param(None, id="not_editable"), "redirect", "inplace"]) def editable(request: pytest.FixtureRequest) -> Editable: editable_mode = request.param - assert editable_mode is None or isinstance(editable_mode, str) + assert editable_mode is None or is_editable_mode(editable_mode) config_settings = [] if editable_mode: config_settings.append(f"--config-settings=editable.mode={editable_mode}")