This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new a03767c  Allow PKG-INFO files in Python source archives
a03767c is described below

commit a03767c840994a2b06c9b5027884d0ad1d917ce8
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Feb 5 18:34:13 2026 +0000

    Allow PKG-INFO files in Python source archives
---
 atr/tasks/checks/compare.py       |  16 +++-
 tests/unit/test_checks_compare.py | 154 +++++++++++++++++++++++---------------
 2 files changed, 108 insertions(+), 62 deletions(-)

diff --git a/atr/tasks/checks/compare.py b/atr/tasks/checks/compare.py
index f209287..d1840ea 100644
--- a/atr/tasks/checks/compare.py
+++ b/atr/tasks/checks/compare.py
@@ -48,6 +48,9 @@ import atr.util as util
 _CONFIG: Final = config.get()
 _DEFAULT_EMAIL: Final[str] = "atr@localhost"
 _DEFAULT_USER: Final[str] = "atr"
+_PERMITTED_ADDED_PATHS: Final[dict[str, list[str]]] = {
+    "PKG-INFO": ["pyproject.toml"],
+}
 
 
 @dataclasses.dataclass
@@ -74,7 +77,7 @@ class TreeComparisonResult:
     repo_only: set[str]
 
 
-async def source_trees(args: checks.FunctionArguments) -> results.Results | 
None:
+async def source_trees(args: checks.FunctionArguments) -> results.Results | 
None:  # noqa: C901
     recorder = await args.recorder()
     is_source = await recorder.primary_path_is_source()
     if not is_source:
@@ -142,8 +145,15 @@ async def source_trees(args: checks.FunctionArguments) -> 
results.Results | None
                     {"error": str(exc)},
                 )
                 return None
