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:
@@ -16,6 +16,7 @@ import json
|
|||||||
import shlex
|
import shlex
|
||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import zipfile
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -133,19 +134,44 @@ def _windows_collect(
|
|||||||
include_logs: bool,
|
include_logs: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
cred = KeyringCredentialStore().get(credential_target)
|
cred = KeyringCredentialStore().get(credential_target)
|
||||||
|
remote_zip = "C:\\CI\\ci-artifacts-transfer.zip"
|
||||||
with WinRmTransport(ip_address, cred.username, cred.password) as t:
|
with WinRmTransport(ip_address, cred.username, cred.password) as t:
|
||||||
# Verify artifact exists.
|
# guest_path is the collect directory (raw build outputs).
|
||||||
|
# Verify it exists and is non-empty.
|
||||||
probe = t.run(
|
probe = t.run(
|
||||||
f"if (Test-Path {_ps_quote(guest_path)}) {{ 'OK' }} else {{ 'MISSING' }}",
|
f"if ((Test-Path {_ps_quote(guest_path)}) -and "
|
||||||
|
f"(Get-ChildItem -Force -- {_ps_quote(guest_path)})) "
|
||||||
|
f"{{ 'OK' }} else {{ 'MISSING' }}",
|
||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
if "MISSING" in probe.stdout:
|
if "MISSING" in probe.stdout:
|
||||||
raise click.ClickException(f"artifact not found in guest: {guest_path}")
|
raise click.ClickException(
|
||||||
|
f"artifact not found or empty in guest: {guest_path}"
|
||||||
|
)
|
||||||
|
|
||||||
artifact_name = Path(guest_path).name
|
# Zip the directory contents as a single transport file, fetch,
|
||||||
host_dest = host_dir / artifact_name
|
# extract into host_dir. Mirrors the Linux tar flow so both OSes
|
||||||
click.echo(f"[artifacts collect] fetching {artifact_name}")
|
# deposit raw files; the final single archive is produced by
|
||||||
t.fetch(guest_path, str(host_dest))
|
# actions/upload-artifact (no zip-in-zip).
|
||||||
|
t.run(
|
||||||
|
f"if (Test-Path {_ps_quote(remote_zip)}) "
|
||||||
|
f"{{ Remove-Item {_ps_quote(remote_zip)} -Force }}; "
|
||||||
|
f"Compress-Archive -Path (Join-Path {_ps_quote(guest_path)} '*') "
|
||||||
|
f"-DestinationPath {_ps_quote(remote_zip)} -Force"
|
||||||
|
)
|
||||||
|
click.echo(f"[artifacts collect] fetching artifacts from {guest_path}")
|
||||||
|
try:
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
local_zip = Path(td) / "artifacts.zip"
|
||||||
|
t.fetch(remote_zip, str(local_zip))
|
||||||
|
with zipfile.ZipFile(local_zip) as zf:
|
||||||
|
zf.extractall(host_dir)
|
||||||
|
finally:
|
||||||
|
t.run(
|
||||||
|
f"Remove-Item {_ps_quote(remote_zip)} -Force "
|
||||||
|
f"-ErrorAction SilentlyContinue",
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
if include_logs:
|
if include_logs:
|
||||||
# List log files in the build dir and fetch each one.
|
# List log files in the build dir and fetch each one.
|
||||||
@@ -169,9 +195,9 @@ def _windows_collect(
|
|||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not host_dest.is_file() or host_dest.stat().st_size == 0:
|
if not any(host_dir.iterdir()):
|
||||||
raise click.ClickException(
|
raise click.ClickException(
|
||||||
f"artifact missing or empty after fetch: {host_dest}"
|
f"no artifacts after collect from guest: {guest_path}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -190,7 +216,7 @@ def _windows_collect(
|
|||||||
"--guest-artifact-path",
|
"--guest-artifact-path",
|
||||||
"guest_artifact_path",
|
"guest_artifact_path",
|
||||||
required=True,
|
required=True,
|
||||||
help="Guest path: file (Windows) or directory (Linux).",
|
help="Guest collect directory (Windows and Linux).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--host-artifact-dir",
|
"--host-artifact-dir",
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ def _windows_build(
|
|||||||
ip_address: str,
|
ip_address: str,
|
||||||
credential_target: str,
|
credential_target: str,
|
||||||
workdir: str,
|
workdir: str,
|
||||||
artifact_zip: str,
|
output_dir: str,
|
||||||
host_source_dir: str | None,
|
host_source_dir: str | None,
|
||||||
clone_url: str | None,
|
clone_url: str | None,
|
||||||
clone_branch: str,
|
clone_branch: str,
|
||||||
@@ -251,7 +251,7 @@ def _windows_build(
|
|||||||
# Prepare workdir + artifact output directory.
|
# Prepare workdir + artifact output directory.
|
||||||
prep_ps = (
|
prep_ps = (
|
||||||
f"$work = {_ps_quote(workdir)}; "
|
f"$work = {_ps_quote(workdir)}; "
|
||||||
f"$out = Split-Path {_ps_quote(artifact_zip)} -Parent; "
|
f"$out = {_ps_quote(output_dir)}; "
|
||||||
"if (-not (Test-Path $out)) { New-Item -ItemType Directory -Path $out -Force | Out-Null }; "
|
"if (-not (Test-Path $out)) { New-Item -ItemType Directory -Path $out -Force | Out-Null }; "
|
||||||
"if (Test-Path $work) { Remove-Item $work -Recurse -Force }; "
|
"if (Test-Path $work) { Remove-Item $work -Recurse -Force }; "
|
||||||
"New-Item -ItemType Directory -Path $work -Force | Out-Null"
|
"New-Item -ItemType Directory -Path $work -Force | Out-Null"
|
||||||
@@ -346,34 +346,44 @@ def _windows_build(
|
|||||||
click.echo("[build run] artifact packaging skipped (--skip-artifact).")
|
click.echo("[build run] artifact packaging skipped (--skip-artifact).")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Package the artifact source into the requested zip on the guest.
|
# Stage the artifact source into the collect dir as raw files
|
||||||
|
# (no intermediate zip — `actions/upload-artifact` produces the
|
||||||
|
# single final archive; mirrors the Linux flow, avoids zip-in-zip).
|
||||||
# artifact_source may be absolute (e.g. C:\CI\output) or relative
|
# artifact_source may be absolute (e.g. C:\CI\output) or relative
|
||||||
# to the build workdir. PowerShell Join-Path does NOT treat a
|
# to the build workdir; PowerShell Join-Path does not treat a
|
||||||
# rooted second segment as absolute (it concatenates, yielding
|
# rooted segment as absolute, so resolve explicitly. When the
|
||||||
# C:\CI\build\C:\CI\output), so resolve it explicitly.
|
# source IS the collect dir (build wrote there directly), collect
|
||||||
if build_command:
|
# in place — do NOT wipe it (that deleted the build output).
|
||||||
src_rel = artifact_source or "dist"
|
src_rel = (artifact_source or "dist") if build_command else "bin\\CIOutput"
|
||||||
else:
|
|
||||||
src_rel = "bin\\CIOutput"
|
|
||||||
|
|
||||||
pkg_ps = (
|
pkg_ps = (
|
||||||
f"$work = {_ps_quote(workdir)}; "
|
f"$work = {_ps_quote(workdir)}; "
|
||||||
f"$rel = {_ps_quote(src_rel)}; "
|
f"$rel = {_ps_quote(src_rel)}; "
|
||||||
"$src = if ([System.IO.Path]::IsPathRooted($rel)) "
|
"$src = if ([System.IO.Path]::IsPathRooted($rel)) "
|
||||||
"{ $rel } else { Join-Path $work $rel }; "
|
"{ $rel } else { Join-Path $work $rel }; "
|
||||||
f"$zip = {_ps_quote(artifact_zip)}; "
|
f"$out = {_ps_quote(output_dir)}; "
|
||||||
"if (-not (Test-Path $src)) { "
|
"$sf = [System.IO.Path]::GetFullPath($src).TrimEnd('\\'); "
|
||||||
" throw \"[build run] artifact source not found: $src\" }; "
|
"$of = [System.IO.Path]::GetFullPath($out).TrimEnd('\\'); "
|
||||||
"$dir = Split-Path $zip -Parent; "
|
"$inPlace = ($sf -ieq $of) -or "
|
||||||
"if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }; "
|
"$sf.StartsWith($of + '\\', "
|
||||||
"if (Test-Path $zip) { Remove-Item $zip -Force }; "
|
"[System.StringComparison]::OrdinalIgnoreCase); "
|
||||||
"if (Test-Path $src -PathType Leaf) { "
|
"if ($inPlace) { "
|
||||||
" Compress-Archive -Path $src -DestinationPath $zip -Force "
|
" if (-not (Test-Path $out) -or "
|
||||||
|
" -not (Get-ChildItem -Force -- $out)) { "
|
||||||
|
" throw \"[build run] artifact source not found or empty: $out\" } "
|
||||||
"} else { "
|
"} else { "
|
||||||
" Compress-Archive -Path (Join-Path $src '*') -DestinationPath $zip -Force }"
|
" if (-not (Test-Path $src)) { "
|
||||||
|
" throw \"[build run] artifact source not found: $src\" }; "
|
||||||
|
" if (Test-Path $out) { Remove-Item $out -Recurse -Force }; "
|
||||||
|
" New-Item -ItemType Directory -Path $out -Force | Out-Null; "
|
||||||
|
" if (Test-Path $src -PathType Leaf) { "
|
||||||
|
" Copy-Item $src -Destination $out -Force "
|
||||||
|
" } else { "
|
||||||
|
" Copy-Item (Join-Path $src '*') -Destination $out -Recurse -Force } "
|
||||||
|
"}"
|
||||||
)
|
)
|
||||||
t.run(pkg_ps)
|
t.run(pkg_ps)
|
||||||
click.echo(f"[build run] artifact written to {artifact_zip}")
|
click.echo(f"[build run] artifact staged at {output_dir}")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- CLI
|
# ---------------------------------------------------------------- CLI
|
||||||
@@ -426,10 +436,11 @@ def _windows_build(
|
|||||||
help="Workdir inside the guest (defaults: Win=C:\\CI\\build, Linux=/opt/ci/build).",
|
help="Workdir inside the guest (defaults: Win=C:\\CI\\build, Linux=/opt/ci/build).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--guest-artifact-zip",
|
"--guest-output-dir",
|
||||||
"guest_artifact_zip",
|
"guest_output_dir",
|
||||||
default=None,
|
default=None,
|
||||||
help="Artifact zip path inside the Windows guest (default: C:\\CI\\output\\artifacts.zip).",
|
help="Collect dir inside the Windows guest (default: C:\\CI\\output). "
|
||||||
|
"Raw build outputs are staged here; upload-artifact zips them.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--guest-linux-work-dir",
|
"--guest-linux-work-dir",
|
||||||
@@ -468,7 +479,7 @@ def build_run(
|
|||||||
gitea_credential_target: str,
|
gitea_credential_target: str,
|
||||||
build_command: str,
|
build_command: str,
|
||||||
guest_work_dir: str | None,
|
guest_work_dir: str | None,
|
||||||
guest_artifact_zip: str | None,
|
guest_output_dir: str | None,
|
||||||
guest_linux_work_dir: str | None,
|
guest_linux_work_dir: str | None,
|
||||||
guest_linux_output_dir: str,
|
guest_linux_output_dir: str,
|
||||||
guest_artifact_source: str,
|
guest_artifact_source: str,
|
||||||
@@ -485,7 +496,7 @@ def build_run(
|
|||||||
* ``--host-source-dir`` — local directory zipped/tarred and uploaded;
|
* ``--host-source-dir`` — local directory zipped/tarred and uploaded;
|
||||||
* ``--clone-url`` — ``git clone`` invoked inside the guest.
|
* ``--clone-url`` — ``git clone`` invoked inside the guest.
|
||||||
|
|
||||||
Phase C hook: ``--guest-work-dir`` and ``--guest-artifact-zip`` are
|
Phase C hook: ``--guest-work-dir`` and ``--guest-output-dir`` are
|
||||||
parameterised; defaults are applied here, never inside the transport
|
parameterised; defaults are applied here, never inside the transport
|
||||||
layer.
|
layer.
|
||||||
"""
|
"""
|
||||||
@@ -533,14 +544,14 @@ def build_run(
|
|||||||
raise click.ClickException(str(exc)) from exc
|
raise click.ClickException(str(exc)) from exc
|
||||||
else:
|
else:
|
||||||
workdir = guest_work_dir or "C:\\CI\\build"
|
workdir = guest_work_dir or "C:\\CI\\build"
|
||||||
artifact_zip = guest_artifact_zip or "C:\\CI\\output\\artifacts.zip"
|
output_dir = guest_output_dir or "C:\\CI\\output"
|
||||||
target = credential_target or config.guest_cred_target
|
target = credential_target or config.guest_cred_target
|
||||||
try:
|
try:
|
||||||
_windows_build(
|
_windows_build(
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
credential_target=target,
|
credential_target=target,
|
||||||
workdir=workdir,
|
workdir=workdir,
|
||||||
artifact_zip=artifact_zip,
|
output_dir=output_dir,
|
||||||
host_source_dir=host_source_dir,
|
host_source_dir=host_source_dir,
|
||||||
clone_url=clone_url,
|
clone_url=clone_url,
|
||||||
clone_branch=clone_branch,
|
clone_branch=clone_branch,
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ def job(
|
|||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
credential_target=target,
|
credential_target=target,
|
||||||
workdir="C:\\CI\\build",
|
workdir="C:\\CI\\build",
|
||||||
artifact_zip="C:\\CI\\output\\artifacts.zip",
|
output_dir="C:\\CI\\output",
|
||||||
host_source_dir=None,
|
host_source_dir=None,
|
||||||
clone_url=authed_repo_url,
|
clone_url=authed_repo_url,
|
||||||
clone_branch=branch,
|
clone_branch=branch,
|
||||||
@@ -526,7 +526,7 @@ def job(
|
|||||||
_windows_collect(
|
_windows_collect(
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
credential_target=target,
|
credential_target=target,
|
||||||
guest_path="C:\\CI\\output\\artifacts.zip",
|
guest_path="C:\\CI\\output",
|
||||||
host_dir=job_artifact_dir,
|
host_dir=job_artifact_dir,
|
||||||
include_logs=True,
|
include_logs=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import tarfile
|
import tarfile
|
||||||
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
@@ -78,8 +79,34 @@ def _patch(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||||||
# ── Windows happy path ────────────────────────────────────────────────────
|
# ── 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:
|
def test_collect_windows_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
_patch(monkeypatch)
|
_patch(monkeypatch)
|
||||||
|
payload = _zip_payload(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
artifacts_module, "WinRmTransport", _win_transport_with_zip(payload)
|
||||||
|
)
|
||||||
out_dir = tmp_path / "artifacts"
|
out_dir = tmp_path / "artifacts"
|
||||||
result = CliRunner().invoke(
|
result = CliRunner().invoke(
|
||||||
cli,
|
cli,
|
||||||
@@ -89,18 +116,19 @@ def test_collect_windows_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: P
|
|||||||
"--ip-address",
|
"--ip-address",
|
||||||
"10.0.0.7",
|
"10.0.0.7",
|
||||||
"--guest-artifact-path",
|
"--guest-artifact-path",
|
||||||
"C:\\CI\\output\\artifacts.zip",
|
"C:\\CI\\output",
|
||||||
"--host-artifact-dir",
|
"--host-artifact-dir",
|
||||||
str(out_dir),
|
str(out_dir),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
assert (out_dir / "artifacts.zip").is_file()
|
# Raw files extracted into host_dir (no zip-in-zip).
|
||||||
instance = _FakeTransport.instances[0]
|
assert (out_dir / "binary.bin").read_bytes() == b"\x01\x02\x03"
|
||||||
assert any("Test-Path" in c for c in instance.runs)
|
assert (out_dir / "log.txt").read_text(encoding="utf-8") == "hello"
|
||||||
assert instance.fetched == [
|
cmds = _FakeTransport.instances[0].runs
|
||||||
("C:\\CI\\output\\artifacts.zip", str(out_dir / "artifacts.zip"))
|
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:
|
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):
|
class _Missing(_FakeTransport):
|
||||||
def __init__(self, *a: Any, **kw: Any) -> None:
|
def __init__(self, *a: Any, **kw: Any) -> None:
|
||||||
super().__init__(*a, **kw)
|
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)
|
monkeypatch.setattr(artifacts_module, "WinRmTransport", _Missing)
|
||||||
result = CliRunner().invoke(
|
result = CliRunner().invoke(
|
||||||
@@ -120,22 +150,23 @@ def test_collect_windows_artifact_missing(monkeypatch: pytest.MonkeyPatch, tmp_p
|
|||||||
"--ip-address",
|
"--ip-address",
|
||||||
"10.0.0.7",
|
"10.0.0.7",
|
||||||
"--guest-artifact-path",
|
"--guest-artifact-path",
|
||||||
"C:\\CI\\output\\artifacts.zip",
|
"C:\\CI\\output",
|
||||||
"--host-artifact-dir",
|
"--host-artifact-dir",
|
||||||
str(tmp_path / "out"),
|
str(tmp_path / "out"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
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:
|
def test_collect_windows_include_logs(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
_patch(monkeypatch)
|
_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:
|
def __init__(self, *a: Any, **kw: Any) -> None:
|
||||||
super().__init__(*a, **kw)
|
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"
|
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",
|
"--ip-address",
|
||||||
"10.0.0.7",
|
"10.0.0.7",
|
||||||
"--guest-artifact-path",
|
"--guest-artifact-path",
|
||||||
"C:\\CI\\output\\artifacts.zip",
|
"C:\\CI\\output",
|
||||||
"--host-artifact-dir",
|
"--host-artifact-dir",
|
||||||
str(tmp_path / "out"),
|
str(tmp_path / "out"),
|
||||||
"--include-logs",
|
"--include-logs",
|
||||||
@@ -164,6 +195,10 @@ def test_collect_writes_manifest_when_job_id_set(
|
|||||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
_patch(monkeypatch)
|
_patch(monkeypatch)
|
||||||
|
payload = _zip_payload(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
artifacts_module, "WinRmTransport", _win_transport_with_zip(payload)
|
||||||
|
)
|
||||||
out = tmp_path / "out"
|
out = tmp_path / "out"
|
||||||
result = CliRunner().invoke(
|
result = CliRunner().invoke(
|
||||||
cli,
|
cli,
|
||||||
@@ -173,7 +208,7 @@ def test_collect_writes_manifest_when_job_id_set(
|
|||||||
"--ip-address",
|
"--ip-address",
|
||||||
"10.0.0.7",
|
"10.0.0.7",
|
||||||
"--guest-artifact-path",
|
"--guest-artifact-path",
|
||||||
"C:\\CI\\output\\artifacts.zip",
|
"C:\\CI\\output",
|
||||||
"--host-artifact-dir",
|
"--host-artifact-dir",
|
||||||
str(out),
|
str(out),
|
||||||
"--job-id",
|
"--job-id",
|
||||||
@@ -189,7 +224,7 @@ def test_collect_writes_manifest_when_job_id_set(
|
|||||||
assert data["jobId"] == "run-42"
|
assert data["jobId"] == "run-42"
|
||||||
assert data["commit"] == "abc1234"
|
assert data["commit"] == "abc1234"
|
||||||
names = [f["name"] for f in data["files"]]
|
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 ──────────────────────────────────────────────────────
|
# ── Linux happy path ──────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -360,5 +360,6 @@ def test_build_run_windows_host_source_dir(
|
|||||||
assert instance.copies[0][1] == "C:\\CI\\src-transfer.zip"
|
assert instance.copies[0][1] == "C:\\CI\\src-transfer.zip"
|
||||||
cmds = [c[0] for c in instance.runs]
|
cmds = [c[0] for c in instance.runs]
|
||||||
assert any("Expand-Archive" in c for c in cmds)
|
assert any("Expand-Archive" in c for c in cmds)
|
||||||
# Packaging step references the configured artifact source dir.
|
# Staging copies the raw source into the collect dir (no zip-in-zip).
|
||||||
assert any("Compress-Archive" in c and "'dist'" in c for c in cmds)
|
assert any("Copy-Item" in c and "'dist'" in c for c in cmds)
|
||||||
|
assert not any("Compress-Archive" in c and "'dist'" in c for c in cmds)
|
||||||
|
|||||||
@@ -322,8 +322,8 @@ def test_job_windows_branch_with_overrides(
|
|||||||
# CPU/RAM override patched the clone VMX.
|
# CPU/RAM override patched the clone VMX.
|
||||||
clone_vmx = next((tmp_path / "vms").rglob("*.vmx"), None)
|
clone_vmx = next((tmp_path / "vms").rglob("*.vmx"), None)
|
||||||
# After successful job the clone dir is deleted, so the file is gone.
|
# After successful job the clone dir is deleted, so the file is gone.
|
||||||
# Check that the Windows collect helper got the correct artifact path instead.
|
# Check that the Windows collect helper got the collect dir instead.
|
||||||
assert calls["windows_collect"][0]["guest_path"].endswith("artifacts.zip")
|
assert calls["windows_collect"][0]["guest_path"] == "C:\\CI\\output"
|
||||||
assert clone_vmx is None # cleaned up
|
assert clone_vmx is None # cleaned up
|
||||||
assert backend.calls[-1] == "delete"
|
assert backend.calls[-1] == "delete"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user