|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import functools |
4 | | -import importlib |
5 | 4 | import io |
6 | 5 | from email import message_from_string |
7 | 6 | from email.generator import Generator |
8 | | -from email.message import EmailMessage, Message |
| 7 | +from email.message import EmailMessage |
9 | 8 | from email.parser import Parser |
10 | 9 | from email.policy import EmailPolicy |
11 | 10 | from inspect import cleandoc |
|
15 | 14 | import jaraco.path |
16 | 15 | import pytest |
17 | 16 | from packaging.metadata import Metadata |
18 | | -from packaging.requirements import Requirement |
19 | 17 |
|
20 | | -from setuptools import _reqs, sic |
| 18 | +from setuptools import sic |
21 | 19 | from setuptools._core_metadata import rfc822_escape, rfc822_unescape |
22 | | -from setuptools.command.egg_info import egg_info, write_requirements |
23 | 20 | from setuptools.config import expand, setupcfg |
24 | 21 | from setuptools.dist import Distribution |
25 | 22 |
|
@@ -384,36 +381,32 @@ def dist(self, request, monkeypatch, tmp_path): |
384 | 381 | yield setupcfg.apply_configuration(Distribution({}), config) |
385 | 382 |
|
386 | 383 | @pytest.mark.uses_network |
387 | | - def test_equivalent_output(self, tmp_path, dist): |
388 | | - """Ensure output from setuptools is equivalent to the one from `pypa/wheel`""" |
389 | | - # Generate a METADATA file using pypa/wheel for comparison |
390 | | - wheel_metadata = importlib.import_module("wheel.metadata") |
391 | | - pkginfo_to_metadata = getattr(wheel_metadata, "pkginfo_to_metadata", None) |
392 | | - |
393 | | - if pkginfo_to_metadata is None: # pragma: nocover |
394 | | - pytest.xfail( |
395 | | - "wheel.metadata.pkginfo_to_metadata is undefined, " |
396 | | - "(this is likely to be caused by API changes in pypa/wheel" |
397 | | - ) |
398 | | - |
399 | | - # Generate an simplified "egg-info" dir for pypa/wheel to convert |
| 384 | + def test_pkg_info_roundtrip(self, tmp_path, dist): |
| 385 | + """Ensure PKG-INFO round trips according to pypa/wheel's methodology""" |
| 386 | + # Generate an simplified "egg-info" with PKG-INFO |
400 | 387 | pkg_info = _get_pkginfo(dist) |
401 | | - egg_info_dir = tmp_path / "pkg.egg-info" |
402 | | - egg_info_dir.mkdir(parents=True) |
403 | | - (egg_info_dir / "PKG-INFO").write_text(pkg_info, encoding="utf-8") |
404 | | - write_requirements(egg_info(dist), egg_info_dir, egg_info_dir / "requires.txt") |
405 | 388 |
|
406 | | - # Get pypa/wheel generated METADATA but normalize requirements formatting |
407 | | - metadata_msg = pkginfo_to_metadata(egg_info_dir, egg_info_dir / "PKG-INFO") |
408 | | - metadata_str = _normalize_metadata(metadata_msg) |
409 | | - pkg_info_msg = message_from_string(pkg_info) |
410 | | - pkg_info_str = _normalize_metadata(pkg_info_msg) |
| 389 | + # Emulate the way old versions of wheel.bdist_wheel used to parse and regenerate |
| 390 | + # the message, then ensures the metadata generated by setuptools is compatible. |
| 391 | + with io.StringIO(pkg_info) as buffer: |
| 392 | + msg = Parser(EmailMessage).parse(buffer) |
411 | 393 |
|
412 | | - # Compare setuptools PKG-INFO x pypa/wheel METADATA |
413 | | - assert metadata_str == pkg_info_str |
| 394 | + serialization_policy = EmailPolicy( |
| 395 | + utf8=True, |
| 396 | + mangle_from_=False, |
| 397 | + max_line_length=0, |
| 398 | + ) |
| 399 | + with io.BytesIO() as buffer: |
| 400 | + out = io.TextIOWrapper(buffer, encoding="utf-8") |
| 401 | + Generator(out, policy=serialization_policy).flatten(msg) |
| 402 | + out.flush() |
| 403 | + regenerated = buffer.getvalue() |
414 | 404 |
|
415 | | - # Make sure it parses/serializes well in pypa/wheel |
416 | | - _assert_roundtrip_message(pkg_info) |
| 405 | + raw_metadata = bytes(pkg_info, "utf-8") |
| 406 | + # Normalise newlines to avoid test errors on Windows: |
| 407 | + raw_metadata = b"\n".join(raw_metadata.splitlines()) |
| 408 | + regenerated = b"\n".join(regenerated.splitlines()) |
| 409 | + assert regenerated == raw_metadata |
417 | 410 |
|
418 | 411 |
|
419 | 412 | class TestPEP643: |
@@ -542,71 +535,6 @@ def _makedist(**attrs): |
542 | 535 | return dist |
543 | 536 |
|
544 | 537 |
|
545 | | -def _assert_roundtrip_message(metadata: str) -> None: |
546 | | - """Emulate the way wheel.bdist_wheel parses and regenerates the message, |
547 | | - then ensures the metadata generated by setuptools is compatible. |
548 | | - """ |
549 | | - with io.StringIO(metadata) as buffer: |
550 | | - msg = Parser(EmailMessage).parse(buffer) |
551 | | - |
552 | | - serialization_policy = EmailPolicy( |
553 | | - utf8=True, |
554 | | - mangle_from_=False, |
555 | | - max_line_length=0, |
556 | | - ) |
557 | | - with io.BytesIO() as buffer: |
558 | | - out = io.TextIOWrapper(buffer, encoding="utf-8") |
559 | | - Generator(out, policy=serialization_policy).flatten(msg) |
560 | | - out.flush() |
561 | | - regenerated = buffer.getvalue() |
562 | | - |
563 | | - raw_metadata = bytes(metadata, "utf-8") |
564 | | - # Normalise newlines to avoid test errors on Windows: |
565 | | - raw_metadata = b"\n".join(raw_metadata.splitlines()) |
566 | | - regenerated = b"\n".join(regenerated.splitlines()) |
567 | | - assert regenerated == raw_metadata |
568 | | - |
569 | | - |
570 | | -def _normalize_metadata(msg: Message) -> str: |
571 | | - """Allow equivalent metadata to be compared directly""" |
572 | | - # The main challenge regards the requirements and extras. |
573 | | - # Both setuptools and wheel already apply some level of normalization |
574 | | - # but they differ regarding which character is chosen, according to the |
575 | | - # following spec it should be "-": |
576 | | - # https://packaging.python.org/en/latest/specifications/name-normalization/ |
577 | | - |
578 | | - # Related issues: |
579 | | - # https://github.com/pypa/packaging/issues/845 |
580 | | - # https://github.com/pypa/packaging/issues/644#issuecomment-2429813968 |
581 | | - |
582 | | - extras = {x.replace("_", "-"): x for x in msg.get_all("Provides-Extra", [])} |
583 | | - reqs = [ |
584 | | - _normalize_req(req, extras) |
585 | | - for req in _reqs.parse(msg.get_all("Requires-Dist", [])) |
586 | | - ] |
587 | | - del msg["Requires-Dist"] |
588 | | - del msg["Provides-Extra"] |
589 | | - |
590 | | - # Ensure consistent ord |
591 | | - for req in sorted(reqs): |
592 | | - msg["Requires-Dist"] = req |
593 | | - for extra in sorted(extras): |
594 | | - msg["Provides-Extra"] = extra |
595 | | - |
596 | | - # TODO: Handle lack of PEP 643 implementation in pypa/wheel? |
597 | | - del msg["Metadata-Version"] |
598 | | - |
599 | | - return msg.as_string() |
600 | | - |
601 | | - |
602 | | -def _normalize_req(req: Requirement, extras: dict[str, str]) -> str: |
603 | | - """Allow equivalent requirement objects to be compared directly""" |
604 | | - as_str = str(req).replace(req.name, req.name.replace("_", "-")) |
605 | | - for norm, orig in extras.items(): |
606 | | - as_str = as_str.replace(orig, norm) |
607 | | - return as_str |
608 | | - |
609 | | - |
610 | 538 | def _get_pkginfo(dist: Distribution): |
611 | 539 | with io.StringIO() as fp: |
612 | 540 | dist.metadata.write_pkg_file(fp) |
|
0 commit comments