Files
TheRock/build_tools/github_actions/test_executable_scripts/test_amdsmi.py
T
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

230 lines
8.3 KiB
Python

#!/usr/bin/env python3
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
===============================================================================
AMDSMI Test Runner
Runs the `amdsmitst` GTest binary shipped with the amd-smi component.
ASIC-specific exclusions are delegated to the amd-smi test tree itself
(amdsmitst.exclude + detect_asic_filter.sh), so this runner stays agnostic to
which tests each ASIC skips. Environment tiering (privileged docker /
unprivileged docker / baremetal) is detected here and surfaced to the binary
via AMDSMI_NON_PRIVILEGED so state-modifying tests skip themselves when the
runtime lacks the privileges they need.
Usage:
python test_amdsmi.py
===============================================================================
"""
import logging
import os
import shlex
import subprocess
from pathlib import Path
logging.basicConfig(level=logging.INFO)
SCRIPT_DIR = Path(__file__).resolve().parent
THEROCK_DIR = SCRIPT_DIR.parent.parent.parent
TESTS_DIR = (THEROCK_DIR / "build" / "share" / "amd_smi" / "tests").resolve()
AMDSMITST_BIN = TESTS_DIR / "amdsmitst"
def get_asic_exclude_filter(test_dir):
"""Source amdsmitst.exclude and detect_asic_filter.sh, return GTEST_EXCLUDE."""
exclude_script = test_dir / "amdsmitst.exclude"
detect_script = test_dir / "detect_asic_filter.sh"
if not exclude_script.exists():
logging.warning(f"amdsmitst.exclude not found in {test_dir}")
return ""
if not detect_script.exists():
logging.warning(f"detect_asic_filter.sh not found in {test_dir}")
return ""
# check=True: a detection failure is a hard error (raises
# CalledProcessError and ends the run) rather than silently degrading to an
# unfiltered test set. capture_output is kept because we still need the
# GTEST_EXCLUDE value echoed on stdout.
result = subprocess.run(
[
"bash",
"-c",
f'source "{exclude_script}" && source "{detect_script}" && echo "$GTEST_EXCLUDE"',
],
capture_output=True,
text=True,
cwd=str(test_dir),
check=True,
)
gtest_exclude = result.stdout.strip()
if gtest_exclude:
logging.info(f"ASIC exclude filter: {gtest_exclude}")
else:
logging.info("ASIC detection returned no exclusions")
return gtest_exclude
def _cgroup_mentions_container():
try:
cgroup = Path("/proc/1/cgroup").read_text()
except OSError:
return False
return any(marker in cgroup for marker in ("docker", "kubepods", "containerd"))
def _in_container():
# /.dockerenv (docker) and /run/.containerenv (podman) are the standard
# marker files; the `container` env var is set by systemd-nspawn and several
# OCI runtimes. Fall back to cgroup inspection for runtimes that set none.
if Path("/.dockerenv").exists() or Path("/run/.containerenv").exists():
return True
if os.environ.get("container"):
return True
return _cgroup_mentions_container()
def detect_privilege_tier():
"""Classify the runtime as 'baremetal', 'privileged', or 'unprivileged'.
amdsmitst gates state-modifying tests on is_sudo_user() (uid == 0). Inside an
unprivileged docker container the process is uid 0 yet lacks the full
capability set a privileged container or baremetal root has, so is_sudo_user()
alone cannot tell the two apart. Inspect the effective capability mask to
distinguish them so the caller can set AMDSMI_NON_PRIVILEGED accordingly.
"""
if not _in_container():
return "baremetal"
# CapEff is a 64-bit hex mask in /proc/self/status. A privileged container
# (or --cap-add=ALL) exposes the full mask; unprivileged containers get the
# trimmed default docker set. CAP_SYS_ADMIN (bit 21) is the practical marker
# for "can touch the sysfs/debugfs knobs the write tests need".
cap_sys_admin = 21
try:
for line in Path("/proc/self/status").read_text().splitlines():
if line.startswith("CapEff:"):
cap_eff = int(line.split()[1], 16)
has_admin = cap_eff & (1 << cap_sys_admin)
return "privileged" if has_admin else "unprivileged"
except (OSError, ValueError):
pass
# Unknown capability state inside a container: treat as unprivileged so we
# err on the side of skipping state-modifying tests rather than hanging.
return "unprivileged"
# -----------------------------
# GTest sharding
# -----------------------------
SHARD_INDEX = os.getenv("SHARD_INDEX", "1")
TOTAL_SHARDS = os.getenv("TOTAL_SHARDS", "1")
environ_vars = os.environ.copy()
environ_vars["GTEST_SHARD_INDEX"] = str(int(SHARD_INDEX) - 1)
environ_vars["GTEST_TOTAL_SHARDS"] = str(TOTAL_SHARDS)
# -----------------------------
# Environment tiering
# -----------------------------
# Surface the privilege tier to the binary. Never override an explicit setting
# from the caller; only add AMDSMI_NON_PRIVILEGED when we detect an unprivileged
# runtime that cannot safely run state-modifying tests.
privilege_tier = detect_privilege_tier()
logging.info(f"Detected privilege tier: {privilege_tier}")
if "AMDSMI_NON_PRIVILEGED" in environ_vars:
logging.info(
"AMDSMI_NON_PRIVILEGED already set by caller "
f"({environ_vars['AMDSMI_NON_PRIVILEGED']!r}); leaving as-is"
)
elif privilege_tier == "unprivileged":
environ_vars["AMDSMI_NON_PRIVILEGED"] = "1"
logging.info("Unprivileged runtime: setting AMDSMI_NON_PRIVILEGED=1")
# -----------------------------
# Test filtering
# -----------------------------
test_type = os.getenv("TEST_TYPE", "standard")
if test_type == "quick":
# Rename-agnostic: matches both the legacy AmdSmiDynamicMetricTest suite and
# the GpuUnit suite it is being renamed to. gtest treats unmatched patterns
# as a no-op, so listing both is safe before and after the rename.
include_tests = [
"AmdSmiDynamicMetricTest.*",
"*Unit*",
]
include_filter = ":".join(include_tests)
gtest_filter_arg = [f"--gtest_filter={include_filter}"]
logging.info(f"Quick mode: include filter = {include_filter}")
else:
# Full mode: rename-agnostic positive whitelist minus the sourced exclusions.
# Listing both the legacy amdsmitst* suites and the new *Functional*/*Unit*
# suites keeps the logical test set stable across the suite rename without
# automatically pulling in unrelated new suites (NIC/IFoE/etc.).
include_tests = [
"amdsmitstReadOnly.*",
"amdsmitstReadWrite.*",
"AmdSmiDynamicMetricTest.*",
"*FunctionalReadOnly.*",
"*FunctionalReadWrite.*",
"*Unit*",
]
# Manual exclusions — always applied regardless of ASIC. Listed under both
# the legacy and renamed suite names so they survive the rename.
exclude_tests = [
"amdsmitstReadOnly.TempRead",
"amdsmitstReadOnly.TestFrequenciesRead",
"amdsmitstReadWrite.TestPowerReadWrite",
"GpuFunctionalReadOnly.TempRead",
"GpuFunctionalReadOnly.TestFrequenciesRead",
"GpuFunctionalReadWrite.TestPowerReadWrite",
]
# ASIC- and environment-specific exclusions come from detect_asic_filter.sh,
# which sources the exclude table shipped alongside the binary, so the names
# always match the binary being tested.
asic_exclude = get_asic_exclude_filter(TESTS_DIR)
if asic_exclude:
for test in asic_exclude.split(":"):
if test and test not in exclude_tests:
exclude_tests.append(test)
logging.info(
f"Combined exclude list ({len(exclude_tests)} entries): {exclude_tests}"
)
gtest_filter = f"{':'.join(include_tests)}:-{':'.join(exclude_tests)}"
gtest_filter_arg = [f"--gtest_filter={gtest_filter}"]
logging.info(f"Full mode: filter = {gtest_filter}")
# -----------------------------
# Build command
# -----------------------------
cmd = [str(AMDSMITST_BIN)] + gtest_filter_arg
logging.info(f"++ Exec [{THEROCK_DIR}]$ {shlex.join(cmd)}")
if not AMDSMITST_BIN.exists():
raise FileNotFoundError(f"amdsmitst not found at {AMDSMITST_BIN}")
if not os.access(AMDSMITST_BIN, os.X_OK):
raise PermissionError(f"amdsmitst is not executable: {AMDSMITST_BIN}")
# -----------------------------
# Run tests
# -----------------------------
subprocess.run(
cmd,
cwd=THEROCK_DIR,
env=environ_vars,
check=True,
)