Skip to content

Commit cbf9660

Browse files
committed
Build cython tests as part of the main test suite
1 parent be5d972 commit cbf9660

13 files changed

Lines changed: 227 additions & 140 deletions

File tree

.github/workflows/build-wheel.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ jobs:
350350
run: |
351351
pip install ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}/*.whl --group ./cuda_bindings/pyproject.toml:test
352352
pushd ${{ env.CUDA_BINDINGS_CYTHON_TESTS_DIR }}
353-
bash build_tests.sh
353+
python test_cython.py
354354
popd
355355
356356
- name: Upload cuda.bindings Cython tests
@@ -364,7 +364,7 @@ jobs:
364364
run: |
365365
pip install ${{ env.CUDA_CORE_ARTIFACTS_DIR }}/"cu${BUILD_CUDA_MAJOR}"/*.whl --group ./cuda_core/pyproject.toml:test
366366
pushd ${{ env.CUDA_CORE_CYTHON_TESTS_DIR }}
367-
bash build_tests.sh
367+
python test_cython.py
368368
popd
369369
370370
- name: Upload cuda.core Cython tests

cuda_bindings/AGENTS.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ subpackage in the `cuda-python` monorepo.
3939
## Testing expectations
4040

4141
- **Primary tests**: `pytest tests/`
42-
- **Cython tests**:
43-
- build: `tests/cython/build_tests.sh` (or platform equivalent)
44-
- run: `pytest tests/cython/`
4542
- **Examples**: example coverage is pytest-based under `examples/`.
4643
- **Benchmarks**: run with `pytest --benchmark-only benchmarks/` when needed.
4744
- **Orchestrated run**: from repo root, `scripts/run_tests.sh bindings`.

cuda_bindings/README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,6 @@ To run these tests:
3737
* `python -m pytest tests/` against editable installations
3838
* `pytest tests/` against installed packages
3939

40-
### Cython Unit Tests
41-
42-
Cython tests are located in `tests/cython` and need to be built. These builds have the same CUDA Toolkit header requirements as [Installing from Source](https://nvidia.github.io/cuda-python/cuda-bindings/latest/install.html#requirements) where the major.minor version must match `cuda.bindings`. To build them:
43-
44-
1. Setup environment variable `CUDA_PATH` (or `CUDA_HOME`) with the path to the CUDA Toolkit installation. Note: If both are set, `CUDA_PATH` takes precedence.
45-
2. Run `build_tests` script located in `test/cython` appropriate to your platform. This will both cythonize the tests and build them.
46-
47-
To run these tests:
48-
* `python -m pytest tests/cython/` against editable installations
49-
* `pytest tests/cython/` against installed packages
50-
5140
### Samples
5241

5342
Various [CUDA Samples](https://github.com/NVIDIA/cuda-samples/tree/master) that were rewritten using CUDA Python are located in `examples`.

cuda_bindings/pixi.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,13 @@ libnvfatbin = "*"
151151
[package.target.linux.run-dependencies]
152152
libcufile = "*"
153153

154-
[target.linux.tasks.build-cython-tests]
155-
cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.sh"]
156-
157-
[target.win-64.tasks.build-cython-tests]
158-
cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.bat"]
159-
160154
[target.linux.tasks.test]
161155
cmd = [
162156
"pytest",
163157
"$PIXI_PROJECT_ROOT",
164158
"--override-ini",
165159
"norecursedirs=examples", # include cython tests (ignore by default config)
166160
]
167-
depends-on = [{ task = "build-cython-tests" }]
168161

169162
[target.linux.tasks.build-docs]
170163
cmd = [

cuda_bindings/tests/cython/build_tests.bat

Lines changed: 0 additions & 10 deletions
This file was deleted.

cuda_bindings/tests/cython/build_tests.sh

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,122 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2021-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
33

4-
import functools
54
import importlib
6-
import sys
5+
import os
6+
import re
7+
from pathlib import Path
78

9+
import pytest
10+
from Cython.Build import cythonize
11+
from setuptools import Extension
12+
from setuptools.dist import Distribution
813

9-
def py_func(func):
10-
"""
11-
Wraps func in a plain Python function.
12-
"""
14+
TESTS_DIR = Path(__file__).resolve().parent
15+
CYTHON_TEST_MODULES = ["test_ccuda", "test_ccudart", "test_interoperability_cython"]
16+
TEST_NAME_RE = re.compile(r"^\s*def\s+(test_[A-Za-z0-9_]+)\s*\(")
1317

14-
@functools.wraps(func)
15-
def wrapped(*args, **kwargs):
16-
return func(*args, **kwargs)
1718

19+
def _get_cuda_include_dir():
20+
cuda_home = os.environ.get("CUDA_PATH") or os.environ.get("CUDA_HOME")
21+
if cuda_home:
22+
return Path(cuda_home) / "include"
23+
return None
24+
25+
26+
def build_cython_test_modules():
27+
include_dirs = []
28+
cuda_include_dir = _get_cuda_include_dir()
29+
if cuda_include_dir:
30+
include_dirs.append(str(cuda_include_dir))
31+
32+
extensions = [
33+
Extension(
34+
name=module_name,
35+
sources=[str(TESTS_DIR / f"{module_name}.pyx")],
36+
include_dirs=include_dirs,
37+
)
38+
for module_name in CYTHON_TEST_MODULES
39+
]
40+
41+
ext_modules = cythonize(
42+
extensions,
43+
compiler_directives={"language_level": "3", "freethreading_compatible": True},
44+
nthreads=1,
45+
)
46+
47+
distribution = Distribution(
48+
{
49+
"name": "cuda-bindings-cython-tests",
50+
"ext_modules": ext_modules,
51+
}
52+
)
53+
build_ext = distribution.get_command_obj("build_ext")
54+
build_ext.inplace = True
55+
build_ext.build_temp = str(TESTS_DIR / "build" / "temp")
56+
distribution.run_command("build_ext")
57+
58+
59+
def _import_cython_test_modules(rebuild_if_needed):
60+
imported_modules = {}
61+
build_attempted = False
62+
63+
for module_name in CYTHON_TEST_MODULES:
64+
try:
65+
imported_modules[module_name] = importlib.import_module(module_name)
66+
except ImportError:
67+
if not rebuild_if_needed:
68+
raise
69+
70+
if not build_attempted:
71+
build_cython_test_modules()
72+
importlib.invalidate_caches()
73+
build_attempted = True
74+
75+
imported_modules[module_name] = importlib.import_module(module_name)
76+
77+
return imported_modules
78+
79+
80+
def _discover_test_function_names(module_name):
81+
module_source = TESTS_DIR / f"{module_name}.pyx"
82+
test_names = []
83+
84+
with module_source.open(encoding="utf-8") as f:
85+
for line in f:
86+
match = TEST_NAME_RE.match(line)
87+
if match:
88+
test_names.append(match.group(1))
89+
90+
return test_names
91+
92+
93+
@pytest.fixture(scope="session")
94+
def cython_test_modules():
95+
return _import_cython_test_modules(rebuild_if_needed=True)
96+
97+
98+
def _make_wrapped_test(module_name, test_name):
99+
def wrapped(cython_test_modules):
100+
test_func = getattr(cython_test_modules[module_name], test_name)
101+
return test_func()
102+
103+
wrapped.__name__ = test_name
104+
wrapped.__module__ = __name__
18105
return wrapped
19106

20107

21-
cython_test_modules = ["test_ccuda", "test_ccudart", "test_interoperability_cython"]
108+
registered_tests = set()
109+
for module_name in CYTHON_TEST_MODULES:
110+
for test_name in _discover_test_function_names(module_name):
111+
if test_name in registered_tests:
112+
raise RuntimeError(f"duplicate cython test name discovered: {test_name}")
113+
registered_tests.add(test_name)
114+
globals()[test_name] = _make_wrapped_test(module_name, test_name)
115+
116+
117+
def main():
118+
build_cython_test_modules()
22119

23120

24-
for mod in cython_test_modules:
25-
try:
26-
# For each callable in `mod` with name `test_*`,
27-
# wrap the callable in a plain Python function
28-
# and set the result as an attribute of this module.
29-
mod = importlib.import_module(mod)
30-
for name in dir(mod):
31-
item = getattr(mod, name)
32-
if callable(item) and name.startswith("test_"):
33-
item = py_func(item)
34-
setattr(sys.modules[__name__], name, item)
35-
except ImportError:
36-
raise
121+
if __name__ == "__main__":
122+
main()

cuda_core/AGENTS.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ This file describes `cuda_core`, the high-level Pythonic CUDA subpackage in the
3535
## Testing expectations
3636

3737
- **Primary tests**: `pytest tests/`
38-
- **Cython tests**:
39-
- build: `tests/cython/build_tests.sh` (or platform equivalent)
40-
- run: `pytest tests/cython/`
4138
- **Examples**: validate affected examples in `examples/` when changing user
4239
workflows or public APIs.
4340
- **Orchestrated run**: from repo root, `scripts/run_tests.sh core`.

cuda_core/README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,3 @@ Alternatively, from the repository root you can use a simple script:
2929
* `./scripts/run_tests.sh core` to run only `cuda_core` tests
3030
* `./scripts/run_tests.sh` to run all package tests (pathfinder → bindings → core)
3131
* `./scripts/run_tests.sh smoke` to run meta-level smoke tests under `tests/integration`
32-
33-
### Cython Unit Tests
34-
35-
Cython tests are located in `tests/cython` and need to be built. These builds have the same CUDA Toolkit header requirements as [those of cuda.bindings](https://nvidia.github.io/cuda-python/cuda-bindings/latest/install.html#requirements) where the major.minor version must match `cuda.bindings`. To build them:
36-
37-
1. Set up environment variable `CUDA_PATH` (or `CUDA_HOME`) with the path to the CUDA Toolkit installation. Note: If both are set, `CUDA_PATH` takes precedence.
38-
2. Run `build_tests` script located in `tests/cython` appropriate to your platform. This will both cythonize the tests and build them.
39-
40-
To run these tests:
41-
* `python -m pytest tests/cython/` with editable installations
42-
* `pytest tests/cython/` with installed packages

cuda_core/pixi.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ cuda-bindings = "*"
187187
cuda-pathfinder = "*"
188188

189189
[target.linux.tasks.build-cython-tests]
190-
cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.sh"]
190+
cmd = ["python", "$PIXI_PROJECT_ROOT/tests/cython/test_cython.py"]
191191

192192
[target.linux.tasks.docs-build]
193193
cmd = ["$PIXI_PROJECT_ROOT/docs/build_docs.sh"]
@@ -203,7 +203,7 @@ env = { SPHINXOPTS = "-v -j 1 -d build/.doctrees" }
203203
default-environment = "docs"
204204

205205
[target.win-64.tasks.build-cython-tests]
206-
cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.bat"]
206+
cmd = ["python", "$PIXI_PROJECT_ROOT/tests/cython/test_cython.py"]
207207

208208
[target.linux.tasks.test]
209209
cmd = [

0 commit comments

Comments
 (0)