From 496537f496ad64283aa0b4bace078ab4da149d4d Mon Sep 17 00:00:00 2001 From: Tom Birdsong Date: Wed, 23 Sep 2026 14:06:47 -0400 Subject: [PATCH] fix(template): explain missing GTest for module tests Background: Internal user reported behavior where missing GTest failed a default C++ native build This behavior is intentional with "_BUILD_TESTS:BOOL=ON" by default. Keep the tests on-by-default and better document the flag to disable. Assisted-by: Codex:GPT-5 Signed-off-by: Tom Birdsong --- .../DEVELOPER.md | 11 ++++ .../README.md | 9 +++ ...guage == 'cpp' %}CMakeLists.txt{% endif %} | 8 ++- tests/unit/test_module_template.py | 58 +++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/DEVELOPER.md b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/DEVELOPER.md index 58dfb382..fe482935 100644 --- a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/DEVELOPER.md +++ b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/DEVELOPER.md @@ -85,6 +85,17 @@ cmake -S . -B build -DBUILD_ALL=ON -D{{ cookiecutter.module_slug | upper }}_BUIL cmake --build build -j"$(nproc)" ``` +`{{ cookiecutter.module_slug | upper }}_BUILD_TESTING` defaults to `ON` for a standalone build +and `OFF` when this module is included by a parent project. To build without +tests, configure with `-D{{ cookiecutter.module_slug | upper }}_BUILD_TESTING:BOOL=OFF`. +The standard `-DBUILD_TESTING=OFF` does not control this module's tests. +{% if cookiecutter.language == 'cpp' -%} +C++ tests require GTest (`libgtest-dev` on Debian/Ubuntu). If GTest is missing +while module tests are enabled, CMake stops with an error that names the test +option. Install GTest to run the C++ tests, or disable module tests for a +build without GTest. +{% endif -%} + {% if cookiecutter.language == 'cpp' -%} Run C++ tests: diff --git a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/README.md b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/README.md index 867c4901..31e61faf 100644 --- a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/README.md +++ b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/README.md @@ -105,6 +105,7 @@ int main() { holoscan::make_application()->run(); } | CMake | ≥ 3.24 | {%- if cookiecutter.language == 'cpp' %} | C++ compiler | C++17-capable | +| GTest | Required only when C++ tests are enabled (`libgtest-dev` on Debian/Ubuntu) | {%- endif %} | Python | ≥ 3.11 | @@ -113,6 +114,14 @@ cmake -S . -B build -DBUILD_ALL=ON -D{{ cookiecutter.module_slug | upper }}_BUIL cmake --build build -j$(nproc) ``` +The module's `{{ cookiecutter.module_slug | upper }}_BUILD_TESTING` option defaults to `ON` in a +standalone build. For a build without tests, set +`-D{{ cookiecutter.module_slug | upper }}_BUILD_TESTING:BOOL=OFF` when configuring. +{% if cookiecutter.language == 'cpp' -%} +With tests enabled, CMake requires GTest and reports how to disable tests if it +cannot find it. The standard `-DBUILD_TESTING=OFF` does not control this module's tests. +{% endif -%} + --- ## Testing diff --git a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/tests/cpp/{% if cookiecutter.language == 'cpp' %}CMakeLists.txt{% endif %} b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/tests/cpp/{% if cookiecutter.language == 'cpp' %}CMakeLists.txt{% endif %} index 24eb046f..51f7def5 100644 --- a/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/tests/cpp/{% if cookiecutter.language == 'cpp' %}CMakeLists.txt{% endif %} +++ b/src/holoscan_cli/templates/module/{{cookiecutter.module_repo_name}}/tests/cpp/{% if cookiecutter.language == 'cpp' %}CMakeLists.txt{% endif %} @@ -1,7 +1,13 @@ # SPDX-FileCopyrightText: Copyright (c) {% now 'utc', '%Y' %} {{ cookiecutter.full_name }}{% if cookiecutter.affiliation %} / {{ cookiecutter.affiliation }}{% endif %}. All rights reserved. # SPDX-License-Identifier: {{ cookiecutter._license }} -find_package(GTest REQUIRED) +find_package(GTest QUIET) +if(NOT GTest_FOUND) + message(FATAL_ERROR + "GTest is required when {{ cookiecutter.module_slug | upper }}_BUILD_TESTING is ON. " + "Install GTest (libgtest-dev on Debian/Ubuntu), or configure with " + "-D{{ cookiecutter.module_slug | upper }}_BUILD_TESTING:BOOL=OFF to build without tests.") +endif() add_executable(test_operators test_operators.cpp) diff --git a/tests/unit/test_module_template.py b/tests/unit/test_module_template.py index ebc11b4a..465a8269 100644 --- a/tests/unit/test_module_template.py +++ b/tests/unit/test_module_template.py @@ -5,6 +5,8 @@ import importlib.util import json +import shutil +import subprocess from pathlib import Path import pytest @@ -71,3 +73,59 @@ def git(*args): def test_direct_cookiecutter_use_cannot_pin_an_arbitrary_cli_release(): defaults = json.loads((TEMPLATE / "cookiecutter.json").read_text(encoding="utf-8")) assert defaults["_holoscan_cli_version"] == "0" + + +@pytest.mark.skipif(shutil.which("cmake") is None, reason="CMake is required") +@pytest.mark.parametrize( + "tests_enabled,gtest_available", [(False, False), (True, False), (True, True)] +) +def test_cpp_test_configuration_explains_missing_gtest(tmp_path, tests_enabled, gtest_available): + """Exercise the generated CMake test logic with and without GTest discovery.""" + cpp_template = next( + (TEMPLATE / "{{cookiecutter.module_repo_name}}/tests/cpp").glob("*CMakeLists.txt*") + ) + cpp_cmake = "\n".join(cpp_template.read_text(encoding="utf-8").splitlines()[2:]) + cpp_cmake = cpp_cmake.replace("{{ cookiecutter.module_slug | upper }}", "MY_MOD") + cpp_cmake = cpp_cmake.replace("{{ cookiecutter.operator_slug }}", "my_mod_op") + + tests_dir = tmp_path / "tests/cpp" + tests_dir.mkdir(parents=True) + (tests_dir / "CMakeLists.txt").write_text(cpp_cmake, encoding="utf-8") + (tests_dir / "test_operators.cpp").write_text("int main() { return 0; }\n", encoding="utf-8") + (tmp_path / "operators").mkdir() + (tmp_path / "CMakeLists.txt").write_text( + "cmake_minimum_required(VERSION 3.24)\n" + "project(module_test_config LANGUAGES CXX)\n" + "add_library(op INTERFACE)\n" + "add_library(holoscan::my_mod_op ALIAS op)\n" + "add_library(holoscan::core ALIAS op)\n" + 'option(MY_MOD_BUILD_TESTING "Build module tests" ON)\n' + "if(MY_MOD_BUILD_TESTING)\n" + " enable_testing()\n" + " add_subdirectory(tests/cpp)\n" + "endif()\n", + encoding="utf-8", + ) + + cmake_args = ["cmake", "-S", str(tmp_path), "-B", str(tmp_path / "build")] + if not tests_enabled: + cmake_args.append("-DMY_MOD_BUILD_TESTING:BOOL=OFF") + if gtest_available: + cmake_modules = tmp_path / "cmake" + cmake_modules.mkdir() + (cmake_modules / "FindGTest.cmake").write_text( + "set(GTest_FOUND TRUE)\nadd_library(GTest::gtest_main INTERFACE IMPORTED)\n", + encoding="utf-8", + ) + cmake_args.append(f"-DCMAKE_MODULE_PATH={cmake_modules}") + else: + cmake_args.append("-DCMAKE_DISABLE_FIND_PACKAGE_GTest=TRUE") + + result = subprocess.run(cmake_args, capture_output=True, text=True, check=False) + output = result.stdout + result.stderr + if tests_enabled and not gtest_available: + assert result.returncode != 0 + assert "GTest is required when MY_MOD_BUILD_TESTING is ON" in output + assert "-DMY_MOD_BUILD_TESTING:BOOL=OFF" in output + else: + assert result.returncode == 0, output