From 210d306b512c04906542a35c7b8bba1773c20757 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Sat, 21 Feb 2026 16:45:45 -0500 Subject: [PATCH 1/4] Tests: Add CUDA tools tests for accelerated-python, cuda-cpp, and stdpar tutorials. --- tutorials/accelerated-python/brev/test.bash | 21 ++-- .../test/test_cuda_tools.py | 95 +++++++++++++++++++ tutorials/cuda-cpp/brev/test.bash | 20 ++++ tutorials/cuda-cpp/test/pytest.ini | 2 + tutorials/cuda-cpp/test/test_cuda_tools.py | 95 +++++++++++++++++++ tutorials/stdpar/brev/test.bash | 20 ++++ tutorials/stdpar/test/pytest.ini | 2 + tutorials/stdpar/test/test_cuda_tools.py | 95 +++++++++++++++++++ 8 files changed, 337 insertions(+), 13 deletions(-) create mode 100644 tutorials/accelerated-python/test/test_cuda_tools.py create mode 100644 tutorials/cuda-cpp/test/pytest.ini create mode 100644 tutorials/cuda-cpp/test/test_cuda_tools.py create mode 100644 tutorials/stdpar/test/pytest.ini create mode 100644 tutorials/stdpar/test/test_cuda_tools.py diff --git a/tutorials/accelerated-python/brev/test.bash b/tutorials/accelerated-python/brev/test.bash index c15e5482..7836fe59 100755 --- a/tutorials/accelerated-python/brev/test.bash +++ b/tutorials/accelerated-python/brev/test.bash @@ -4,25 +4,20 @@ START_TIME=$(date +%s.%N) nvidia-smi -# Run regular package tests. -echo "Running regular package tests..." -pytest /accelerated-computing-hub/tutorials/accelerated-python/test/test_packages.py -EXIT_CODE_PACKAGES=$? +# Run tests. +echo "Running tests..." +pytest /accelerated-computing-hub/tutorials/accelerated-python/test/ \ + --ignore=/accelerated-computing-hub/tutorials/accelerated-python/test/test_rapids.py +EXIT_CODE_TESTS=$? -# Run RAPIDS tests. +# Run RAPIDS tests separately because they require a different virtual environment. echo "" -echo "Running RAPIDS package tests in virtual environment..." +echo "Running RAPIDS tests in virtual environment..." /opt/venvs/rapids/bin/pytest /accelerated-computing-hub/tutorials/accelerated-python/test/test_rapids.py EXIT_CODE_RAPIDS=$? -# Test solution notebooks. -echo "" -echo "Running solution notebook tests..." -pytest /accelerated-computing-hub/tutorials/accelerated-python/test/test_notebooks.py -EXIT_CODE_NOTEBOOKS=$? - # Overall exit code is non-zero if any test suite failed. -EXIT_CODE=$((EXIT_CODE_PACKAGES || EXIT_CODE_RAPIDS || EXIT_CODE_NOTEBOOKS)) +EXIT_CODE=$((EXIT_CODE_TESTS || EXIT_CODE_RAPIDS)) END_TIME=$(date +%s.%N) ELAPSED=$(awk "BEGIN {print $END_TIME - $START_TIME}") diff --git a/tutorials/accelerated-python/test/test_cuda_tools.py b/tutorials/accelerated-python/test/test_cuda_tools.py new file mode 100644 index 00000000..b1ef7064 --- /dev/null +++ b/tutorials/accelerated-python/test/test_cuda_tools.py @@ -0,0 +1,95 @@ +""" +Test that CUDA tools (nvcc, nsys, ncu) work correctly. + +Compiles a minimal CUDA C++ program using Thrust and verifies that both +Nsight Systems and Nsight Compute can successfully profile it. +""" + +import pytest +import subprocess +from pathlib import Path + +CUDA_PROGRAM = r""" +#include +#include +#include +#include +#include + +int main() { + constexpr int n = 256; + + thrust::device_vector d(n); + thrust::sequence(d.begin(), d.end()); + + float sum = thrust::reduce(d.begin(), d.end()); + float expected = n * (n - 1) / 2.0f; + + if (sum != expected) { + std::cerr << "Mismatch: got " << sum << ", expected " << expected << std::endl; + return EXIT_FAILURE; + } + + std::cout << "PASS" << std::endl; +} +""" + + +@pytest.fixture(scope="module") +def cuda_binary(tmp_path_factory): + """Compile a minimal CUDA C++ program and return the path to the binary.""" + tmp_dir = tmp_path_factory.mktemp("nsight_test") + src_path = tmp_dir / "test_program.cu" + bin_path = tmp_dir / "test_program" + + src_path.write_text(CUDA_PROGRAM) + + result = subprocess.run( + ["nvcc", "-o", str(bin_path), str(src_path)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nvcc compilation failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert bin_path.exists(), "Binary was not created" + + return bin_path + + +def test_cuda_binary_runs(cuda_binary): + """Verify the compiled CUDA binary runs successfully.""" + result = subprocess.run( + [str(cuda_binary)], + capture_output=True, text=True, timeout=30 + ) + assert result.returncode == 0, \ + f"CUDA binary failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert "PASS" in result.stdout + + +def test_nsys_profile(cuda_binary, tmp_path): + """Test that nsys can profile the CUDA binary.""" + report_path = tmp_path / "test_report.nsys-rep" + + result = subprocess.run( + ["nsys", "profile", + "--force-overwrite=true", + "--output", str(report_path), + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nsys profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert report_path.exists(), "nsys report file was not created" + + +def test_ncu_profile(cuda_binary): + """Test that ncu can profile the CUDA binary.""" + result = subprocess.run( + ["ncu", + "--target-processes=all", + "--set=basic", + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"ncu profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" diff --git a/tutorials/cuda-cpp/brev/test.bash b/tutorials/cuda-cpp/brev/test.bash index 715d07c0..e58c56dd 100755 --- a/tutorials/cuda-cpp/brev/test.bash +++ b/tutorials/cuda-cpp/brev/test.bash @@ -1,3 +1,23 @@ #! /bin/bash +START_TIME=$(date +%s.%N) + nvidia-smi + +# Run tests. +echo "Running tests..." +pytest /accelerated-computing-hub/tutorials/cuda-cpp/test/ +EXIT_CODE=$? + +END_TIME=$(date +%s.%N) +ELAPSED=$(awk "BEGIN {print $END_TIME - $START_TIME}") + +echo "" +awk -v elapsed="$ELAPSED" 'BEGIN { + hours = int(elapsed / 3600) + minutes = int((elapsed % 3600) / 60) + seconds = elapsed % 60 + printf "Elapsed time: %dh %dm %.3fs\n", hours, minutes, seconds +}' + +exit $EXIT_CODE diff --git a/tutorials/cuda-cpp/test/pytest.ini b/tutorials/cuda-cpp/test/pytest.ini new file mode 100644 index 00000000..1dd0dacc --- /dev/null +++ b/tutorials/cuda-cpp/test/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -v -s --durations=0 --durations-min=0.0 diff --git a/tutorials/cuda-cpp/test/test_cuda_tools.py b/tutorials/cuda-cpp/test/test_cuda_tools.py new file mode 100644 index 00000000..b1ef7064 --- /dev/null +++ b/tutorials/cuda-cpp/test/test_cuda_tools.py @@ -0,0 +1,95 @@ +""" +Test that CUDA tools (nvcc, nsys, ncu) work correctly. + +Compiles a minimal CUDA C++ program using Thrust and verifies that both +Nsight Systems and Nsight Compute can successfully profile it. +""" + +import pytest +import subprocess +from pathlib import Path + +CUDA_PROGRAM = r""" +#include +#include +#include +#include +#include + +int main() { + constexpr int n = 256; + + thrust::device_vector d(n); + thrust::sequence(d.begin(), d.end()); + + float sum = thrust::reduce(d.begin(), d.end()); + float expected = n * (n - 1) / 2.0f; + + if (sum != expected) { + std::cerr << "Mismatch: got " << sum << ", expected " << expected << std::endl; + return EXIT_FAILURE; + } + + std::cout << "PASS" << std::endl; +} +""" + + +@pytest.fixture(scope="module") +def cuda_binary(tmp_path_factory): + """Compile a minimal CUDA C++ program and return the path to the binary.""" + tmp_dir = tmp_path_factory.mktemp("nsight_test") + src_path = tmp_dir / "test_program.cu" + bin_path = tmp_dir / "test_program" + + src_path.write_text(CUDA_PROGRAM) + + result = subprocess.run( + ["nvcc", "-o", str(bin_path), str(src_path)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nvcc compilation failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert bin_path.exists(), "Binary was not created" + + return bin_path + + +def test_cuda_binary_runs(cuda_binary): + """Verify the compiled CUDA binary runs successfully.""" + result = subprocess.run( + [str(cuda_binary)], + capture_output=True, text=True, timeout=30 + ) + assert result.returncode == 0, \ + f"CUDA binary failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert "PASS" in result.stdout + + +def test_nsys_profile(cuda_binary, tmp_path): + """Test that nsys can profile the CUDA binary.""" + report_path = tmp_path / "test_report.nsys-rep" + + result = subprocess.run( + ["nsys", "profile", + "--force-overwrite=true", + "--output", str(report_path), + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nsys profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert report_path.exists(), "nsys report file was not created" + + +def test_ncu_profile(cuda_binary): + """Test that ncu can profile the CUDA binary.""" + result = subprocess.run( + ["ncu", + "--target-processes=all", + "--set=basic", + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"ncu profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" diff --git a/tutorials/stdpar/brev/test.bash b/tutorials/stdpar/brev/test.bash index 715d07c0..f42f7bc6 100644 --- a/tutorials/stdpar/brev/test.bash +++ b/tutorials/stdpar/brev/test.bash @@ -1,3 +1,23 @@ #! /bin/bash +START_TIME=$(date +%s.%N) + nvidia-smi + +# Run tests. +echo "Running tests..." +pytest /accelerated-computing-hub/tutorials/stdpar/test/ +EXIT_CODE=$? + +END_TIME=$(date +%s.%N) +ELAPSED=$(awk "BEGIN {print $END_TIME - $START_TIME}") + +echo "" +awk -v elapsed="$ELAPSED" 'BEGIN { + hours = int(elapsed / 3600) + minutes = int((elapsed % 3600) / 60) + seconds = elapsed % 60 + printf "Elapsed time: %dh %dm %.3fs\n", hours, minutes, seconds +}' + +exit $EXIT_CODE diff --git a/tutorials/stdpar/test/pytest.ini b/tutorials/stdpar/test/pytest.ini new file mode 100644 index 00000000..1dd0dacc --- /dev/null +++ b/tutorials/stdpar/test/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -v -s --durations=0 --durations-min=0.0 diff --git a/tutorials/stdpar/test/test_cuda_tools.py b/tutorials/stdpar/test/test_cuda_tools.py new file mode 100644 index 00000000..b1ef7064 --- /dev/null +++ b/tutorials/stdpar/test/test_cuda_tools.py @@ -0,0 +1,95 @@ +""" +Test that CUDA tools (nvcc, nsys, ncu) work correctly. + +Compiles a minimal CUDA C++ program using Thrust and verifies that both +Nsight Systems and Nsight Compute can successfully profile it. +""" + +import pytest +import subprocess +from pathlib import Path + +CUDA_PROGRAM = r""" +#include +#include +#include +#include +#include + +int main() { + constexpr int n = 256; + + thrust::device_vector d(n); + thrust::sequence(d.begin(), d.end()); + + float sum = thrust::reduce(d.begin(), d.end()); + float expected = n * (n - 1) / 2.0f; + + if (sum != expected) { + std::cerr << "Mismatch: got " << sum << ", expected " << expected << std::endl; + return EXIT_FAILURE; + } + + std::cout << "PASS" << std::endl; +} +""" + + +@pytest.fixture(scope="module") +def cuda_binary(tmp_path_factory): + """Compile a minimal CUDA C++ program and return the path to the binary.""" + tmp_dir = tmp_path_factory.mktemp("nsight_test") + src_path = tmp_dir / "test_program.cu" + bin_path = tmp_dir / "test_program" + + src_path.write_text(CUDA_PROGRAM) + + result = subprocess.run( + ["nvcc", "-o", str(bin_path), str(src_path)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nvcc compilation failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert bin_path.exists(), "Binary was not created" + + return bin_path + + +def test_cuda_binary_runs(cuda_binary): + """Verify the compiled CUDA binary runs successfully.""" + result = subprocess.run( + [str(cuda_binary)], + capture_output=True, text=True, timeout=30 + ) + assert result.returncode == 0, \ + f"CUDA binary failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert "PASS" in result.stdout + + +def test_nsys_profile(cuda_binary, tmp_path): + """Test that nsys can profile the CUDA binary.""" + report_path = tmp_path / "test_report.nsys-rep" + + result = subprocess.run( + ["nsys", "profile", + "--force-overwrite=true", + "--output", str(report_path), + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"nsys profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert report_path.exists(), "nsys report file was not created" + + +def test_ncu_profile(cuda_binary): + """Test that ncu can profile the CUDA binary.""" + result = subprocess.run( + ["ncu", + "--target-processes=all", + "--set=basic", + str(cuda_binary)], + capture_output=True, text=True, timeout=120 + ) + assert result.returncode == 0, \ + f"ncu profile failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" From b9ecdd811defe287ec0ec8adde68a4c399b30ed9 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Sun, 22 Feb 2026 11:55:58 -0500 Subject: [PATCH 2/4] Tutorials: Add pytest to cuda-cpp and stdpar tutorials. --- tutorials/cuda-cpp/brev/requirements.txt | 3 +++ tutorials/stdpar/brev/requirements.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tutorials/cuda-cpp/brev/requirements.txt b/tutorials/cuda-cpp/brev/requirements.txt index bc76adc7..7bbe453c 100644 --- a/tutorials/cuda-cpp/brev/requirements.txt +++ b/tutorials/cuda-cpp/brev/requirements.txt @@ -8,3 +8,6 @@ jupyterlab-execute-time # NVIDIA devtools nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git + +# Testing +pytest diff --git a/tutorials/stdpar/brev/requirements.txt b/tutorials/stdpar/brev/requirements.txt index 2cbc4712..ff6ced05 100644 --- a/tutorials/stdpar/brev/requirements.txt +++ b/tutorials/stdpar/brev/requirements.txt @@ -19,3 +19,6 @@ jupyterlab-execute-time # NVIDIA devtools nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git + +# Testing +pytest From bd60d937b6cedc287d6d7dbc72b701eb11521d80 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Sun, 22 Feb 2026 12:30:29 -0500 Subject: [PATCH 3/4] Tests: Add nsightful is_interactive_notebook test notebook for accelerated-python. --- .../test/test_notebook.ipynb | 29 +++++++++++++++++++ .../accelerated-python/test/test_notebooks.py | 14 +++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tutorials/accelerated-python/test/test_notebook.ipynb diff --git a/tutorials/accelerated-python/test/test_notebook.ipynb b/tutorials/accelerated-python/test/test_notebook.ipynb new file mode 100644 index 00000000..7a7bdb5e --- /dev/null +++ b/tutorials/accelerated-python/test/test_notebook.ipynb @@ -0,0 +1,29 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from nsightful.notebook import is_interactive_notebook\n", + "\n", + "assert is_interactive_notebook() == False, \"nsightful interactive notebook check failed\"" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/accelerated-python/test/test_notebooks.py b/tutorials/accelerated-python/test/test_notebooks.py index 1e60c52f..51a28e2e 100644 --- a/tutorials/accelerated-python/test/test_notebooks.py +++ b/tutorials/accelerated-python/test/test_notebooks.py @@ -11,6 +11,7 @@ # Define the path to the notebooks directory NOTEBOOKS_DIR = Path(__file__).resolve().parent.parent / 'notebooks' +TEST_DIR = Path(__file__).resolve().parent # Discover all solution notebooks (excluding checkpoint files) solution_notebooks = sorted([ @@ -18,8 +19,17 @@ if '.ipynb_checkpoints' not in str(nb) ]) +# Discover test notebooks in the test directory +test_notebooks = sorted([ + nb for nb in TEST_DIR.glob('*.ipynb') + if '.ipynb_checkpoints' not in str(nb) +]) + +all_notebooks = solution_notebooks + test_notebooks + # Create test IDs from notebook paths for better test output -notebook_ids = [nb.relative_to(NOTEBOOKS_DIR).as_posix() for nb in solution_notebooks] +notebook_ids = [nb.relative_to(NOTEBOOKS_DIR).as_posix() for nb in solution_notebooks] + \ + [nb.name for nb in test_notebooks] def extract_cell_outputs(nb, cell_times=None): @@ -66,7 +76,7 @@ def check_gpu_state(): print(f" GPU State check failed: {e}") -@pytest.mark.parametrize('notebook_path', solution_notebooks, ids=notebook_ids) +@pytest.mark.parametrize('notebook_path', all_notebooks, ids=notebook_ids) def test_solution_notebook_executes(notebook_path): """ Test that a solution notebook executes without errors. From 513df3fd81da8719646ad4f8051f4e3603549f1f Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Mon, 23 Feb 2026 11:08:40 -0500 Subject: [PATCH 4/4] Tests: Print out path to nvcc/ncu/nsys during testing. --- tutorials/accelerated-python/test/test_cuda_tools.py | 10 ++++++++++ tutorials/cuda-cpp/test/test_cuda_tools.py | 10 ++++++++++ tutorials/stdpar/test/test_cuda_tools.py | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/tutorials/accelerated-python/test/test_cuda_tools.py b/tutorials/accelerated-python/test/test_cuda_tools.py index b1ef7064..61063f50 100644 --- a/tutorials/accelerated-python/test/test_cuda_tools.py +++ b/tutorials/accelerated-python/test/test_cuda_tools.py @@ -6,6 +6,7 @@ """ import pytest +import shutil import subprocess from pathlib import Path @@ -44,6 +45,9 @@ def cuda_binary(tmp_path_factory): src_path.write_text(CUDA_PROGRAM) + nvcc_path = shutil.which("nvcc") + print(f"nvcc resolves to: {nvcc_path}") + result = subprocess.run( ["nvcc", "-o", str(bin_path), str(src_path)], capture_output=True, text=True, timeout=120 @@ -68,6 +72,9 @@ def test_cuda_binary_runs(cuda_binary): def test_nsys_profile(cuda_binary, tmp_path): """Test that nsys can profile the CUDA binary.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + report_path = tmp_path / "test_report.nsys-rep" result = subprocess.run( @@ -84,6 +91,9 @@ def test_nsys_profile(cuda_binary, tmp_path): def test_ncu_profile(cuda_binary): """Test that ncu can profile the CUDA binary.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + result = subprocess.run( ["ncu", "--target-processes=all", diff --git a/tutorials/cuda-cpp/test/test_cuda_tools.py b/tutorials/cuda-cpp/test/test_cuda_tools.py index b1ef7064..61063f50 100644 --- a/tutorials/cuda-cpp/test/test_cuda_tools.py +++ b/tutorials/cuda-cpp/test/test_cuda_tools.py @@ -6,6 +6,7 @@ """ import pytest +import shutil import subprocess from pathlib import Path @@ -44,6 +45,9 @@ def cuda_binary(tmp_path_factory): src_path.write_text(CUDA_PROGRAM) + nvcc_path = shutil.which("nvcc") + print(f"nvcc resolves to: {nvcc_path}") + result = subprocess.run( ["nvcc", "-o", str(bin_path), str(src_path)], capture_output=True, text=True, timeout=120 @@ -68,6 +72,9 @@ def test_cuda_binary_runs(cuda_binary): def test_nsys_profile(cuda_binary, tmp_path): """Test that nsys can profile the CUDA binary.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + report_path = tmp_path / "test_report.nsys-rep" result = subprocess.run( @@ -84,6 +91,9 @@ def test_nsys_profile(cuda_binary, tmp_path): def test_ncu_profile(cuda_binary): """Test that ncu can profile the CUDA binary.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + result = subprocess.run( ["ncu", "--target-processes=all", diff --git a/tutorials/stdpar/test/test_cuda_tools.py b/tutorials/stdpar/test/test_cuda_tools.py index b1ef7064..61063f50 100644 --- a/tutorials/stdpar/test/test_cuda_tools.py +++ b/tutorials/stdpar/test/test_cuda_tools.py @@ -6,6 +6,7 @@ """ import pytest +import shutil import subprocess from pathlib import Path @@ -44,6 +45,9 @@ def cuda_binary(tmp_path_factory): src_path.write_text(CUDA_PROGRAM) + nvcc_path = shutil.which("nvcc") + print(f"nvcc resolves to: {nvcc_path}") + result = subprocess.run( ["nvcc", "-o", str(bin_path), str(src_path)], capture_output=True, text=True, timeout=120 @@ -68,6 +72,9 @@ def test_cuda_binary_runs(cuda_binary): def test_nsys_profile(cuda_binary, tmp_path): """Test that nsys can profile the CUDA binary.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + report_path = tmp_path / "test_report.nsys-rep" result = subprocess.run( @@ -84,6 +91,9 @@ def test_nsys_profile(cuda_binary, tmp_path): def test_ncu_profile(cuda_binary): """Test that ncu can profile the CUDA binary.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + result = subprocess.run( ["ncu", "--target-processes=all",