-            if comparison.invalid:
-                invalid_list = sorted(comparison.invalid)
+            invalid_filtered: set[str] = set()
+            for path in comparison.invalid:
+                required = _PERMITTED_ADDED_PATHS.get(path)
+                if required is None:
+                    invalid_filtered.add(path)
+                elif not all((archive_content_dir / r).is_file() for r in 
required):
+                    invalid_filtered.add(path)
+            if invalid_filtered:
+                invalid_list = sorted(invalid_filtered)
                 await recorder.failure(
                     "Source archive contains files not in GitHub checkout or 
with different content",
                     {"invalid_count": len(invalid_list), "invalid_paths": 
invalid_list},
diff --git a/tests/unit/test_checks_compare.py 
b/tests/unit/test_checks_compare.py
index 22126de..5a90a02 100644
--- a/tests/unit/test_checks_compare.py
+++ b/tests/unit/test_checks_compare.py
@@ -439,16 +439,16 @@ def test_compare_trees_rsync_content_differs(monkeypatch: 
pytest.MonkeyPatch, tm
     assert result.repo_only == set()
 
 
-def test_compare_trees_rsync_ignores_timestamp_only(monkeypatch: 
pytest.MonkeyPatch, tmp_path: pathlib.Path) -> None:
+def test_compare_trees_rsync_distinct_files(monkeypatch: pytest.MonkeyPatch, 
tmp_path: pathlib.Path) -> None:
     repo_dir = tmp_path / "repo"
     archive_dir = tmp_path / "archive"
     _make_tree(repo_dir, ["a.txt"])
-    _make_tree(archive_dir, ["a.txt"])
+    _make_tree(archive_dir, ["b.txt"])
     completed = subprocess.CompletedProcess(
         args=["rsync"],
         returncode=0,
-        stdout=".f..t...... a.txt\n",
-        stderr="",
+        stdout=">f+++++++++ a.txt\n",
+        stderr="*deleting b.txt\n",
     )
     run_recorder = RunRecorder(completed)
 
@@ -457,20 +457,20 @@ def 
test_compare_trees_rsync_ignores_timestamp_only(monkeypatch: pytest.MonkeyPa
 
     result = atr.tasks.checks.compare._compare_trees_rsync(repo_dir, 
archive_dir)
 
-    assert result.invalid == set()
-    assert result.repo_only == set()
+    assert result.invalid == {"b.txt"}
+    assert result.repo_only == {"a.txt"}
 
 
-def test_compare_trees_rsync_distinct_files(monkeypatch: pytest.MonkeyPatch, 
tmp_path: pathlib.Path) -> None:
+def test_compare_trees_rsync_ignores_timestamp_only(monkeypatch: 
pytest.MonkeyPatch, tmp_path: pathlib.Path) -> None:
     repo_dir = tmp_path / "repo"
     archive_dir = tmp_path / "archive"
     _make_tree(repo_dir, ["a.txt"])
-    _make_tree(archive_dir, ["b.txt"])
+    _make_tree(archive_dir, ["a.txt"])
     completed = subprocess.CompletedProcess(
         args=["rsync"],
         returncode=0,
-        stdout=">f+++++++++ a.txt\n",
-        stderr="*deleting b.txt\n",
+        stdout=".f..t...... a.txt\n",
+        stderr="",
     )
     run_recorder = RunRecorder(completed)
 
@@ -479,8 +479,8 @@ def test_compare_trees_rsync_distinct_files(monkeypatch: 
pytest.MonkeyPatch, tmp
 
     result = atr.tasks.checks.compare._compare_trees_rsync(repo_dir, 
archive_dir)
 
-    assert result.invalid == {"b.txt"}
-    assert result.repo_only == {"a.txt"}
+    assert result.invalid == set()
+    assert result.repo_only == set()
 
 
 def test_compare_trees_rsync_repo_has_extra_file(monkeypatch: 
pytest.MonkeyPatch, tmp_path: pathlib.Path) -> None:
@@ -557,6 +557,35 @@ async def test_decompress_archive_handles_extraction_error(
     assert result is False
 
 
[email protected]
+async def test_find_archive_root_accepts_any_single_directory(tmp_path: 
pathlib.Path) -> None:
+    archive_path = tmp_path / "my-project-1.0.0.tar.gz"
+    extract_dir = tmp_path / "extracted"
+    extract_dir.mkdir(parents=True)
+    root_dir = extract_dir / "package"
+    root_dir.mkdir()
+
+    result = await atr.tasks.checks.compare._find_archive_root(archive_path, 
extract_dir)
+
+    assert result.root == "package"
+    assert result.extra_entries == []
+
+
[email protected]
+async def test_find_archive_root_detects_extra_file_entries(tmp_path: 
pathlib.Path) -> None:
+    archive_path = tmp_path / "my-project-1.0.0.tar.gz"
+    extract_dir = tmp_path / "extracted"
+    root_dir = extract_dir / "my-project-1.0.0"
+    root_dir.mkdir(parents=True)
+    (extract_dir / "extra.txt").write_text("extra")
+    (extract_dir / "README").write_text("readme")
+
+    result = await atr.tasks.checks.compare._find_archive_root(archive_path, 
extract_dir)
+
+    assert result.root == "my-project-1.0.0"
+    assert sorted(result.extra_entries) == ["README", "extra.txt"]
+
+
 @pytest.mark.asyncio
 async def test_find_archive_root_finds_expected_root(tmp_path: pathlib.Path) 
-> None:
     archive_path = tmp_path / "my-project-1.0.0.tar.gz"
@@ -597,16 +626,17 @@ async def 
test_find_archive_root_finds_root_without_source_suffix(tmp_path: path
 
 
 @pytest.mark.asyncio
-async def test_find_archive_root_accepts_any_single_directory(tmp_path: 
pathlib.Path) -> None:
+async def test_find_archive_root_ignores_macos_metadata(tmp_path: 
pathlib.Path) -> None:
     archive_path = tmp_path / "my-project-1.0.0.tar.gz"
     extract_dir = tmp_path / "extracted"
-    extract_dir.mkdir(parents=True)
-    root_dir = extract_dir / "package"
-    root_dir.mkdir()
+    root_dir = extract_dir / "my-project-1.0.0"
+    root_dir.mkdir(parents=True)
+    metadata_file = extract_dir / "._my-project-1.0.0"
+    metadata_file.write_text("metadata")
 
     result = await atr.tasks.checks.compare._find_archive_root(archive_path, 
extract_dir)
 
-    assert result.root == "package"
+    assert result.root == "my-project-1.0.0"
     assert result.extra_entries == []
 
 
@@ -635,36 +665,6 @@ async def 
test_find_archive_root_returns_none_when_no_directories(tmp_path: path
     assert result.root is None
 
 
[email protected]
-async def test_find_archive_root_detects_extra_file_entries(tmp_path: 
pathlib.Path) -> None:
-    archive_path = tmp_path / "my-project-1.0.0.tar.gz"
-    extract_dir = tmp_path / "extracted"
-    root_dir = extract_dir / "my-project-1.0.0"
-    root_dir.mkdir(parents=True)
-    (extract_dir / "extra.txt").write_text("extra")
-    (extract_dir / "README").write_text("readme")
-
-    result = await atr.tasks.checks.compare._find_archive_root(archive_path, 
extract_dir)
-
-    assert result.root == "my-project-1.0.0"
-    assert sorted(result.extra_entries) == ["README", "extra.txt"]
-
-
[email protected]
-async def test_find_archive_root_ignores_macos_metadata(tmp_path: 
pathlib.Path) -> None:
-    archive_path = tmp_path / "my-project-1.0.0.tar.gz"
-    extract_dir = tmp_path / "extracted"
-    root_dir = extract_dir / "my-project-1.0.0"
-    root_dir.mkdir(parents=True)
-    metadata_file = extract_dir / "._my-project-1.0.0"
-    metadata_file.write_text("metadata")
-
-    result = await atr.tasks.checks.compare._find_archive_root(archive_path, 
extract_dir)
-
-    assert result.root == "my-project-1.0.0"
-    assert result.extra_entries == []
-
-
 @pytest.mark.asyncio
 async def test_source_trees_creates_temp_workspace_and_cleans_up(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
@@ -725,6 +725,42 @@ async def 
test_source_trees_payload_none_skips_temp_workspace(monkeypatch: pytes
     await atr.tasks.checks.compare.source_trees(args)
 
 
[email protected]
+async def test_source_trees_permits_pkg_info_when_pyproject_toml_exists(
+    monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
+) -> None:
+    recorder = RecorderStub(True)
+    args = _make_args(recorder)
+    payload = _make_payload()
+    checkout = CheckoutRecorder()
+    find_root = FindArchiveRootRecorder("artifact")
+    compare = CompareRecorder(invalid={"PKG-INFO"})
+    tmp_root = tmp_path / "temporary-root"
+
+    async def decompress_with_pyproject(
+        archive_path: pathlib.Path,
+        extract_dir: pathlib.Path,
+        max_extract_size: int,
+        chunk_size: int,
+    ) -> bool:
+        archive_content = extract_dir / "artifact"
+        archive_content.mkdir(parents=True, exist_ok=True)
+        (archive_content / "pyproject.toml").write_text("[project]\nname = 
'test'\n")
+        return True
+
+    monkeypatch.setattr(atr.tasks.checks.compare, "_load_tp_payload", 
PayloadLoader(payload))
+    monkeypatch.setattr(atr.tasks.checks.compare, "_checkout_github_source", 
checkout)
+    monkeypatch.setattr(atr.tasks.checks.compare, "_decompress_archive", 
decompress_with_pyproject)
+    monkeypatch.setattr(atr.tasks.checks.compare, "_find_archive_root", 
find_root)
+    monkeypatch.setattr(atr.tasks.checks.compare, "_compare_trees", compare)
+    monkeypatch.setattr(atr.tasks.checks.compare.util, "get_tmp_dir", 
ReturnValue(tmp_root))
+
+    await atr.tasks.checks.compare.source_trees(args)
+
+    assert len(recorder.failure_calls) == 0
+    assert len(recorder.success_calls) == 1
+
+
 @pytest.mark.asyncio
 async def test_source_trees_records_failure_when_archive_has_invalid_files(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
@@ -783,57 +819,57 @@ async def 
test_source_trees_records_failure_when_archive_root_not_found(
 
 
 @pytest.mark.asyncio
-async def test_source_trees_records_failure_when_extra_entries_in_archive(
+async def test_source_trees_records_failure_when_decompress_fails(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     recorder = RecorderStub(True)
     args = _make_args(recorder)
     payload = _make_payload()
     checkout = CheckoutRecorder()
-    decompress = DecompressRecorder()
-    find_root = FindArchiveRootRecorder(root="artifact", 
extra_entries=["README.txt", "extra.txt"])
+    decompress = DecompressRecorder(return_value=False)
     tmp_root = tmp_path / "temporary-root"
 
     monkeypatch.setattr(atr.tasks.checks.compare, "_load_tp_payload", 
PayloadLoader(payload))
     monkeypatch.setattr(atr.tasks.checks.compare, "_checkout_github_source", 
checkout)
     monkeypatch.setattr(atr.tasks.checks.compare, "_decompress_archive", 
decompress)
-    monkeypatch.setattr(atr.tasks.checks.compare, "_find_archive_root", 
find_root)
     monkeypatch.setattr(atr.tasks.checks.compare.util, "get_tmp_dir", 
ReturnValue(tmp_root))
 
     await atr.tasks.checks.compare.source_trees(args)
 
     assert len(recorder.failure_calls) == 1
     message, data = recorder.failure_calls[0]
-    assert message == "Archive contains entries outside the root directory"
+    assert message == "Failed to extract source archive for comparison"
     assert isinstance(data, dict)
-    assert data["root"] == "artifact"
-    assert data["extra_entries"] == ["README.txt", "extra.txt"]
+    assert data["archive_path"] == str(await recorder.abs_path())
+    assert data["extract_dir"] == str(decompress.extract_dir)
 
 
 @pytest.mark.asyncio
-async def test_source_trees_records_failure_when_decompress_fails(
+async def test_source_trees_records_failure_when_extra_entries_in_archive(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     recorder = RecorderStub(True)
     args = _make_args(recorder)
     payload = _make_payload()
     checkout = CheckoutRecorder()
-    decompress = DecompressRecorder(return_value=False)
+    decompress = DecompressRecorder()
+    find_root = FindArchiveRootRecorder(root="artifact", 
extra_entries=["README.txt", "extra.txt"])
     tmp_root = tmp_path / "temporary-root"
 
     monkeypatch.setattr(atr.tasks.checks.compare, "_load_tp_payload", 
PayloadLoader(payload))
     monkeypatch.setattr(atr.tasks.checks.compare, "_checkout_github_source", 
checkout)
     monkeypatch.setattr(atr.tasks.checks.compare, "_decompress_archive", 
decompress)
+    monkeypatch.setattr(atr.tasks.checks.compare, "_find_archive_root", 
find_root)
     monkeypatch.setattr(atr.tasks.checks.compare.util, "get_tmp_dir", 
ReturnValue(tmp_root))
 
     await atr.tasks.checks.compare.source_trees(args)
 
     assert len(recorder.failure_calls) == 1
     message, data = recorder.failure_calls[0]
-    assert message == "Failed to extract source archive for comparison"
+    assert message == "Archive contains entries outside the root directory"
     assert isinstance(data, dict)
-    assert data["archive_path"] == str(await recorder.abs_path())
-    assert data["extract_dir"] == str(decompress.extract_dir)
+    assert data["root"] == "artifact"
+    assert data["extra_entries"] == ["README.txt", "extra.txt"]
 
 
 @pytest.mark.asyncio


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to