Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/github-actions/installer-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ jobs:
uses: Mresyzz/opsscript-gate@v0.9.4
with:
script-path: installer-smoke.sh
shell: bash
shell: auto
matrix: debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20
network: bridge
packages: bash,curl,ca-certificates,tar
timeout: '120'
format: json
output: reports/installer-smoke.json

- name: Save report, including failed installs
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Troubleshooting = "https://github.com/Mresyzz/opsscript-gate/blob/main/docs/comm
test = [
"pytest>=8.0.0",
"markdown-it-py>=3.0.0",
"PyYAML>=6.0.0",
]

[project.scripts]
Expand Down
76 changes: 76 additions & 0 deletions tests/test_installer_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Exercise the downloadable workflow's inputs and generated script together."""

import json
from pathlib import Path
import shutil
import subprocess

import pytest
import yaml

from opsscript_gate.cli import main


@pytest.fixture
def installer_example():
path = Path(__file__).resolve().parents[1] / "examples/github-actions/installer-smoke.yml"
workflow = yaml.safe_load(path.read_text(encoding="utf-8"))
steps = workflow["jobs"]["installer"]["steps"]
recipe = next(step["run"] for step in steps if step.get("name") == "Prepare installer and installation check")
inputs = next(step["with"] for step in steps if step.get("name") == "Install and verify in each distribution")
args = ["run", inputs["script-path"]]
for key, value in inputs.items():
if key != "script-path":
args.extend([f"--{key}", str(value)])
return recipe, args, inputs


def test_downloadable_installer_inputs_are_accepted(installer_example, tmp_path, monkeypatch):
_, args, inputs = installer_example
monkeypatch.chdir(tmp_path)
Path(inputs["script-path"]).write_text("#!/bin/bash\nexit 0\n", encoding="utf-8")
# This follows the copied inputs through CLI validation, without Docker.
assert main(args + ["--dry-run"]) == 0


def docker_available():
try:
import docker
client = docker.from_env()
try:
client.ping()
finally:
client.close()
return True
except Exception:
return False


@pytest.mark.integration
@pytest.mark.skipif(not docker_available() or not shutil.which("bash"), reason="Requires Docker and Bash")
@pytest.mark.parametrize("installed", [True, False], ids=["working-binary", "false-success"])
def test_downloadable_installer_runs_in_real_matrix(installer_example, tmp_path, monkeypatch, installed):
recipe, args, inputs = installer_example
monkeypatch.chdir(tmp_path)
installer = "#!/bin/bash\n"
if installed:
installer += (
'mkdir -p "$HOME/.local/bin"\n'
'printf \'#!/bin/sh\\n[ "$1" = "--version" ] || exit 9\\necho mytool-test\\n\' '
'> "$HOME/.local/bin/mytool"\n'
'chmod +x "$HOME/.local/bin/mytool"\n'
)
# Explicit exit 0 must not skip the example's installation postcondition.
installer += 'echo "Installation finished"\nexit 0\n'
Path("install.sh").write_text(installer, encoding="utf-8")
subprocess.run(["bash", "-e", "-c", recipe], check=True, timeout=10)
assert main(args) == (0 if installed else 1)
report = json.loads(Path(inputs["output"]).read_text(encoding="utf-8"))
assert {result["distro"] for result in report["results"]} == set(inputs["matrix"].split(","))
for result in report["results"]:
assert result["status"] == ("PASS" if installed else "FAIL"), result
assert result["exit_code"] == (0 if installed else 1), result
if installed:
assert "mytool-test" in result["output_snippet"]
else:
assert "Installer did not create" in result["output_snippet"]
Loading