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..61063f50 --- /dev/null +++ b/tutorials/accelerated-python/test/test_cuda_tools.py @@ -0,0 +1,105 @@ +""" +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 shutil +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) + + 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 + ) + 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.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + + 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.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + + 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/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. 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/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..61063f50 --- /dev/null +++ b/tutorials/cuda-cpp/test/test_cuda_tools.py @@ -0,0 +1,105 @@ +""" +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 shutil +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) + + 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 + ) + 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.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + + 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.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + + 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/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 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..61063f50 --- /dev/null +++ b/tutorials/stdpar/test/test_cuda_tools.py @@ -0,0 +1,105 @@ +""" +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 shutil +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) + + 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 + ) + 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.""" + nsys_path = shutil.which("nsys") + print(f"nsys resolves to: {nsys_path}") + + 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.""" + ncu_path = shutil.which("ncu") + print(f"ncu resolves to: {ncu_path}") + + 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}"