diff --git a/.gitignore b/.gitignore index 31fcbd5..978172a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ docs/prj/*.dtmp build/* install/* +# Ignore generated artifacts +compile_commands.json + # Ignore generated sources lib/include/chevron_meta/info.h lib/include/chevron_meta/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ddb236..2d867f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ if (${CMAKE_PROJECT_NAME}_IS_TOP_LEVEL) # Include root CML helper module include(cmake/utility/root_setup.cmake) + setup_compile_commands_sync() setup_testing_dependencies() setup_clang_format_target() setup_clang_tidy_target() diff --git a/cmake/utility/root_setup.cmake b/cmake/utility/root_setup.cmake index 45c9545..150e992 100644 --- a/cmake/utility/root_setup.cmake +++ b/cmake/utility/root_setup.cmake @@ -58,3 +58,23 @@ macro(setup_clang_tidy_target) endif() endif() endmacro() + +# Sync compile_commands.json to project root for LSP and tooling support +macro(setup_compile_commands_sync) + if(CMAKE_EXPORT_COMPILE_COMMANDS) + message(STATUS "${CMAKE_PROJECT_NAME} compile commands sync - enabled") + + add_custom_target( + compile_commands + ALL # Run target command every build + + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_SOURCE_DIR}/compile_commands.json + + COMMENT "Syncing compile_commands.json to project root..." + VERBATIM + ) + endif() +endmacro() diff --git a/cmake/utility/testing.cmake b/cmake/utility/testing.cmake new file mode 100644 index 0000000..6bc1681 --- /dev/null +++ b/cmake/utility/testing.cmake @@ -0,0 +1,170 @@ + +#======================================== +# PROJECT TESTING HELPER MODULE +#======================================== + +# -------------------------------------------------------------------- +# @note +# Intended to be included from the `tests/CMakeLists.txt` script, +# generally after the `/3rdparty` directory has made GoogleTest and +# Google Benchmark available as imported targets. +# +# @details +# Defines test target creation functions for different categories of +# tests. This includes strategies such as unit tests, stress tests, +# and benchmarks. Each of the public helper function defined here +# encapsulates the process of executable creation, library linking, +# and CTest registration so that `CMakeLists.txt` scripts under the +# `/test` directory are clear, concise, and consistent. +# +# @author Jamon T. Bailey +# @date 05-23-2026 +# -------------------------------------------------------------------- + +include_guard(GLOBAL) + +include(GoogleTest) + +# ---> Establish GoogleTest interface target <------------------------ +add_library(googletest_external INTERFACE) + +target_include_directories( + googletest_external + + INTERFACE + "${googletest_SOURCE_DIR}/googletest/include" +) + +target_link_libraries( + googletest_external + + INTERFACE + gtest + gtest_main +) +# -------------------------------------------------------------------- + +# ---> Establish Google Benchmark interface target <------------------ +add_library(benchmark_external INTERFACE) + +target_include_directories( + benchmark_external + + INTERFACE + "${benchmark_SOURCE_DIR}/include" +) + +target_link_libraries( + benchmark_external + + INTERFACE + benchmark + benchmark_main +) +# -------------------------------------------------------------------- + +# Create test executable, apply common link dependencies, and register +# the target with CTest via `gtest_discover_tests()`. +function(_add_test_target_base) + set(single_args NAME) + set(multi_args SOURCES LINK LABELS) + cmake_parse_arguments(ARG "" "${single_args}" "${multi_args}" ${ARGN}) + + if(NOT ARG_NAME) + message( + FATAL_ERROR + "Name argument is required for the creation of a testing target." + ) + endif() + + # Create testing executable + if(NOT ARG_SOURCES) + # Sources will be provided manually later by caller + add_executable(${ARG_NAME}) + else() + add_executable(${ARG_NAME} ${ARG_SOURCES}) + endif() + + # Link against relevant testing libraries + target_link_libraries( + ${ARG_NAME} + + PRIVATE + ${ARG_LINK} + ) + + # Conditionally link against compiler configuration + if(${CHEVRON_CXX_GCC}) + target_link_libraries( + ${ARG_NAME} + + PRIVATE + CHEVRON_gcc_cxx_bundle + CHEVRON_gcc_c_defines + ) + elseif(${CHEVRON_CXX_CLANG}) + target_link_libraries( + ${ARG_NAME} + + PRIVATE + CHEVRON_clang_cxx_bundle + CHEVRON_clang_c_defines + ) + elseif(${CHEVRON_CXX_MSVC}) + target_link_libraries( + ${ARG_NAME} + + PRIVATE + CHEVRON_msvc_cxx_bundle + ) + endif() + + gtest_discover_tests( + ${ARG_NAME} + + PROPERTIES + LABELS "${ARG_LABELS}" + ) +endfunction() + +# Create unit testing target +function(add_unit_test_target) + set(single_args NAME) + set(multi_args SOURCES LABELS) + cmake_parse_arguments(ARG "" "${single_args}" "${multi_args}" ${ARGN}) + + _add_test_target_base( + NAME ${ARG_NAME} + + SOURCES + ${ARG_SOURCES} + + LINK + googletest_external + + LABELS + unit + ${ARG_LABELS} + ) +endfunction() + +# Create stress/soak testing target +function(add_stress_test_target) + set(single_args NAME) + set(multi_args SOURCES LABELS) + cmake_parse_arguments(ARG "" "${single_args}" "${multi_args}" ${ARGN}) + + _add_test_target_base( + NAME ${ARG_NAME} + + SOURCES + ${ARG_SOURCES} + + LINK + googletest_external + + LABELS + stress + ${ARG_LABELS} + ) +endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10396c5..1b117e0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,8 @@ # /tests Directory Script #================================== +include("${CHEVRON_CMAKE_MODULES_DIR}/utility/testing.cmake") + if(CHEVRON_CLI_DEBUG) add_subdirectory(cli) endif() diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index e6fb92c..9595318 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -3,22 +3,5 @@ # /tests/benchmark Directory Script #============================================ -add_library(benchmark_external INTERFACE) - -target_include_directories( - benchmark_external - - INTERFACE - "${benchmark_SOURCE_DIR}/include" -) - -target_link_libraries( - benchmark_external - - INTERFACE - benchmark - benchmark_main -) - # Benchmarking Sources # TODO: Add benchmarking source dirs... diff --git a/tests/cli/dev_exe.cpp b/tests/cli/dev_exe.cpp index 3de8672..004c0d5 100644 --- a/tests/cli/dev_exe.cpp +++ b/tests/cli/dev_exe.cpp @@ -51,6 +51,54 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) // + chevron::process::ProcessMemoryPool pool{config}; + chevron::memory::MemoryRegion blockAlloc = pool.allocate(); + + auto* ffw = static_cast(blockAlloc.base()); + new (ffw) chevron::process::MemoryPoolConfig{}; + *ffw = config; + *(ffw + 1) = config; + + pool.deallocate(blockAlloc); + + constexpr bool conceptTest1 = chevron::units::concepts::lossless_digital_size_conversion; + constexpr bool conceptTest2 = chevron::units::concepts::matching_digital_size_systems; + constexpr bool conceptTest3 = chevron::units::concepts::decimal_digital_size_units; + constexpr bool conceptTest4 = chevron::units::concepts::binary_digital_size_units; + constexpr bool conceptTest5 = chevron::units::concepts::digital_size_unit_api; + constexpr bool conceptTest6 = chevron::units::concepts::digital_size_unit_arithmetic; + constexpr bool conceptTest7 = chevron::units::concepts::digital_size_units; + + constexpr uint64_t maxPow2 = chevron::bits::max_power_of_two_exponent; + constexpr uint32_t maxPow10 = chevron::bits::max_power_of_ten_exponent; + constexpr uint64_t align12 = chevron::bits::alignUp(13ULL, 12ULL); + constexpr uint64_t nPOfTwo = chevron::bits::powerOfTwoCeil(3ULL); + constexpr uint64_t pPOfTwo = chevron::bits::powerOfTwoFloor(1ULL); + constexpr uint64_t nPOfTen = chevron::bits::powerOfTenCeil(101ULL); + constexpr uint64_t pPOfTen = chevron::bits::powerOfTenFloor(999ULL); + constexpr double sizeDiv = 1_MiB / 1_KiB; + constexpr double sizeDiv2 = 1_GiB / 1_KiB; + constexpr bool pOfTwo = chevron::bits::isPowerOfTwo(128ULL); + constexpr bool pOfTen = chevron::bits::isPowerOfTen(1000ULL); + + constexpr chevron::MiB twoGiB = 1024_MiB * 2; + constexpr chevron::MiB qtrTwoGB = twoGiB / 4; + constexpr chevron::GiB noGiB = 12_GiB - 12_GiB; + constexpr chevron::MiB addMiB = twoGiB + qtrTwoGB; + constexpr chevron::Bytes mixUp = 1_MiB + 1_MB + 24_Bytes; + constexpr chevron::MiB modTiB = 2_MiB % 11_MiB; + constexpr chevron::MiB mixUpCast = chevron::size_cast(mixUp); + constexpr chevron::GiB initDiff = chevron::GiB{ 2048_MiB }; + constexpr chevron::MiB absDiff = noGiB.absoluteDifference(4096_MiB); + + chevron::MiB trz{ 12_MiB }; + chevron::MiB* gv = &trz; + chevron::MiB* gvz = chevron::bits::alignUp(gv, alignof(chevron::MiB)); + gvz = chevron::bits::alignDown(gv, alignof(chevron::MiB)); + trz %= 12_GiB; + + constexpr uint64_t expon = chevron::math::exponent_v<2, 8>; + // //\\// diff --git a/tests/testing_setup_attempt.diff b/tests/testing_setup_attempt.diff new file mode 100644 index 0000000..52a87ce --- /dev/null +++ b/tests/testing_setup_attempt.diff @@ -0,0 +1,363 @@ +diff --git a/.clangd b/.clangd +new file mode 100644 +index 0000000..0a4958d +--- /dev/null ++++ b/.clangd +@@ -0,0 +1,2 @@ ++CompileFlags: ++ CompilationDatabase: build/linux-x64-gcc-debug +diff --git a/CMakePresets.json b/CMakePresets.json +index e28e74a..b4e5b73 100644 +--- a/CMakePresets.json ++++ b/CMakePresets.json +@@ -71,7 +71,7 @@ + "CMAKE_CXX_COMPILER": "g++", + "chevron_EXPORTS": "ON", + "CHEVRON_CLI_DEBUG": "ON", +- "CHEVRON_UNIT_TEST": "OFF", ++ "CHEVRON_UNIT_TEST": "ON", + "CHEVRON_BENCHMARK": "OFF", + "CHEVRON_CLANG_FORMAT": "ON", + "CHEVRON_CLANG_TIDY": "ON" +@@ -88,7 +88,7 @@ + "CMAKE_CXX_COMPILER": "clang++", + "chevron_EXPORTS": "ON", + "CHEVRON_CLI_DEBUG": "ON", +- "CHEVRON_UNIT_TEST": "OFF", ++ "CHEVRON_UNIT_TEST": "ON", + "CHEVRON_BENCHMARK": "OFF", + "CHEVRON_CLANG_FORMAT": "ON", + "CHEVRON_CLANG_TIDY": "ON" +diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt +index 4420aa0..be86bd6 100644 +--- a/tests/unit/CMakeLists.txt ++++ b/tests/unit/CMakeLists.txt +@@ -3,5 +3,4 @@ + # /tests/unit Directory Script + #======================================= + +-# Unit-Test Sources +-# TODO: Add unit-testing source dirs... ++add_subdirectory(utility) +diff --git a/tests/unit/utility/CMakeLists.txt b/tests/unit/utility/CMakeLists.txt +new file mode 100644 +index 0000000..b95e099 +--- /dev/null ++++ b/tests/unit/utility/CMakeLists.txt +@@ -0,0 +1,18 @@ ++ ++#=============================================== ++# /tests/unit/utility Directory Script ++#=============================================== ++ ++add_unit_test_target( ++ NAME utility_bit_unit_tests ++ ++ SOURCES ++ "${CMAKE_CURRENT_SOURCE_DIR}/bits/align_up.test.cpp" ++) ++ ++target_link_libraries( ++ utility_bit_unit_tests ++ ++ PRIVATE ++ ${CHEVRON_MAIN_BINARY_NAME} ++) +diff --git a/tests/unit/utility/bits/align_down.test.cpp b/tests/unit/utility/bits/align_down.test.cpp +new file mode 100644 +index 0000000..a83799d +--- /dev/null ++++ b/tests/unit/utility/bits/align_down.test.cpp +@@ -0,0 +1,50 @@ ++ ++// Copyright (C) 2026 by Jamon T. Bailey and InfinSys, LLC. All rights reserved. ++// Released under the terms of the GNU Affero General Public License version 3 ++ ++// [ISJTB-CXX-XL20260108-000003] ++ ++/*! ++ * @file align_down.test.cpp ++ * ++ * @brief ++ * Bit alignment utilities unit tests. ++ * ++ * @author ++ * Jamon T. Bailey ++ * ++ * @date 06-05-2026 ++ */ ++ ++#include ++#include "alignment.test.hpp" ++#include "chevron/utility/bits/alignment.hpp" ++ ++using namespace chevron::bits; ++ ++class AlignDownScalar : public testing::TestWithParam {}; ++ ++// Note: The only thing that can be done to alignment param to cause issues... ++TEST_P(AlignDownScalar, ThrowsOnZeroAlignment) ++{ ++ ASSERT_THROW( ++ alignDown(GetParam(), 0), ///< "Align some value to a multiple of zero" ++ std::invalid_argument ///< Expected exception type ++ ); ++} ++ ++INSTANTIATE_TEST_SUITE_P( ++ OddValues, ++ AlignDownScalar, ++ ODD_SCALAR_DATA_VALUES ++); ++ ++INSTANTIATE_TEST_SUITE_P( ++ EvenValues, ++ AlignDownScalar, ++ EVEN_SCALAR_DATA_VALUES ++); ++ ++// SIGNATURES BEING TESTED HERE: ++// -> size_t alignDown(const size_t value, const size_t alignment) noexcept(false) ++// -> template T* alignDown(const T* ptr, const size_t alignment) noexcept(false) +diff --git a/tests/unit/utility/bits/align_up.test.cpp b/tests/unit/utility/bits/align_up.test.cpp +new file mode 100644 +index 0000000..6b6d827 +--- /dev/null ++++ b/tests/unit/utility/bits/align_up.test.cpp +@@ -0,0 +1,144 @@ ++ ++// Copyright (C) 2026 by Jamon T. Bailey and InfinSys, LLC. All rights reserved. ++// Released under the terms of the GNU Affero General Public License version 3 ++ ++// [ISJTB-CXX-XL20260108-000003] ++ ++/*! ++ * @file align_up.test.cpp ++ * ++ * @brief ++ * TODO: INCOMPLETE DOCUMENTATION!!! ++ * ++ * @author ++ * Jamon T. Bailey ++ * ++ * @date 06-05-2026 ++ */ ++ ++#include ++#include "alignment.test.hpp" ++#include "chevron/utility/bits/alignment.hpp" ++ ++using namespace chevron::bits; ++ ++// ===================================================================================== // ++// <> AlignValueToZero Test Suite | Parameterized Test ++// ===================================================================================== // ++ ++class AlignValueToZero : public testing::TestWithParam {}; ++ ++// Note: The only thing that can be done to alignment param to cause issues... ++TEST_P(AlignValueToZero, ThrowsOnZeroAlignment) ++{ ++ ASSERT_THROW( ++ alignUp(GetParam(), 0), ///< "Align some value to a multiple of zero" ++ std::invalid_argument ///< Expected exception type ++ ); ++} ++ ++INSTANTIATE_TEST_SUITE_P( ++ PowerOfTwoValues, ++ AlignValueToZero, ++ POWER_OF_TWO_VALUES ++); ++ ++INSTANTIATE_TEST_SUITE_P( ++ PowerOfTwoEdgeCaseValues, ++ AlignValueToZero, ++ POWER_OF_TWO_EDGE_CASE_VALUES ++); ++ ++INSTANTIATE_TEST_SUITE_P( ++ NonPowerOfTwoValues, ++ AlignValueToZero, ++ NON_POWER_OF_TWO_VALUES ++); ++ ++// ===================================================================================== // ++// <> AlignToMultipleOfValue Test Suite | Parameterized Test ++// ===================================================================================== // ++ ++class AlignToMultipleOfValue : public testing::TestWithParam {}; ++ ++TEST_P(AlignToMultipleOfValue, ReturnsSameValueWhenAlreadyAligned) ++{ ++ const ScalarParams params = GetParam(); ++ ++ ASSERT_EQ( ++ alignUp(params.value, params.alignment), ///< "Align to some multiple of value" ++ params.value ///< Expecting given value on return ++ ); ++} ++ ++INSTANTIATE_TEST_SUITE_P( ++ AlignedPowerOfTwoPairs, ++ AlignToMultipleOfValue, ++ ALIGNED_POWER_OF_TWO_PAIRS ++); ++ ++INSTANTIATE_TEST_SUITE_P( ++ AlignedNonPowerOfTwoPairs, ++ AlignToMultipleOfValue, ++ ALIGNED_NON_POWER_OF_TWO_PAIRS ++); ++ ++INSTANTIATE_TEST_SUITE_P( ++ AlignedEdgeCasePairs, ++ AlignToMultipleOfValue, ++ ALIGNED_EDGE_CASE_PAIRS ++); ++ ++class ZeroMultipleOfAllValues : public testing::TestWithParam {}; ++ ++TEST_P(ZeroMultipleOfAllValues, ReturnsZeroWhenGivenZeroValue) ++{ ++ // ++} ++ ++// SIGNATURES BEING TESTED HERE: ++// -> size_t alignUp(const size_t value, const size_t alignment) noexcept(false) ++// -> template T* alignUp(const T* ptr, const size_t alignment) noexcept(false) ++ ++// TEST TODO LIST: ++// -> ThrowsOnZeroAlignment ++// -> ReturnsSameValueWhenAlreadyAligned ++// -> ReturnsZeroWhenGivenZeroValue <- Zero is a multiple of everything ++// -> ReturnsSameValueWhenAlignmentOfOne ++// -> RoundsValueUpToNearestMultiple [MISALIGNED_POWER_OF_TWO, MISALIGNED_POWER_OF_TEN] ++ ++ ++ ++ ++// /. ++// ~ ++// ++// AlignmentOfZeroCases/ScalarRoundUpFailureTest. ++// ~ ThrowsInvalidArgumentOnRoundUp ++// ++// AlignmentOfOneCases/ScalarIdentityRoundUpTest. ++// ZeroValueCases/ScalarIdentityRoundUpTest. ++// ~ ReturnsSameValuePostRoundUp ++// ++// PrealignedCases/ScalarRoundUpTest. ++// MisalignedCases/ScalarRoundUpTest. ++// PowerOfTwoAlignCases/ScalarRoundUpTest. ++// PowerOfTenAlignCases/ScalarRoundUpTest. ++// PowerOfTwoEdgeCases/ScalarRoundUpTest. ++// PowerOfTenEdgeCases/ScalarRoundUpTest. ++// ~ RoundsValueUpToNearestMultiple ++// ++// Note: ++// At this point, the first two are under consideration for ++// a different testing construct approach. The last seems to ++// fit the profile of a parameterized test better than the ++// other two listed above it. Maybe these were meant to be ++// stateless tests that use shared definitions and helpers ++// from a header (but without GTest parameterization)? ++// ++// (old) Notes: ++// "How do I structure parameterized tests as Google devs intended?" ++// "Generally, was it meant to be only one test in each parameterized test suite?" ++// --> (test name and suite are tightly coupled in this construct) ++// ---> (introducing another test in suite means it gets same params as first test) ++// ---> (we instantiate by suite, not test) +diff --git a/tests/unit/utility/bits/alignment.test.hpp b/tests/unit/utility/bits/alignment.test.hpp +new file mode 100644 +index 0000000..a5d2e57 +--- /dev/null ++++ b/tests/unit/utility/bits/alignment.test.hpp +@@ -0,0 +1,86 @@ ++ ++// Copyright (C) 2026 by Jamon T. Bailey and InfinSys, LLC. All rights reserved. ++// Released under the terms of the GNU Affero General Public License version 3 ++ ++// [ISJTB-CXX-XL20260108-000003] ++ ++/*! ++ * @file alignment.test.hpp ++ * ++ * @brief ++ * TODO: INCOMPLETE DOCUMENTATION!!! ++ * ++ * @author ++ * Jamon T. Bailey ++ * ++ * @date 06-05-2026 ++ */ ++ ++#pragma once ++ ++#include ++ ++inline const auto POWER_OF_TWO_VALUES = testing::Values( ++ size_t{2}, ++ size_t{8}, ++ size_t{16}, ++ size_t{32}, ++ size_t{64}, ++ size_t{128}, ++ size_t{256}, ++ size_t{1024} ++); ++ ++inline const auto POWER_OF_TWO_EDGE_CASE_VALUES = testing::Values( ++ size_t{0}, ++ size_t{1}, ++ size_t{1} << (std::numeric_limits::digits - 1) ++); ++ ++inline const auto NON_POWER_OF_TWO_VALUES = testing::Values( ++ size_t{3}, ++ size_t{7}, ++ size_t{10}, ++ size_t{127}, ++ size_t{255}, ++ size_t{1000}, ++ size_t{std::numeric_limits::max()} ++); ++ ++typedef struct ScalarAlignmentParams { ++ const size_t value; ++ const size_t alignment; ++} ScalarParams; ++ ++inline const auto ALIGNED_POWER_OF_TWO_PAIRS = testing::Values( ++ ScalarAlignmentParams{.value = 0, .alignment = 8}, ++ ScalarAlignmentParams{.value = 8, .alignment = 8}, ++ ScalarAlignmentParams{.value = 16, .alignment = 8}, ++ ScalarAlignmentParams{.value = 64, .alignment = 8}, ++ ScalarAlignmentParams{.value = 0, .alignment = 16}, ++ ScalarAlignmentParams{.value = 256, .alignment = 32}, ++ ScalarAlignmentParams{.value = 512, .alignment = 256} ++); ++ ++inline const auto ALIGNED_NON_POWER_OF_TWO_PAIRS = testing::Values( ++ ScalarAlignmentParams{.value = 0, .alignment = 6}, ++ ScalarAlignmentParams{.value = 6, .alignment = 6}, ++ ScalarAlignmentParams{.value = 12, .alignment = 3}, ++ ScalarAlignmentParams{.value = 0, .alignment = 36}, ++ ScalarAlignmentParams{.value = 100, .alignment = 10}, ++ ScalarAlignmentParams{.value = 99, .alignment = 3}, ++ ScalarAlignmentParams{.value = 414, .alignment = 23} ++); ++ ++inline const auto ALIGNED_EDGE_CASE_PAIRS = testing::Values( ++ ScalarAlignmentParams{.value = 0, .alignment = 1}, ++ ScalarAlignmentParams{.value = 1, .alignment = 1}, ++ ScalarAlignmentParams{ ++ .value = std::numeric_limits::max(), ++ .alignment = 1 ++ }, ++ ScalarAlignmentParams{ ++ .value = std::numeric_limits::max(), ++ .alignment = std::numeric_limits::max() ++ } ++); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 74b0b53..4420aa0 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -3,22 +3,5 @@ # /tests/unit Directory Script #======================================= -add_library(googletest_external INTERFACE) - -target_include_directories( - googletest_external - - INTERFACE - "${googletest_SOURCE_DIR}/googletest/include" -) - -target_link_libraries( - googletest_external - - INTERFACE - gtest - gtest_main -) - # Unit-Test Sources # TODO: Add unit-testing source dirs...