refactor(artifacts): harmonize Windows to Linux raw-file collection
Windows produced an intermediate C:\CI\output\artifacts.zip that upload-artifact then re-zipped (zip-in-zip), while Linux deposited raw files. Make both paths consistent: the build stages raw outputs into a collect dir; collect zips it only as a transport archive, fetches, and extracts into the host artifact dir; actions/upload-artifact produces the single final archive. - _windows_build: param artifact_zip -> output_dir; stage raw files (in-place when source IS the collect dir, else wipe+copy), no Compress-Archive; absolute artifact-source resolved. - _windows_collect: zip guest dir as transport, fetch, extract (mirror _linux_collect); guest_path is now the collect directory. - job.py / build_run CLI: pass output_dir; rename --guest-artifact-zip -> --guest-output-dir. - Tests updated for the raw-file contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tarfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar
|
||||
|
||||
@@ -78,8 +79,34 @@ def _patch(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# ── Windows happy path ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _zip_payload(tmp_path: Path) -> bytes:
|
||||
"""Build a real zip whose contents the fake transport will 'fetch'."""
|
||||
src = tmp_path / "zsrc"
|
||||
src.mkdir()
|
||||
(src / "binary.bin").write_bytes(b"\x01\x02\x03")
|
||||
(src / "log.txt").write_text("hello", encoding="utf-8")
|
||||
zip_local = tmp_path / "transfer.zip"
|
||||
with zipfile.ZipFile(zip_local, "w") as zf:
|
||||
for f in src.iterdir():
|
||||
zf.write(f, arcname=f.name)
|
||||
return zip_local.read_bytes()
|
||||
|
||||
|
||||
def _win_transport_with_zip(payload: bytes) -> type:
|
||||
class _WinTransport(_FakeTransport):
|
||||
def fetch(self, remote_path: str, local_path: str) -> None:
|
||||
self.fetched.append((remote_path, str(local_path)))
|
||||
Path(local_path).write_bytes(payload)
|
||||
|
||||
return _WinTransport
|
||||
|
||||
|
||||
def test_collect_windows_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_patch(monkeypatch)
|
||||
payload = _zip_payload(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
artifacts_module, "WinRmTransport", _win_transport_with_zip(payload)
|
||||
)
|
||||
out_dir = tmp_path / "artifacts"
|
||||
result = CliRunner().invoke(
|
||||
cli,
|
||||
@@ -89,18 +116,19 @@ def test_collect_windows_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: P
|
||||
"--ip-address",
|
||||
"10.0.0.7",
|
||||
"--guest-artifact-path",
|
||||
"C:\\CI\\output\\artifacts.zip",
|
||||
"C:\\CI\\output",
|
||||
"--host-artifact-dir",
|
||||
str(out_dir),
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert (out_dir / "artifacts.zip").is_file()
|
||||
instance = _FakeTransport.instances[0]
|
||||
assert any("Test-Path" in c for c in instance.runs)
|
||||
assert instance.fetched == [
|
||||
("C:\\CI\\output\\artifacts.zip", str(out_dir / "artifacts.zip"))
|
||||
]
|
||||
# Raw files extracted into host_dir (no zip-in-zip).
|
||||
assert (out_dir / "binary.bin").read_bytes() == b"\x01\x02\x03"
|
||||
assert (out_dir / "log.txt").read_text(encoding="utf-8") == "hello"
|
||||
cmds = _FakeTransport.instances[0].runs
|
||||
assert any("Compress-Archive" in c for c in cmds)
|
||||
# Temp transport zip on the guest is cleaned up.
|
||||
assert any("Remove-Item" in c and "ci-artifacts-transfer.zip" in c for c in cmds)
|
||||
|
||||
|
||||
def test_collect_windows_artifact_missing(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
@@ -109,7 +137,9 @@ def test_collect_windows_artifact_missing(monkeypatch: pytest.MonkeyPatch, tmp_p
|
||||
class _Missing(_FakeTransport):
|
||||
def __init__(self, *a: Any, **kw: Any) -> None:
|
||||
super().__init__(*a, **kw)
|
||||
self.run_overrides["Test-Path"] = _FakeResult(stdout="MISSING\r\n")
|
||||
self.run_overrides["-and (Get-ChildItem"] = _FakeResult(
|
||||
stdout="MISSING\r\n"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(artifacts_module, "WinRmTransport", _Missing)
|
||||
result = CliRunner().invoke(
|
||||
@@ -120,22 +150,23 @@ def test_collect_windows_artifact_missing(monkeypatch: pytest.MonkeyPatch, tmp_p
|
||||
"--ip-address",
|
||||
"10.0.0.7",
|
||||
"--guest-artifact-path",
|
||||
"C:\\CI\\output\\artifacts.zip",
|
||||
"C:\\CI\\output",
|
||||
"--host-artifact-dir",
|
||||
str(tmp_path / "out"),
|
||||
],
|
||||
)
|
||||
assert result.exit_code != 0
|
||||
assert "artifact not found" in result.output
|
||||
assert "artifact not found or empty" in result.output
|
||||
|
||||
|
||||
def test_collect_windows_include_logs(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
_patch(monkeypatch)
|
||||
payload = _zip_payload(tmp_path)
|
||||
|
||||
class _WithLogs(_FakeTransport):
|
||||
class _WithLogs(_win_transport_with_zip(payload)): # type: ignore[misc]
|
||||
def __init__(self, *a: Any, **kw: Any) -> None:
|
||||
super().__init__(*a, **kw)
|
||||
self.run_overrides["Get-ChildItem"] = _FakeResult(
|
||||
self.run_overrides["C:\\CI\\build"] = _FakeResult(
|
||||
stdout="C:\\CI\\build\\one.log\r\nC:\\CI\\build\\sub\\two.log\r\n"
|
||||
)
|
||||
|
||||
@@ -148,7 +179,7 @@ def test_collect_windows_include_logs(monkeypatch: pytest.MonkeyPatch, tmp_path:
|
||||
"--ip-address",
|
||||
"10.0.0.7",
|
||||
"--guest-artifact-path",
|
||||
"C:\\CI\\output\\artifacts.zip",
|
||||
"C:\\CI\\output",
|
||||
"--host-artifact-dir",
|
||||
str(tmp_path / "out"),
|
||||
"--include-logs",
|
||||
@@ -164,6 +195,10 @@ def test_collect_writes_manifest_when_job_id_set(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
_patch(monkeypatch)
|
||||
payload = _zip_payload(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
artifacts_module, "WinRmTransport", _win_transport_with_zip(payload)
|
||||
)
|
||||
out = tmp_path / "out"
|
||||
result = CliRunner().invoke(
|
||||
cli,
|
||||
@@ -173,7 +208,7 @@ def test_collect_writes_manifest_when_job_id_set(
|
||||
"--ip-address",
|
||||
"10.0.0.7",
|
||||
"--guest-artifact-path",
|
||||
"C:\\CI\\output\\artifacts.zip",
|
||||
"C:\\CI\\output",
|
||||
"--host-artifact-dir",
|
||||
str(out),
|
||||
"--job-id",
|
||||
@@ -189,7 +224,7 @@ def test_collect_writes_manifest_when_job_id_set(
|
||||
assert data["jobId"] == "run-42"
|
||||
assert data["commit"] == "abc1234"
|
||||
names = [f["name"] for f in data["files"]]
|
||||
assert "artifacts.zip" in names
|
||||
assert "binary.bin" in names and "log.txt" in names
|
||||
|
||||
|
||||
# ── Linux happy path ──────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user