Skip to content

Commit e5b2fcd

Browse files
add tests for PEP 658 metadata with wheel files too
1 parent 2aa1c2f commit e5b2fcd

File tree

1 file changed

+99
-39
lines changed

1 file changed

+99
-39
lines changed

tests/functional/test_download.py

Lines changed: 99 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,17 +1264,14 @@ class Package:
12641264

12651265
name: str
12661266
version: str
1267+
filename: str
12671268
metadata: MetadataKind
12681269
# This will override any dependencies specified in the actual dist's METADATA.
12691270
requires_dist: Tuple[str, ...] = ()
12701271

1271-
def filename(self) -> str:
1272-
"""We will only be pointed at .tar.gz packages, so we can guess the name."""
1273-
return f"{self.name}-{self.version}.tar.gz"
1274-
12751272
def metadata_filename(self) -> str:
12761273
"""This is specified by PEP 658."""
1277-
return f"{self.filename()}.metadata"
1274+
return f"{self.filename}.metadata"
12781275

12791276
def generate_additional_tag(self) -> str:
12801277
"""This gets injected into the <a> tag in the generated PyPI index page for this
@@ -1289,26 +1286,27 @@ def generate_additional_tag(self) -> str:
12891286
checksum = sha256(self.generate_metadata()).hexdigest()
12901287
return f'data-dist-info-metadata="sha256={checksum}"'
12911288

1289+
def requires_str(self) -> str:
1290+
if not self.requires_dist:
1291+
return ""
1292+
joined = " and ".join(self.requires_dist)
1293+
return f"Requires-Dist: {joined}"
1294+
12921295
def generate_metadata(self) -> bytes:
12931296
"""This is written to `self.metadata_filename()` and will override the actual
12941297
dist's METADATA, unless `self.metadata == MetadataKind.NoFile`."""
1295-
if self.requires_dist:
1296-
joined = " and ".join(self.requires_dist)
1297-
requires_str = f"Requires-Dist: {joined}"
1298-
else:
1299-
requires_str = ""
13001298
return dedent(
13011299
f"""\
13021300
Metadata-Version: 2.1
13031301
Name: {self.name}
13041302
Version: {self.version}
1305-
{requires_str}
1303+
{self.requires_str()}
13061304
"""
13071305
).encode("utf-8")
13081306

13091307

13101308
@pytest.fixture(scope="function")
1311-
def index_html_content(tmpdir: Path) -> Callable[..., Path]:
1309+
def write_index_html_content(tmpdir: Path) -> Callable[[str], Path]:
13121310
"""Generate a PyPI package index.html within a temporary local directory."""
13131311
html_dir = tmpdir / "index_html_content"
13141312
html_dir.mkdir()
@@ -1327,13 +1325,14 @@ def generate_index_html_subdir(index_html: str) -> Path:
13271325

13281326

