Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 105 additions & 127 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,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
Expand Down Expand Up @@ -153,6 +159,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:
Expand All @@ -169,6 +190,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
Expand All @@ -192,158 +214,114 @@ def source_date_epoch(self) -> str:


def process_package(
package: PackageInfo, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
package: PackageInfo,
monkeypatch: pytest.MonkeyPatch,
) -> None:
package_dir = tmp_path / "pkg"
shutil.copytree(DIR / "packages" / package.name, package_dir)
monkeypatch.chdir(package_dir)


@pytest.fixture
def package_simple_pyproject_ext(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> PackageInfo:
package = PackageInfo(
"simple_pyproject_ext",
"71b4e95854ef8d04886758d24d18fe55ebe63648310acf58c7423387cca73508",
"ed930179fbf5adc2e71a64a6f9686c61fdcce477c85bc94dd51598641be886a7",
"0178462b64b4eb9c41ae70eb413a9cc111c340e431b240af1b218fe81b0c2ecb",
"de79895a9d5c2112257715214ab419d3635e841716655e8a55390e5d52445819",
)
process_package(package, tmp_path, monkeypatch)
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
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_mixed_setuptools(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
def package(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So this is now not just a factory for a PackageInfo, but takes care of staging the named package sources into a fresh temporary directory and changing into that directory, which imposes the normalized package layout that motivates all of the file moves. Test functions only need to capture the fixture as an argument if they want to check the hashes. I wish I could think of an alternative name to suggest. This is more readable, overall, I think. The parameterization is not intuitive, but the examples are clear.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I could think of an alternative name to suggest.

Always welcome if you have naming candidates.

Test functions only need to capture the fixture as an argument if they want to check the hashes

Could expose some functionality for it to avoid needing to ignore the fixture in parameters, just haven't found a worthy one to do so with.

request: pytest.FixtureRequest,
tmp_path_factory: pytest.TempPathFactory,
monkeypatch: pytest.MonkeyPatch,
) -> PackageInfo:
package = PackageInfo("mixed_setuptools")
process_package(package, tmp_path, monkeypatch)
pkg_name = request.param
assert isinstance(pkg_name, str)
package = PackageInfo(pkg_name, tmp_path_factory.mktemp("pkg"))
assert (DIR / "packages" / package.name).exists()
process_package(package, 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
def multiple_packages(
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, tmp_path_factory.mktemp("pkg"))
process_package(pkg, monkeypatch)
packages.append(pkg)
monkeypatch.chdir(tmp_path_factory.getbasetemp())
return packages


@pytest.fixture
def package_dynamic_metadata(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
def package_simple_pyproject_ext(
tmp_path_factory: pytest.TempPathFactory,
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",
"simple_pyproject_ext",
tmp_path_factory.mktemp("pkg"),
"71b4e95854ef8d04886758d24d18fe55ebe63648310acf58c7423387cca73508",
"ed930179fbf5adc2e71a64a6f9686c61fdcce477c85bc94dd51598641be886a7",
"0178462b64b4eb9c41ae70eb413a9cc111c340e431b240af1b218fe81b0c2ecb",
"de79895a9d5c2112257715214ab419d3635e841716655e8a55390e5d52445819",
)
process_package(package, tmp_path, monkeypatch)
process_package(package, monkeypatch)
return package


@pytest.fixture
def broken_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
package = PackageInfo(
"broken_fallback",
@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,
)
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
def is_editable_mode(maybe_mode: str) -> TypeGuard[Literal["redirect", "inplace"]]:
return maybe_mode in {"redirect", "inplace"}


@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

@dataclasses.dataclass(frozen=True)
class Editable:
mode: Literal["redirect", "inplace"] | None
config_settings: list[str]

@pytest.fixture
def package_pep639_pure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
package = PackageInfo(
"pep639_pure",
@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 is_editable_mode(editable_mode)
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,
)
process_package(package, tmp_path, monkeypatch)
return package


def which_mock(name: str) -> str | None:
Expand Down
1 change: 1 addition & 0 deletions tests/packages/importlib_editable/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ name = "pkg"
version = "0.0.1"

[tool.scikit-build]
cmake.source-dir = "src"
build-dir = "build/{wheel_tag}"
9 changes: 6 additions & 3 deletions tests/test_broken_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
):
Expand All @@ -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]
):
Expand Down
Loading
Loading