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
230 lines
8.3 KiB
Python
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,
|
|
)
|