Skip to content

Commit 9ad48b1

Browse files
wip: add test for build behaviour across python/setuptools versions
1 parent 5814c76 commit 9ad48b1

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ repos:
2626
- importlib_metadata
2727
- typing-extensions>=4.5
2828
- rich
29+
- build
2930

3031
- repo: https://github.com/scientific-python/cookie
3132
rev: 2025.01.22
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
from __future__ import annotations
2+
3+
import datetime
4+
import os
5+
import pathlib
6+
import subprocess
7+
8+
import build
9+
import pytest
10+
11+
from packaging.utils import parse_wheel_filename
12+
from packaging.version import Version
13+
14+
PROJECT = pathlib.Path(__file__).parent.parent
15+
16+
17+
class UVEnvMaker:
18+
root: pathlib.Path
19+
self_wheel: pathlib.Path
20+
21+
def __init__(self, root: pathlib.Path, self_wheel: pathlib.Path):
22+
self.root = root
23+
self.self_wheel = self_wheel
24+
25+
def make_builder_with(
26+
self, project: pathlib.Path, python: str, setuptools: str
27+
) -> build.ProjectBuilder:
28+
venv = self.root.joinpath(f"{python}--{setuptools}")
29+
venv_python = venv.joinpath("bin/python")
30+
31+
if not venv.exists():
32+
subprocess.run(["uv", "venv", venv, "--python", python], check=True)
33+
subprocess.run(
34+
[
35+
"uv",
36+
"pip",
37+
"install",
38+
"--python",
39+
venv_python,
40+
setuptools,
41+
self.self_wheel,
42+
"wheel",
43+
"pip",
44+
"typing-extensions",
45+
"tomli",
46+
],
47+
check=True,
48+
)
49+
return build.ProjectBuilder(project, python_executable=os.fspath(venv_python))
50+
51+
52+
@pytest.fixture(scope="module")
53+
def self_wheel(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path:
54+
wheelhouse = tmp_path_factory.mktemp("wheels", numbered=False)
55+
project_builder = build.ProjectBuilder(PROJECT)
56+
with pytest.MonkeyPatch.context() as cx:
57+
cx.delenv("SETUPTOOLS_SCM_DEBUG")
58+
dist = project_builder.build(
59+
distribution="editable", output_directory=wheelhouse
60+
)
61+
return pathlib.Path(dist)
62+
63+
64+
@pytest.fixture(scope="module")
65+
def uvmkr(pytestconfig: pytest.Config, self_wheel: pathlib.Path) -> UVEnvMaker:
66+
assert pytestconfig.cache is not None
67+
root = pytestconfig.cache.mkdir("uv_envs")
68+
uv_mkr = UVEnvMaker(root, self_wheel)
69+
return uv_mkr
70+
71+
72+
MIN_SETUPTOOLS = 40
73+
74+
MISSING = {55}
75+
76+
77+
HAS_NO_BUILD_BACKEND = {
78+
f"setuptools~={n}.0" for n in range(MIN_SETUPTOOLS, 61) if n not in MISSING
79+
}
80+
81+
82+
PYTHON_VERSIONS = [
83+
"python3.8",
84+
"python3.9",
85+
"python3.10",
86+
"python3.11",
87+
"python3.12",
88+
"python3.13",
89+
]
90+
91+
92+
PYTHON_COMPAT = {
93+
"setuptools~=40.0": PYTHON_VERSIONS[:1],
94+
"setuptools~=41.0": PYTHON_VERSIONS.copy(),
95+
"setuptools~=42.0": PYTHON_VERSIONS.copy(),
96+
"setuptools~=43.0": PYTHON_VERSIONS.copy(),
97+
"setuptools~=44.0": PYTHON_VERSIONS.copy(),
98+
"setuptools~=45.0": PYTHON_VERSIONS.copy(),
99+
"setuptools~=46.0": PYTHON_VERSIONS.copy(),
100+
"setuptools~=47.0": PYTHON_VERSIONS.copy(),
101+
"setuptools~=48.0": PYTHON_VERSIONS.copy(),
102+
"setuptools~=49.0": PYTHON_VERSIONS.copy(),
103+
"setuptools~=50.0": PYTHON_VERSIONS.copy(),
104+
"setuptools~=51.0": PYTHON_VERSIONS.copy(),
105+
"setuptools~=52.0": PYTHON_VERSIONS.copy(),
106+
"setuptools~=53.0": PYTHON_VERSIONS.copy(),
107+
"setuptools~=54.0": PYTHON_VERSIONS.copy(),
108+
"setuptools~=56.0": PYTHON_VERSIONS.copy(),
109+
"setuptools~=57.0": PYTHON_VERSIONS.copy(),
110+
"setuptools~=58.0": PYTHON_VERSIONS.copy(),
111+
"setuptools~=59.0": PYTHON_VERSIONS.copy(),
112+
"setuptools~=60.0": PYTHON_VERSIONS.copy(),
113+
"setuptools~=61.0": PYTHON_VERSIONS.copy(),
114+
"setuptools~=62.0": PYTHON_VERSIONS.copy(),
115+
"setuptools~=63.0": PYTHON_VERSIONS.copy(),
116+
"setuptools~=64.0": PYTHON_VERSIONS.copy(),
117+
"setuptools~=65.0": PYTHON_VERSIONS.copy(),
118+
"setuptools~=66.0": PYTHON_VERSIONS.copy(),
119+
"setuptools~=67.0": PYTHON_VERSIONS.copy(),
120+
"setuptools~=68.0": PYTHON_VERSIONS.copy(),
121+
"setuptools~=69.0": PYTHON_VERSIONS.copy(),
122+
"setuptools~=70.0": PYTHON_VERSIONS.copy(),
123+
"setuptools~=71.0": PYTHON_VERSIONS.copy(),
124+
"setuptools~=72.0": PYTHON_VERSIONS.copy(),
125+
"setuptools~=73.0": PYTHON_VERSIONS.copy(),
126+
"setuptools~=74.0": PYTHON_VERSIONS.copy(),
127+
"setuptools~=75.0": PYTHON_VERSIONS.copy(),
128+
"setuptools~=76.0": PYTHON_VERSIONS[1:],
129+
}
130+
131+
132+
@pytest.mark.parametrize(
133+
"setuptools_version",
134+
[f"setuptools~={n}.0" for n in range(MIN_SETUPTOOLS, 77) if n not in MISSING],
135+
)
136+
@pytest.mark.parametrize("python", PYTHON_VERSIONS)
137+
@pytest.mark.parametrize(
138+
"backend",
139+
[
140+
pytest.param(None, id="backend-was-not-set"),
141+
"setuptools.build_meta",
142+
"setuptools.build_meta:__legacy__",
143+
],
144+
)
145+
@pytest.mark.parametrize(
146+
"version_scheme", ["calver-by-date", "python-simplified-semver", "guess-next-dev"]
147+
)
148+
def test_setuptools_project_with_versions(
149+
setuptools_version: str,
150+
version_scheme: str,
151+
python: str,
152+
backend: str | None,
153+
uvmkr: UVEnvMaker,
154+
tmp_path: pathlib.Path,
155+
monkeypatch: pytest.MonkeyPatch,
156+
) -> None:
157+
if python == "python3.8" and setuptools_version == "setuptools~=76.0":
158+
pytest.skip("no python38 on setuptools 76")
159+
160+
if python not in PYTHON_COMPAT[setuptools_version]:
161+
pytest.skip(f"no {python} on {setuptools_version}")
162+
163+
if python in ("python3.12", "python3.13") and Version(
164+
setuptools_version.split("~=")[-1]
165+
) <= Version("65.0"):
166+
pytest.skip("no zipimport")
167+
168+
project = tmp_path.joinpath("project")
169+
project.mkdir()
170+
171+
output = tmp_path.joinpath("dist")
172+
output.mkdir()
173+
174+
pyproject = project.joinpath("pyproject.toml")
175+
if backend is not None:
176+
pyproject.write_text(f"""
177+
[build-system]
178+
requires = ["setuptools", "setuptools_scm"]
179+
build-backend = {backend!r}
180+
[package]
181+
name = "test-version"
182+
dynamic = "version"
183+
[tool._setuptools_scm]
184+
""")
185+
else:
186+
pyproject.write_text("""
187+
[build-system]
188+
requires = ["setuptools", "setuptools_scm"]
189+
[package]
190+
name = "test-version"
191+
dynamic = "version"
192+
[tool._setuptools_scm]
193+
""")
194+
setup_py = project.joinpath("setup.py")
195+
setup_py.write_text(f"""if True:
196+
from setuptools import setup
197+
setup(use_scm_version={{"version_scheme": {version_scheme!r}}})
198+
""")
199+
200+
builder = uvmkr.make_builder_with(project, python, setuptools_version)
201+
202+
subprocess.run(
203+
[
204+
"git",
205+
"init",
206+
],
207+
cwd=project,
208+
check=True,
209+
)
210+
211+
result = subprocess.run(
212+
[builder.python_executable, "setup.py", "--version"],
213+
capture_output=True,
214+
cwd=project,
215+
encoding="utf-8",
216+
)
217+
if result.returncode:
218+
print(result.stderr)
219+
assert not result.returncode
220+
try:
221+
version = Version(result.stdout)
222+
except Exception:
223+
print(result.stderr)
224+
raise
225+
else:
226+
verify_version(version_scheme, version)
227+
228+
if setuptools_version not in HAS_NO_BUILD_BACKEND:
229+
with monkeypatch.context() as xy:
230+
xy.chdir(project)
231+
res = builder.build("wheel", output_directory=output)
232+
version = parse_wheel_filename(pathlib.Path(res).name)[1]
233+
verify_version(version_scheme, version)
234+
235+
236+
def verify_version(version_scheme: str, version: Version) -> None:
237+
expect_release: tuple[int, ...]
238+
if version_scheme == "calver-by-date":
239+
today = datetime.datetime.now(datetime.UTC).date() # type: ignore[attr-defined]
240+
expect_release = (today.year % 1000, today.month, today.day, 0)
241+
elif version_scheme == "python-simplified-semver":
242+
expect_release = (0, 0, 1)
243+
else:
244+
expect_release = (0, 1)
245+
assert version.release == expect_release

0 commit comments

Comments
 (0)