Skip to content

Commit 1aa1d32

Browse files
committed
Add build constraints tests
1 parent 2e4086e commit 1aa1d32

File tree

2 files changed

+326
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)