diff --git a/.github/workflows/hostnumerics-ci.yml b/.github/workflows/hostnumerics-ci.yml index fea6a089e56f..8190e970d33f 100644 --- a/.github/workflows/hostnumerics-ci.yml +++ b/.github/workflows/hostnumerics-ci.yml @@ -25,7 +25,7 @@ concurrency: jobs: standalone: - name: GCC, Python ${{ matrix.python }}, tests ${{ matrix.testing }}, ${{ matrix.generator }} + name: GCC, Python ${{ matrix.python }}, tests ${{ matrix.testing }}, ${{ matrix.generator }}, GTest ${{ matrix.gtest }} runs-on: ubuntu-24.04 env: CXX: g++ @@ -36,9 +36,13 @@ jobs: - python: "ON" testing: "ON" generator: "Ninja Multi-Config" + gtest: "installed" + fetch: "OFF" - python: "OFF" testing: "OFF" generator: "Ninja" + gtest: "fetched" + fetch: "ON" steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: @@ -53,6 +57,12 @@ jobs: if: matrix.python == 'ON' run: python -m pip install nanobind==3.0.1 + - name: Install GoogleTest + if: matrix.gtest == 'installed' + run: | + sudo apt-get update + sudo apt-get install --yes libgtest-dev + - name: Configure and build run: | if [[ "${{ matrix.python }}" == ON ]]; then @@ -61,13 +71,17 @@ jobs: python_options=(-DCMAKE_DISABLE_FIND_PACKAGE_Python=TRUE -DCMAKE_DISABLE_FIND_PACKAGE_nanobind=TRUE) fi + gtest_options=() + if [[ "${{ matrix.testing }}" == ON ]]; then + gtest_options=(-DHOSTNUMERICS_ENABLE_FETCH=${{ matrix.fetch }}) + fi cmake -S shared/hostnumerics \ -B "${RUNNER_TEMP}/hostnumerics-build" \ -G "${{ matrix.generator }}" \ -DHOSTNUMERICS_BUILD_TESTING=${{ matrix.testing }} \ -DHOSTNUMERICS_BUILD_PYTHON=${{ matrix.python }} \ -DCMAKE_BUILD_TYPE=Release \ - "${python_options[@]}" + "${python_options[@]}" "${gtest_options[@]}" cmake --build "${RUNNER_TEMP}/hostnumerics-build" \ --config Release --parallel "$(nproc)" @@ -89,6 +103,8 @@ jobs: cmake -S shared/hostnumerics/tests/package \ -B "${RUNNER_TEMP}/hostnumerics-package-test" \ -G Ninja \ + -DHOSTNUMERICS_SOURCE_DIR="${GITHUB_WORKSPACE}/shared/hostnumerics" \ + -DHOSTNUMERICS_ENABLE_FETCH=${{ matrix.fetch }} \ -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/hostnumerics-install" cmake --build "${RUNNER_TEMP}/hostnumerics-package-test" --parallel "$(nproc)" ctest --test-dir "${RUNNER_TEMP}/hostnumerics-package-test" \ @@ -106,6 +122,7 @@ jobs: run: | cmake -S shared/hostnumerics/tests/embedded \ -B "${RUNNER_TEMP}/hostnumerics-embedded-test" \ + -DHOSTNUMERICS_SOURCE_DIR="${GITHUB_WORKSPACE}/shared/hostnumerics" \ -G Ninja cmake --build "${RUNNER_TEMP}/hostnumerics-embedded-test" --parallel "$(nproc)" ctest --test-dir "${RUNNER_TEMP}/hostnumerics-embedded-test" \ diff --git a/shared/hostnumerics/CMakeLists.txt b/shared/hostnumerics/CMakeLists.txt index 326dc2031ccb..1634bbbae98e 100644 --- a/shared/hostnumerics/CMakeLists.txt +++ b/shared/hostnumerics/CMakeLists.txt @@ -45,6 +45,7 @@ add_library(roc::hostnumerics ALIAS hostnumerics) target_link_libraries(hostnumerics INTERFACE roc::hostnumerics-core) if(HOSTNUMERICS_BUILD_TESTING) + list(APPEND CMAKE_MODULE_PATH "${HostNumerics_SOURCE_DIR}/cmake") enable_testing() add_subdirectory(tests) endif() diff --git a/shared/hostnumerics/README.md b/shared/hostnumerics/README.md index 43ade6d613cd..098ff4d77f9e 100644 --- a/shared/hostnumerics/README.md +++ b/shared/hostnumerics/README.md @@ -68,6 +68,8 @@ reference. Compare representative input-generation and reference-computation timings, as well as their effect on complete test and benchmark runs, before removing an old accelerated path. +C++ tests use GoogleTest 1.12.1 or newer, retaining the repository's older compatibility baseline because these assertions need no newer API. Top-level test builds default to fetching 1.17.0, matching the shared superbuild, when no compatible installed package is available; set `HOSTNUMERICS_ENABLE_FETCH=OFF` to require an existing package. When a parent enables HostNumerics tests through `add_subdirectory`, fetching defaults to `OFF`: provide a compatible GoogleTest package or target, or explicitly set `HOSTNUMERICS_ENABLE_FETCH=ON`. Existing `GTest::gtest_main` targets must expose their version through the target's `VERSION` property or `GTest_VERSION`. GoogleTest is not installed or exported with HostNumerics. + ## Build and test The standalone build requires CMake 3.25.2 or newer, a C++20 compiler, and Ninja. @@ -97,7 +99,7 @@ are then unnecessary. when building HostNumerics standalone and `OFF` when another project includes it with `add_subdirectory`. CI covers ON/ON with Ninja Multi-Config and OFF/OFF with Ninja, including installation and an external C++ consumer for both. It also -checks the embedded defaults. +checks the embedded defaults and an installed GoogleTest package with fetching disabled. When configuring the package or embedded test project directly, pass `-DHOSTNUMERICS_SOURCE_DIR=/path/to/hostnumerics` so it can locate the shared test source and CMake helper. Install into a local prefix with: diff --git a/shared/hostnumerics/cmake/HostNumericsTestDependencies.cmake b/shared/hostnumerics/cmake/HostNumericsTestDependencies.cmake new file mode 100644 index 000000000000..902ee2c41a0d --- /dev/null +++ b/shared/hostnumerics/cmake/HostNumericsTestDependencies.cmake @@ -0,0 +1,51 @@ +# Copyright Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +# Let embedding checks detect this helper even when imported targets are local +# to the component directory. A directory property resets on each configure. +set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" + PROPERTY HOSTNUMERICS_TEST_DEPENDENCIES_LOADED TRUE) + +option(HOSTNUMERICS_ENABLE_FETCH "Allow test builds to fetch missing dependencies." + ${PROJECT_IS_TOP_LEVEL}) + +if(TARGET GTest::gtest_main) + # Source builds record VERSION on the target; installed packages expose + # GTest_VERSION instead. Do not silently accept an old or unversioned target. + get_target_property(_hostnumerics_gtest_version GTest::gtest_main VERSION) + if(NOT _hostnumerics_gtest_version) + set(_hostnumerics_gtest_version "${GTest_VERSION}") + endif() + if(NOT _hostnumerics_gtest_version OR _hostnumerics_gtest_version VERSION_LESS 1.12.1) + message(FATAL_ERROR + "HostNumerics tests require GTest 1.12.1 or newer; the existing " + "GTest::gtest_main target has version '${_hostnumerics_gtest_version}'. " + "Provide a compatible target with VERSION or GTest_VERSION set.") + endif() + unset(_hostnumerics_gtest_version) + return() +endif() +# Retain the repository's older 1.12.1 compatibility baseline: these assertions +# do not require newer APIs. Fetch the current superbuild pin separately below. +find_package(GTest 1.12.1 QUIET) +if(NOT GTest_FOUND) + if(NOT HOSTNUMERICS_ENABLE_FETCH) + message(FATAL_ERROR "HostNumerics tests require GTest 1.12.1 or newer.") + endif() + include(FetchContent) + # GoogleTest belongs to the test build, never the installed component. + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + # Match the parent runtime on MSVC, independently of static/shared libraries. + if(MSVC AND (NOT CMAKE_MSVC_RUNTIME_LIBRARY OR CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "DLL")) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + endif() + # Match the pin in cmake/modules/shared_third_party.cmake in the superbuild. + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz + URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c + SYSTEM + ) + FetchContent_MakeAvailable(googletest) +endif() diff --git a/shared/hostnumerics/tests/CMakeLists.txt b/shared/hostnumerics/tests/CMakeLists.txt index c06e5beaf2d6..b49a630bc01c 100644 --- a/shared/hostnumerics/tests/CMakeLists.txt +++ b/shared/hostnumerics/tests/CMakeLists.txt @@ -1,10 +1,12 @@ # Copyright Advanced Micro Devices, Inc., or its affiliates. # SPDX-License-Identifier: MIT +include(HostNumericsTestDependencies) + add_executable(hostnumerics-version-test version_test.cpp) -target_link_libraries(hostnumerics-version-test PRIVATE roc::hostnumerics) +target_link_libraries(hostnumerics-version-test PRIVATE roc::hostnumerics GTest::gtest_main) add_test(NAME hostnumerics-version COMMAND hostnumerics-version-test) add_executable(hostnumerics-scalar-type-test scalar_type_test.cpp) -target_link_libraries(hostnumerics-scalar-type-test PRIVATE roc::hostnumerics-core) +target_link_libraries(hostnumerics-scalar-type-test PRIVATE roc::hostnumerics-core GTest::gtest_main) add_test(NAME hostnumerics-scalar-type COMMAND hostnumerics-scalar-type-test) diff --git a/shared/hostnumerics/tests/embedded/CMakeLists.txt b/shared/hostnumerics/tests/embedded/CMakeLists.txt index dc9e6269df69..b7f8f3512af2 100644 --- a/shared/hostnumerics/tests/embedded/CMakeLists.txt +++ b/shared/hostnumerics/tests/embedded/CMakeLists.txt @@ -5,15 +5,24 @@ cmake_minimum_required(VERSION 3.25.2) project(HostNumericsEmbeddedTest LANGUAGES CXX) +if(NOT HOSTNUMERICS_SOURCE_DIR) + message(FATAL_ERROR "Set HOSTNUMERICS_SOURCE_DIR to the HostNumerics source tree.") +endif() + set(CMAKE_DISABLE_FIND_PACKAGE_Python TRUE) set(CMAKE_DISABLE_FIND_PACKAGE_nanobind TRUE) -add_subdirectory(../.. hostnumerics) -if(HOSTNUMERICS_BUILD_TESTING OR HOSTNUMERICS_BUILD_PYTHON) +add_subdirectory("${HOSTNUMERICS_SOURCE_DIR}" hostnumerics) +get_directory_property(_hostnumerics_test_dependencies_loaded + DIRECTORY "${HOSTNUMERICS_SOURCE_DIR}" HOSTNUMERICS_TEST_DEPENDENCIES_LOADED) +if(HOSTNUMERICS_BUILD_TESTING OR HOSTNUMERICS_BUILD_PYTHON OR TARGET GTest::gtest + OR _hostnumerics_test_dependencies_loaded) message(FATAL_ERROR "Embedded HostNumerics should default to no tests or Python.") endif() -add_executable(hostnumerics-embedded-test ../version_test.cpp) -target_link_libraries(hostnumerics-embedded-test PRIVATE roc::hostnumerics) +list(APPEND CMAKE_MODULE_PATH "${HOSTNUMERICS_SOURCE_DIR}/cmake") +include(HostNumericsTestDependencies) +add_executable(hostnumerics-embedded-test "${HOSTNUMERICS_SOURCE_DIR}/tests/version_test.cpp") +target_link_libraries(hostnumerics-embedded-test PRIVATE roc::hostnumerics GTest::gtest_main) enable_testing() add_test(NAME hostnumerics-embedded-test COMMAND hostnumerics-embedded-test) diff --git a/shared/hostnumerics/tests/package/CMakeLists.txt b/shared/hostnumerics/tests/package/CMakeLists.txt index 03fee51edb1d..29d9793bcc7c 100644 --- a/shared/hostnumerics/tests/package/CMakeLists.txt +++ b/shared/hostnumerics/tests/package/CMakeLists.txt @@ -7,8 +7,14 @@ project(HostNumericsPackageTest LANGUAGES CXX) find_package(HostNumerics CONFIG REQUIRED) -add_executable(hostnumerics-package-test ../version_test.cpp) -target_link_libraries(hostnumerics-package-test PRIVATE roc::hostnumerics) +if(NOT HOSTNUMERICS_SOURCE_DIR) + message(FATAL_ERROR "Set HOSTNUMERICS_SOURCE_DIR to the HostNumerics source tree.") +endif() + +list(APPEND CMAKE_MODULE_PATH "${HOSTNUMERICS_SOURCE_DIR}/cmake") +include(HostNumericsTestDependencies) +add_executable(hostnumerics-package-test "${HOSTNUMERICS_SOURCE_DIR}/tests/version_test.cpp") +target_link_libraries(hostnumerics-package-test PRIVATE roc::hostnumerics GTest::gtest_main) enable_testing() add_test(NAME hostnumerics-package-test COMMAND hostnumerics-package-test) diff --git a/shared/hostnumerics/tests/scalar_type_test.cpp b/shared/hostnumerics/tests/scalar_type_test.cpp index 9e68a03b64ce..9530268104ac 100644 --- a/shared/hostnumerics/tests/scalar_type_test.cpp +++ b/shared/hostnumerics/tests/scalar_type_test.cpp @@ -1,14 +1,16 @@ // Copyright Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT +#include + #include #include #include -#include #include +#include #include -int main() { +TEST(Core, ScalarTypes) { using namespace roc::hostnumerics; // nativeScalarType is a compilation error. Use NativeScalar // to query whether T has a mapping without causing an error. @@ -39,30 +41,31 @@ int main() { static_assert(!NativeScalar); static_assert(!NativeScalar); - const auto require = [](bool condition, const char* message) { - if (!condition) throw std::runtime_error(message); - }; - static_assert(scalarTypeInfo(ScalarType::Float32).exponentBits == 8); // IEEE binary16 has a five-bit exponent, ten fraction bits, and bias 15. const auto& half = scalarTypeInfo(ScalarType::Float16); - require(half.name == "f16" && half.category == ScalarCategory::FloatingPoint && - half.storageBits == 16 && half.exponentBits == 5 && half.mantissaBits == 10 && - half.exponentBias == 15 && half.supportsNaN && half.supportsInfinity && - !half.isPacked(), - "Incorrect binary16 metadata."); + EXPECT_EQ(half.name, "f16"); + EXPECT_EQ(half.category, ScalarCategory::FloatingPoint); + EXPECT_EQ(half.storageBits, 16); + EXPECT_EQ(half.exponentBits, 5); + EXPECT_EQ(half.mantissaBits, 10); + EXPECT_EQ(half.exponentBias, 15); + EXPECT_TRUE(half.supportsNaN); + EXPECT_TRUE(half.supportsInfinity); + EXPECT_FALSE(half.isPacked()); const auto& complex = scalarTypeInfo(ScalarType::ComplexFloat32); - require(complex.category == ScalarCategory::Complex && complex.storageBits == 64 && - complex.exponentBits == 8 && complex.mantissaBits == 23, - "Complex metadata must count both components' storage."); + EXPECT_EQ(complex.category, ScalarCategory::Complex); + EXPECT_EQ(complex.storageBits, 64); + EXPECT_EQ(complex.exponentBits, 8); + EXPECT_EQ(complex.mantissaBits, 23); const auto& packed = scalarTypeInfo(ScalarType::Float6E3M2); - require(packed.storageBits == 6 && packed.isPacked() && !packed.supportsNaN && - !packed.supportsInfinity, - "Incorrect finite six-bit encoding metadata."); - require(scalarTypeInfo(ScalarType::Int4).category == ScalarCategory::SignedInteger && - scalarTypeInfo(ScalarType::E8M0).category == ScalarCategory::FloatingPoint, - "Incorrect integer or floating-point category."); + EXPECT_EQ(packed.storageBits, 6); + EXPECT_TRUE(packed.isPacked()); + EXPECT_FALSE(packed.supportsNaN); + EXPECT_FALSE(packed.supportsInfinity); + EXPECT_EQ(scalarTypeInfo(ScalarType::Int4).category, ScalarCategory::SignedInteger); + EXPECT_EQ(scalarTypeInfo(ScalarType::E8M0).category, ScalarCategory::FloatingPoint); // Check each identifier independently of its position in the metadata table. constexpr std::pair expectedNames[] = { @@ -96,25 +99,26 @@ int main() { }; static_assert(std::size(expectedNames) == scalarTypeCount); for (const auto& [type, name] : expectedNames) { - require(scalarTypeName(type) == name, "Scalar identifier has the wrong metadata row."); - require(scalarTypeInfo(type).category < ScalarCategory::Count, - "Scalar metadata has an invalid category."); + SCOPED_TRACE(name); + EXPECT_EQ(scalarTypeName(type), name); + EXPECT_LT(scalarTypeInfo(type).category, ScalarCategory::Count); } for (ScalarType type : {ScalarType::E4M3, ScalarType::E5M3, ScalarType::E8M0, ScalarType::E8M0Zero}) { + SCOPED_TRACE(scalarTypeName(type)); const auto& info = scalarTypeInfo(type); - require(info.category == ScalarCategory::FloatingPoint && !info.isSigned, - "Unsigned floating-point metadata mismatch."); + EXPECT_EQ(info.category, ScalarCategory::FloatingPoint); + EXPECT_FALSE(info.isSigned); } - require(scalarTypeInfo(ScalarType::Float32).isSigned && - scalarTypeInfo(ScalarType::ComplexFloat32).isSigned && - scalarTypeInfo(ScalarType::Int4).isSigned && - !scalarTypeInfo(ScalarType::UInt8).isSigned && - !scalarTypeInfo(ScalarType::Boolean).isSigned, - "Scalar signedness metadata mismatch."); + EXPECT_TRUE(scalarTypeInfo(ScalarType::Float32).isSigned); + EXPECT_TRUE(scalarTypeInfo(ScalarType::ComplexFloat32).isSigned); + EXPECT_TRUE(scalarTypeInfo(ScalarType::Int4).isSigned); + EXPECT_FALSE(scalarTypeInfo(ScalarType::UInt8).isSigned); + EXPECT_FALSE(scalarTypeInfo(ScalarType::Boolean).isSigned); for (ScalarType invalid : {ScalarType::Count, static_cast(0xffff)}) { - require(!isConcreteScalarType(invalid), "Invalid scalar type classified as concrete."); + EXPECT_FALSE(isConcreteScalarType(invalid)) + << "ScalarType value: " << static_cast(invalid); } } diff --git a/shared/hostnumerics/tests/version_test.cpp b/shared/hostnumerics/tests/version_test.cpp index 089e23ce49a4..68691a43c633 100644 --- a/shared/hostnumerics/tests/version_test.cpp +++ b/shared/hostnumerics/tests/version_test.cpp @@ -1,10 +1,13 @@ // Copyright Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT +#include + #include #include -int main() { +TEST(Core, Version) { using namespace roc::hostnumerics; - return version() == "0.1.0" && scalarTypeInfo(ScalarType::Float16).storageBits == 16 ? 0 : 1; + EXPECT_EQ(version(), "0.1.0"); + EXPECT_EQ(scalarTypeInfo(ScalarType::Float16).storageBits, 16); }