Skip to content

Commit e6c1f84

Browse files
committed
First pass at coverage automation
1 parent 476cf1d commit e6c1f84

File tree

5 files changed

+202
-2
lines changed

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

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": _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)