Skip to content

Commit 60df4a0

Browse files
committed
Add build constraints tests
1 parent a41924e commit 60df4a0

File tree

2 files changed

+328
-0
lines changed

2 files changed

+328
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
"""Tests for the build constraints feature."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from tests.lib import PipTestEnvironment, TestPipResult, create_test_package_with_setup
10+
11+
12+
def _create_simple_test_package(script: PipTestEnvironment, name: str) -> Path:
13+
"""Create a simple test package with minimal setup."""
14+
return create_test_package_with_setup(
15+
script,
16+
name=name,
17+
version="1.0",
18+
py_modules=[name],
19+
)
20+
21+
22+
def _create_constraints_file(
23+
script: PipTestEnvironment, filename: str, content: str
24+
) -> Path:
25+
"""Create a constraints file with the given content."""
26+
constraints_file = script.scratch_path / filename
27+
constraints_file.write_text(content)
28+
return constraints_file
29+
30+
31+
def _run_pip_install_with_build_constraints(
32+
script: PipTestEnvironment,
33+
project_dir: Path,
34+
build_constraints_file: Path,
35+
extra_args: list[str] | None = None,
36+
expect_error: bool = False,
37+
) -> TestPipResult:
38+
"""Run pip install with build constraints and common arguments."""
39+
args = [
40+
"install",
41+
"--no-cache-dir",
42+
"--build-constraint",
43+
str(build_constraints_file),
44+
"--use-feature",
45+
"build-constraint",
46+
]
47+
48+
if extra_args:
49+
args.extend(extra_args)
50+
51+
args.append(str(project_dir))
52+
53+
return script.pip(*args, expect_error=expect_error)
54+
55+
56+
def _assert_successful_installation(result: TestPipResult, package_name: str) -> None:
57+
"""Assert that the package was successfully installed."""
58+
assert f"Successfully installed {package_name}" in result.stdout
59+
60+
61+
def _run_pip_install_with_build_constraints_no_feature_flag(
62+
script: PipTestEnvironment,
63+
project_dir: Path,
64+
constraints_file: Path,
65+
) -> TestPipResult:
66+
"""Run pip install with build constraints but without the feature flag."""
67+
return script.pip(
68+
"install",
69+
"--build-constraint",
70+
str(constraints_file),
71+
str(project_dir),
72+
expect_error=True,
73+
)
74+
75+
76+
def test_build_constraints_basic_functionality_simple(
77+
script: PipTestEnvironment, tmpdir: Path
78+
) -> None:
79+
"""Test that build constraints options are accepted and processed."""
80+
project_dir = _create_simple_test_package(
81+
script=script, name="test_build_constraints"
82+
)
83+
constraints_file = _create_constraints_file(
84+
script=script, filename="constraints.txt", content="setuptools>=40.0.0\n"
85+
)
86+
result = _run_pip_install_with_build_constraints(
87+
script=script, project_dir=project_dir, build_constraints_file=constraints_file
88+
)
89+
_assert_successful_installation(
90+
result=result, package_name="test_build_constraints"
91+
)
92+
93+
94+
@pytest.mark.network
95+
def test_build_constraints_vs_regular_constraints_simple(
96+
script: PipTestEnvironment, tmpdir: Path
97+
) -> None:
98+
"""Test that build constraints and regular constraints work independently."""
99+
project_dir = create_test_package_with_setup(
100+
script,
101+
name="test_isolation",
102+
version="1.0",
103+
py_modules=["test_isolation"],
104+
install_requires=["six"],
105+
)
106+
build_constraints_file = _create_constraints_file(
107+
script=script, filename="build_constraints.txt", content="setuptools>=40.0.0\n"
108+
)
109+
regular_constraints_file = _create_constraints_file(
110+
script=script, filename="constraints.txt", content="six>=1.10.0\n"
111+
)
112+
result = script.pip(
113+
"install",
114+
"--no-cache-dir",
115+
"--build-constraint",
116+
build_constraints_file,
117+
"--constraint",
118+
regular_constraints_file,
119+
"--use-feature",
120+
"build-constraint",
121+
str(project_dir),
122+
expect_error=False,
123+
)
124+
assert "Successfully installed" in result.stdout
125+
assert "test_isolation" in result.stdout
126+
127+
128+
@pytest.mark.network
129+
def test_build_constraints_environment_isolation_simple(
130+
script: PipTestEnvironment, tmpdir: Path
131+
) -> None:
132+
"""Test that build constraints work correctly in isolated build environments."""
133+
project_dir = _create_simple_test_package(script=script, name="test_env_isolation")
134+
constraints_file = _create_constraints_file(
135+
script=script, filename="build_constraints.txt", content="setuptools>=40.0.0\n"
136+
)
137+
result = _run_pip_install_with_build_constraints(
138+
script=script,
139+
project_dir=project_dir,
140+
build_constraints_file=constraints_file,
141+
extra_args=["--isolated"],
142+
)
143+
_assert_successful_installation(result=result, package_name="test_env_isolation")
144+
145+
146+
def test_build_constraints_file_not_found(
147+
script: PipTestEnvironment, tmpdir: Path
148+
) -> None:
149+
"""Test behavior when build constraints file doesn't exist."""
150+
project_dir = _create_simple_test_package(
151+
script=script, name="test_missing_constraints"
152+
)
153+
missing_constraints = script.scratch_path / "missing_constraints.txt"
154+
result = _run_pip_install_with_build_constraints(
155+
script=script,
156+
project_dir=project_dir,
157+
build_constraints_file=missing_constraints,
158+
)
159+
_assert_successful_installation(
160+
result=result, package_name="test_missing_constraints"
161+
)
162+
163+
164+
def test_build_constraints_without_feature_flag(
165+
script: PipTestEnvironment, tmpdir: Path
166+
) -> None:
167+
"""Test that --build-constraint requires the feature flag."""
168+
project_dir = _create_simple_test_package(script=script, name="test_no_feature")
169+
constraints_file = _create_constraints_file(
170+
script=script, filename="constraints.txt", content="setuptools==45.0.0\n"
171+
)
172+
result = _run_pip_install_with_build_constraints_no_feature_flag(
173+
script=script, project_dir=project_dir, constraints_file=constraints_file
174+
)
175+
assert result.returncode != 0
176+
assert "build-constraint" in result.stderr.lower()

