Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
89789e6
Enable coverage options for project in superbuild
reboss Feb 5, 2026
b7559cc
Add an example code coverage work flow
reboss Feb 9, 2026
bb78631
Add coverage step in CI
reboss Feb 9, 2026
905ff4d
remove whitespace
reboss Feb 9, 2026
ebec3d2
Rename back to projects_to_test (plural)
reboss Mar 6, 2026
5c0ece0
fix coverage-test syntax
reboss Mar 6, 2026
4ade6c6
Fix keyname
reboss Mar 6, 2026
5b538ad
[codecov] fix codecov flow
reboss Apr 2, 2026
ba9a805
[hipdnn] remove redundant -fcoverage-mapping from link options
reboss Apr 2, 2026
e0333a7
add a script to configure coverage jobs
reboss Apr 2, 2026
c85cf3b
integrate therock_configure_coverage.py with coverage workflow
reboss Apr 2, 2026
5d8ce94
produce the project name for coverage
reboss Apr 2, 2026
9de4309
configure project separately for code coverage
reboss Apr 2, 2026
499a3ee
consume project_name in test step as well
reboss Apr 2, 2026
58cf9ea
handle linting
reboss Apr 2, 2026
9f02fb0
fix linting
reboss Apr 2, 2026
b096f30
fix lint
reboss Apr 2, 2026
2921e9e
fix lint
reboss Apr 2, 2026
9bbe49c
set enable_coverage to ON
reboss Apr 2, 2026
8c0af6d
Have coverage as a standalone test matrix
reboss Apr 9, 2026
50a0e7a
WIP: Temporarily disable other test matices
reboss Apr 9, 2026
e75eee3
rename to coverage_projects for coverage
reboss Apr 14, 2026
62ba9e7
bump hashes
reboss Apr 14, 2026
e09d764
Only deploy code cov on gfx942
reboss Apr 14, 2026
74ae1ba
WIP: just hardcode coverage directory to see if it works
reboss Apr 14, 2026
b14386f
Add a mapping to project directories
reboss Apr 14, 2026
c658ed6
Build any defined project's code coverage flow
reboss Apr 14, 2026
77aef50
make sure llvm-cov and llvm-profdata are accessible
reboss Apr 14, 2026
459f3e7
bump hashes
reboss Apr 14, 2026
c966a07
use commit in TheRock that exports llvm-cov and llvm-profdata
reboss Apr 15, 2026
87dfc68
use llvm version 22
reboss Apr 15, 2026
b89ea2a
expose variables needed for code cov
reboss Apr 15, 2026
30da390
actually run on a GPU node :/
reboss Apr 16, 2026
4b36722
split build and test for coverage
reboss Apr 17, 2026
35ccdbf
bump hashes
reboss Apr 20, 2026
c7a3910
change dir name
reboss Apr 20, 2026
1dd599c
bump container images
reboss Apr 20, 2026
c20a5ef
use proper container for testing
reboss Apr 21, 2026
f6ec900
remove redundant pip install
reboss Apr 21, 2026
44505a3
align with therock-ci-linux test flow
reboss Apr 21, 2026
c8a6856
bump container image
reboss Apr 21, 2026
34a6729
Add gpu sanity check and bump hashes
reboss Apr 21, 2026
790777c
use correct build dir for coverage
reboss Apr 22, 2026
b96b7d3
try rebuilding cmake build tree
reboss Apr 23, 2026
7e7589b
fetch iree-libs sources in coverage build when IREE is enabled
jainprad Jun 16, 2026
d974af3
Merge remote-tracking branch 'upstream/develop' into jainprad/codecov…
jainprad Jun 16, 2026
06f4994
hipRAND
jainprad Jun 16, 2026
0f0f7e8
hipRANDcmake
jainprad Jun 16, 2026
f92882b
enable hipRAND coverage and fix primary-project selection
jainprad Jun 17, 2026
6734587
only override coverage primary with a coverage-enabled changed project
jainprad Jun 17, 2026
e512035
pin hipRAND coverage build to RAND-only and fix coverage report tooling
jainprad Jun 17, 2026
8a1a7cf
remove therock-coverage-test job from coverage workflow
jainprad Jun 17, 2026
94300a7
run coverage tests via the standard package test flow and add report …
jainprad Jun 17, 2026
feaf41d
fix coverage artifact paths for containerized jobs
jainprad Jun 18, 2026
6367901
revert hipdnn coverage changes (hipRAND-only PR)
jainprad Jun 18, 2026
411aa2e
make coverage report job self-sufficient for llvm tooling
jainprad Jun 18, 2026
5a9bd27
use a current TheRock ref for the coverage test stage
jainprad Jun 18, 2026
d7cb3ee
run coverage report step under bash
jainprad Jun 18, 2026
2bfee8a
stage llvm tool dependency libraries for the coverage report
jainprad Jun 19, 2026
778a9b0
stage SONAME symlinks for coverage llvm tool dependencies
jainprad Jun 19, 2026
13a81b4
annotate coverage HTML, upload to Codecov, and emit a paths/links file
jainprad Jun 20, 2026
4107d19
adopt documented coverage structure (YAML + metadata + coverage_runne…
jainprad Jun 22, 2026
211f7d7
fix Codecov upload (glibc) and surface coverage summary in step log
jainprad Jun 22, 2026
7fd82d4
extend coverage artifact retention
jainprad Jun 23, 2026
9ec5d19
only show the Codecov link in the summary when the upload succeeded
jainprad Jun 24, 2026
fc42d35
add rocRAND as an independent coverage component
jainprad Jun 25, 2026
b7d9e3a
add rocFFT as an independent coverage component
jainprad Jun 25, 2026
f0601ac
scope coverage profraw download to the component
jainprad Jun 25, 2026
f269236
add rocBLAS and rocSPARSE as independent coverage components
jainprad Jun 28, 2026
1529434
format coverage metadata table with black
jainprad Jun 28, 2026
6ab43d7
Merge remote-tracking branch 'upstream/develop' into jainprad/codecov…
jainprad Jun 28, 2026
b0d3601
format coverage runner and metadata exporter with black
jainprad Jun 28, 2026
71ec37d
skip coverage report gracefully when no profraw is produced
jainprad Jun 29, 2026
ef829fb
drop rocSPARSE coverage component
jainprad Jun 29, 2026
994739e
exit coverage report step cleanly when no profraw was produced
jainprad Jun 29, 2026
daef4c3
add header-only coverage for rocPRIM, hipCUB, rocThrust
jainprad Jun 29, 2026
811bb4a
scope header-only test binaries to the component's test subproject
jainprad Jun 29, 2026
8efef19
gate coverage on <PROJECT>_ENABLE_COVERAGE instead of shared vars
jainprad Jun 30, 2026
69b3ed3
move codecov flag and rocThrust SQLite fallback to separate PRs
jainprad Jun 30, 2026
a13f790
gate coverage solely on <PROJECT>_ENABLE_COVERAGE
jainprad Jun 30, 2026
b0a66ab
Merge branch 'develop' into users/jainprad/pr8656-backup-a13f790f
jainprad Jul 8, 2026
ed256d9
drop non-rocBLAS coverage components (keep only rocBLAS)
jainprad Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions .github/scripts/coverage_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
#!/usr/bin/env python3
"""Run tests with coverage profiling and/or generate coverage reports.

This is the report-time half of the coverage flow described in TheRock's coverage
design docs. It is driven by the ``coverage_metadata.json`` produced by
``export_coverage_metadata.py`` and can either:

* run tests to produce profraw files (default), or
* skip tests and merge pre-collected profraw files (``--skip-tests``), which is
how the GitHub Actions flow uses it (tests run in the shared package-test
workflow and upload their profraw).

Object and llvm tool locations are resolved by name within ``--build-dir`` so the
script works whether it is pointed at a full build/dist tree or a staged artifact.
"""
import argparse
import json
import logging
import os
import subprocess
from pathlib import Path

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")


def find_tool(build_dir: Path, name: str) -> Path | None:
"""Locate an executable ``name`` inside ``build_dir`` (prefer bin/ dirs)."""
candidates = [p for p in build_dir.rglob(name) if p.is_file()]
if not candidates:
return None
candidates.sort(key=lambda p: (0 if p.parent.name == "bin" else 1, len(str(p))))
return candidates[0]


def resolve_objects(build_dir: Path, metadata: dict) -> list[Path]:
"""Resolve coverage object files (libraries + test binaries) within build_dir."""
objects: list[Path] = []

# Prefer the explicit relative paths recorded at export time, if present.
rel_objects = []
for kind in ("libraries", "test_binaries"):
rel_objects.extend(metadata.get("coverage_objects", {}).get(kind, []) or [])
for rel in rel_objects:
candidate = build_dir / rel
if candidate.is_file():
objects.append(candidate)

# Fall back to resolving by basename (handles a different layout than export).
if not objects:
basenames = []
for kind in ("libraries", "test_binaries"):
basenames.extend(metadata.get("object_basenames", {}).get(kind, []) or [])
for basename in basenames:
hits = [p for p in build_dir.rglob(f"{basename}*") if p.is_file()]
# Prefer concrete files (e.g. libhiprand.so.1.1) over bare symlinks,
# and the most specific (longest) name.
hits.sort(key=lambda p: (not p.is_symlink(), len(p.name)), reverse=True)
if hits:
objects.append(hits[0])

# De-duplicate while preserving order.
seen, unique = set(), []
for obj in objects:
key = obj.resolve()
if key not in seen:
seen.add(key)
unique.append(obj)
return unique


def set_coverage_environment(metadata: dict, coverage_dir: Path):
pattern = metadata.get("llvm_profile_pattern", "%m")
profraw_dir = coverage_dir / "profraw"
profraw_dir.mkdir(parents=True, exist_ok=True)
profile_file = str(profraw_dir / f"{pattern}.profraw")
os.environ["LLVM_PROFILE_FILE"] = profile_file
logging.info("Set LLVM_PROFILE_FILE=%s", profile_file)


def run_tests(test_dir: Path, metadata: dict) -> int:
category = metadata.get("test_category", "")
cmd = [
"ctest",
"--test-dir",
str(test_dir),
"--output-on-failure",
"--parallel",
"8",
"--timeout",
"7200",
]
if category:
cmd += ["-L", category]
logging.info("Running tests: %s", " ".join(cmd))
return subprocess.run(cmd).returncode


def merge_profraw_files(llvm_profdata: Path, profraw_dir: Path, out_path: Path) -> Path:
profraw_files = [str(p) for p in profraw_dir.rglob("*.profraw")]
if not profraw_files:
raise RuntimeError(f"No .profraw files found under {profraw_dir}")
logging.info("Merging %d profraw file(s)", len(profraw_files))
subprocess.run(
[str(llvm_profdata), "merge", "-sparse", "-o", str(out_path), *profraw_files],
check=True,
)
logging.info("Created %s", out_path)
return out_path


def generate_reports(
llvm_cov: Path,
llvm_cxxfilt: Path | None,
objects: list[Path],
ignore_regex: str,
profdata: Path,
coverage_dir: Path,
project: str,
):
object_args: list[str] = []
for obj in objects:
object_args += ["-object", str(obj)]
ignore_args = [f"-ignore-filename-regex={ignore_regex}"] if ignore_regex else []

text_report = coverage_dir / f"code_cov_{project}.report"
logging.info("Generating text report -> %s", text_report)
with open(text_report, "w") as f:
subprocess.run(
[
str(llvm_cov),
"report",
*object_args,
f"-instr-profile={profdata}",
*ignore_args,
],
stdout=f,
check=True,
)
print(text_report.read_text())

logging.info("Generating HTML report -> %s", coverage_dir)
show_cmd = [
str(llvm_cov),
"show",
*object_args,
f"-instr-profile={profdata}",
*ignore_args,
"--format=html",
f"--output-dir={coverage_dir}",
]
if llvm_cxxfilt is not None:
show_cmd.insert(2, f"-Xdemangler={llvm_cxxfilt}")
subprocess.run(show_cmd, check=True)

lcov_file = coverage_dir / "coverage.info"
logging.info("Generating LCOV export -> %s", lcov_file)
with open(lcov_file, "w") as f:
subprocess.run(
[
str(llvm_cov),
"export",
*object_args,
f"-instr-profile={profdata}",
*ignore_args,
"--format=lcov",
],
stdout=f,
check=True,
)


def main():
parser = argparse.ArgumentParser(description="Run/aggregate coverage and report")
parser.add_argument(
"--build-dir",
type=Path,
required=True,
help="Tree to resolve coverage objects and llvm tools from",
)
parser.add_argument(
"--metadata",
type=Path,
required=True,
help="coverage_metadata.json from export_coverage_metadata.py",
)
parser.add_argument(
"--coverage-dir", type=Path, required=True, help="Output directory for reports"
)
parser.add_argument(
"--profraw-dir",
type=Path,
default=None,
help="Directory of profraw files (default: <coverage-dir>/profraw)",
)
parser.add_argument(
"--test-dir",
type=Path,
default=None,
help="ctest directory (default: --build-dir) when running tests",
)
parser.add_argument(
"--skip-tests",
action="store_true",
help="Do not run tests; merge existing profraw files",
)
args = parser.parse_args()

with open(args.metadata) as f:
metadata = json.load(f)
project = metadata.get("project", "project")
logging.info("Coverage for project: %s", project)

args.coverage_dir.mkdir(parents=True, exist_ok=True)
profraw_dir = args.profraw_dir or (args.coverage_dir / "profraw")

# Resolve tooling. Tools link libLLVM and bundled sysdeps that sit next to
# them, so add their directory to LD_LIBRARY_PATH.
llvm_profdata = find_tool(args.build_dir, "llvm-profdata")
llvm_cov = find_tool(args.build_dir, "llvm-cov")
llvm_cxxfilt = find_tool(args.build_dir, "llvm-cxxfilt")
if llvm_profdata is None or llvm_cov is None:
raise FileNotFoundError(
f"llvm-profdata/llvm-cov not found under {args.build_dir}"
)
tool_dirs = {str(t.parent) for t in (llvm_profdata, llvm_cov, llvm_cxxfilt) if t}
os.environ["LD_LIBRARY_PATH"] = os.pathsep.join(
[*tool_dirs, os.environ.get("LD_LIBRARY_PATH", "")]
)
for t in (llvm_profdata, llvm_cov, llvm_cxxfilt):
if t:
t.chmod(0o755)

objects = resolve_objects(args.build_dir, metadata)
if not objects:
raise RuntimeError("No coverage object files could be resolved.")
logging.info("Coverage objects: %s", ", ".join(str(o) for o in objects))

if not args.skip_tests:
set_coverage_environment(metadata, args.coverage_dir)
rc = run_tests(args.test_dir or args.build_dir, metadata)
if rc != 0:
logging.warning("Tests exited with %d; continuing with coverage.", rc)

# Some test runners (e.g. GPU families whose machines have no matching GPU)
# run only host-side quick tests that never exercise the instrumented
# library, so they upload no profraw. With nothing to merge there is no
# coverage to report; treat that as a no-op success rather than failing the
# job, so the absence of data on one GPU family does not break CI.
if not list(profraw_dir.rglob("*.profraw")):
logging.warning(
"No .profraw files found under %s; skipping coverage report "
"(no instrumented test data was produced for this build).",
profraw_dir,
)
return

profdata = merge_profraw_files(
llvm_profdata, profraw_dir, args.coverage_dir / f"{project}.profdata"
)
generate_reports(
llvm_cov,
llvm_cxxfilt,
objects,
metadata.get("ignore_filename_regex", ""),
profdata,
args.coverage_dir,
project,
)
logging.info("Coverage generation complete.")


if __name__ == "__main__":
main()
Loading
Loading