Skip to content

Commit 0d48eaa

Browse files
authored
Add coverage automation (#1340)
* First pass at coverage automation * Add code coverage information
1 parent 83eaec1 commit 0d48eaa

File tree

6 files changed

+202
-2
lines changed

6 files changed

+202
-2
lines changed

.coveragerc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
3+
4+
[report]
5+
show_missing = true
6+
7+
[run]
8+
plugins = Cython.Coverage
9+
core = ctrace

.github/workflows/coverage.yml

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
name: "CI: Coverage"
6+
7+
on:
8+
schedule:
9+
- cron: '0 0 * * *' # This runs the workflow every day at 12:00 AM UTC
10+
workflow_dispatch: {}
11+
12+
defaults:
13+
run:
14+
shell: bash --noprofile --norc -xeuo pipefail {0}
15+
16+
env:
17+
PY_VER: "3.14"
18+
CUDA_VER: "13.1.0"
19+
LOCAL_CTK: "1"
20+
GPU: "a100"
21+
DRIVER: "latest"
22+
ARCH: "x86_64"
23+
HOST_PLATFORM: "linux-64"
24+
25+
jobs:
26+
coverage:
27+
name: Coverage
28+
runs-on: "linux-amd64-gpu-a100-latest-1"
29+
permissions:
30+
id-token: write
31+
contents: write
32+
# Our self-hosted runners require a container
33+
# TODO: use a different (nvidia?) container
34+
container:
35+
options: -u root --security-opt seccomp=unconfined --shm-size 16g
36+
image: ubuntu:22.04
37+
env:
38+
NVIDIA_VISIBLE_DEVICES: ${{ env.NVIDIA_VISIBLE_DEVICES }}
39+
steps:
40+
- name: Ensure GPU is working
41+
run: nvidia-smi
42+
43+
# We have to install git before checking out the repo (so that we can
44+
# deploy the docs at the end). This means we can't use install_unix_deps
45+
# action so install the git package.
46+
- name: Install git
47+
run: |
48+
apt-get update
49+
apt-get install -y git
50+
51+
- name: Checkout ${{ github.event.repository.name }}
52+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
53+
54+
- name: Install dependencies
55+
uses: ./.github/actions/install_unix_deps
56+
continue-on-error: false
57+
with:
58+
dependencies: "tree rsync libsqlite3-0 g++ jq wget libgl1 libegl1"
59+
dependent_exes: "tree rsync libsqlite3-0 g++ jq wget libgl1 libegl1"
60+
61+
- name: Setup proxy cache
62+
uses: nv-gha-runners/setup-proxy-cache@main
63+
continue-on-error: true
64+
65+
- name: Set environment variables
66+
env:
67+
BUILD_CUDA_VER: ${{ env.CUDA_VER }}
68+
CUDA_VER: ${{ env.CUDA_VER }}
69+
HOST_PLATFORM: ${{ env.HOST_PLATFORM }}
70+
LOCAL_CTK: ${{ env.LOCAL_CTK }}
71+
PY_VER: ${{ env.PY_VER }}
72+
SHA: ${{ github.sha }}
73+
run: |
74+
./ci/tools/env-vars test
75+
echo "CUDA_PYTHON_COVERAGE=1" >> $GITHUB_ENV
76+
77+
- name: Set up Python
78+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
79+
with:
80+
python-version: ${{ env.PY_VER }}
81+
env:
82+
# we use self-hosted runners on which setup-python behaves weirdly...
83+
AGENT_TOOLSDIRECTORY: "/opt/hostedtoolcache"
84+
85+
- name: Set up mini CTK
86+
if: ${{ env.LOCAL_CTK == '1' }}
87+
uses: ./.github/actions/fetch_ctk
88+
continue-on-error: false
89+
with:
90+
host-platform: ${{ env.HOST_PLATFORM }}
91+
cuda-version: ${{ env.CUDA_VER }}
92+
93+
- name: Create venv
94+
run: |
95+
python -m venv .venv
96+
97+
- name: Build cuda-pathfinder
98+
run: |
99+
.venv/bin/pip install -v ./cuda_pathfinder
100+
101+
- name: Build cuda-python-test-helpers
102+
run: |
103+
.venv/bin/pip install -v ./cuda_python_test_helpers
104+
105+
- name: Build cuda-bindings
106+
run: |
107+
cd cuda_bindings
108+
../.venv/bin/pip install -v . --group test
109+
110+
- name: Build cuda-core
111+
run: |
112+
cd cuda_core
113+
../.venv/bin/pip install -v . --group test
114+
115+
- name: Install coverage tools
116+
run: |
117+
.venv/bin/pip install coverage pytest-cov
118+
119+
- name: Set cuda package install root
120+
run: |
121+
echo "INSTALL_ROOT=$(.venv/bin/python -c 'import cuda; print(cuda.__path__[0])')/.." >> $GITHUB_ENV
122+
echo "REPO_ROOT=$(pwd)" >> $GITHUB_ENV
123+
124+
- name: Run cuda.pathfinder tests
125+
run: |
126+
cd $INSTALL_ROOT
127+
$REPO_ROOT/.venv/bin/pytest -v --cov=./cuda --cov-append --cov-config=$REPO_ROOT/.coveragerc $REPO_ROOT/cuda_pathfinder/tests
128+
129+
- name: Run cuda.bindings tests
130+
run: |
131+
cd $INSTALL_ROOT
132+
$REPO_ROOT/.venv/bin/pytest -v --cov=./cuda --cov-append --cov-config=$REPO_ROOT/.coveragerc $REPO_ROOT/cuda_bindings/tests
133+
134+
- name: Run cuda.core tests
135+
run: |
136+
cd $INSTALL_ROOT
137+
$REPO_ROOT/.venv/bin/pytest -v --cov=./cuda --cov-append --cov-config=$REPO_ROOT/.coveragerc $REPO_ROOT/cuda_core/tests
138+
139+
- name: Generate coverage report
140+
run: |
141+
cd $INSTALL_ROOT
142+
$REPO_ROOT/.venv/bin/coverage html --rcfile=$REPO_ROOT/.coveragerc
143+
$REPO_ROOT/.venv/bin/coverage xml --rcfile=$REPO_ROOT/.coveragerc -o htmlcov/coverage.xml
144+
mkdir $REPO_ROOT/docs
145+
mv htmlcov $REPO_ROOT/docs/coverage
146+
147+
- name: Archive code coverage results
148+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
149+
with:
150+
name: coverage
151+
path: docs/coverage/
152+
153+
- name: Deploy to gh-pages
154+
uses: JamesIves/github-pages-deploy-action@4a3abc783e1a24aeb44c16e869ad83caf6b4cc23 # v4.7.4
155+
with:
156+
git-config-name: cuda-python-bot
157+
git-config-email: cuda-python-bot@users.noreply.github.com
158+
folder: docs/
159+
target-folder: docs/
160+
commit-message: "Deploy coverage: ${{ env.COMMIT_HASH }}"
161+
clean: false

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ repos:
8080
hooks:
8181
- id: actionlint
8282
args: ["-shellcheck="]
83+
exclude: ^\.github/workflows/coverage.yml$
8384

8485
- repo: https://github.com/MarcoGorelli/cython-lint
8586
rev: "d9ff7ce99ef4f2ae8fba93079ca9d76c4651d4ac" # frozen: v0.18.0

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ flowchart TD
207207
- **Parallel execution**: Matrix builds across Python versions and platforms
208208
- **Component isolation**: Each component (core, bindings, pathfinder, python) can be built/released independently
209209

210+
## Code coverage
211+
212+
Code coverage reports are produced nightly and posted to [GitHub Pages](https://nvidia.github.io/cuda-python/coverage).
213+
214+
Known limitations: Code coverage is only run on Linux x86_64 with an a100 GPU. We plan to add more platform and GPU coverage in the future.
215+
210216
---
211217

212218
<a>1</a>: The `cuda-python` meta package shares the same license and the contributing guidelines as those of `cuda-bindings`.

cuda_bindings/setup.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
PARSER_CACHING = os.environ.get("CUDA_PYTHON_PARSER_CACHING", False)
4545
PARSER_CACHING = bool(PARSER_CACHING)
4646

47+
COMPILE_FOR_COVERAGE = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0")))
48+
4749
# ----------------------------------------------------------------------
4850
# Parse user-provided CUDA headers
4951

@@ -276,6 +278,10 @@ def generate_output(infile, local):
276278
# extra_compile_args += ["-D _LIBCPP_ENABLE_ASSERTIONS"] # Consider: if clang, use libc++ preprocessor macros.
277279
else:
278280
extra_compile_args += ["-O3"]
281+
if COMPILE_FOR_COVERAGE:
282+
# CYTHON_TRACE_NOGIL indicates to trace nogil functions. It is not
283+
# related to free-threading builds.
284+
extra_compile_args += ["-DCYTHON_TRACE_NOGIL=1", "-DCYTHON_USE_SYS_MONITORING=0"]
279285

280286
# For Setup
281287
extensions = []
@@ -342,10 +348,15 @@ def cleanup_dst_files():
342348

343349

344350
def do_cythonize(extensions):
351+
compiler_directives = dict(language_level=3, embedsignature=True, binding=True, freethreading_compatible=True)
352+
353+
if COMPILE_FOR_COVERAGE:
354+
compiler_directives["linetrace"] = True
355+
345356
return cythonize(
346357
extensions,
347358
nthreads=nthreads,
348-
compiler_directives=dict(language_level=3, embedsignature=True, binding=True, freethreading_compatible=True),
359+
compiler_directives=compiler_directives,
349360
**extra_cythonize_kwargs,
350361
)
351362

cuda_core/build_hooks.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
build_sdist = _build_meta.build_sdist
2323
get_requires_for_build_sdist = _build_meta.get_requires_for_build_sdist
2424

25+
COMPILE_FOR_COVERAGE = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0")))
26+
2527

2628
@functools.cache
2729
def _get_proper_cuda_bindings_major_version() -> str:
@@ -84,24 +86,34 @@ def get_cuda_paths():
8486
print("CUDA paths:", CUDA_PATH)
8587
return CUDA_PATH
8688

89+
extra_compile_args = []
90+
if COMPILE_FOR_COVERAGE:
91+
# CYTHON_TRACE_NOGIL indicates to trace nogil functions. It is not
92+
# related to free-threading builds.
93+
extra_compile_args += ["-DCYTHON_TRACE_NOGIL=1", "-DCYTHON_USE_SYS_MONITORING=0"]
94+
8795
ext_modules = tuple(
8896
Extension(
8997
f"cuda.core.experimental.{mod.replace(os.path.sep, '.')}",
9098
sources=[f"cuda/core/experimental/{mod}.pyx"],
9199
include_dirs=list(os.path.join(root, "include") for root in get_cuda_paths()),
92100
language="c++",
101+
extra_compile_args=extra_compile_args,
93102
)
94103
for mod in module_names
95104
)
96105

97106
nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", os.cpu_count() // 2))
98107
compile_time_env = {"CUDA_CORE_BUILD_MAJOR": int(_get_proper_cuda_bindings_major_version())}
108+
compiler_directives = {"embedsignature": True, "warn.deprecated.IF": False, "freethreading_compatible": True}
109+
if COMPILE_FOR_COVERAGE:
110+
compiler_directives["linetrace"] = True
99111
_extensions = cythonize(
100112
ext_modules,
101113
verbose=True,
102114
language_level=3,
103115
nthreads=nthreads,
104-
compiler_directives={"embedsignature": True, "warn.deprecated.IF": False, "freethreading_compatible": True},
116+
compiler_directives=compiler_directives,
105117
compile_time_env=compile_time_env,
106118
)
107119

0 commit comments

Comments
 (0)