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]