Files
TheRock/rocm-systems/projects/amdsmi/tests/python/common/common.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

1363 lines
55 KiB
Python

#!/usr/bin/env python3
#
# Copyright (C) Advanced Micro Devices. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Core shared infrastructure for the AMD SMI Python test suite.
Owns three responsibilities:
* amdsmi module discovery -- resolves the install via
AMDSMI_PATH -> ROCM_HOME -> ROCM_PATH -> /opt/rocm, inserts it on
sys.path, and guards against a shadowing site-packages copy
(see ROCM-1552 / PR #6359).
* the ``Common`` helper base -- enum tables, ``Test_API_*`` drivers,
``check_ret`` and friends, held as ``self.common`` by the API tests
(intentionally NOT a unittest.TestCase).
* the unittest runner machinery -- ``run_test_dir``,
``GTestSummaryRunner``, verbosity/-k/-l parsing and the help/legend
printers used by the three top-level runners.
Imported by every functional/unit leaf test and by all three runners
(integration_test.py, cli_unit_test.py, unit_tests.py).
"""
import contextlib
import inspect
import io
import json
import os
import pathlib
import sys
import time
import unittest
def _print_path_remediation(script, file):
"""Print the shared 'fix with one of:' remediation block.
Used by both print_amdsmi_path_help() (-h context) and print_shadow_error()
(import-error context) to avoid duplicating the step list.
"""
print(
"\t 1. Run the tests from the installed amd-smi-lib-tests package location (RECOMMENDED):\n"
f"\t\t `sudo $ROCM_PATH/share/amd_smi/tests/python_unittest/{script} -v`\n"
f"\t\t e.g. `sudo /opt/rocm/share/amd_smi/tests/python_unittest/{script} -v`\n\n"
"\t 2. Set AMDSMI_PATH to point at the correct build:\n"
"\t\t `export AMDSMI_PATH=$ROCM_PATH/share/amd_smi`\n\n"
"\t 3. Reinstall the correct amd-smi-lib package (removes the stale Python package too):\n"
"\t\t `sudo apt remove amd-smi-lib && sudo apt install amd-smi-lib`\n"
"\t\t or, if amd-smi was installed directly via pip:\n"
"\t\t `sudo pip uninstall amdsmi`",
file=file,
)
def print_amdsmi_path_help(file=sys.stdout):
"""Print env-var documentation, path remediation guidance, and usage examples for -h output."""
_script = os.path.basename(sys.argv[0]) or "<script>"
print("\nAdditional options:", file=file)
print(" -l, --list List all available tests and exit", file=file)
print(" -x, --exclude PAT Skip tests whose id contains PAT (inverse of -k)", file=file)
print(file=file)
print("Environment variables:", file=file)
print(" AMDSMI_PATH Path to amd_smi share dir (overrides ROCM_HOME/ROCM_PATH)", file=file)
print(" ROCM_HOME ROCm install root, used when AMDSMI_PATH is unset", file=file)
print(" ROCM_PATH Fallback ROCm install root (default: /opt/rocm)", file=file)
print(file=file)
print("If amd-smi loads from the wrong path, fix with one of:", file=file)
_print_path_remediation(_script, file)
_full = "sudo " + (sys.argv[0] or _script)
_base = sys.argv[0] or _script
_cmds = [
(_base + " -l", "list all available tests"),
(_full + " -v", "run all tests, verbose with print statements (RECOMMENDED)"),
(_full + ' -k "test_name" -v', "run only tests matching substring"),
(_full + " -x performance -v", "run everything except the performance tests"),
(_full + " -q", "run all tests, quiet with no print statements"),
]
_w = max(len(cmd) for cmd, _ in _cmds) + 2
print(file=file)
print("Examples:", file=file)
for cmd, desc in _cmds:
print(f" {cmd:<{_w}} - {desc}", file=file)
def print_shadow_error(script, loaded_from, expected_path, file=sys.stderr):
"""Print an actionable error when amdsmi was loaded from the wrong path.
Prints an ERROR header identifying the unexpected source, the shadowing
diagnosis, the shared remediation steps, and a pointer to -h for more details.
"""
print(
f"ERROR: amdsmi loaded from '{loaded_from}' instead of expected path '{expected_path}'.",
file=file,
)
print("A system-installed amdsmi package is shadowing the test target.", file=file)
print("Fix with one of:", file=file)
_print_path_remediation(script, file)
print(f"\nRefer to `{script} -h` for more details.", file=file)
amdsmi_path = os.environ.get("AMDSMI_PATH") or os.path.join(
os.environ.get("ROCM_HOME") or os.environ.get("ROCM_PATH") or "/opt/rocm", "share/amd_smi"
)
if not os.path.exists(amdsmi_path):
raise FileNotFoundError(
f'amdsmi path "{amdsmi_path}" does not exist. '
"Set ROCM_HOME, ROCM_PATH, or AMDSMI_PATH to the correct install location."
)
sys.path.insert(0, amdsmi_path)
try:
import amdsmi
except ImportError as e:
raise ImportError(f'Could not import the "amdsmi" module from "{amdsmi_path}"') from e
# Verify the imported amdsmi package came from amdsmi_path, not a system-installed
# package in dist-packages. A stale system install wins over sys.path.append() because
# dist-packages is already on sys.path at interpreter startup; insert(0,...) above
# prevents this, but error explicitly in case amdsmi was already cached in sys.modules.
#
# Example of how to trigger this error output (for test purposes):
# sudo AMDSMI_PATH=/tmp /opt/rocm/share/amd_smi/tests/python_unittest/cli_unit_test.py -v
_amdsmi_file = getattr(amdsmi, "__file__", None) or ""
if not os.path.realpath(_amdsmi_file).startswith(os.path.realpath(amdsmi_path) + os.sep):
_script = os.path.basename(sys.argv[0]) or "unit_tests.py"
print_shadow_error(_script, _amdsmi_file, amdsmi_path)
# For direct test-script invocations use sys.exit so no Python traceback
# clutters the remediation output. For anything else (pytest, IDE runners,
# ad-hoc imports) raise so the caller gets a clear error with location.
_known_scripts = ("unit_tests.py", "integration_test.py", "cli_unit_test.py")
_main_file = getattr(sys.modules.get("__main__"), "__file__", "") or ""
if os.path.basename(_main_file) in _known_scripts:
sys.exit(1)
raise RuntimeError(
f"amdsmi loaded from wrong path: '{_amdsmi_file}' (expected under '{amdsmi_path}')"
)
#################################################
# Module level functions, not part of the class #
#################################################
# Canonical status-string sentinels, shared by the Common class and the
# standalone benchmark suites (single source of truth).
PASS = "AMDSMI_STATUS_SUCCESS"
FAIL = "AMDSMI_STATUS_INVAL"
# Status-code -> name map, derived once from the AmdSmiStatus enum so the table
# never drifts from the library. Shared by the Common class and the standalone
# performance benchmark suites (which intentionally do not inherit Common).
ERROR_MAP = {str(member.value): f"AMDSMI_STATUS_{member.name}" for member in amdsmi.AmdSmiStatus}
VERBOSITY_QUIET = 0 # -q / --quiet
VERBOSITY_NORMAL = 1 # default (dot-per-test)
VERBOSITY_VERBOSE = 2 # -v / --verbose (per-test result lines)
def build_type_lists():
"""Enum-derived test-parameter lists, each ``[(name, enum_value, expected)]``.
Pure: built solely from the amdsmi enums/wrapper with no ``amdsmi_init`` or
hardware access, so it is safe to call at import or test-discovery time.
Shared by the ``Common`` class and the standalone performance benchmark
suites (which intentionally avoid constructing ``Common`` to keep hardware
init out of collection), so this enum-to-list logic lives in one place.
"""
status_types = [
(member.name, amdsmi.AmdSmiStatus(member.value), PASS) for member in amdsmi.AmdSmiStatus
]
clk_types = []
for member in amdsmi.AmdSmiClkType:
cond = PASS
if member.name in ["DCEF", "PCIE"]:
cond = [PASS, FAIL]
clk_types.append((member.name, amdsmi.AmdSmiClkType(member.value), cond))
clk_limit_types = [
(member.name, amdsmi.AmdSmiClkLimitType(member.value), PASS)
for member in amdsmi.AmdSmiClkLimitType
]
# amdsmi_get_cpu_current_io_bandwidth(handle, encoding: int, link_name: str) takes a
# bandwidth-type int and a link-ID string (see amdsmi_interface). The valid values come
# from the amdsmi_link_id_bw_type_t struct in amdsmi.h: link names P0-P4/G0-G7, and
# bandwidth types 1 (Aggregate), 2 (Read), 4 (Write). For IO bandwidth only AGG (1) is
# used; Read/Write (2/4) apply to XGMI bandwidth. Each tuple is
# (link_name, bw_type, expected): the consumers pass element 0 as link_name and element
# 1 as the encoding int. This mirrors the public interface contract rather than scraping
# wrapper *_BW0 symbols (the old scrape passed the symbol name, e.g. "AGG_BW0", as
# link_name, which is not a valid link ID).
io_bw_agg_type = 1 # AGG: the bandwidth type used for IO bandwidth
io_bw_encodings = [
(link_name, io_bw_agg_type, PASS)
for link_name in (
"P0",
"P1",
"P2",
"P3",
"P4",
"G0",
"G1",
"G2",
"G3",
"G4",
"G5",
"G6",
"G7",
)
]
gpu_blocks = []
for member in amdsmi.AmdSmiGpuBlock:
cond = PASS
if member.name in ["INVALID", "RESERVED"]:
cond = FAIL
gpu_blocks.append((member.name, amdsmi.AmdSmiGpuBlock(member.value), cond))
memory_types = [
(member.name, amdsmi.AmdSmiMemoryType(member.value), PASS)
for member in amdsmi.AmdSmiMemoryType
]
reg_types = [
(member.name, amdsmi.AmdSmiRegType(member.value), PASS) for member in amdsmi.AmdSmiRegType
]
voltage_metrics = [
(member.name, amdsmi.AmdSmiVoltageMetric(member.value), PASS)
for member in amdsmi.AmdSmiVoltageMetric
]
voltage_types = []
for member in amdsmi.AmdSmiVoltageType:
cond = PASS
if member.name in ["INVALID"]:
cond = FAIL
voltage_types.append((member.name, amdsmi.AmdSmiVoltageType(member.value), cond))
link_types = []
for member in amdsmi.AmdSmiLinkType:
cond = PASS
if member.name in ["AMDSMI_LINK_TYPE_NOT_APPLICABLE", "AMDSMI_LINK_TYPE_UNKNOWN"]:
cond = FAIL
link_types.append((member.name, amdsmi.AmdSmiLinkType(member.value), cond))
temperature_types = [
(member.name, amdsmi.AmdSmiTemperatureType(member.value), PASS)
for member in amdsmi.AmdSmiTemperatureType
]
temperature_metrics = [
(member.name, amdsmi.AmdSmiTemperatureMetric(member.value), PASS)
for member in amdsmi.AmdSmiTemperatureMetric
]
utilization_counter_types: list = [
(member.name, amdsmi.AmdSmiUtilizationCounterType(member.value), PASS)
for member in amdsmi.AmdSmiUtilizationCounterType
]
# Negative test: integer 100 is out of range for AmdSmiUtilizationCounterType;
# the API must reject it. Not a real enum member so must be added explicitly.
utilization_counter_types.append(("UTILIZATION_COUNTER_BAD", 100, FAIL))
event_groups = []
for member in amdsmi.AmdSmiEventGroup:
cond = PASS
if member.name in ["GRP_INVALID"]:
cond = FAIL
event_groups.append((member.name, amdsmi.AmdSmiEventGroup(member.value), cond))
event_types = [
(member.name, amdsmi.AmdSmiEventType(member.value), PASS)
for member in amdsmi.AmdSmiEventType
]
counter_commands = [
(member.name, amdsmi.AmdSmiCounterCommand(member.value), PASS)
for member in amdsmi.AmdSmiCounterCommand
]
compute_partition_types = []
for member in amdsmi.AmdSmiComputePartitionType:
cond = PASS
if member.name in ["INVALID"]:
cond = FAIL
compute_partition_types.append(
(member.name, amdsmi.AmdSmiComputePartitionType(member.value), cond)
)
memory_partition_types = []
for member in amdsmi.AmdSmiMemoryPartitionType:
cond = PASS
if member.name in ["UNKNOWN"]:
cond = FAIL
elif member.name in ["NPS4", "NPS8"]:
# NPS4/NPS8 are hardware-dependent; accept success or invalid depending on support
# BTW - no asic supports NPS8...
cond = [PASS, FAIL]
memory_partition_types.append(
(member.name, amdsmi.AmdSmiMemoryPartitionType(member.value), cond)
)
accelerator_partition_mem_alloc_mode_types = []
for member in amdsmi.AmdSmiAcceleratorPartitionMemAllocModeType:
cond = PASS
if member.name in ["INVALID"]:
cond = FAIL
accelerator_partition_mem_alloc_mode_types.append(
(member.name, amdsmi.AmdSmiAcceleratorPartitionMemAllocModeType(member.value), cond)
)
freq_inds = []
for member in amdsmi.AmdSmiFreqInd:
cond = PASS
if member.name in ["INVALID"]:
cond = FAIL
freq_inds.append((member.name, amdsmi.AmdSmiFreqInd(member.value), cond))
power_profile_preset_masks = []
for member in amdsmi.AmdSmiPowerProfilePresetMasks:
cond = PASS
if member.name in ["INVALID"]:
# INVALID (0xFFFFFFFFFFFFFFFF) is intentionally tested with a specific expected
# status rather than the generic FAIL (AMDSMI_STATUS_INVAL). The API returns
# AMDSMI_STATUS_INPUT_OUT_OF_BOUNDS for this sentinel, not AMDSMI_STATUS_INVAL.
cond = "AMDSMI_STATUS_INPUT_OUT_OF_BOUNDS"
power_profile_preset_masks.append(
(member.name, amdsmi.AmdSmiPowerProfilePresetMasks(member.value), cond)
)
processor_types = []
for member in amdsmi.AmdSmiProcessorType:
cond = PASS
if member.name in ["UNKNOWN"]:
cond = FAIL
processor_types.append((member.name, amdsmi.AmdSmiProcessorType(member.value), cond))
dev_perf_levels = []
for member in amdsmi.AmdSmiDevPerfLevel:
cond = PASS
if member.name in ["UNKNOWN"]:
cond = FAIL
dev_perf_levels.append((member.name, amdsmi.AmdSmiDevPerfLevel(member.value), cond))
return {
"status_types": status_types,
"clk_types": clk_types,
"clk_limit_types": clk_limit_types,
"io_bw_encodings": io_bw_encodings,
"gpu_blocks": gpu_blocks,
"memory_types": memory_types,
"reg_types": reg_types,
"voltage_metrics": voltage_metrics,
"voltage_types": voltage_types,
"link_types": link_types,
"temperature_types": temperature_types,
"temperature_metrics": temperature_metrics,
"utilization_counter_types": utilization_counter_types,
"event_groups": event_groups,
"event_types": event_types,
"counter_commands": counter_commands,
"compute_partition_types": compute_partition_types,
"memory_partition_types": memory_partition_types,
"accelerator_partition_mem_alloc_mode_types": accelerator_partition_mem_alloc_mode_types,
"freq_inds": freq_inds,
"power_profile_preset_masks": power_profile_preset_masks,
"processor_types": processor_types,
"dev_perf_levels": dev_perf_levels,
}
# Module-level views of the enum-derived parameter lists, computed once at import.
# They resolve natively (e.g. ``common.GPU_BLOCKS``) so the standalone benchmark
# suites can consume them directly instead of via dynamic ``setattr`` a type checker
# can't follow. Read-only test parameters, so sharing a single instance is safe.
_TYPE_LISTS = build_type_lists()
STATUS_TYPES = _TYPE_LISTS["status_types"]
CLK_TYPES = _TYPE_LISTS["clk_types"]
CLK_LIMIT_TYPES = _TYPE_LISTS["clk_limit_types"]
IO_BW_ENCODINGS = _TYPE_LISTS["io_bw_encodings"]
GPU_BLOCKS = _TYPE_LISTS["gpu_blocks"]
MEMORY_TYPES = _TYPE_LISTS["memory_types"]
REG_TYPES = _TYPE_LISTS["reg_types"]
VOLTAGE_METRICS = _TYPE_LISTS["voltage_metrics"]
VOLTAGE_TYPES = _TYPE_LISTS["voltage_types"]
LINK_TYPES = _TYPE_LISTS["link_types"]
TEMPERATURE_TYPES = _TYPE_LISTS["temperature_types"]
TEMPERATURE_METRICS = _TYPE_LISTS["temperature_metrics"]
UTILIZATION_COUNTER_TYPES = _TYPE_LISTS["utilization_counter_types"]
EVENT_GROUPS = _TYPE_LISTS["event_groups"]
EVENT_TYPES = _TYPE_LISTS["event_types"]
COUNTER_COMMANDS = _TYPE_LISTS["counter_commands"]
COMPUTE_PARTITION_TYPES = _TYPE_LISTS["compute_partition_types"]
MEMORY_PARTITION_TYPES = _TYPE_LISTS["memory_partition_types"]
ACCELERATOR_PARTITION_MEM_ALLOC_MODE_TYPES = _TYPE_LISTS[
"accelerator_partition_mem_alloc_mode_types"
]
FREQ_INDS = _TYPE_LISTS["freq_inds"]
POWER_PROFILE_PRESET_MASKS = _TYPE_LISTS["power_profile_preset_masks"]
PROCESSOR_TYPES = _TYPE_LISTS["processor_types"]
DEV_PERF_LEVELS = _TYPE_LISTS["dev_perf_levels"]
def _parse_verbose(argv=None):
"""Resolve the verbosity selector from *argv* (defaults to ``sys.argv``).
``-q``/``--quiet`` -> QUIET; ``-v``/``-vv``/``--verbose`` -> VERBOSE;
otherwise NORMAL. Parsed once at import as the module-level ``verbose`` so
every runner and test module shares a single source of truth instead of
re-parsing ``sys.argv`` in each file.
"""
argv = sys.argv if argv is None else argv
if "-q" in argv or "--quiet" in argv:
return VERBOSITY_QUIET
if any(a in ("-v", "-vv", "--verbose") for a in argv):
return VERBOSITY_VERBOSE
return VERBOSITY_NORMAL
# Shared verbosity for the whole process (runners + the test modules they import).
verbose = _parse_verbose()
def _print_test_ids(suite):
for test in suite:
if isinstance(test, unittest.TestSuite):
_print_test_ids(test)
else:
# Print the full dotted test id (e.g. "cli.test_event.TestEvent.test_command")
# so -l output matches the GTestSummaryRunner's per-test labels. Strip the
# "__main__." prefix the same way so module-run and discover-run agree.
#
# The -l/--list output is the data the user asked for, so it goes to stdout
# (lets `... -l | grep xgmi` work without 2>&1). Test-run chatter (legend,
# progress, summary, errors, help) stays on stderr.
test_id = test.id()
if test_id.startswith("__main__."):
test_id = test_id[len("__main__.") :]
print(f"\t{test_id}", file=sys.stdout)
return
def print_test_ids(suite):
"""Print every test ID in an already-loaded *suite* to stdout.
Public entry point for the discover()-based runners (cli_unit_test.py,
integration_test.py, unit_tests.py), which build their suite from a
directory rather than from a single module. Emits an "Available tests:"
header followed by one dotted test id per line. Output goes to stdout so
the listing can be captured/piped independently of the normal test-run
output on stderr (e.g. ``... -l | grep xgmi``).
"""
print("Available tests:", file=sys.stdout)
_print_test_ids(suite)
return
def print_legend():
"""Print the dot-character legend for unittest output to stderr.
Call this before running tests in non-verbose mode so users know what
the single-character result indicators (., s, F, E) mean.
"""
print("=" * 70, file=sys.stderr)
print("Legend: . = pass, s = skipped, F = fail, E = error", file=sys.stderr)
print("=" * 70, file=sys.stderr)
return
def print_unittest_help():
"""Print unittest's -h output with its built-in Examples epilog stripped.
unittest (via argparse) appends its own Examples block; we capture stdout,
remove everything from the last 'Examples:' onward, and reprint — leaving
our print_amdsmi_path_help() as the sole Examples section at the bottom.
"""
buf = io.StringIO()
try:
with contextlib.redirect_stdout(buf):
unittest.main(argv=[sys.argv[0] or "unit_tests.py", "--help"])
except SystemExit:
pass
output = buf.getvalue()
examples_idx = output.rfind("\nExamples:")
if examples_idx != -1:
output = output[:examples_idx]
sys.stdout.write(output)
sys.stdout.flush()
def make_runner_verbosity(verbose):
"""Map our three verbosity levels to the correct unittest.TextTestRunner verbosity.
In verbose mode, test bodies self-report each result via print_func_name() /
check_ret(), so we suppress the runner's own per-test lines (VERBOSITY_QUIET)
to avoid printing each test name twice. In normal and quiet modes we keep the
runner at VERBOSITY_NORMAL (dots) so CI has a per-test progress indicator and
hung tests are detectable.
verbose value → runner verbosity
VERBOSITY_QUIET (0) → VERBOSITY_NORMAL (dots only)
VERBOSITY_NORMAL (1) → VERBOSITY_NORMAL (dots only)
VERBOSITY_VERBOSE (2) → VERBOSITY_QUIET (runner silent; test bodies own output)
"""
if verbose >= VERBOSITY_VERBOSE:
return VERBOSITY_QUIET # test bodies print their own per-test output
return VERBOSITY_NORMAL # dots keep CI informed of progress
def _parse_k_pattern(argv=None):
"""Return the ``-k``/``--keyword`` filter substring from *argv*, or ``None``.
Accepts both the separate ("-k", "pattern") and joined ("-kpattern") forms,
matching the spellings unittest itself understands.
"""
argv = sys.argv if argv is None else argv
for i, arg in enumerate(argv):
if arg in ("-k", "--keyword") and i + 1 < len(argv):
return argv[i + 1]
if arg.startswith("-k") and arg != "-k":
return arg[2:]
return None
def _parse_x_pattern(argv=None):
"""Return the ``-x``/``--exclude`` filter substring from *argv*, or ``None``.
Tests whose id contains this substring are skipped (the inverse of ``-k``).
Accepts both the separate ("-x", "pattern") and joined ("-xpattern") forms.
"""
argv = sys.argv if argv is None else argv
for i, arg in enumerate(argv):
if arg in ("-x", "--exclude") and i + 1 < len(argv):
return argv[i + 1]
if arg.startswith("-x") and arg != "-x":
return arg[2:]
return None
def _filter_suite_exclude(suite, pattern):
"""Return a copy of *suite* with every test whose id contains *pattern* removed.
unittest's ``-k`` can only include matching tests; this provides the inverse so
callers can run everything except, e.g., the performance suites (``-x performance``).
"""
filtered = unittest.TestSuite()
for test in suite:
if isinstance(test, unittest.TestSuite):
filtered.addTest(_filter_suite_exclude(test, pattern))
elif pattern not in test.id():
filtered.addTest(test)
return filtered
def run_test_dir(subdir, title, top_level_dir):
"""Discover and run every test under *top_level_dir*/*subdir*, then exit.
Single implementation of the runner boilerplate shared by the three entry
scripts (integration_test.py / cli_unit_test.py / unit_tests.py): it handles
``-h``/``--help``, ``-k``/``--keyword`` filtering, ``-l``/``--list``, the
root-privilege check, the legend/title preamble, output buffering
(``-b``/``--buffer``) and the GTest-style summary runner. Never returns —
always calls ``sys.exit()`` (0 on success, 1 on failure).
"""
argv = sys.argv
# Skip legend/title/"Running" preamble when the user just wants help text.
if "-h" in argv or "--help" in argv:
print_unittest_help()
print_amdsmi_path_help()
sys.exit(0)
loader = unittest.TestLoader()
# -k does substring matching; wrapping in * lets globs (test_ttm*) match too.
k_pattern = _parse_k_pattern(argv)
if k_pattern:
loader.testNamePatterns = [f"*{k_pattern}*"]
suite = loader.discover(
start_dir=os.path.join(top_level_dir, subdir),
pattern="test_*.py",
top_level_dir=top_level_dir,
)
# -x/--exclude drops any test whose id contains the substring (the inverse of
# -k), e.g. run everything but the perf suites with `-x performance`.
x_pattern = _parse_x_pattern(argv)
if x_pattern:
suite = _filter_suite_exclude(suite, x_pattern)
if "-l" in argv or "--list" in argv:
print_test_ids(suite)
sys.exit(0)
# Detect if ran without sudo or root privileges
if os.geteuid() != 0:
print(
"Warning: Some tests may require elevated privileges (sudo/root) to run completely.\n",
file=sys.stderr,
)
print("Please relaunch with elevated privileges.\n", file=sys.stderr)
sys.exit(1)
# Only show the dot-character legend when not in verbose mode; in verbose
# mode each test prints its own result line so the dot legend is irrelevant.
if verbose < VERBOSITY_VERBOSE:
print_legend()
if verbose > VERBOSITY_QUIET:
print(f"{title}\n")
print("Running tests...\n")
runner = GTestSummaryRunner(
stream=sys.stderr,
verbosity=make_runner_verbosity(verbose),
buffer="-b" in argv or "--buffer" in argv,
)
result = runner.run(suite)
sys.exit(0 if result.wasSuccessful() else 1)
class GTestSummaryRunner(unittest.TextTestRunner):
"""TextTestRunner that appends a GTest-style pass/skip/fail summary.
After the standard unittest output, prints a colored block like::
[----------] 40 tests ran.
[ PASSED ] 39 tests.
[ SKIPPED ] 1 test, listed below:
[ SKIPPED ] TestClass.test_method_name
Color scheme mirrors GTest:
cyan = separator line ([----------])
green = PASSED
yellow = SKIPPED
red = FAILED
Colors are automatically suppressed when the output stream is not a TTY
(e.g. when piped to a file or CI log capture), so file-based runners such
as those in perf_tests.py will never receive ANSI escape codes in their logs.
Example::
runner = common.GTestSummaryRunner(verbosity=common.make_runner_verbosity(verbose))
unittest.main(testRunner=runner)
# To redirect output to stderr (e.g. when stdout is used for data):
runner = common.GTestSummaryRunner(stream=sys.stderr, verbosity=common.make_runner_verbosity(verbose))
"""
_CYAN = "\033[36m"
_GREEN = "\033[32m"
_YELLOW = "\033[33m"
_RED = "\033[31m"
_RESET = "\033[0m"
@staticmethod
def _plural(n):
return "s" if n != 1 else ""
@staticmethod
def _test_label(test):
"""Return the GTest-format label for *test*.
``TestCase.id()`` is the public API for a test's full dotted name
(e.g. ``"__main__.ClassName.method_name"``). When the id starts with
the standard ``__main__.`` prefix we strip it, yielding
``ClassName.method_name`` without risking label collisions between
tests from different modules that share a class name.
"""
test_id = test.id()
if test_id.startswith("__main__."):
return test_id[len("__main__.") :]
return test_id
def _color(self, code, text):
"""Wrap *text* in *code* only when colors are appropriate.
Colors are suppressed when the ``NO_COLOR`` environment variable is set
(https://no-color.org/) or when the output stream is not a real TTY.
"""
if os.environ.get("NO_COLOR"):
return text
underlying = getattr(self.stream, "stream", self.stream)
if hasattr(underlying, "isatty") and underlying.isatty():
return f"{code}{text}{self._RESET}"
return text
def run(self, test):
start = time.perf_counter()
result = super().run(test)
elapsed_ms = round((time.perf_counter() - start) * 1000)
self._print_gtest_summary(result, elapsed_ms)
return result
def _print_gtest_summary(self, result, elapsed_ms):
"""Write the GTest-style pass/skip/fail block to *self.stream*."""
stream = self.stream # unittest._WritelnDecorator, supports .writeln()
skipped = len(result.skipped)
# unexpectedSuccesses are tests marked @expectedFailure that passed instead.
# They cause wasSuccessful() to return False, so count them as failures so
# the summary accurately reflects the overall run status.
unexpectedSuccesses = len(getattr(result, "unexpectedSuccesses", []))
failures = len(result.failures) + len(result.errors) + unexpectedSuccesses
passed = result.testsRun - skipped - failures
stream.writeln()
stream.writeln(
self._color(
self._CYAN,
f"[----------] {result.testsRun} test{self._plural(result.testsRun)} ran. ({elapsed_ms} ms total)",
)
)
stream.writeln(
self._color(self._GREEN, f"[ PASSED ] {passed} test{self._plural(passed)}.")
)
if failures:
stream.writeln(
self._color(
self._RED,
f"[ FAILED ] {failures} test{self._plural(failures)}, listed below:",
)
)
for t, _ in result.failures:
stream.writeln(self._color(self._RED, f"[ FAILED ] {self._test_label(t)}"))
for t, _ in result.errors:
stream.writeln(self._color(self._RED, f"[ FAILED ] {self._test_label(t)}"))
# unexpectedSuccesses items are bare TestCase instances (not (test, traceback) tuples).
for t in getattr(result, "unexpectedSuccesses", []):
stream.writeln(
self._color(
self._RED, f"[ FAILED ] {self._test_label(t)} (unexpected success)"
)
)
if skipped:
stream.writeln(
self._color(
self._YELLOW,
f"[ SKIPPED ] {skipped} test{self._plural(skipped)}, listed below:",
)
)
for t, _ in result.skipped:
stream.writeln(self._color(self._YELLOW, f"[ SKIPPED ] {self._test_label(t)}"))
stream.writeln()
def has_gpu_od_interface(bdf):
"""Check if a GPU has the gpu_od sysfs interface.
Delegates to amdsmi_helpers.AMDSMIHelpers.detect_gpu_od(). Requires the
AMD-SMI CLI to be installed (amdsmi_helpers is imported on first call).
Args:
bdf: PCI Bus/Device/Function string (e.g. '0000:26:00.0')
Returns:
bool: True if gpu_od directory exists for this GPU
"""
# TODO(amdsmi_team): Refactor to create an amdsmi_get_gpu_fan_speed_range() API
# amdsmi_get_gpu_fan_speed_range(
# amdsmi_processor_handle processor_handle,
# uint32_t sensor_ind,
# amdsmi_range_t* fan_speed_range)
# and begin deprecation of amdsmi_get_gpu_fan_speed_max(). This will allow us
# to remove the dependency on amdsmi_helpers from this common module.
# Exposing non-public SYSFS API interfaces in the CLI (and in general)
# is a bad design pattern and needs to be addressed in the future.
amdsmi_cli_path = os.path.normpath(
os.path.join(amdsmi_path, "..", "..", "libexec", "amdsmi_cli")
)
if not os.path.exists(amdsmi_cli_path):
raise FileNotFoundError(
f'amdsmi_cli path "{amdsmi_cli_path}" does not exist. '
f"Ensure the AMD-SMI CLI is installed, or set AMDSMI_PATH correctly."
)
if amdsmi_cli_path not in sys.path:
sys.path.append(amdsmi_cli_path)
try:
import amdsmi_helpers # type: ignore
except ImportError as e:
raise ImportError(
f'Could not import the "amdsmi_helpers" module from "{amdsmi_cli_path}"'
) from e
has_gpu_od, _ = amdsmi_helpers.AMDSMIHelpers.detect_gpu_od(bdf)
return has_gpu_od
class Common:
VIRTUALIZATION_MODE_MAP = {}
for member in amdsmi.AmdSmiVirtualizationMode:
VIRTUALIZATION_MODE_MAP[amdsmi.AmdSmiVirtualizationMode(member.value)] = member.name
def __init__(self, verbose, *args, **kwargs):
self.verbose = verbose
self.max_num_physical_devices = (
amdsmi.amdsmi_interface.AMDSMI_MAX_NUM_XCP * amdsmi.amdsmi_interface.AMDSMI_MAX_DEVICES
)
self.PASS = PASS
self.FAIL = FAIL
self.ANY_FAIL = "ANY_FAIL"
# Tests marked with either of these flags will be skipped
# and need to be implemented later.
self.TODO_SKIP_FAIL = True
self.TODO_SKIP_NOT_COMPLETE = True
try:
self.amdsmi_smart_init()
# Get gpus
self.processors = amdsmi.amdsmi_get_processor_handles()
# Set bad gpu: None is rejected by the isinstance check in amdsmi_interface.py,
# raising AmdSmiParameterException(INVAL). Platform-agnostic and unambiguous.
self.bad_gpu = None
self.virt_mode = []
self.asic_info = []
self.board_info = []
for gpu in self.processors:
# Get virtualization mode info
try:
ret = amdsmi.amdsmi_get_gpu_virtualization_mode(gpu)
mode_name = self.VIRTUALIZATION_MODE_MAP.get(ret["mode"], "UNKNOWN")
self.virt_mode.append({"mode": mode_name})
except amdsmi.AmdSmiLibraryException:
# Virtualization mode is not supported on every ASIC; record
# UNKNOWN silently (it is still surfaced via print_device_header).
self.virt_mode.append({"mode": "UNKNOWN"})
# Get asic info
self.asic_info.append(amdsmi.amdsmi_get_gpu_asic_info(gpu))
# Get board info
self.board_info.append(amdsmi.amdsmi_get_gpu_board_info(gpu))
amdsmi.amdsmi_shut_down()
except amdsmi.AmdSmiLibraryException as e:
print(f"In class Common, Cannot get processor information, {e}")
self.not_supported_error_codes = [
("2", "AMDSMI_STATUS_NOT_SUPPORTED"),
("3", "AMDSMI_STATUS_NOT_YET_IMPLEMENTED"),
("49", "AMDSMI_STATUS_NO_HSMP_MSG_SUP"),
]
self.error_map = ERROR_MAP
# All enum-derived parameter lists are built by the shared, hardware-free
# build_type_lists() so the logic lives in one place (also importable by
# the standalone benchmark suites).
for _attr, _value in build_type_lists().items():
setattr(self, _attr, _value)
return
def print(self, msg, data=None):
if self.verbose > VERBOSITY_QUIET:
if data is None:
print(msg, flush=True)
elif any(data in value for value in self.not_supported_error_codes):
print(f"{msg} {data}", flush=True)
else:
if isinstance(data, str) and data in self.error_map.values():
print(msg, end="")
else:
print(msg)
if isinstance(data, dict) or isinstance(data, list):
print(json.dumps(data, sort_keys=False, indent=4), flush=True)
else:
print(data)
return
def print_func_name(self, msg=None):
if self.verbose == VERBOSITY_VERBOSE:
stk = inspect.stack()
if stk[1].function == "_callSetUp":
return
print(f"\n## {stk[1].function}()", flush=True)
if msg:
print(msg, flush=True)
return
def print_device_header(self, i):
# Guard against stale caches: processors may be refreshed in setUp without
# refreshing these display-only lists (which are populated once at __init__).
# Print virtualization mode info
msg = f"virtualization mode(gpu={i})"
self.print(f"\t{msg}")
if i < len(self.virt_mode):
mode = self.virt_mode[i]["mode"]
self.print(f"\t\tmode : {mode}")
# Print asic info
msg = f"asic info(gpu={i})"
self.print(f"\t{msg}")
if i < len(self.asic_info):
for key, value in self.asic_info[i].items():
self.print(f"\t\t{key} : {value}")
# Print board info
msg = f"board info(gpu={i})"
self.print(f"\t{msg}")
if i < len(self.board_info):
for key, value in self.board_info[i].items():
self.print(f"\t\t{key} : {value}")
return
def get_error_code(self, exc):
error_code = "-1"
error_code_name = "UNKNOWN_ERROR"
if hasattr(exc, "get_error_code"):
error_code = str(exc.get_error_code())
if error_code in self.error_map:
error_code_name = self.error_map[error_code]
return (error_code, error_code_name)
def get_dict_key_from_value(self, _value, _dict):
if _value not in _dict.values():
return None
for key, value in _dict.items():
if value == _value:
return key
return None
def get_error_code_from_name(self, error_code_name):
error_code = self.get_dict_key_from_value(error_code_name, self.error_map)
if error_code is None:
error_code = -1
return error_code
def check_ret(self, msg, exc, expected_code_name=None, printIt=True):
# Returns True if the test FAILED (i.e. the result did not match expected).
# Callers use the pattern: `if self.check_ret(...): raise_exception = e`
if isinstance(exc, str) and len(exc) == 0:
error_code_name = expected_code_name
error_code = "-1"
if error_code_name in self.error_map.values():
for key, value in self.error_map.items():
if value == error_code_name:
error_code = key
break
elif hasattr(exc, "get_error_code"):
error_code, error_code_name = self.get_error_code(exc)
else:
error_code = str(exc).split(":", maxsplit=1)[0]
error_code_name = "AMDSMI_STATUS_INVAL"
# Check for when there are multiple passing conditions
if isinstance(expected_code_name, list):
for ec_name in expected_code_name:
if not self.check_ret(msg, exc, ec_name, False): # check without printing
# This expected code matched - print once and return success
if self.verbose > VERBOSITY_QUIET and printIt:
if msg:
print(f"{msg}\n", end="")
ec_code = self.get_error_code_from_name(ec_name)
print(f"\tTEST SUCCESS, AMDSMI API Returned {ec_code:>2s}, {ec_name}")
return False
# No expected result matched - print failure (respects same guards as single-condition path)
if self.verbose > VERBOSITY_QUIET and printIt:
if msg:
print(f"{msg}\n", end="")
status_msg = (
f"\tTEST FAILURE, AMDSMI API Returned {error_code:>2s}, {error_code_name}\n"
)
for ec_name in expected_code_name:
ec_code = self.get_error_code_from_name(ec_name)
status_msg += (
f"\t AMDSMI API Expected {ec_code:>2s}, {expected_code_name}"
)
return True
# Check for single passing condition
status_msg = ""
status_ret = False
if any(error_code in value for value in self.not_supported_error_codes):
status_msg = f"\tAMDSMI API Returned {error_code:>2s}, {error_code_name}"
elif error_code_name == expected_code_name:
status_msg = f"\tTEST SUCCESS, AMDSMI API Returned expected {error_code:>2s}, {expected_code_name}"
elif error_code_name != self.PASS and expected_code_name == self.ANY_FAIL:
status_msg = f"\tTEST SUCCESS, AMDSMI API Returned expected fail {error_code:>2s}, {expected_code_name}"
else:
expected_error_code = self.get_error_code_from_name(expected_code_name)
status_msg = (
f"\tTEST FAILURE, AMDSMI API Returned {error_code:>2s}, {error_code_name}\n"
)
status_msg += f"\t AMDSMI API Expected {expected_error_code:>2s}, {expected_code_name}"
status_ret = True
if self.verbose > VERBOSITY_QUIET and printIt:
if msg:
print(f"{msg}\n", end="")
print(f"{status_msg}", flush=True)
return status_ret
def _check_amdgpu_driver(self):
"""Returns true if amdgpu is found in the list of initialized modules"""
amd_gpu_status_file = pathlib.Path("/sys/module/amdgpu/initstate")
if amd_gpu_status_file.exists():
try:
return amd_gpu_status_file.read_text(encoding="ascii").strip() == "live"
except OSError:
pass
# If the driver is loaded either as a module OR built in, this dir will be populated
drv = pathlib.Path("/sys/bus/pci/drivers/amdgpu")
if not drv.exists():
return False
# Check if a symlink exists that loosely matches PCI BDF format
# ex: 0000:03:00.0
for p in drv.iterdir():
if p.is_symlink() and ":" in p.name and "." in p.name:
return True
return False
def _check_amd_hsmp_driver(self):
"""Returns true if amd_hsmp or hsmp_acpi is found in the list of initialized modules"""
return pathlib.Path("/dev/hsmp").exists()
def _init_with_flag(self, init_flag, driver_msg):
ret = None
try:
ret = amdsmi.amdsmi_init(init_flag)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
# AmdSmiLibraryException exposes get_error_code(); AmdSmiParameterException does not.
# Accessing .err_code on AmdSmiParameterException raises AttributeError.
err_code = e.get_error_code() if hasattr(e, "get_error_code") else None
if err_code in (amdsmi.AmdSmiStatus.NOT_INIT, amdsmi.AmdSmiStatus.DRIVER_NOT_LOADED):
self.print(driver_msg)
raise unittest.SkipTest(driver_msg)
raise
return ret
def _skip_if_missing(self, names):
# All public APIs must be re-exported at the top-level amdsmi namespace.
# If an API is missing there, it belongs in amdsmi_interface.py, not a sub-module.
# Note: self.id() is NOT used here — Common is not a TestCase subclass, so self.id()
# does not exist when called through a Common instance. Instead, walk the call stack to
# find the name of the enclosing test_* method, which works regardless of calling convention.
missing = [name for name in names if not hasattr(amdsmi, name)]
if missing:
test_name = next(
(frame.function for frame in inspect.stack() if frame.function.startswith("test_")),
"<unknown>",
)
msg = f"{test_name} | Missing amdsmi API(s) in amdsmi_interface.py: " + ", ".join(
missing
)
# self may be a Common instance (has .verbose directly) or a TestCase instance
# that stores Common as self.common (when called via common.Common._skip_if_missing(self, ...)).
verbose = getattr(self, "verbose", None)
if verbose is None:
verbose = getattr(getattr(self, "common", None), "verbose", VERBOSITY_NORMAL)
if verbose > VERBOSITY_QUIET:
print(msg, file=sys.stderr)
raise unittest.SkipTest(msg)
return
def _build_call_msg(self, func_name, i, j, params):
msg = f"\t### {func_name}("
if i is not None:
msg += f"gpu={i}"
if j is not None:
msg += f", gpu={j}"
for param_name, param_value in params.items():
if isinstance(param_value, list):
msg += f", {param_name}={{value}}"
else:
msg += f", {param_name}={param_value}"
msg += ")"
return msg
def amdsmi_smart_init(self):
"""Initializes AMDSMI Library based on live drivers found in the system.
Checks for the presence of the amdgpu, amd_hsmp or hsmp_acpi drivers and initializes the
AMD SMI library based on the live drivers found. Mirrors the flag-building logic in the
CLI (amdsmi_init.py): flags are OR-combined additively so that systems with both a
discrete GPU and CPU power management (non-APU servers) work correctly. INIT_AMD_APUS
is intentionally not used here; it targets integrated APU configurations only.
Return:
tuple: A tuple containing:
- ret: The return value from amdsmi_init() (typically None on success)
- init_flag: The combined flag used to initialize the AMD SMI library
(bitwise OR of INIT_AMD_GPUS and/or INIT_AMD_CPUS)
Raises:
AmdSmiLibraryException: If initialization fails for reasons other than driver not loaded
AmdSmiParameterException: If invalid parameters are passed to amdsmi_init
unittest.SkipTest: If no compatible AMD drivers are detected on the system
"""
# Build init_flag additively from detected drivers (mirrors CLI amdsmi_init.py pattern).
# Using INIT_AMD_APUS when both GPU and CPU drivers are present is wrong on discrete-GPU
# servers — use OR-combined flags instead, exactly as the CLI does.
init_flag = 0
if self._check_amdgpu_driver():
init_flag |= amdsmi.AmdSmiInitFlags.INIT_AMD_GPUS
if self._check_amd_hsmp_driver():
init_flag |= amdsmi.AmdSmiInitFlags.INIT_AMD_CPUS
if init_flag == 0:
msg = "Drivers not loaded (amdgpu, amd_hsmp or hsmp_acpi drivers not found in modules)"
self.print(msg)
raise unittest.SkipTest(msg)
msg = "AMDSMI init failed for detected drivers"
ret = self._init_with_flag(init_flag, msg)
return (ret, init_flag)
def Test_API(self, **kwargs):
"""
Tests API with zero or more arguments
Arguments:
func_name: API to be executed
Optional:
param1_name: Name of parameter 1
"""
params = kwargs
iterator = iter(params.items())
func_name, func = next(iterator)
del params[func_name]
raise_exception = None
cond = self.PASS
msg = self._build_call_msg(func_name, None, None, params)
try:
data = func(*[value for value in params.values()])
self.print(msg, data)
self.check_ret("", "", cond)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
if self.check_ret(msg, e, cond):
raise_exception = e
if raise_exception:
raise raise_exception
return
def Test_API_Per_GPU(self, **kwargs):
"""
Tests API per GPU with zero or more arguments
Arguments:
func_name: API to be executed
Optional:
param1_name: Name of parameter 1
param2_name: Name of parameter 2
param3_name: Name of parameter 3
"""
params = kwargs
iterator = iter(params.items())
func_name, func = next(iterator)
del params[func_name]
raise_exception = None
for i in range(len(self.processors) + 1):
cond = self.PASS
if i < len(self.processors):
gpu = self.processors[i]
self.print_device_header(i)
else:
# bad gpu
gpu = self.bad_gpu
i = "invalid"
cond = self.FAIL
msg = self._build_call_msg(func_name, i, None, params)
try:
data = func(gpu, *[value for value in params.values()])
self.print(msg, data)
self.check_ret("", "", cond)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
if self.check_ret(msg, e, cond):
raise_exception = e
self.print("")
if raise_exception:
raise raise_exception
return
def Test_Per_GPU_With_One_Enum(self, **kwargs):
"""
Tests API per GPU per Enum with zero or more arguments
Arguments:
func_name: API to be executed
value1_name=[(value_name, value, value_cond), ...]
Optional:
param1_name: Name of parameter 1
param2_name: Name of parameter 2
"""
params = kwargs
iterator = iter(params.items())
func_name, func = next(iterator)
_, values1 = next(iterator)
del params[func_name]
raise_exception = None
for i in range(len(self.processors) + 1):
if i < len(self.processors):
gpu = self.processors[i]
self.print_device_header(i)
else:
# bad gpu
gpu = self.bad_gpu
i = "invalid"
for value1_name, value1, value1_cond in values1:
cond = self.PASS
if i == "invalid" or value1_cond == self.FAIL:
cond = self.FAIL
msg = self._build_call_msg(func_name, i, None, params)
msg = msg.replace("{value}", value1_name, 1)
try:
data = func(
gpu,
*[
value if not isinstance(value, list) else value1
for value in params.values()
],
)
self.print(msg, data)
self.check_ret("", "", cond)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
if value1_cond != self.PASS:
if self.check_ret(msg, e, value1_cond):
raise_exception = e
else:
if self.check_ret(msg, e, cond):
raise_exception = e
self.print("")
if raise_exception:
raise raise_exception
return
def Test_Per_GPU_With_Two_Enums(self, **kwargs):
"""
Tests API per GPU per 2 Enums with zero or more arguments
Arguments:
func_name: API to be executed
value1_name=[(value_name, value, value_cond), ...]
value2_name=[(value_name, value, value_cond), ...]
Optional:
param1_name: Name of parameter 1
param2_name: Name of parameter 2
"""
params = kwargs
iterator = iter(params.items())
func_name, func = next(iterator)
_, values1 = next(iterator)
_, values2 = next(iterator)
del params[func_name]
raise_exception = None
for i in range(len(self.processors) + 1):
if i < len(self.processors):
gpu = self.processors[i]
self.print_device_header(i)
else:
# bad gpu
gpu = self.bad_gpu
i = "invalid"
for value1_name, value1, value1_cond in values1:
for value2_name, value2, value2_cond in values2:
cond = self.PASS
if i == "invalid" or value1_cond == self.FAIL or value2_cond == self.FAIL:
cond = self.FAIL
msg = self._build_call_msg(func_name, i, None, params)
msg = msg.replace("{value}", value1_name, 1)
msg = msg.replace("{value}", value2_name, 1)
try:
data = func(
gpu,
*[
value
if not isinstance(value, list)
else value1
if index == 0
else value2
for index, value in enumerate(params.values())
],
)
self.print(msg, data)
self.check_ret("", "", cond)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
if value1_cond != self.PASS:
if self.check_ret(msg, e, value1_cond):
raise_exception = e
elif value2_cond != self.PASS:
if self.check_ret(msg, e, value2_cond):
raise_exception = e
else:
if self.check_ret(msg, e, cond):
raise_exception = e
self.print("")
if raise_exception:
raise raise_exception
return
def Test_Per_GPU_With_GPU(self, **kwargs):
"""
Tests API per GPU per GPU with zero or more arguments
Arguments:
func_name: API to be executed
Optional:
param1_name: Name of parameter 1
param2_name: Name of parameter 2
"""
params = kwargs
iterator = iter(params.items())
func_name, func = next(iterator)
del params[func_name]
raise_exception = None
for i in range(len(self.processors) + 1):
if i < len(self.processors):
gpu_i = self.processors[i]
self.print_device_header(i)
else:
# bad gpu
gpu_i = self.bad_gpu
i = "invalid"
for j in range(len(self.processors) + 1):
if j < len(self.processors):
gpu_j = self.processors[j]
self.print_device_header(j)
else:
# bad gpu
gpu_j = self.bad_gpu
j = "invalid"
cond = self.PASS
if i == "invalid" or j == "invalid" or i == j:
cond = self.FAIL
msg = self._build_call_msg(func_name, i, j, params)
try:
data = func(gpu_i, gpu_j, *[value for value in params.values()])
self.print(msg, data)
self.check_ret("", "", cond)
except (amdsmi.AmdSmiLibraryException, amdsmi.AmdSmiParameterException) as e:
if self.check_ret(msg, e, cond):
raise_exception = e
self.print("")
if raise_exception:
raise raise_exception
return