Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

196 lines
6.4 KiB
Python

"""Tests for perfxpert.tools._tooldep — external-tool verifier."""
import os
import subprocess
from unittest import mock
import pytest
from perfxpert.tools import _tooldep
from perfxpert.tools._tooldep import (
ExternalToolMissing,
check_tool_available,
require_tool,
offer_install,
)
# -- registry sanity --------------------------------------------------------
def test_registry_contains_expected_externals():
"""The registry must enumerate every external dep used by the codebase."""
required = {
"opencode",
"rocprofv3",
"amdclang++",
"rocprof-trace-decoder", # ATT library
"mcp", # python
"pexpect",
}
registered = set(_tooldep._TOOL_REGISTRY.keys())
missing = required - registered
assert not missing, f"missing from registry: {missing}"
# -- check_tool_available ---------------------------------------------------
def test_check_binary_present_returns_true(monkeypatch, tmp_path):
fake = tmp_path / "some-binary"
fake.write_text("#!/bin/sh\n")
fake.chmod(0o755)
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: str(fake))
# Stub subprocess.run so the smoke-test always succeeds
monkeypatch.setattr(
_tooldep.subprocess,
"run",
lambda cmd, **kw: mock.MagicMock(returncode=0),
)
ok, detail = check_tool_available("opencode")
assert ok is True
assert str(fake) in detail
def test_check_binary_smoke_test_oserror_returns_false(monkeypatch, tmp_path):
"""Binary found on PATH but OSError during smoke-test → (False, msg)."""
fake = tmp_path / "broken-binary"
fake.write_text("#!/bin/sh\n")
fake.chmod(0o755)
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: str(fake))
def _raise_oserror(cmd, **kw):
raise OSError("exec format error")
monkeypatch.setattr(_tooldep.subprocess, "run", _raise_oserror)
ok, detail = check_tool_available("opencode")
assert ok is False
assert "smoke-test failed" in detail
def test_check_binary_smoke_test_nonzero_returns_false(monkeypatch, tmp_path):
"""Binary found on PATH but smoke-test exits non-zero → (False, msg)."""
fake = tmp_path / "bad-opencode"
fake.write_text("#!/bin/sh\n")
fake.chmod(0o755)
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: str(fake))
monkeypatch.setattr(
_tooldep.subprocess,
"run",
lambda cmd, **kw: mock.MagicMock(returncode=127),
)
ok, detail = check_tool_available("opencode")
assert ok is False
assert "exited 127" in detail
def test_check_binary_smoke_test_timeout_returns_false(monkeypatch, tmp_path):
"""Binary found on PATH but smoke-test times out → (False, msg)."""
fake = tmp_path / "hung-opencode"
fake.write_text("#!/bin/sh\n")
fake.chmod(0o755)
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: str(fake))
def _raise_timeout(cmd, **kw):
raise subprocess.TimeoutExpired(cmd, 2)
monkeypatch.setattr(_tooldep.subprocess, "run", _raise_timeout)
ok, detail = check_tool_available("opencode")
assert ok is False
assert "timed out" in detail
def test_check_binary_missing_returns_false(monkeypatch):
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: None)
ok, detail = check_tool_available("opencode")
assert ok is False
assert "install" in detail.lower() or "not found" in detail.lower()
def test_require_tool_accepts_explicit_binary_path_for_registered_dep(tmp_path):
fake_cxx = tmp_path / "amdclang++"
fake_cxx.write_text("#!/bin/sh\n")
fake_cxx.chmod(0o755)
detail = require_tool("amdclang++", executable=str(fake_cxx))
assert str(fake_cxx) in detail
def test_check_pylib_present_returns_true():
# mcp is a required runtime dep and should be importable
ok, detail = check_tool_available("mcp")
# Either mcp is installed OR the test env doesn't have it — both are OK.
assert isinstance(ok, bool)
def test_check_pylib_missing_returns_false(monkeypatch):
monkeypatch.setattr(_tooldep.importlib.util, "find_spec", lambda name: None)
ok, detail = check_tool_available("mcp")
assert ok is False
assert "pip install" in detail
# -- require_tool -----------------------------------------------------------
def test_require_tool_raises_when_missing(monkeypatch):
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: None)
with pytest.raises(ExternalToolMissing) as exc:
require_tool("opencode")
assert "opencode" in str(exc.value)
assert exc.value.install_hint
# Install command exists for opencode (curl install script)
assert exc.value.install_cmd is not None
def test_require_tool_with_allow_install_offers(monkeypatch, capsys):
"""If PERFXPERT_ALLOW_INSTALL=1 the helper prompts the user."""
monkeypatch.setenv("PERFXPERT_ALLOW_INSTALL", "1")
monkeypatch.setattr(_tooldep.shutil, "which", lambda name: None)
# Simulate user pressing 'n'
monkeypatch.setattr("builtins.input", lambda _: "n")
with pytest.raises(ExternalToolMissing):
require_tool("opencode", allow_install=True)
captured = capsys.readouterr()
assert "install" in captured.out.lower()
# -- offer_install ----------------------------------------------------------
def test_offer_install_declined_returns_false(monkeypatch, capsys):
monkeypatch.setattr("builtins.input", lambda _: "n")
ok = offer_install("opencode")
assert ok is False
def test_offer_install_accepted_runs_command(monkeypatch, capsys):
monkeypatch.setattr("builtins.input", lambda _: "y")
run_calls = []
def fake_run(cmd, **kw):
run_calls.append(cmd)
return mock.MagicMock(returncode=0)
monkeypatch.setattr(_tooldep.subprocess, "run", fake_run)
ok = offer_install("opencode")
assert ok is True
assert run_calls, "install command should have been invoked"
# -- ATT decoder (shared_lib kind) -----------------------------------------
def test_att_decoder_check_searches_rocm_paths(monkeypatch, tmp_path):
# Simulate the library existing under /opt/rocm
fake_lib = tmp_path / "librocprof-trace-decoder.so"
fake_lib.write_bytes(b"fake\n")
monkeypatch.setenv("ROCM_PATH", str(tmp_path))
monkeypatch.setattr(
_tooldep,
"_shared_lib_search_paths",
lambda name: [str(tmp_path)],
)
ok, detail = check_tool_available("rocprof-trace-decoder")
assert ok is True