tests/unit/test_build_constraints.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Tests for build constraints functionality."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
from unittest import mock
7+
8+
import pytest
9+
10+
from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
11+
from pip._internal.utils.deprecation import PipDeprecationWarning
12+
13+
from tests.lib import make_test_finder
14+
15+
16+
class TestSubprocessBuildEnvironmentInstaller:
17+
"""Test SubprocessBuildEnvironmentInstaller build constraints functionality."""
18+
19+
@mock.patch.dict(os.environ, {}, clear=True)
20+
def test_deprecation_check_no_pip_constraint(self) -> None:
21+
"""Test no deprecation warning is shown when PIP_CONSTRAINT is not set."""
22+
finder = make_test_finder()
23+
installer = SubprocessBuildEnvironmentInstaller(
24+
finder,
25+
constraints=["constraints.txt"],
26+
build_constraint_feature_enabled=False,
27+
)
28+
29+
# Should not raise any warning
30+
installer._deprecation_constraint_check()
31+
32+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraints.txt"})
33+
def test_deprecation_check_feature_enabled(self) -> None:
34+
"""
35+
Test no deprecation warning is shown when
36+
build-constraint feature is enabled
37+
"""
38+
finder = make_test_finder()
39+
installer = SubprocessBuildEnvironmentInstaller(
40+
finder,
41+
constraints=["constraints.txt"],
42+
build_constraint_feature_enabled=True,
43+
)
44+
45+
# Should not raise any warning
46+
installer._deprecation_constraint_check()
47+
48+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraints.txt"})
49+
def test_deprecation_check_constraint_mismatch(self) -> None:
50+
"""
51+
Test no deprecation warning is shown when
52+
PIP_CONSTRAINT doesn't match regular constraints.
53+
"""
54+
finder = make_test_finder()
55+
installer = SubprocessBuildEnvironmentInstaller(
56+
finder,
57+
constraints=["different.txt"],
58+
build_constraint_feature_enabled=False,
59+
)
60+
61+
# Should not raise any warning
62+
installer._deprecation_constraint_check()
63+
64+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraints.txt"})
65+
def test_deprecation_check_warning_shown(self) -> None:
66+
"""Test deprecation warning is shown when conditions are met."""
67+
finder = make_test_finder()
68+
installer = SubprocessBuildEnvironmentInstaller(
69+
finder,
70+
constraints=["constraints.txt"],
71+
build_constraint_feature_enabled=False,
72+
)
73+
74+
with pytest.warns(PipDeprecationWarning) as warning_info:
75+
installer._deprecation_constraint_check()
76+
77+
assert len(warning_info) == 1
78+
message = str(warning_info[0].message)
79+
assert (
80+
"Setting PIP_CONSTRAINT will not affect build constraints in the future"
81+
in message
82+
)
83+
assert 'PIP_BUILD_CONSTRAINT with PIP_USE_FEATURE="build-constraint"' in message
84+
85+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraint1.txt constraint2.txt"})
86+
def test_deprecation_check_multiple_constraints(self) -> None:
87+
"""Test deprecation warning works with multiple constraints."""
88+
finder = make_test_finder()
89+
installer = SubprocessBuildEnvironmentInstaller(
90+
finder,
91+
constraints=["constraint1.txt", "constraint2.txt"],
92+
build_constraint_feature_enabled=False,
93+
)
94+
95+
with pytest.warns(PipDeprecationWarning):
96+
installer._deprecation_constraint_check()
97+
98+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraint1.txt constraint2.txt"})
99+
def test_deprecation_check_multiple_constraints_different_order(self) -> None:
100+
"""Test deprecation warning works when constraints are in different order."""
101+
finder = make_test_finder()
102+
installer = SubprocessBuildEnvironmentInstaller(
103+
finder,
104+
constraints=["constraint2.txt", "constraint1.txt"],
105+
build_constraint_feature_enabled=False,
106+
)
107+
108+
with pytest.warns(PipDeprecationWarning):
109+
installer._deprecation_constraint_check()
110+
111+
@mock.patch.dict(
112+
os.environ, {"PIP_CONSTRAINT": "constraint1.txt constraint2.txt extra.txt"}
113+
)
114+
def test_deprecation_check_partial_match_no_warning(self) -> None:
115+
"""Test no deprecation warning is shown when only partial match."""
116+
finder = make_test_finder()
117+
installer = SubprocessBuildEnvironmentInstaller(
118+
finder,
119+
constraints=["constraint1.txt", "constraint2.txt"],
120+
build_constraint_feature_enabled=False,
121+
)
122+
123+
# Should not raise any warning since PIP_CONSTRAINT has extra file
124+
installer._deprecation_constraint_check()
125+
126+
@mock.patch("pip._internal.build_env.call_subprocess")
127+
@mock.patch.dict(os.environ, {"PIP_CONSTRAINT": "constraints.txt"})
128+
def test_install_calls_deprecation_check(
129+
self, mock_call_subprocess: mock.Mock
130+
) -> None:
131+
"""Test install method calls deprecation check."""
132+
finder = make_test_finder()
133+
installer = SubprocessBuildEnvironmentInstaller(
134+
finder,
135+
constraints=["constraints.txt"],
136+
build_constraint_feature_enabled=False,
137+
)
138+
139+
from pip._internal.build_env import _Prefix
140+
141+
prefix = _Prefix("/tmp/test")
142+
143+
with pytest.warns(PipDeprecationWarning):
144+
installer.install(
145+
requirements=["setuptools"],
146+
prefix=prefix,
147+
kind="build dependencies",
148+
for_req=None,
149+
)
150+
151+
# Verify that call_subprocess was called (install proceeded after warning)
152+
mock_call_subprocess.assert_called_once()

0 commit comments

Comments
 (0)