13291327
@pytest.fixture(scope="function")
1330-
def index_for_packages(
1328+
def html_index_for_packages(
13311329
shared_data: TestData,
1332-
index_html_content: Callable[..., Path],
1330+
write_index_html_content: Callable[[str], Path],
13331331
) -> Callable[..., Path]:
1334-
"""Generate a PyPI package index within a local directory pointing to blank data."""
1332+
"""Generate a PyPI HTML package index within a local directory pointing to
1333+
blank data."""
13351334

1336-
def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
1335+
def generate_html_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
13371336
"""
13381337
Produce a PyPI directory structure pointing to the specified packages.
13391338
"""
@@ -1353,7 +1352,7 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
13531352
</body>
13541353
</html>"""
13551354
# (2) Generate the index.html in a new subdirectory of the temp directory.
1356-
index_html_subdir = index_html_content(index_html)
1355+
index_html_subdir = write_index_html_content(index_html)
13571356

13581357
# (3) Generate subdirectories for individual packages, each with their own
13591358
# index.html.
@@ -1366,12 +1365,12 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
13661365
# (3.1) Generate the <a> tag which pip can crawl pointing to this
13671366
# specific package version.
13681367
download_links.append(
1369-
f' <a href="{package_link.filename()}" {package_link.generate_additional_tag()}>{package_link.filename()}</a><br/>' # noqa: E501
1368+
f' <a href="{package_link.filename}" {package_link.generate_additional_tag()}>{package_link.filename}</a><br/>' # noqa: E501
13701369
)
13711370
# (3.2) Copy over the corresponding file in `shared_data.packages`.
13721371
shutil.copy(
1373-
shared_data.packages / package_link.filename(),
1374-
pkg_subdir / package_link.filename(),
1372+
shared_data.packages / package_link.filename,
1373+
pkg_subdir / package_link.filename,
13751374
)
13761375
# (3.3) Write a metadata file, if applicable.
13771376
if package_link.metadata != MetadataKind.NoFile:
@@ -1399,13 +1398,13 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
13991398

14001399
return index_html_subdir
14011400

1402-
return generate_index_for_packages
1401+
return generate_html_index_for_packages
14031402

14041403

14051404
@pytest.fixture(scope="function")
1406-
def download_generated_index(
1405+
def download_generated_html_index(
14071406
script: PipTestEnvironment,
1408-
index_for_packages: Callable[..., Path],
1407+
html_index_for_packages: Callable[[Dict[str, List[Package]]], Path],
14091408
tmpdir: Path,
14101409
) -> Callable[..., Tuple[TestPipResult, Path]]:
14111410
"""Execute `pip download` against a generated PyPI index."""
@@ -1420,7 +1419,7 @@ def run_for_generated_index(
14201419
Produce a PyPI directory structure pointing to the specified packages, then
14211420
execute `pip download -i ...` pointing to our generated index.
14221421
"""
1423-
index_dir = index_for_packages(packages)
1422+
index_dir = html_index_for_packages(packages)
14241423
pip_args = [
14251424
"download",
14261425
"-d",
@@ -1438,17 +1437,61 @@ def run_for_generated_index(
14381437
# The package database we generate for testing PEP 658 support.
14391438
_simple_packages: Dict[str, List[Package]] = {
14401439
"simple": [
1441-
Package("simple", "1.0", MetadataKind.Sha256),
1442-
Package("simple", "2.0", MetadataKind.No),
1440+
Package("simple", "1.0", "simple-1.0.tar.gz", MetadataKind.Sha256),
1441+
Package("simple", "2.0", "simple-2.0.tar.gz", MetadataKind.No),
14431442
# This will raise a hashing error.
1444-
Package("simple", "3.0", MetadataKind.WrongHash),
1443+
Package("simple", "3.0", "simple-3.0.tar.gz", MetadataKind.WrongHash),
14451444
],
14461445
"simple2": [
14471446
# Override the dependencies here in order to force pip to download
14481447
# simple-1.0.tar.gz as well.
1449-
Package("simple2", "1.0", MetadataKind.Unhashed, ("simple==1.0",)),
1448+
Package(
1449+
"simple2",
1450+
"1.0",
1451+
"simple2-1.0.tar.gz",
1452+
MetadataKind.Unhashed,
1453+
("simple==1.0",),
1454+
),
14501455
# This will raise an error when pip attempts to fetch the metadata file.
1451-
Package("simple2", "2.0", MetadataKind.NoFile),
1456+
Package("simple2", "2.0", "simple2-2.0.tar.gz", MetadataKind.NoFile),
1457+
],
1458+
"colander": [
1459+
# Ensure we can read the dependencies from a metadata file within a wheel
1460+
# *without* PEP 658 metadata.
1461+
Package(
1462+
"colander", "0.9.9", "colander-0.9.9-py2.py3-none-any.whl", MetadataKind.No
1463+
),
1464+
],
1465+
"compilewheel": [
1466+
# Ensure we can override the dependencies of a wheel file by injecting PEP
1467+
# 658 metadata.
1468+
Package(
1469+
"compilewheel",
1470+
"1.0",
1471+
"compilewheel-1.0-py2.py3-none-any.whl",
1472+
MetadataKind.Unhashed,
1473+
("simple==1.0",),
1474+
),
1475+
],
1476+
"has-script": [
1477+
# Ensure we check PEP 658 metadata hashing errors for wheel files.
1478+
Package(
1479+
"has-script",
1480+
"1.0",
1481+
"has.script-1.0-py2.py3-none-any.whl",
1482+
MetadataKind.WrongHash,
1483+
),
1484+
],
1485+
"translationstring": [
1486+
Package(
1487+
"translationstring", "1.1", "translationstring-1.1.tar.gz", MetadataKind.No
1488+
),
1489+
],
1490+
"priority": [
1491+
# Ensure we check for a missing metadata file for wheels.
1492+
Package(
1493+
"priority", "1.0", "priority-1.0-py2.py3-none-any.whl", MetadataKind.NoFile
1494+
),
14521495
],
14531496
}
14541497

@@ -1458,16 +1501,24 @@ def run_for_generated_index(
14581501
[
14591502
("simple2==1.0", ["simple-1.0.tar.gz", "simple2-1.0.tar.gz"]),
14601503
("simple==2.0", ["simple-2.0.tar.gz"]),
1504+
(
1505+
"colander",
1506+
["colander-0.9.9-py2.py3-none-any.whl", "translationstring-1.1.tar.gz"],
1507+
),
1508+
(
1509+
"compilewheel",
1510+
["compilewheel-1.0-py2.py3-none-any.whl", "simple-1.0.tar.gz"],
1511+
),
14611512
],
14621513
)
14631514
def test_download_metadata(
1464-
download_generated_index: Callable[..., Tuple[TestPipResult, Path]],
1515+
download_generated_html_index: Callable[..., Tuple[TestPipResult, Path]],
14651516
requirement_to_download: str,
14661517
expected_outputs: List[str],
14671518
) -> None:
14681519
"""Verify that if a data-dist-info-metadata attribute is present, then it is used
14691520
instead of the actual dist's METADATA."""
1470-
_, download_dir = download_generated_index(
1521+
_, download_dir = download_generated_html_index(
14711522
_simple_packages,
14721523
[requirement_to_download],
14731524
)
@@ -1480,17 +1531,21 @@ def test_download_metadata(
14801531
(
14811532
"simple==3.0",
14821533
"95e0f200b6302989bcf2cead9465cf229168295ea330ca30d1ffeab5c0fed996",
1483-
)
1534+
),
1535+
(
1536+
"has-script",
1537+
"16ba92d7f6f992f6de5ecb7d58c914675cf21f57f8e674fb29dcb4f4c9507e5b",
1538+
),
14841539
],
14851540
)
14861541
def test_incorrect_metadata_hash(
1487-
download_generated_index: Callable[..., Tuple[TestPipResult, Path]],
1542+
download_generated_html_index: Callable[..., Tuple[TestPipResult, Path]],
14881543
requirement_to_download: str,
14891544
real_hash: str,
14901545
) -> None:
14911546
"""Verify that if a hash for data-dist-info-metadata is provided, it must match the
14921547
actual hash of the metadata file."""
1493-
result, _ = download_generated_index(
1548+
result, _ = download_generated_html_index(
14941549
_simple_packages,
14951550
[requirement_to_download],
14961551
allow_error=True,
@@ -1503,23 +1558,28 @@ def test_incorrect_metadata_hash(
15031558

15041559

15051560
@pytest.mark.parametrize(
1506-
"requirement_to_download",
1507-
["simple2==2.0"],
1561+
"requirement_to_download, expected_url",
1562+
[
1563+
("simple2==2.0", "simple2-2.0.tar.gz.metadata"),
1564+
("priority", "priority-1.0-py2.py3-none-any.whl.metadata"),
1565+
],
15081566
)
15091567
def test_metadata_not_found(
1510-
download_generated_index: Callable[..., Tuple[TestPipResult, Path]],
1568+
download_generated_html_index: Callable[..., Tuple[TestPipResult, Path]],
15111569
requirement_to_download: str,
1570+
expected_url: str,
15121571
) -> None:
15131572
"""Verify that if a data-dist-info-metadata attribute is provided, that pip will
15141573
fetch the .metadata file at the location specified by PEP 658, and error
15151574
if unavailable."""
1516-
result, _ = download_generated_index(
1575+
result, _ = download_generated_html_index(
15171576
_simple_packages,
15181577
[requirement_to_download],
15191578
allow_error=True,
15201579
)
15211580
assert result.returncode != 0
1581+
expected_re = re.escape(expected_url)
15221582
pattern = re.compile(
1523-
r"ERROR: 404 Client Error: FileNotFoundError for url:.*simple2-2\.0\.tar\.gz\.metadata" # noqa: E501
1583+
f"ERROR: 404 Client Error: FileNotFoundError for url:.*{expected_re}"
15241584
)
15251585
assert pattern.search(result.stderr), (pattern, result.stderr)

0 commit comments

Comments
 (0)