Files
hermes-agent/tests/scripts/test_dmgbuild_diagnostics.py

160 lines
7.4 KiB
Python

"""DMG diagnostics observe failures without changing the native result."""
import io
import json
import os
import plistlib
import subprocess
import sys
from pathlib import Path
import pytest
from scripts.bundles import dmgbuild_diagnostics as diagnostics
def test_detach_diagnostics_run_before_cleanup_and_preserve_results():
events = []
attachment = {"system-entities": [
{"dev-entry": "/dev/disk24"},
{"dev-entry": "/dev/disk25s1", "mount-point": "/Volumes/Install Hermes Agent"},
]}
failure = (16, "hdiutil: couldn't eject disk25 - Resource busy")
def native(command, *args, **kwargs):
events.append((command, args, kwargs))
if command == "attach":
return 0, attachment
return (0, "detached") if "-force" in args or command != "detach" else failure
def report(target, image, output):
events.append(("diagnostic", target, image, output))
observed = diagnostics.wrap_hdiutil(native, report=report)
observed("attach", "-nobrowse", "/tmp/our staging.dmg")
assert observed("detach", "/dev/disk25s1", plist=False) is failure
observed("detach", "-force", "/dev/disk25s1", plist=False)
assert [event[0] for event in events] == ["attach", "detach", "diagnostic", "detach"]
assert events[2][1] == "/dev/disk25s1"
assert events[2][2] == {"image": "/tmp/our staging.dmg", "entities": attachment["system-entities"]}
assert events[2][3] == failure[1]
def broken_report(*args):
raise OSError("diagnostic command unavailable")
errors = io.StringIO()
broken = diagnostics.wrap_hdiutil(native, report=broken_report, stream=errors)
assert broken("detach", "/dev/disk25s1", plist=False) is failure
assert "diagnostic command unavailable" in errors.getvalue()
assert broken("convert", "input", "-o", "output")[0] == 0
calls = []
def run(argv, **kwargs):
calls.append(argv)
assert kwargs["timeout"] <= 15
if argv[-1] == "/usr/bin/true":
return subprocess.CompletedProcess(argv, 0, "", "")
return subprocess.CompletedProcess(argv, 0, "COMMAND PID PPID USER FD TYPE NAME\nmds 4321 1 root 7r DIR /Volumes/Install Hermes Agent\n", "")
diagnostics.report_detach_failure("/dev/disk25s1", events[2][2], failure[1], run=run, stream=errors)
text = errors.getvalue()
assert "mds 4321 1 root 7r" in text
assert any("+f" in argv and argv[-1] == "/Volumes/Install Hermes Agent" for argv in calls)
file_query = next(argv for argv in calls if "-f" in argv)
assert "/tmp/our staging.dmg" in file_query and "/dev/disk24" in file_query
assert "/dev/disk25s1" in file_query and "/dev/rdisk25s1" in file_query
assert all("+D" not in argv for argv in calls)
def timeout(argv, **kwargs):
if argv[-1] == "/usr/bin/true":
return subprocess.CompletedProcess(argv, 1, "", "sudo: a password is required")
raise subprocess.TimeoutExpired(argv, kwargs["timeout"], output=b"partial holder data")
errors = io.StringIO()
diagnostics.report_detach_failure("/dev/disk25s1", events[2][2], failure[1], run=timeout, stream=errors)
assert "unprivileged" in errors.getvalue()
assert "partial holder data" in errors.getvalue()
assert "timed out" in errors.getvalue()
def test_resize_timings_preserve_native_arguments_and_failure():
output = io.StringIO()
failure = (6, "")
calls = []
def native(command, *args, **kwargs):
assert '[dmg-hdiutil] start resize' in output.getvalue()
calls.append((command, args, kwargs))
return failure
observed = diagnostics.wrap_hdiutil(native, stream=output)
assert observed("resize", "-quiet", "-sectors", "min", "image.dmg", plist=False) is failure
assert calls == [("resize", ("-quiet", "-sectors", "min", "image.dmg"), {"plist": False})]
assert "[dmg-hdiutil] end resize" in output.getvalue()
assert "status=6" in output.getvalue()
assert "elapsed=" in output.getvalue()
def test_diagnostic_entrypoint_preserves_cli_arguments_and_failure(tmp_path):
# This package replays the supplier call contract, not a native DMG build.
package = tmp_path / "dmgbuild"
package.mkdir()
(package / "__init__.py").write_text("", encoding="utf-8")
(package / "core.py").write_text(
'def hdiutil(command, *args, **kwargs):\n'
' print("fixture native", command, *args, flush=True)\n'
' if command == "attach":\n'
' return 0, {"system-entities": [{"dev-entry": "/dev/hermes-dmg-fixture-not-a-device"}]}\n'
' return (0, "cleanup") if "-force" in args else (16, "Resource busy")\n', encoding="utf-8")
(package / "__main__.py").write_text(
'import json, sys\nfrom . import core\n'
'def main():\n'
' print(json.dumps(sys.argv[1:]), flush=True)\n'
' core.hdiutil("attach", "-nobrowse", "/hermes-dmg-fixture-no-image.dmg")\n'
' result = core.hdiutil("detach", "/dev/hermes-dmg-fixture-not-a-device", plist=False)\n'
' core.hdiutil("detach", "-force", "/dev/hermes-dmg-fixture-not-a-device", plist=False)\n'
' raise SystemExit(result[0])\n', encoding="utf-8")
args = ["-s", "settings with spaces.json", "Install Hermes Agent", "output.dmg"]
result = subprocess.run([sys.executable, str(Path(diagnostics.__file__)), *args],
cwd=tmp_path, env={**os.environ, "PYTHONPATH": str(tmp_path)},
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, encoding="utf-8", timeout=60)
assert result.returncode == 16
assert json.dumps(args) in result.stdout
assert result.stdout.index("[dmg-detach]") < result.stdout.index("fixture native detach -force")
@pytest.mark.platforms("macos")
def test_native_busy_image_reports_the_process_holding_its_file(tmp_path):
image, mount = tmp_path.resolve() / "held.dmg", tmp_path.resolve() / "mount"
mount.mkdir()
subprocess.run(["/usr/bin/hdiutil", "create", "-size", "16m", "-fs", "HFS+",
"-volname", "Hermes diagnostic fixture", str(image)],
check=True, capture_output=True, timeout=60)
def native(command, *args, plist=True):
argv = ["/usr/bin/hdiutil", command, *args, *(["-plist"] if plist else [])]
result = subprocess.run(argv, capture_output=True, timeout=60)
output = plistlib.loads(result.stdout) if plist and result.returncode == 0 else (result.stdout + result.stderr).decode()
return result.returncode, output
log = io.StringIO()
observed = diagnostics.wrap_hdiutil(native, report=lambda *args: diagnostics.report_detach_failure(*args, stream=log))
device = None
try:
code, info = observed("attach", "-nobrowse", "-mountpoint", str(mount), str(image))
assert code == 0, info
device = next(e["dev-entry"] for e in info["system-entities"] if e.get("mount-point"))
held = mount / "held-open.txt"
with held.open("w") as handle:
handle.write("owned test handle\n")
handle.flush()
code, output = observed("detach", device, plist=False)
assert code != 0, "native fixture must reproduce a busy detach"
report = log.getvalue()
assert str(os.getpid()) in report
assert str(held) in report
assert "COMMAND" in report and "PID" in report
finally:
if device:
subprocess.run(["/usr/bin/hdiutil", "detach", "-force", device], capture_output=True, timeout=60)