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 427a333e Propagate archive extraction error messages
427a333e is described below

commit 427a333e8cfd927aee4a1236e2a3feef33c2fb97
Author: Sean B. Palmer <[email protected]>
AuthorDate: Sun Mar 8 14:50:44 2026 +0000

    Propagate archive extraction error messages
---
 atr/tasks/quarantine.py            | 15 +++++++---
 tests/unit/test_quarantine_task.py | 60 ++++++++++++++++++++++++++++++++------
 2 files changed, 62 insertions(+), 13 deletions(-)

diff --git a/atr/tasks/quarantine.py b/atr/tasks/quarantine.py
index b0d16f23..8cb699ea 100644
--- a/atr/tasks/quarantine.py
+++ b/atr/tasks/quarantine.py
@@ -84,9 +84,11 @@ async def validate(args: QuarantineValidate) -> 
results.Results | None:
         return None
 
     try:
-        await _extract_archives_to_cache(args.archives, quarantine_dir, 
str(project_name), str(version_name))
-    except Exception:
-        await _mark_failed(quarantined, file_entries, "Archive extraction to 
cache failed")
+        await _extract_archives_to_cache(
+            args.archives, quarantine_dir, str(project_name), 
str(version_name), file_entries
+        )
+    except Exception as exc:
+        await _mark_failed(quarantined, file_entries, f"Archive extraction to 
cache failed: {exc}")
         await aioshutil.rmtree(quarantine_dir)
         return None
 
@@ -99,6 +101,7 @@ async def _extract_archives_to_cache(
     quarantine_dir: pathlib.Path,
     project_name: str,
     version_name: str,
+    file_entries: list[sql.QuarantineFileEntryV1],
 ) -> None:
     conf = config.get()
     cache_base = paths.get_cache_archives_dir() / project_name / version_name
@@ -143,9 +146,13 @@ async def _extract_archives_to_cache(
                     await aioshutil.rmtree(staging_dir, ignore_errors=True)
                 else:
                     raise
-        except Exception:
+        except Exception as exc:
             log.exception(f"Failed to extract archive {archive.rel_path} to 
cache")
             await aioshutil.rmtree(staging_dir, ignore_errors=True)
+            for entry in file_entries:
+                if entry.rel_path == archive.rel_path:
+                    entry.errors.append(f"Extraction failed: {exc}")
+                    break
             raise
 
 
diff --git a/tests/unit/test_quarantine_task.py 
b/tests/unit/test_quarantine_task.py
index 08ad6326..6be95e3d 100644
--- a/tests/unit/test_quarantine_task.py
+++ b/tests/unit/test_quarantine_task.py
@@ -31,7 +31,7 @@ import atr.tasks.quarantine as quarantine
 
 
 @pytest.mark.asyncio
-async def 
test_extract_archives_to_cache_discards_staging_dir_when_other_worker_wins(
+async def 
test_extract_archives_to_cache_discards_staging_dir_on_enotempty_collision(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     quarantine_dir = tmp_path / "quarantine"
@@ -52,21 +52,24 @@ async def 
test_extract_archives_to_cache_discards_staging_dir_when_other_worker_
         await aiofiles.os.makedirs(dst_path, exist_ok=True)
         async with aiofiles.open(dst_path / "winner.txt", "w") as f:
             await f.write("winner")
-        raise FileExistsError(dst)
+        raise OSError(errno.ENOTEMPTY, "Directory not empty", str(dst_path))
 
     monkeypatch.setattr(quarantine.paths, "get_cache_archives_dir", lambda: 
cache_root)
     monkeypatch.setattr(quarantine.paths, "get_tmp_dir", lambda: tmp_root)
     monkeypatch.setattr(quarantine.exarch, "extract_archive", extract_archive)
     monkeypatch.setattr(quarantine.aiofiles.os, "rename", rename)
 
+    entries = [sql.QuarantineFileEntryV1(rel_path=archive_rel_path, 
size_bytes=7, content_hash="blake3:ghi", errors=[])]
+
     await quarantine._extract_archives_to_cache(
-        [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:def")],
+        [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:ghi")],
         quarantine_dir,
         "proj",
         "1.0",
+        entries,
     )
 
-    cache_dir = cache_root / "proj" / "1.0" / 
quarantine.hashes.filesystem_cache_archives_key("blake3:def")
+    cache_dir = cache_root / "proj" / "1.0" / 
quarantine.hashes.filesystem_cache_archives_key("blake3:ghi")
 
     assert cache_dir.is_dir()
     assert (cache_dir / "winner.txt").read_text() == "winner"
@@ -74,7 +77,7 @@ async def 
test_extract_archives_to_cache_discards_staging_dir_when_other_worker_
 
 
 @pytest.mark.asyncio
-async def 
test_extract_archives_to_cache_discards_staging_dir_on_enotempty_collision(
+async def 
test_extract_archives_to_cache_discards_staging_dir_when_other_worker_wins(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     quarantine_dir = tmp_path / "quarantine"
@@ -95,27 +98,63 @@ async def 
test_extract_archives_to_cache_discards_staging_dir_on_enotempty_colli
         await aiofiles.os.makedirs(dst_path, exist_ok=True)
         async with aiofiles.open(dst_path / "winner.txt", "w") as f:
             await f.write("winner")
-        raise OSError(errno.ENOTEMPTY, "Directory not empty", str(dst_path))
+        raise FileExistsError(dst)
 
     monkeypatch.setattr(quarantine.paths, "get_cache_archives_dir", lambda: 
cache_root)
     monkeypatch.setattr(quarantine.paths, "get_tmp_dir", lambda: tmp_root)
     monkeypatch.setattr(quarantine.exarch, "extract_archive", extract_archive)
     monkeypatch.setattr(quarantine.aiofiles.os, "rename", rename)
 
+    entries = [sql.QuarantineFileEntryV1(rel_path=archive_rel_path, 
size_bytes=7, content_hash="blake3:def", errors=[])]
+
     await quarantine._extract_archives_to_cache(
-        [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:ghi")],
+        [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:def")],
         quarantine_dir,
         "proj",
         "1.0",
+        entries,
     )
 
-    cache_dir = cache_root / "proj" / "1.0" / 
quarantine.hashes.filesystem_cache_archives_key("blake3:ghi")
+    cache_dir = cache_root / "proj" / "1.0" / 
quarantine.hashes.filesystem_cache_archives_key("blake3:def")
 
     assert cache_dir.is_dir()
     assert (cache_dir / "winner.txt").read_text() == "winner"
     assert not recorded["staging_dir"].exists()
 
 
[email protected]
+async def test_extract_archives_to_cache_propagates_exarch_error_to_file_entry(
+    monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
+) -> None:
+    quarantine_dir = tmp_path / "quarantine"
+    quarantine_dir.mkdir()
+    archive_rel_path = "artifact.tar.gz"
+    (quarantine_dir / archive_rel_path).write_bytes(b"archive")
+    cache_root = tmp_path / "cache"
+    tmp_root = tmp_path / "temporary"
+
+    def extract_archive(_archive_path: str, _extract_dir: str, _config: 
object) -> None:
+        raise RuntimeError("unsafe zip detected")
+
+    monkeypatch.setattr(quarantine.paths, "get_cache_archives_dir", lambda: 
cache_root)
+    monkeypatch.setattr(quarantine.paths, "get_tmp_dir", lambda: tmp_root)
+    monkeypatch.setattr(quarantine.exarch, "extract_archive", extract_archive)
+
+    entries = [sql.QuarantineFileEntryV1(rel_path=archive_rel_path, 
size_bytes=7, content_hash="blake3:bad", errors=[])]
+
+    with pytest.raises(RuntimeError, match="unsafe zip detected"):
+        await quarantine._extract_archives_to_cache(
+            [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:bad")],
+            quarantine_dir,
+            "proj",
+            "1.0",
+            entries,
+        )
+
+    assert len(entries[0].errors) == 1
+    assert "unsafe zip detected" in entries[0].errors[0]
+
+
 @pytest.mark.asyncio
 async def test_extract_archives_to_cache_stages_in_temporary_then_promotes(
     monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
@@ -138,11 +177,14 @@ async def 
test_extract_archives_to_cache_stages_in_temporary_then_promotes(
     monkeypatch.setattr(quarantine.paths, "get_tmp_dir", lambda: tmp_root)
     monkeypatch.setattr(quarantine.exarch, "extract_archive", extract_archive)
 
+    entries = [sql.QuarantineFileEntryV1(rel_path=archive_rel_path, 
size_bytes=7, content_hash="blake3:abc", errors=[])]
+
     await quarantine._extract_archives_to_cache(
         [quarantine.QuarantineArchiveEntry(rel_path=archive_rel_path, 
content_hash="blake3:abc")],
         quarantine_dir,
         "proj",
         "1.0",
+        entries,
     )
 
     cache_dir = cache_root / "proj" / "1.0" / 
quarantine.hashes.filesystem_cache_archives_key("blake3:abc")
@@ -312,7 +354,7 @@ async def 
test_validate_extraction_failure_marks_failed_and_deletes_dir(tmp_path
         )
 
     assert result is None
-    mock_mark.assert_awaited_once_with(row, ok_entries, "Archive extraction to 
cache failed")
+    mock_mark.assert_awaited_once_with(row, ok_entries, "Archive extraction to 
cache failed: Extraction failure")
     mock_rmtree.assert_awaited_once_with(quarantine_dir)
 
 


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

Reply via email to