mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
Some checks failed
CI / quick-test (push) Has been cancelled
CI / full-test (macos-latest) (push) Has been cancelled
CI / full-test (ubuntu-latest) (push) Has been cancelled
CI / full-test (windows-latest) (push) Has been cancelled
CI / integration-test (push) Has been cancelled
CI / skill-smoke (macos-latest) (push) Has been cancelled
CI / skill-smoke (ubuntu-latest) (push) Has been cancelled
CI / skill-smoke (windows-latest) (push) Has been cancelled
CI / marker-guards (push) Has been cancelled
CI / ui-smoke (push) Has been cancelled
CI / docker-ui-smoke (push) Has been cancelled
CI / docker-portable-test (push) Has been cancelled
Scorecard analysis workflow / Scorecard analysis (push) Has been cancelled
Sync Joi Lab Fork Mirror / sync (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · macos-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · ubuntu-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · windows-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Has been cancelled
CI / release-preflight (push) Has been cancelled
CI / build (dmg, macos-latest, macos-arm64, syft_1.50.0_darwin_arm64.tar.gz, syft, e32fdb9d47823fa633748a1efca2528fd77c37469ea93c9e40ab835da44e4cce) (push) Has been cancelled
CI / build (tar.gz, ubuntu-latest, linux-x86_64, syft_1.50.0_linux_amd64.tar.gz, syft, bf7b29ff57f06da30918266a0e1c2885a8f99784798d1bdb1628886aa015d788) (push) Has been cancelled
CI / vendor-package-smoke (push) Has been cancelled
CI / release (push) Has been cancelled
CI / build (zip, windows-latest, windows-x64, syft_1.50.0_windows_amd64.zip, syft.exe, 815ee6973ec5dff6a671d7f41b0e78835a8c45b91d5a39f4743ea1cee833d3be) (push) Has been cancelled
Own nested AppImage extraction cleanup in the marker-gated AppRun custodian, preserve path-resolution failure semantics across supported Python versions, and advance all synchronized release carriers for the fix-forward release after v6.97.1. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import pathlib
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
|
|
REPO = pathlib.Path(__file__).resolve().parents[1]
|
|
pytestmark = pytest.mark.skipif(
|
|
os.name == "nt", reason="AppImage packaging is POSIX-only"
|
|
)
|
|
|
|
|
|
def test_appdir_layout_wraps_the_existing_pyinstaller_payload(tmp_path: pathlib.Path):
|
|
dist = tmp_path / "dist"
|
|
payload = dist / "Ouroboros"
|
|
payload.mkdir(parents=True)
|
|
launcher = payload / "Ouroboros"
|
|
launcher.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
launcher.chmod(0o755)
|
|
internal = payload / "_internal"
|
|
internal.mkdir()
|
|
(internal / "repo.bundle").write_bytes(b"bundle")
|
|
(internal / "VERSION").write_text("6.96.2\n", encoding="utf-8")
|
|
embedded_python = internal / "python-standalone/bin/python3"
|
|
embedded_python.parent.mkdir(parents=True)
|
|
embedded_python.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
|
|
appdir = tmp_path / "Ouroboros.AppDir"
|
|
env = os.environ.copy()
|
|
env["OUROBOROS_DIST_DIR"] = str(dist)
|
|
env["OUROBOROS_APPDIR"] = str(appdir)
|
|
subprocess.run(
|
|
["bash", "scripts/build_appimage.sh", "--appdir-only"],
|
|
cwd=REPO,
|
|
env=env,
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert (appdir / "AppRun").stat().st_mode & 0o111
|
|
assert (appdir / "ouroboros.desktop").is_file()
|
|
assert (appdir / "ouroboros.png").is_file()
|
|
assert (appdir / "usr/lib/ouroboros/Ouroboros").is_file()
|
|
assert (appdir / "usr/lib/ouroboros/_internal/repo.bundle").is_file()
|
|
assert (
|
|
appdir / "usr/lib/ouroboros/_internal/python-standalone/bin/python3"
|
|
).is_file()
|
|
desktop = (appdir / "ouroboros.desktop").read_text(encoding="utf-8")
|
|
assert "Exec=Ouroboros" in desktop
|
|
assert "Icon=ouroboros" in desktop
|
|
|
|
version = subprocess.run(
|
|
[str(appdir / "AppRun"), "--version"],
|
|
env={**os.environ, "APPDIR": str(appdir)},
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert version.stdout.strip() == "Ouroboros 6.96.2"
|
|
|
|
|
|
def test_apprun_exposes_cli_without_writing_to_the_mount(tmp_path: pathlib.Path):
|
|
appdir = tmp_path / "AppDir"
|
|
cli = appdir / "usr/lib/ouroboros/bin/ouroboros"
|
|
cli.parent.mkdir(parents=True)
|
|
cli.write_text('#!/bin/sh\nprintf "%s\\n" "$OUROBOROS_BUNDLE_DIR"\n', encoding="utf-8")
|
|
cli.chmod(0o755)
|
|
launcher = appdir / "usr/lib/ouroboros/Ouroboros"
|
|
launcher.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
launcher.chmod(0o755)
|
|
apprun = appdir / "AppRun"
|
|
apprun.write_bytes((REPO / "packaging/appimage/AppRun").read_bytes())
|
|
apprun.chmod(0o755)
|
|
|
|
result = subprocess.run(
|
|
[str(apprun), "--cli", "--help"],
|
|
env={**os.environ, "APPDIR": str(appdir)},
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert result.stdout.strip() == str(appdir / "usr/lib/ouroboros/_internal")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("original_tmpdir", "payload_status"),
|
|
[(None, 0), ("/caller/tmp", 37)],
|
|
)
|
|
def test_apprun_custodian_restores_environment_and_removes_private_runtime(
|
|
tmp_path: pathlib.Path,
|
|
original_tmpdir: str | None,
|
|
payload_status: int,
|
|
):
|
|
private_base = tmp_path / "ouroboros-appimage-runtime-test"
|
|
appdir = private_base / "appimage_extracted_test"
|
|
observed = tmp_path / "payload-environment.txt"
|
|
launcher = appdir / "usr/lib/ouroboros/Ouroboros"
|
|
launcher.parent.mkdir(parents=True)
|
|
launcher.write_text(
|
|
"#!/bin/sh\n"
|
|
"if [ \"${TMPDIR+x}\" = x ]; then value=set:$TMPDIR; else value=unset; fi\n"
|
|
"if env | grep -q '^OUROBOROS_APPIMAGE_'; then exit 9; fi\n"
|
|
"printf '%s\\n' \"$value\" > \"$OBSERVED_PATH\"\n"
|
|
"exit \"$TEST_PAYLOAD_STATUS\"\n",
|
|
encoding="utf-8",
|
|
)
|
|
launcher.chmod(0o755)
|
|
apprun = appdir / "AppRun"
|
|
apprun.write_bytes((REPO / "packaging/appimage/AppRun").read_bytes())
|
|
apprun.chmod(0o755)
|
|
env = {
|
|
**os.environ,
|
|
"APPDIR": str(appdir),
|
|
"TMPDIR": str(private_base),
|
|
"OUROBOROS_APPIMAGE_RESTORE_TMPDIR": "1",
|
|
"OUROBOROS_APPIMAGE_ORIGINAL_TMPDIR_SET": "1" if original_tmpdir is not None else "0",
|
|
"OUROBOROS_APPIMAGE_ORIGINAL_TMPDIR": original_tmpdir or "",
|
|
"OBSERVED_PATH": str(observed),
|
|
"TEST_PAYLOAD_STATUS": str(payload_status),
|
|
}
|
|
|
|
result = subprocess.run(
|
|
[str(apprun)],
|
|
env=env,
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == payload_status
|
|
assert observed.read_text(encoding="utf-8").strip() == (
|
|
f"set:{original_tmpdir}" if original_tmpdir is not None else "unset"
|
|
)
|
|
assert not appdir.exists()
|
|
assert not private_base.exists()
|
|
|
|
|
|
def test_apprun_custodian_refuses_unrelated_private_runtime(tmp_path: pathlib.Path):
|
|
private_base = tmp_path / "ouroboros-appimage-runtime-test"
|
|
private_base.mkdir()
|
|
sentinel = private_base / "keep.txt"
|
|
sentinel.write_text("keep", encoding="utf-8")
|
|
appdir = tmp_path / "elsewhere" / "appimage_extracted_test"
|
|
launcher = appdir / "usr/lib/ouroboros/Ouroboros"
|
|
launcher.parent.mkdir(parents=True)
|
|
launcher.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
launcher.chmod(0o755)
|
|
apprun = appdir / "AppRun"
|
|
apprun.write_bytes((REPO / "packaging/appimage/AppRun").read_bytes())
|
|
apprun.chmod(0o755)
|
|
|
|
result = subprocess.run(
|
|
[str(apprun)],
|
|
env={
|
|
**os.environ,
|
|
"APPDIR": str(appdir),
|
|
"TMPDIR": str(private_base),
|
|
"OUROBOROS_APPIMAGE_RESTORE_TMPDIR": "1",
|
|
"OUROBOROS_APPIMAGE_ORIGINAL_TMPDIR_SET": "0",
|
|
"OUROBOROS_APPIMAGE_ORIGINAL_TMPDIR": "",
|
|
},
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode != 0
|
|
assert "outside its private runtime root" in result.stderr
|
|
assert sentinel.read_text(encoding="utf-8") == "keep"
|
|
assert appdir.is_dir()
|
|
|
|
|
|
def test_apprun_unmarked_desktop_path_keeps_exec_pid_and_appdir(tmp_path: pathlib.Path):
|
|
appdir = tmp_path / "AppDir"
|
|
pid_output = tmp_path / "launcher.pid"
|
|
launcher = appdir / "usr/lib/ouroboros/Ouroboros"
|
|
launcher.parent.mkdir(parents=True)
|
|
launcher.write_text(
|
|
"#!/bin/sh\nprintf '%s\\n' \"$$\" > \"$PID_OUTPUT\"\n",
|
|
encoding="utf-8",
|
|
)
|
|
launcher.chmod(0o755)
|
|
apprun = appdir / "AppRun"
|
|
apprun.write_bytes((REPO / "packaging/appimage/AppRun").read_bytes())
|
|
apprun.chmod(0o755)
|
|
|
|
process = subprocess.Popen(
|
|
[str(apprun)],
|
|
env={**os.environ, "APPDIR": str(appdir), "PID_OUTPUT": str(pid_output)},
|
|
)
|
|
process_pid = process.pid
|
|
assert process.wait(timeout=10) == 0
|
|
|
|
assert int(pid_output.read_text(encoding="utf-8")) == process_pid
|
|
assert appdir.is_dir()
|
|
|
|
|
|
def test_appimage_builder_pins_tool_and_embedded_runtime():
|
|
script = (REPO / "scripts/build_appimage.sh").read_text(encoding="utf-8")
|
|
|
|
assert "RUNTIME_VERSION=20251108" in script
|
|
assert "2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d" in script
|
|
assert "00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444" in script
|
|
assert "releases/download/${RUNTIME_VERSION}/runtime-${TOOL_ARCH}" in script
|
|
assert 'fetch_verified "$RUNTIME" "$RUNTIME_URL" "$RUNTIME_SHA256"' in script
|
|
assert '"$TOOL" --runtime-file "$RUNTIME" "$APPDIR" "$OUTPUT"' in script
|