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

518 lines
16 KiB
Python

# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Automated coverage for the command-line help surface of the rocprof-sys
launchers (``rocprof-sys-run`` and ``rocprof-sys-sample``).
Validates every help / informational invocation: the help entry points
(``--version`` / ``--help`` / ``--help=all`` / compact help), every
``--help=<topic>`` / ``--help=<domain>`` page, the preset listing and the
``--explain=<preset>`` pages.
Each case is a ``pytest.param`` of ``(run_args, pass_regex)`` consumed by the
single parametrized test below, which runs it against every target in
``TARGETS``. Patterns use the ``{prog}`` placeholder wherever the program name
is embedded in the output; it is substituted with the target at runtime. Add or
adjust coverage by editing the ``HELP_CASES`` list.
"""
from __future__ import annotations
import pytest
from conftest import RocprofsysTest
pytestmark = [pytest.mark.cli_help]
# Both launchers share the same help/flag surface; the embedded mark selects the
# matching runner category so a single test body covers run + sample.
TARGETS = [
pytest.param("rocprof-sys-run", marks=pytest.mark.sys_run, id="run"),
pytest.param("rocprof-sys-sample", marks=pytest.mark.sampling, id="sample"),
]
# ----------------------------------------------------------------------------
# Shared pattern groups (kept here to avoid repeating them across cases)
# ----------------------------------------------------------------------------
# Sections printed by the compact help screens (--help / -h / -?).
_COMPACT = [
r"Usage: {prog}",
r"QUICK START",
r"DOMAIN FLAGS",
r"COMMON OPTIONS",
r"HELP TOPICS",
r"EXAMPLES",
]
# Common scaffolding printed by every --explain=<preset> page.
_EXPLAIN = [
r"Description:",
r"Use case:",
r"Environment Variables:",
r"ROCPROFSYS_\w+ = ",
]
def _explain(preset: str, category: str) -> list[str]:
"""pass_regex for an ``--explain=<preset>`` page (preset-specific + shared)."""
return [
rf"Preset: {preset}",
rf"Category:\s+{category}",
rf"Usage: {{prog}} --preset={preset}",
] + _EXPLAIN
# ----------------------------------------------------------------------------
# Expected-output cases: (run_args, pass_regex)
# ----------------------------------------------------------------------------
HELP_CASES = [
# --- Domain help pages (--help=<domain>) ------------------------------
pytest.param(
["--help=cpu"],
[
r"CPU OPTIONS \(",
r"-H, --host",
r"-f, --sampling-freq",
r"-t, --tids",
r"--sampling-wait",
r"--sampling-duration",
r"--sample-cputime",
r"--sample-realtime",
r"--sample-overflow",
r"-C, --cpu-events",
r"-G, --gpu-events",
r"--cpu",
r"See also",
],
id="domain_cpu",
),
pytest.param(
["--help=gpu"],
[
r"GPU OPTIONS \(",
r"-D, --device",
r"--use-amd-smi",
r"--process-freq",
r"--process-wait",
r"--process-duration",
r"--gpus",
r"--ai-nics",
r"-G, --gpu-events",
r"--gpu",
r"See also",
],
id="domain_gpu",
),
pytest.param(
["--help=parallel"],
[
r"PARALLEL OPTIONS \(",
r"-I, --include",
r"-E, --exclude",
r"--parallel",
r"See also",
],
id="domain_parallel",
),
pytest.param(
["--help=rocm"],
[
r"ROCM OPTIONS \(",
r"-T, --trace",
r"--use-amd-smi",
r"--selected-regions",
r"--gpus",
r"--ai-nics",
r"--hsa-interrupt",
r"--rocm",
r"kfd_events",
r"See also",
],
id="domain_rocm",
),
# --- Preset explanations (--explain=<preset>) -------------------------
pytest.param(
["--explain=balanced"], _explain("balanced", "general"), id="explain_balanced"
),
pytest.param(
["--explain=detailed"], _explain("detailed", "general"), id="explain_detailed"
),
pytest.param(
["--explain=profile-mpi"],
_explain("profile-mpi", "hpc"),
id="explain_profile-mpi",
),
pytest.param(
["--explain=profile-only"],
_explain("profile-only", "general"),
id="explain_profile-only",
),
pytest.param(
["--explain=runtime-trace"],
_explain("runtime-trace", "tracing"),
id="explain_runtime-trace",
),
pytest.param(
["--explain=sys-trace"], _explain("sys-trace", "tracing"), id="explain_sys-trace"
),
pytest.param(
["--explain=trace-gpu"], _explain("trace-gpu", "gpu"), id="explain_trace-gpu"
),
pytest.param(
["--explain=trace-hpc"], _explain("trace-hpc", "hpc"), id="explain_trace-hpc"
),
pytest.param(
["--explain=trace-hw-counters"],
_explain("trace-hw-counters", "gpu"),
id="explain_trace-hw-counters",
),
pytest.param(
["--explain=trace-openmp"],
_explain("trace-openmp", "hpc"),
id="explain_trace-openmp",
),
pytest.param(
["--explain=workload-trace"],
_explain("workload-trace", "gpu"),
id="explain_workload-trace",
),
# --- Full option dump (--help=all): assert the stable section headers.
# Individual flags are intentionally not asserted here: many are
# conditionally compiled (AMD SMI, CI/debug build options, etc.), so the
# bracketed section headers are the portable, build-independent signal.
pytest.param(
["--help=all"],
[
r"Options:",
r"-h, -\?, --help(?![-\w])",
r"--version(?![-\w])",
r"\[DEBUG OPTIONS\]",
r"\[MODE OPTIONS\]",
r"\[GENERAL OPTIONS\]",
r"\[BACKEND OPTIONS\]",
r"\[PARALLELISM OPTIONS\]",
r"\[TRACING OPTIONS\]",
r"\[PROFILE OPTIONS\]",
r"\[HOST/DEVICE \(PROCESS SAMPLING\) OPTIONS\]",
r"\[GENERAL SAMPLING OPTIONS\]",
r"\[SAMPLING TIMER OPTIONS\]",
r"\[ADVANCED SAMPLING OPTIONS\]",
r"\[HARDWARE COUNTER OPTIONS\]",
r"\[CATEGORY OPTIONS\]",
r"\[IO OPTIONS\]",
r"\[PERFETTO OPTIONS\]",
r"\[TIMEMORY OPTIONS\]",
r"\[ROCM OPTIONS\]",
r"\[MISCELLANEOUS OPTIONS\]",
r"\[LIBROCPROF-SYS OPTIONS\]",
r"\[PRESET OPTIONS\]",
r"\[DOMAIN OPTIONS\]",
r"\[EXPORT OPTIONS\]",
],
id="all",
),
# --- Compact help screens (--help / -h / -?) --------------------------
pytest.param(["--help"], _COMPACT, id="compact_long"),
pytest.param(["-h"], _COMPACT, id="compact_short_h"),
pytest.param(["-?"], _COMPACT, id="compact_short_q"),
# --- Preset listing (--list-presets) ----------------------------------
pytest.param(
["--list-presets"],
[
r"Available Presets:",
r"Usage: {prog} --preset=<name>",
r"{prog} --explain=<name>",
r"general:",
r"gpu:",
r"hpc:",
r"tracing:",
r"\bbalanced\b",
r"\bdetailed\b",
r"\bprofile-only\b",
r"\btrace-gpu\b",
r"\btrace-hw-counters\b",
r"\bworkload-trace\b",
r"\bprofile-mpi\b",
r"\btrace-hpc\b",
r"\btrace-openmp\b",
r"\bruntime-trace\b",
r"\bsys-trace\b",
],
id="list_presets",
),
# --- Topic help pages (--help=<topic>) --------------------------------
pytest.param(
["--help=backend"],
[
r"{prog} --help=backend",
r"-I, --include",
r"-E, --exclude",
r"--use-amd-smi",
r"--use-causal",
r"--use-code-coverage",
r"--use-kokkosp",
r"--use-mpip",
r"--use-rcclp",
r"--use-rocpd",
r"--use-sampling",
r"--use-openshmem",
r"--use-ucx",
r"--trace-thread-barriers",
r"--trace-thread-join",
r"--trace-thread-locks",
r"--use-process-sampling",
r"--trace-thread-rw-locks",
r"--trace-thread-spin-locks",
r"--use-unified-memory-profiling",
r"See also",
],
id="topic_backend",
),
pytest.param(
["--help=counters"],
[
r"{prog} --help=counters",
r"-C, --cpu-events",
r"-G, --gpu-events",
r"See also",
],
id="topic_counters",
),
pytest.param(
["--help=debug"],
[
r"{prog} --help=debug",
r"--log-level",
r"--monochrome",
r"--debug",
r"-v, --verbose",
r"--log-file",
r"--dl-verbose",
r"--perfetto-annotations",
r"--kokkosp-kernel-logger",
r"--sampling-allocator-size",
r"--kokkosp-name-length-max",
r"--kokkosp-prefix",
r"--ci",
],
id="topic_debug",
),
pytest.param(
["--help=general"],
[
r"{prog} --help=general",
r"-c, --config",
r"-o, --output",
r"-T, --trace",
r"-L, --trace-legacy",
r"-P, --profile",
r"-F, --flat-profile",
r"-H, --host",
r"-D, --device",
r"-w, --wait",
r"-d, --duration",
r"--periods",
r"--rank-filter-id",
r"--rank-filter-output",
r"--rank-filter-logs",
r"See also",
],
id="topic_general",
),
pytest.param(
["--help=misc"],
[
r"{prog} --help=misc",
r"-i, --inlines",
r"--hsa-interrupt",
r"See also",
],
id="topic_misc",
),
pytest.param(
# --help=preset is curated (get_help_topic_map) to render PRESET +
# DOMAIN + EXPORT sections together; assert the three section headers
# plus every left-column flag they list.
["--help=preset"],
[
r"{prog} --help=preset",
r"\[PRESET OPTIONS\]",
r"\[DOMAIN OPTIONS\]",
r"\[EXPORT OPTIONS\]",
r"--list-presets",
r"--explain",
r"--preset",
r"--gpu",
r"--rocm",
r"--cpu",
r"--parallel",
r"--export-config",
r"See also",
],
id="topic_preset",
),
pytest.param(
["--help=process"],
[
r"{prog} --help=process",
r"--process-freq",
r"--process-wait",
r"--process-duration",
r"--cpus",
r"--gpus",
r"--ai-nics",
r"See also",
],
id="topic_process",
),
pytest.param(
["--help=profiling"],
[
r"{prog} --help=profiling",
r"--profile-format",
r"--profile-diff",
r"See also",
],
id="topic_profiling",
),
pytest.param(
["--help=sampling"],
[
r"{prog} --help=sampling",
r"-f, --sampling-freq",
r"-t, --tids",
r"--sampling-wait",
r"--sampling-duration",
r"--sample-cputime",
r"--sample-realtime",
r"--sample-overflow",
r"--sampling-cputime-delay",
r"--sampling-cputime-freq",
r"--sampling-cputime-signal",
r"--sampling-cputime-tids",
r"--sampling-include-inlines",
r"--sampling-keep-internal",
r"--sampling-overflow-event",
r"--sampling-overflow-freq",
r"--sampling-overflow-signal",
r"--sampling-overflow-tids",
r"--sampling-realtime-delay",
r"--sampling-realtime-freq",
r"--sampling-realtime-signal",
r"--sampling-realtime-tids",
r"See also",
],
id="topic_sampling",
),
pytest.param(
["--help=tracing"],
[
r"{prog} --help=tracing",
r"--trace-file",
r"--trace-buffer-size",
r"--trace-fill-policy",
r"--trace-wait",
r"--trace-duration",
r"--trace-periods",
r"--selected-regions",
r"--trace-clock-id",
r"See also",
],
id="topic_tracing",
),
# --- Error / version handling -----------------------------------------
pytest.param(
["--help=does-not-exist"],
[
r"Unknown help topic 'does-not-exist'",
r"Available topics",
],
id="unknown_topic",
),
pytest.param(
["--version"],
[r"{prog} v\d+\.\d+\.\d+"],
id="version",
),
]
@pytest.mark.timeout(30)
@pytest.mark.class_name("cli-help")
class TestCliHelp(RocprofsysTest):
"""Validate every help / informational invocation of the rocprof-sys launchers."""
@pytest.mark.parametrize("target", TARGETS)
@pytest.mark.parametrize("run_args, pass_regex", HELP_CASES)
def test(self, target, run_args, pass_regex):
# Substitute the program name into name-dependent patterns.
pass_regex = [p.replace("{prog}", target) for p in pass_regex]
# rocprof-sys-run additionally exposes an [EXECUTION OPTIONS] section in
# the full dump; rocprof-sys-sample does not.
if target == "rocprof-sys-run" and run_args == ["--help=all"]:
pass_regex = pass_regex + [r"\[EXECUTION OPTIONS\]"]
result = self.run_test(
"baseline",
target=target,
run_args=run_args,
fail_on_not_found=True,
)
self.assert_regex(result, pass_regex=pass_regex)
@pytest.mark.parametrize("target", TARGETS)
def test_explain_invalid(self, target):
"""Negative: --explain with an unknown preset exits non-zero and
directs the user to --list-presets.
Kept out of HELP_CASES because that suite assumes a zero exit code;
an invalid --explain is a hard failure (unlike an unknown --help topic).
"""
result = self.run_test(
"baseline",
target=target,
run_args=["--explain=invalid-preset-xyz"],
fail_on_pass=True,
fail_on_not_found=True,
)
self.assert_regex(
result,
pass_regex=[r"not found", r"--list-presets"],
use_abort_fail_regex=False, # negative test intentionally exits non-zero
)
@pytest.mark.parametrize("target", TARGETS)
def test_explain_requires_value(self, target):
"""Negative: --explain with no value is an argument-validation error
(distinct from an unknown preset name)."""
result = self.run_test(
"baseline",
target=target,
run_args=["--explain="],
fail_on_pass=True,
fail_on_not_found=True,
)
self.assert_regex(
result,
use_abort_fail_regex=False, # negative test intentionally exits non-zero
)
@pytest.mark.parametrize("target", TARGETS)
def test_unrecognized_option(self, target):
"""Negative: an unknown command-line option is rejected with a non-zero exit."""
result = self.run_test(
"baseline",
target=target,
run_args=["--does-not-exist-flag"],
fail_on_pass=True,
fail_on_not_found=True,
)
self.assert_regex(
result,
use_abort_fail_regex=False, # negative test intentionally exits non-zero
)