From 0d86c78b85459da9a541070ecf0557b52d972d5a Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Tue, 21 Mar 2023 11:29:56 -0700 Subject: [PATCH 01/10] Adding VariableLengthArray and PMR namesapce Providing a polyfill of much of the pmr namespace for C++14. Adding VariableLengthArray as an allocator-aware vector. CETL's VLA differs from vector in three respects: 1. it supports the CETL-specific reallocation extension to PMR::memory_resource 2. it will tightly fit into a maximum size when elements are pushed without calling reserve first. 3. it avoids undefined behaviour when exceptions are disabled. An unintended benefit: this works with C++17/AppleClang which does not support PMR. --- .../cmake/toolchains/clang-native.cmake | 3 - .../cmake/toolchains/gcc-native.cmake | 2 - .devcontainer/devcontainer.json | 13 +- .vscode/cmake-kits.json | 57 +- .vscode/cmake-variants.json | 51 +- .vscode/extensions.json | 12 +- .vscode/launch.json | 20 + .vscode/open_docs_mac.sh | 3 + .vscode/settings.json | 221 +- .vscode/tasks.json | 49 + README.md | 10 +- build-tools/bin/verify.py | 783 +++++++ cetlvast/CMakeLists.txt | 162 +- .../{modules => bin}/clang-format-check.py | 64 +- cetlvast/cmake/bin/test_report_util.py | 195 ++ .../compiler_flag_sets/_native_common.cmake | 61 - .../cmake/compiler_flag_sets/default.cmake | 93 + .../cmake/compiler_flag_sets/native.cmake | 39 - cetlvast/cmake/modules/CompileTest.cmake | 87 + cetlvast/cmake/modules/Findclangformat.cmake | 119 +- cetlvast/cmake/modules/Finddocs.cmake | 134 -- cetlvast/cmake/modules/Findgcovr.cmake | 180 ++ cetlvast/cmake/modules/Findgoogletest.cmake | 408 ++++ cetlvast/cmake/modules/Findgtest.cmake | 79 - cetlvast/cmake/modules/Findo1heap.cmake | 62 + cetlvast/cmake/modules/Gtest.txt.in | 15 - cetlvast/cmake/modules/ProjectLibrary.cmake | 174 ++ cetlvast/cmake/suites/compile.cmake | 95 - cetlvast/cmake/suites/docs.cmake | 100 - cetlvast/cmake/suites/lint.cmake | 20 - cetlvast/cmake/suites/unittest.cmake | 261 --- cetlvast/include/cetlvast/datasets.h | 239 ++ cetlvast/include/cetlvast/helpers.hpp | 41 +- cetlvast/include/cetlvast/helpers_gtest.hpp | 19 + .../helpers_gtest_memory_resource.hpp | 439 ++++ cetlvast/suites/compile/CMakeLists.txt | 39 + cetlvast/suites/docs/CMakeLists.txt | 145 ++ cetlvast/suites/docs/doxygen-flat-style.css | 638 +++++ cetlvast/suites/docs/doxygen.ini | 26 +- cetlvast/suites/docs/examples/CMakeLists.txt | 73 + .../docs/examples/example_01_polyfill.cpp | 9 +- .../docs/examples/example_01_span_dynamic.cpp | 6 +- .../docs/examples/example_01_span_static.cpp | 6 +- .../docs/examples/example_02_polyfill.cpp | 9 +- .../examples/example_03_memory_resource.cpp | 392 ++++ ...ple_05_array_memory_resource_alignment.cpp | 124 + ...example_05_array_memory_resource_array.cpp | 128 + ...ple_05_variable_length_array_vs_vector.cpp | 202 ++ .../example_06_memory_resource_deleter.cpp | 57 + .../example_06_polymorphic_alloc_deleter.cpp | 152 ++ cetlvast/suites/docs/footer.html | 9 +- .../suites/docs/images/html/closed-folder.png | Bin 0 -> 365 bytes .../suites/docs/images/html/docs-ok-green.svg | 1 - cetlvast/suites/docs/images/html/document.png | Bin 0 -> 535 bytes cetlvast/suites/docs/images/html/off_sync.png | Bin 0 -> 1127 bytes cetlvast/suites/docs/images/html/on_sync.png | Bin 0 -> 876 bytes .../suites/docs/images/html/opened-folder.png | Bin 0 -> 562 bytes cetlvast/suites/docs/stylesheet.css | 181 +- cetlvast/suites/unittest/CMakeLists.txt | 73 + .../unittest/test_o1heap_memory_resource.cpp | 33 + .../unittest/test_pf17_memory_resource.cpp | 126 + .../test_pf17_monotonic_buffer_resource.cpp | 105 + .../test_pf17_polymorphic_allocator.cpp | 258 +++ cetlvast/suites/unittest/test_pf20.cpp | 11 +- .../{test_span.cpp => test_pf20_span.cpp} | 2 +- ...asserts.cpp => test_pf20_span_asserts.cpp} | 2 +- .../test_variable_length_array_bool.cpp | 234 ++ .../test_variable_length_array_compat.cpp | 1039 +++++++++ .../test_variable_length_array_compiles.cpp | 83 + ...st_variable_length_array_copy_and_move.cpp | 288 +++ ...iable_length_array_detailed_allocation.cpp | 693 ++++++ cetlvast/verify.py | 1020 -------- include/cetl/cetl.hpp | 32 +- include/cetl/pf17/byte.hpp | 34 + include/cetl/pf17/cetlpf.hpp | 184 ++ include/cetl/pf17/memory_resource.hpp | 769 +++++++ include/cetl/pf17/sys/memory_resource.hpp | 197 ++ include/cetl/pf20/cetlpf.hpp | 24 +- include/cetl/pf20/span.hpp | 38 +- include/cetl/pmr/array_memory_resource.hpp | 196 ++ include/cetl/pmr/memory.hpp | 245 ++ include/cetl/pmr/o1heap_memory_resource.hpp | 103 + include/cetl/variable_length_array.hpp | 2049 +++++++++++++++++ 83 files changed, 11995 insertions(+), 2350 deletions(-) rename {cetlvast => .devcontainer}/cmake/toolchains/clang-native.cmake (82%) rename {cetlvast => .devcontainer}/cmake/toolchains/gcc-native.cmake (83%) create mode 100644 .vscode/launch.json create mode 100644 .vscode/open_docs_mac.sh create mode 100644 .vscode/tasks.json create mode 100755 build-tools/bin/verify.py rename cetlvast/cmake/{modules => bin}/clang-format-check.py (57%) create mode 100755 cetlvast/cmake/bin/test_report_util.py delete mode 100644 cetlvast/cmake/compiler_flag_sets/_native_common.cmake create mode 100644 cetlvast/cmake/compiler_flag_sets/default.cmake delete mode 100644 cetlvast/cmake/compiler_flag_sets/native.cmake create mode 100644 cetlvast/cmake/modules/CompileTest.cmake delete mode 100644 cetlvast/cmake/modules/Finddocs.cmake create mode 100644 cetlvast/cmake/modules/Findgoogletest.cmake delete mode 100644 cetlvast/cmake/modules/Findgtest.cmake create mode 100644 cetlvast/cmake/modules/Findo1heap.cmake delete mode 100644 cetlvast/cmake/modules/Gtest.txt.in create mode 100644 cetlvast/cmake/modules/ProjectLibrary.cmake delete mode 100644 cetlvast/cmake/suites/compile.cmake delete mode 100644 cetlvast/cmake/suites/docs.cmake delete mode 100644 cetlvast/cmake/suites/lint.cmake delete mode 100644 cetlvast/cmake/suites/unittest.cmake create mode 100644 cetlvast/include/cetlvast/datasets.h create mode 100644 cetlvast/include/cetlvast/helpers_gtest.hpp create mode 100644 cetlvast/include/cetlvast/helpers_gtest_memory_resource.hpp create mode 100644 cetlvast/suites/compile/CMakeLists.txt create mode 100644 cetlvast/suites/docs/CMakeLists.txt create mode 100644 cetlvast/suites/docs/doxygen-flat-style.css create mode 100644 cetlvast/suites/docs/examples/CMakeLists.txt create mode 100644 cetlvast/suites/docs/examples/example_03_memory_resource.cpp create mode 100644 cetlvast/suites/docs/examples/example_05_array_memory_resource_alignment.cpp create mode 100644 cetlvast/suites/docs/examples/example_05_array_memory_resource_array.cpp create mode 100644 cetlvast/suites/docs/examples/example_05_variable_length_array_vs_vector.cpp create mode 100644 cetlvast/suites/docs/examples/example_06_memory_resource_deleter.cpp create mode 100644 cetlvast/suites/docs/examples/example_06_polymorphic_alloc_deleter.cpp create mode 100644 cetlvast/suites/docs/images/html/closed-folder.png delete mode 100644 cetlvast/suites/docs/images/html/docs-ok-green.svg create mode 100644 cetlvast/suites/docs/images/html/document.png create mode 100644 cetlvast/suites/docs/images/html/off_sync.png create mode 100644 cetlvast/suites/docs/images/html/on_sync.png create mode 100644 cetlvast/suites/docs/images/html/opened-folder.png create mode 100644 cetlvast/suites/unittest/CMakeLists.txt create mode 100644 cetlvast/suites/unittest/test_o1heap_memory_resource.cpp create mode 100644 cetlvast/suites/unittest/test_pf17_memory_resource.cpp create mode 100644 cetlvast/suites/unittest/test_pf17_monotonic_buffer_resource.cpp create mode 100644 cetlvast/suites/unittest/test_pf17_polymorphic_allocator.cpp rename cetlvast/suites/unittest/{test_span.cpp => test_pf20_span.cpp} (99%) rename cetlvast/suites/unittest/{test_span_asserts.cpp => test_pf20_span_asserts.cpp} (99%) create mode 100644 cetlvast/suites/unittest/test_variable_length_array_bool.cpp create mode 100644 cetlvast/suites/unittest/test_variable_length_array_compat.cpp create mode 100644 cetlvast/suites/unittest/test_variable_length_array_compiles.cpp create mode 100644 cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp create mode 100644 cetlvast/suites/unittest/test_variable_length_array_detailed_allocation.cpp delete mode 100755 cetlvast/verify.py create mode 100644 include/cetl/pf17/byte.hpp create mode 100644 include/cetl/pf17/cetlpf.hpp create mode 100644 include/cetl/pf17/memory_resource.hpp create mode 100644 include/cetl/pf17/sys/memory_resource.hpp create mode 100644 include/cetl/pmr/array_memory_resource.hpp create mode 100644 include/cetl/pmr/memory.hpp create mode 100644 include/cetl/pmr/o1heap_memory_resource.hpp create mode 100644 include/cetl/variable_length_array.hpp diff --git a/cetlvast/cmake/toolchains/clang-native.cmake b/.devcontainer/cmake/toolchains/clang-native.cmake similarity index 82% rename from cetlvast/cmake/toolchains/clang-native.cmake rename to .devcontainer/cmake/toolchains/clang-native.cmake index 6b348d64..3e243838 100644 --- a/cetlvast/cmake/toolchains/clang-native.cmake +++ b/.devcontainer/cmake/toolchains/clang-native.cmake @@ -10,7 +10,4 @@ set(CMAKE_C_COMPILER clang CACHE FILEPATH "C compiler") set(CMAKE_CXX_COMPILER clang++ CACHE FILEPATH "C++ compiler") set(CMAKE_ASM_COMPILER clang CACHE FILEPATH "assembler") -set(CMAKE_CXX_STANDARD ${CETLVAST_CPP_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_C_COVERAGE_PROCESSOR "llvm-cov gcov") diff --git a/cetlvast/cmake/toolchains/gcc-native.cmake b/.devcontainer/cmake/toolchains/gcc-native.cmake similarity index 83% rename from cetlvast/cmake/toolchains/gcc-native.cmake rename to .devcontainer/cmake/toolchains/gcc-native.cmake index 92ec7563..12a017ac 100644 --- a/cetlvast/cmake/toolchains/gcc-native.cmake +++ b/.devcontainer/cmake/toolchains/gcc-native.cmake @@ -9,6 +9,4 @@ set(CMAKE_C_COMPILER gcc CACHE FILEPATH "C compiler") set(CMAKE_CXX_COMPILER g++ CACHE FILEPATH "C++ compiler") set(CMAKE_ASM_COMPILER gcc CACHE FILEPATH "assembler") -set(CMAKE_CXX_STANDARD ${CETLVAST_CPP_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_C_COVERAGE_PROCESSOR gcov) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ef218813..84b18ea3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,18 +4,19 @@ "customizations": { "vscode": { "extensions": [ - "ms-vscode.cpptools", - "twxs.cmake", - "ms-vscode.cmake-tools", - "ban.spellright", - "xaver.clang-format" + "ms-vscode.cpptools-extension-pack", + "streetsidesoftware.code-spell-checker", + "xaver.clang-format", + "vadimcn.vscode-lldb", + "matepek.vscode-catch2-test-adapter", + "ms-vscode.hexeditor" ] } }, "workspaceFolder": "/repo", "workspaceMount": "source=${localWorkspaceFolder},target=/repo,type=bind,consistency=delegated", "mounts": [ - "source=profile,target=/root,type=volume", + "source=profile-cetl,target=/root,type=volume", "target=/root/.vscode-server,type=volume" ] } diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json index 0c281f3c..3881c35e 100644 --- a/.vscode/cmake-kits.json +++ b/.vscode/cmake-kits.json @@ -1,63 +1,18 @@ [ { - "name": "unittest-gcc", + "name": "toolshed-gcc", "description": "Compile and run unit tests using the host's default gcc toolchain.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/gcc-native.cmake", + "toolchainFile": "${workspaceFolder}/.devcontainer/cmake/toolchains/gcc-native.cmake", "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/unittest.cmake" + "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/default.cmake" } }, { - "name": "unittest-clang", + "name": "toolshed-clang", "description": "Compile and run unit tests using the host's default clang toolchain.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/clang-native.cmake", + "toolchainFile": "${workspaceFolder}/.devcontainer/cmake/toolchains/clang-native.cmake", "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/unittest.cmake" - } - }, - { - "name": "compile-gcc", - "description": "Compile and run compilation tests using the host's default gcc toolchain.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/gcc-native.cmake", - "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/compile.cmake" - } - }, - { - "name": "compile-clang", - "description": "Compile and run compilation tests using the host's default clang toolchain.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/clang-native.cmake", - "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/compile.cmake" - } - }, - { - "name": "docs-gcc", - "description": "Build the documentation and examples programs.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/gcc-native.cmake", - "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/docs.cmake" - } - }, - { - "name": "docs-clang", - "description": "Build the documentation and examples programs.", - "toolchainFile": "${workspaceFolder}/cetlvast/cmake/toolchains/clang-native.cmake", - "cmakeSettings": { - "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/native.cmake", - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/docs.cmake" - } - }, - { - "name": "lint", - "description": "Static checkers.", - "cmakeSettings": { - "CETLVAST_TEST_SUITE": "${workspaceFolder}/cetlvast/cmake/suites/lint.cmake" + "CETLVAST_FLAG_SET": "${workspaceFolder}/cetlvast/cmake/compiler_flag_sets/default.cmake" } } ] diff --git a/.vscode/cmake-variants.json b/.vscode/cmake-variants.json index 970892f0..ba981ce2 100644 --- a/.vscode/cmake-variants.json +++ b/.vscode/cmake-variants.json @@ -8,6 +8,11 @@ "long": "Debug builds, no optimizations.", "buildType": "Debug" }, + "coverage": { + "short": "Coverage", + "long": "Coverage builds, no optimizations.", + "buildType": "Coverage" + }, "release": { "short": "Release", "long": "Release builds, some optimizations", @@ -22,33 +27,16 @@ "short": "no-asserts", "long": "disable intrinsic asserts intended to be used only for testing or verification.", "settings": { - "CETL_ENABLE_DEBUG_ASSERT": false + "CETL_ENABLE_DEBUG_ASSERT": false, + "LIBCXX_ENABLE_ASSERTIONS": false } }, "on": { "short": "asserts", "long": "enable intrinsic asserts intended to be used only for testing or verification.", "settings": { - "CETL_ENABLE_DEBUG_ASSERT": true - } - } - } - }, - "coverage":{ - "default": "disabled", - "choices": { - "disabled": { - "short": "no-coverage", - "long": "code coverage generation and reporting is disabled.", - "settings": { - "CETLVAST_ENABLE_COVERAGE": false - } - }, - "enabled": { - "short": "coverage", - "long": "code coverage generation and reporting is enabled.", - "settings": { - "CETLVAST_ENABLE_COVERAGE": true + "CETL_ENABLE_DEBUG_ASSERT": true, + "LIBCXX_ENABLE_ASSERTIONS": true } } } @@ -78,5 +66,24 @@ } } } - } + }, + "exceptions":{ + "default": "enabled", + "choices": { + "enabled": { + "short": "exceptions", + "long": "Compile with standard C++ exception handling.", + "settings": { + "CETLVAST_DISABLE_CPP_EXCEPTIONS": false + } + }, + "disabled": { + "short": "no-exceptions", + "long": "Disable C++ exceptions where the compiler supports this.", + "settings": { + "CETLVAST_DISABLE_CPP_EXCEPTIONS": true + } + } + } + }, } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 828c3993..df69e9f9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,11 +2,11 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.cpptools", - "ms-azuretools.vscode-docker", - "twxs.cmake", - "ms-vscode.cmake-tools", - "ban.spellright", - "xaver.clang-format" + "ms-vscode.cpptools-extension-pack", + "streetsidesoftware.code-spell-checker", + "xaver.clang-format", + "vadimcn.vscode-lldb", + "matepek.vscode-catch2-test-adapter", + "ms-vscode.hexeditor" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..72bb16fe --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "program": "${command:cmake.launchTargetPath}", + "name": "Launch", + "args": [], + "cwd": "${workspaceFolder}", + "initCommands": ["settings set target.process.thread.step-avoid-regexp \"\""], + "env": { + "PATH": "$PATH:${command:cmake.launchTargetDirectory}", + }, + } + ] +} diff --git a/.vscode/open_docs_mac.sh b/.vscode/open_docs_mac.sh new file mode 100644 index 00000000..d46a8c8a --- /dev/null +++ b/.vscode/open_docs_mac.sh @@ -0,0 +1,3 @@ + +# source me from the root directory on mac to open vscode generated docs in browser +open "$(cetlvast/verify.py -cd cetlvast --build-dir-name vscode/build -ls builddir docs)/html/index.html" diff --git a/.vscode/settings.json b/.vscode/settings.json index 82cc5015..8bbb9d41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,72 +1,179 @@ { - "spellright.ignoreRegExps": [ - "/DLCs/g", - "/rhs/ig", - "/_?rx_?/", - "/_?tx_?/", - "/Kirienko/g", - "/Pavel/g", - "/gtests/g", - "/(lib)?cyphal/g", - "/ovfl/g", - "/Cyphal/ig", - "/cetl/i", - "/cetlvast/i", - "/tparam/g", - "/\\.hpp/g", - "/endcode/g", - "/defgroup/", - "/dst/", - "/lvs/", - "/g(oogle)?mock/", - "/g(oogle)?test/", - "/(?:de?|con)struct(?:able?|or)/i", - "/copydoc/", - "/\\b(r)?begin/", - "/\\b(r)?end/", - "/cetl(vast)?/", - "/(c|d)tor/i", - "/arr/", - "/subspan/i", - "/nodiscard/", - "/trimleft/", - "/polyfill/i", - "/autosar/i", - "/__cplusplus/", - "//", - "/godbolt/i", - "/cppreference/", - "/doxygen/i" - ], - "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "cmake.configureOnOpen": false, "cmake.configureOnEdit": false, - "cmake.buildDirectory": "${workspaceFolder}/cetlvast/build_vscode/build", + "cmake.buildDirectory": "${workspaceFolder}/build", "cmake.sourceDirectory": "${workspaceFolder}/cetlvast", "cmake.buildBeforeRun": true, - "cmake.generator": "Ninja", + "cmake.configureSettings": { + "FETCHCONTENT_UPDATES_DISCONNECTED": true, + "FETCHCONTENT_FULLY_DISCONNECTED": false, + "CMAKE_MESSAGE_LOG_LEVEL": "STATUS", + "CETL_VERSION": "0.0.0" + }, + "cmake.buildArgs": [ + "--verbose" + ], "cmake.configureArgs": [ - "-DCETLVAST_EXT_FOLDER=${workspaceFolder}/cetlvast/build_vscode/ext", "--warn-uninitialized" ], + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "editor.wordWrapColumn": 120, - "editor.rulers": [ - 120 - ], - "files.eol": "\n", "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, - "spellright.language": [ - "en" - ], - "spellright.documentTypes": [ - "markdown", - "latex", - "plaintext", - "cpp" - ], - "sonarlint.pathToCompileCommands": "${workspaceFolder}/cetlvast/build_vscode/build/compile_commands.json", "python.linting.mypyEnabled": true, "python.linting.enabled": true, + "testMate.cpp.test.advancedExecutables": [ + { + "pattern": "${workspaceFolder}/build/suites/unittest/test_*" + }, + { + "pattern": "${workspaceFolder}/build/suites/docs/examples/example_*" + } + ], + "sonarlint.pathToCompileCommands": "${workspaceFolder}/build/compile_commands.json", + "sonarlint.connectedMode.project": { + "connectionId": "opencyphal", + "projectKey": "OpenCyphal_CETL" + }, + "sonarlint.analyzerProperties": { + "sonar.cfamily.reportingCppStandardOverride": "c++14" + }, + "cSpell.words": [ + "cetl", + "DCETL_", + "cetlvast", + "DCETLVAST_", + "cyphal", + "opencyphal", + "cetlpf", + "AUTOSAR", + "tparam", + "unsynchronized", + "copydoc", + "trimleft", + "Pavel", + "Kirienko", + "gtest", + "gmock", + "DSDL", + "rend", + "rbegin", + "builddir", + "sonarqube", + "sonarcloud", + "doxygen", + "googletest", + "googlemock", + "DCMAKE_", + "ctest", + "DCTEST_", + "pushd", + "popd", + "gcovr", + "devcontainer", + "DFETCHCONTENT_FULLY_DISCONNECTED", + "arity" + ], + "files.associations": { + "memory_resource": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "regex": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "__verbose_abort": "cpp", + "ios": "cpp", + "locale": "cpp", + "queue": "cpp", + "version": "cpp", + "cassert": "cpp", + "__memory": "cpp", + "__nullptr": "cpp", + "__string": "cpp", + "ranges": "cpp", + "complex": "cpp", + "stack": "cpp", + "strstream": "cpp", + "typeindex": "cpp", + "charconv": "cpp" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..e2928182 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,49 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: cpp build active file", + "command": "/usr/bin/cpp", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "Task generated by Debugger." + }, + { + "type": "cppbuild", + "label": "C/C++: clang++ build active file", + "command": "/usr/bin/clang++", + "args": [ + "-fcolor-diagnostics", + "-fansi-escape-codes", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/README.md b/README.md index a6cb98f5..c5279b9b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -![OpenCyphal](cetlvast/suites/docs/images/html/cetl_logo.svg#gh-light-mode-only) \ -![OpenCyphal](cetlvast/suites/docs/images/html/cetl_logo_dark.svg#gh-dark-mode-only) \ +![OpenCyphal](https://opencyphal.github.io/CETL/images/html/cetl_logo_dark.svg) \ Cyphal Embedded Template Library =================== > We like to pronounce CETL as "settle" -[![Forum](https://img.shields.io/discourse/https/forum.opencyphal.org/users.svg)](https://forum.opencyphal.org) -[![Documentation](https://opencyphal.github.io/CETL/docs-ok-green.svg)](https://opencyphal.github.io/CETL/) +[![Forum](https://img.shields.io/discourse/https/forum.opencyphal.org/users.svg?label=forum&logo=discourse)](https://forum.opencyphal.org) +[![Documentation](https://img.shields.io/badge/docs-doxygen%20%28html%29-green?logo=github)](https://opencyphal.github.io/CETL/) [![CodeQL](https://github.com/OpenCyphal/CETL/actions/workflows/codeql.yml/badge.svg)](https://github.com/OpenCyphal/CETL/actions/workflows/codeql.yml) [![CETLVaSt](https://github.com/OpenCyphal/CETL/actions/workflows/cetlvast.yml/badge.svg)](https://github.com/OpenCyphal/CETL/actions/workflows/cetlvast.yml) [![Open Bugs](https://img.shields.io/github/issues/OpenCyphal/CETL/bug?label=bugs&logo=github)](https://github.com/OpenCyphal/CETL/issues?q=is%3Aopen+is%3Aissue+label%3Abug) @@ -86,5 +85,4 @@ as practical and users that want to copy and paste one of the CETL headers into with minimal effort. -![OpenCyphal](cetlvast/suites/docs/images/html/opencyphal_logo_dark.svg#gh-dark-mode-only)\ -![OpenCyphal](cetlvast/suites/docs/images/html/opencyphal_logo.svg#gh-light-mode-only) +![OpenCyphal](https://opencyphal.github.io/CETL/images/html/opencyphal_logo_dark.svg) diff --git a/build-tools/bin/verify.py b/build-tools/bin/verify.py new file mode 100755 index 00000000..d27980a6 --- /dev/null +++ b/build-tools/bin/verify.py @@ -0,0 +1,783 @@ +#!/usr/bin/env python3 +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# +""" + Command-line helper for running verification builds. +""" +# cSpell: words levelname ontarget dlibcxx gitdir + +import argparse +import functools +import logging +import os +import pathlib +import re +import subprocess +import sys +import textwrap +import typing + + +# +---------------------------------------------------------------------------+ +# | UTILS +# +---------------------------------------------------------------------------+ + + +def _clone_cmake_args(args: argparse.Namespace, cmake_args: typing.List[str]) -> typing.List[str]: + return cmake_args.copy() + + +# +---------------------------------------------------------------------------+ + + +@functools.lru_cache +def _get_version_number(gitdir: pathlib.Path) -> typing.Tuple[int, int, int, str]: + git_output = subprocess.run(["git", "describe", "--abbrev=0", "--tags"], cwd=gitdir, capture_output=True, text=True).stdout + match_obj = re.match(r"^v(\d+)\.(\d+)\.(\d+)[-_]?(\w*)", git_output) + if match_obj is not None: + qualifier = match_obj.group(4) + _version_string = (int(match_obj.group(1)), + int(match_obj.group(2)), + int(match_obj.group(3)), + qualifier if qualifier else "") + else: + _version_string = (0,0,0,"") + return _version_string + + + +# +---------------------------------------------------------------------------+ + + +def _to_cmake_logging_level(verbose: int) -> str: + if verbose == 1: + cmake_logging_level = "STATUS" + elif verbose == 2: + cmake_logging_level = "VERBOSE" + elif verbose == 3: + cmake_logging_level = "DEBUG" + elif verbose > 3: + cmake_logging_level = "TRACE" + else: + cmake_logging_level = "NOTICE" + + return cmake_logging_level + + +# +---------------------------------------------------------------------------+ + + +def _create_build_dir_name(args: argparse.Namespace) -> str: + return "{}".format(args.build_dir_name) + + +# +---------------------------------------------------------------------------+ +# | UTILS::DIRECTORIES +# +---------------------------------------------------------------------------+ + + +def _root_dir(args: argparse.Namespace) -> pathlib.Path: + return args.root_dir + + +# +---------------------------------------------------------------------------+ + + +def _build_dir(args: argparse.Namespace) -> pathlib.Path: + return _root_dir(args) / _create_build_dir_name(args) + + +# +---------------------------------------------------------------------------+ + + +def _test_suite_dir(args: argparse.Namespace) -> pathlib.Path: + return _root_dir(args) / args.test_suite_dir + + + +# +---------------------------------------------------------------------------+ +# | ARGPARSE +# +---------------------------------------------------------------------------+ + + +class QualifiedAction: + """ + Used to allow action names that have a qualified suffix like + + "clean" versus "clean-only" both being the same "clean" action but the + latter means only the clean action instead of also the clean action. + """ + + ActionPattern = re.compile(r"(?P\w+)(?:-+(?P\S+))?") + + def __init__(self, input: str): + match_obj = self.ActionPattern.match(str(input)) + if match_obj is None: + self._name = "" + self._suffix = None + else: + self._name = match_obj.group("action") + self._suffix = match_obj.group("suffix") + + @staticmethod + def __call__(cls, input: typing.Any) -> 'QualifiedAction': + return cls(input) + + def __str__(self) -> str: + return self._name + + def __eq__(self, other: typing.Any) -> bool: + if (isinstance(other, QualifiedAction)): + return (other._name == self._name) and (other._suffix == self._suffix) + else: + return str(other) == str(self) + + @property + def suffix(self) -> typing.Optional[str]: + return self._suffix + + +# +---------------------------------------------------------------------------+ + + +def _make_parser() -> argparse.ArgumentParser: + + prolog = textwrap.dedent( + """ + _ _ + ___ _ __ ___ _ __ ___ _ _ _ __ | |__ __ _| | + / _ \| '_ \ / _ | '_ \ / __| | | | '_ \| '_ \ / _` | | + | (_) | |_) | __| | | | (__| |_| | |_) | | | | (_| | | + \___/| .__/ \___|_| |_|\___|\__, | .__/|_| |_|\__,_|_| + |_| |___/|_| +----------------------------------------------------------------------------------------- +CMake command-line helper for running verification builds of opencyphal C/C++ projects. + """ + ) + + epilog = textwrap.dedent( + """ + + **Example Usage**:: + + # default configure, build, test, and release + ./verify.py + + # configure, build, and test of a Coverage build. + ./bin/verify.py test -bf Coverage + + # verbose clean, configure, build, of a Debug build + # with runtime asserts enabled. + ./bin/verify.py clean-build -bf Debug -cda -vv + + --- + """ + ) + + parser = argparse.ArgumentParser( + description=prolog, + epilog=epilog, + formatter_class=argparse.RawTextHelpFormatter, + ) + + # --[COMMON ARGS]-------------------------------------- + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help=textwrap.dedent( + """ + Used to form -DCMAKE_MESSAGE_LOG_LEVEL and other options passed into + cmake as well as the verbosity of this script. + + # | cmake log-level | verify.py | cmake options + ---------------------------------------------------- + (none) : NOTICE : warning : + 1 : STATUS : warning : --warn-uninitialized + 2 : VERBOSE : info : --warn-uninitialized + 3 : DEBUG : debug : --warn-uninitialized + 4 : TRACE : debug + env : --warn-uninitialized + 5 : TRACE : debug + env : --trace --warn-uninitialized + 6+ : TRACE : debug + env : --trace-expand --warn-uninitialized + + """[1:]) + ) + + parser.add_argument( + "--version", + action="store_true", + help=textwrap.dedent( + """ + Emits the current version. + + export CETL_VERSION=$(./verify.py --version) + + """[1:]) + ) + + # --[ACTIONS]------------------------------------------ + action_args = parser.add_argument_group( + title="Actions", + description=textwrap.dedent( + """ + Select the actions to take. + """[1:]) + ) + + action_args.add_argument( + "-tc", + "--toolchain", + choices=["gcc", "clang"], + default="gcc", + help=textwrap.dedent( + """ + + Used to form -DCMAKE_TOOLCHAIN_FILE value + + This selects the toolchain description cetlvast will tell Cmake to use. + + """[1:]) + ) + + action_args.add_argument( + "action", + choices=["clean", "configure", "build", "test", "release"], + default="release", + nargs="?", + type=typing.cast(typing.Callable[[str], str], QualifiedAction), + help=textwrap.dedent( + """ + + BUILD ACTIONS: + ----------------------------------------------------------------------- + + configure - Using cmake, create a build output folder, validate + that all build dependencies are available, and generate + build scripts. + + build - Execute the generated build scripts producing build + artifacts. + + test - Execute various tests and generate test reporting data + including coverage reports. + + release - Package build artifacts and test reports for + publication. + + This script treats these as sequential steps, starting with configure, + where specifying any one is will halt the build at this step. + For example: + + ./bin/verify.py test + + ... would configure, then build, then run tests but would not execute + the release phase. + + + CLEAN + ----------------------------------------------------------------------- + clean - Clean is special. If specified on its own then the + build only performs a clean action. If used with a + hyphenated second set then clean will run first + followed by the rules mentioned above. For example: + + ./bin/verify.py clean-test + + ... would first clean, then configure, then build, then + run the tests whereas: + + ./bin/verify.py clean + + ... would only run clean. + + ----------------------------------------------------------------------- + NOTE: the verify script does not expose the full capabilities of the + cmake build it fronts. To run individual targets or introspect + dependencies you should cd into the build directory and use cmake or + ninja directly. + + For example: + + cd build + ninja -t list + ninja -t deps + ... + --- + """[1:]) + ) + + # --[VARIANTS]----------------------------------------- + variant_args = parser.add_argument_group( + title="build variants", + description=textwrap.dedent( + """ + Arguments that modify build parameters. + """[1:]) + ) + + variant_args.add_argument( + "-bf", + "--build-flavor", + choices=["Debug", "Release", "Coverage"], + default="Debug", + help=textwrap.dedent( + """ + Sets -DCMAKE_BUILD_TYPE value + + Coverage : builds will be un-optimized and code will be instrumented + to emit coverage data files when tests are run. + Debug : builds will be lightly optimized or not optimized. Debug + symbols will be included. + Release : builds will be reasonably optimized. + + """[1:]) + ) + + variant_args.add_argument( + "-cda", + "--asserts", + action="store_true", + help=textwrap.dedent( + """ + Enables various debug asserts. + + -DCETL_ENABLE_DEBUG_ASSERT:BOOL=ON + -DLIBCXX_ENABLE_ASSERTIONS:BOOL=ON + + """[1:]) + ) + + variant_args.add_argument( + "-std", + "--cpp-standard", + default="14", + help=textwrap.dedent( + """ + + The number part of a valid --std=c++{number} argument. + + Sets -DCETLVAST_CPP_STANDARD value + + """[1:]) + ) + + # --[ACTION MOD]--------------------------------------- + action_mod_args = parser.add_argument_group( + title="action modifiers", + description=textwrap.dedent( + """ + Arguments that change the actions taken by this script. + """[1:]) + ) + + action_mod_args.add_argument( + "--dry-run", + action="store_true", + help=textwrap.dedent( + """ + Don't actually do anything. Just log what this script would have done. + Combine with --verbose to ensure you actually see the script's log + output. + """[1:]) + ) + + action_mod_args.add_argument( + "-ol", + "--online", + action="store_true", + help=textwrap.dedent( + """ + By default this script assumes no internet access. Specifying --online + may enable additional steps like checking external dependencies or + connecting to online linting services, etc. + """[1:]) + ) + + # --[MISC]--------------------------------------------- + other_args = parser.add_argument_group( + title="other options", + description=textwrap.dedent( + """ + Additional stuff you probably can ignore. + """[1:]) + ) + + other_args.add_argument( + "--build-dir-name", + default="build", + help=textwrap.dedent( + """ + This script always uses {root-dir}/{build-dir-name} as the name of the + build directory it creates. This option lets you change the + {build-dir-name} part of that file name. + + See --root-dir argument for changing the root directory. + + + """[1:]) + ) + + other_args.add_argument( + "-cd", + "--root-dir", + default=pathlib.Path.cwd(), + type=pathlib.Path, + help=textwrap.dedent( + """ + By default this script uses the current-working directory as the + project root. Use this option to specify a different root directory when + running the script. + + """[1:]) + ) + + other_args.add_argument( + "-ts", + "--test-suite-dir", + default="cetlvast", + type=pathlib.Path, + help=textwrap.dedent( + """ + The name of the folder under the root-dir where the verification test suite's + CMakeLists.txt can be found. + + """[1:]) + ) + + other_args.add_argument( + "--dont-force-ninja", + action="store_true", + help=textwrap.dedent( + """ + + -DCMAKE_GENERATOR=Ninja is used by default. Set this to remove the + preference and allow cmake to pick a default. + + """[1:]) + ) + + + return parser + + +# +---------------------------------------------------------------------------+ +# | SUBPROCESS +# +---------------------------------------------------------------------------+ + + +def _cmake_run( + args: argparse.Namespace, + cmake_args: typing.List[str], + env: typing.Optional[typing.Dict] = None, +) -> int: + """ + Simple wrapper around cmake execution logic to handle dry-run and verbose logging. + """ + logging.info( + textwrap.dedent( + """ + ***************************************************************** + About to run command: {} + in directory : {} + ***************************************************************** + """ + ).format(" ".join(cmake_args), str(_build_dir(args))) + ) + + copy_of_env: typing.Dict = {} + copy_of_env.update(os.environ) + if env is not None: + copy_of_env.update(env) + + if args.verbose >= 4: + logging.debug(" *****************************************************************") + logging.debug(" Using Environment:") + for key, value in copy_of_env.items(): + overridden = key in env if env is not None else False + logging.debug(" {} = {}{}".format(key, value, (" (override)" if overridden else ""))) + logging.debug(" *****************************************************************\n") + + if not args.dry_run: + return subprocess.run(cmake_args, cwd=_build_dir(args), env=copy_of_env).returncode + else: + return 0 + + +# +---------------------------------------------------------------------------+ + + +def _create_build_dir_action(args: argparse.Namespace) -> int: + """ + Handle all the logic, user input, logging, and file-system operations needed to + create the cmake build directory ahead of invoking cmake. + """ + if not _build_dir(args).exists(): + if not args.dry_run: + logging.info("Creating build directory at {}".format(_build_dir(args))) + _build_dir(args).mkdir() + else: + logging.info("Dry run: Would have created build directory at {}".format(_build_dir(args))) + else: + logging.info("Using existing build directory at {}".format(_build_dir(args))) + + return 0 + + +# +---------------------------------------------------------------------------+ +# | CMAKE ACTIONS +# +---------------------------------------------------------------------------+ + +_cmake_configure_cmake_suffix = ".cmake" + + +def _cmake_configure(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake configure command. + """ + + cmake_configure_args = _clone_cmake_args(args, cmake_args) + + # --[VERSION NUMBER]----------------------------------- + # set version number from git tag + version = _get_version_number(_root_dir(args)) + version_string = "{}.{}.{}".format(version[0], version[1], version[2]) + cmake_configure_args.append("-DCETL_VERSION={}".format(version_string)) + + if not args.online: + # see https://cmake.org/cmake/help/latest/module/FetchContent.html + cmake_configure_args.append("-DFETCHCONTENT_FULLY_DISCONNECTED:BOOL=ON") + + # --[VERBOSITY]---------------------------------------- + cmake_configure_args.append("-DCMAKE_MESSAGE_LOG_LEVEL:STRING={}".format(_to_cmake_logging_level(args.verbose))) + + if args.verbose >= 1: + cmake_configure_args.append("--warn-uninitialized") + if args.verbose == 5: + cmake_configure_args.append("--trace") + elif args.verbose >= 6: + cmake_configure_args.append("--trace-expand") + + # --[COMPILER FLAGS]----------------------------------- + flag_set_dir = _test_suite_dir(args) / pathlib.Path("cmake") / pathlib.Path("compiler_flag_sets") + flag_set_file = (flag_set_dir / pathlib.Path("default")).with_suffix(_cmake_configure_cmake_suffix) + + cmake_configure_args.append("-DCETLVAST_FLAG_SET={}".format(str(flag_set_file))) + cmake_configure_args.append("-DCETLVAST_CPP_STANDARD={}".format(args.cpp_standard)) + + # --[TOOL CHAIN]--------------------------------------- + if args.toolchain != "none": + toolchain = _root_dir(args) / pathlib.Path(".devcontainer") / pathlib.Path("cmake") / pathlib.Path("toolchains") + if args.toolchain == "clang": + toolchain_file = toolchain / pathlib.Path("clang-native").with_suffix(_cmake_configure_cmake_suffix) + else: + toolchain_file = toolchain / pathlib.Path("gcc-native").with_suffix(_cmake_configure_cmake_suffix) + + cmake_configure_args.append("-DCMAKE_TOOLCHAIN_FILE={}".format(str(toolchain_file))) + + # --[DEBUG ASSERTIONS]--------------------------------- + if args.asserts: + cmake_configure_args.append("-DCETL_ENABLE_DEBUG_ASSERT:BOOL=ON") + cmake_configure_args.append("-DLIBCXX_ENABLE_ASSERTIONS:BOOL=ON") + + # --[BUILD TOOL]--------------------------------------- + if not args.dont_force_ninja: + cmake_configure_args.append("-DCMAKE_GENERATOR=Ninja") + + # --[CMAKE IS GO!]------------------------------------- + cmake_configure_args.append(str(_test_suite_dir(args))) + + return _cmake_run(args, cmake_configure_args) + + +# +---------------------------------------------------------------------------+ + + +def _cmake_build(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake build command. This method assumes that the build directory + is already properly configured. + """ + cmake_build_args = _clone_cmake_args(args, cmake_args) + + cmake_build_args += ["--build", str(_build_dir(args)), "--target", "build"] + + return _cmake_run(args, cmake_build_args) + + +# +---------------------------------------------------------------------------+ + + +def _cmake_test(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake test command. This method assumes that the build directory + is already properly configured. + """ + cmake_test_args = _clone_cmake_args(args, cmake_args) + + cmake_test_args += ["--build", str(_build_dir(args)), "--target", "unittest"] + + return _cmake_run(args, cmake_test_args) + + +# +---------------------------------------------------------------------------+ + + +def _cmake_ctest(args: argparse.Namespace, _: typing.List[str]) -> int: + """ + run ctest + """ + # we use ctest to run the compile tests so we take a different + # branch here. + report_path = pathlib.Path.cwd().joinpath(_build_dir(args) / "ctest.xml") + ctest_run = ["ctest", "-DCTEST_FULL_OUTPUT", "--output-junit", str(report_path)] + if not args.dry_run: + logging.debug("about to run {}".format(str(ctest_run))) + return subprocess.run(ctest_run, cwd=_build_dir(args)).returncode + else: + logging.info("Is dry-run. Would have run ctest: {}".format(str(ctest_run))) + return 0 + + +# +---------------------------------------------------------------------------+ + + +def _cmake_release(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake release command. This method assumes that build step has already completed + successfully. + """ + cmake_build_args = _clone_cmake_args(args, cmake_args) + + cmake_build_args += ["--build", str(_build_dir(args)), "--target", "release"] + + return _cmake_run(args, cmake_build_args) + + +# +---------------------------------------------------------------------------+ + + +def _cmake_install(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake install command. This method assumes that build step has already completed + successfully. + """ + cmake_build_args = _clone_cmake_args(args, cmake_args) + + cmake_build_args += ["--build", str(_build_dir(args)), "--target", "install"] + + return _cmake_run(args, cmake_build_args) + + +# +---------------------------------------------------------------------------+ + + +def _cmake_clean(args: argparse.Namespace, cmake_args: typing.List[str]) -> int: + """ + Format and execute cmake clean command. This method assumes that the configure step has already completed + successfully. + """ + cmake_build_args = _clone_cmake_args(args, cmake_args) + + cmake_build_args += ["--build", str(_build_dir(args)), "--target", "clean"] + + return _cmake_run(args, cmake_build_args) + + +# +---------------------------------------------------------------------------+ +# | MAIN +# +---------------------------------------------------------------------------+ + + +def main() -> int: + """ + Main method to execute when this package/script is invoked as a command. + """ + args = _make_parser().parse_args() + + cmake_args = ["cmake"] + + logging_level = logging.WARN + + if args.verbose == 2: + logging_level = logging.INFO + elif args.verbose >= 3: + logging_level = logging.DEBUG + + logging.basicConfig(format="%(levelname)s: %(message)s", level=logging_level) + + logging.info( + textwrap.dedent( + """ + + ***************************************************************** + Command-line Arguments to {} for build folder {}: + + {} + + For verify version {} + ***************************************************************** + + """ + ).format(os.path.basename(__file__), str(_build_dir(args)), str(args), _get_version_number(_root_dir(args))) + ) + + # --[CLEAN]---------------------------------------------------------------- + if args.action == "clean": + _cmake_clean(args, cmake_args) + if args.action.suffix is None or args.action.suffix == "only": + return 0 + args.action = args.action.suffix + + # --[CONFIGURE]------------------------------------------------------------ + result = _create_build_dir_action(args) + if result != 0: + return result + + result = _cmake_configure(args, cmake_args) + if result != 0: + return result + + if args.action == "configure": + return 0 + + # --[BUILD]---------------------------------------------------------------- + result = _cmake_build(args, cmake_args) + if result != 0: + return result + + if args.action == "build": + return 0 + + # --[TEST]----------------------------------------------------------------- + result = _cmake_test(args, cmake_args) + if result != 0: + return result + + result = _cmake_ctest(args, cmake_args) + if result != 0: + return result + + if args.action == "test": + return 0 + + # --[RELEASE]-------------------------------------------------------------- + result = _cmake_release(args, cmake_args) + if result != 0: + return result + + result = _cmake_install(args, cmake_args) + if result != 0: + return result + + return 0 + + +# +---------------------------------------------------------------------------+ + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cetlvast/CMakeLists.txt b/cetlvast/CMakeLists.txt index 4959eb59..62361553 100644 --- a/cetlvast/CMakeLists.txt +++ b/cetlvast/CMakeLists.txt @@ -20,7 +20,16 @@ cmake_minimum_required(VERSION 3.22.0) # | PROJECT # +---------------------------------------------------------------------------+ -project(cetlvast CXX) +project(cetlvast C CXX) + +if(NOT DEFINED CMAKE_MESSAGE_LOG_LEVEL) + set(CMAKE_MESSAGE_LOG_LEVEL STATUS) +endif() + +if (NOT DEFINED CETL_VERSION) + message(WARNING "CETL_VERSION was not set. Defaulting to 0.0.0.") + set(CETL_VERSION "0.0.0") +endif() if (NOT DEFINED CETL_ROOT) set(LOCAL_PARENT_OF_SOURCE_PATH "${CMAKE_SOURCE_DIR}/../") @@ -33,38 +42,8 @@ else() message(DEBUG "Using ${CETL_ROOT} for CETL_ROOT") endif() - -if (NOT DEFINED CETLVAST_PROJECT_ROOT) - set(LOCAL_PARENT_OF_SOURCE_PATH "${CMAKE_SOURCE_DIR}/../") - cmake_path(ABSOLUTE_PATH CMAKE_SOURCE_DIR - OUTPUT_VARIABLE CETLVAST_PROJECT_ROOT - BASE_DIRECTORY "${CMAKE_BINARY_DIR}") - cmake_path(NORMAL_PATH CMAKE_BINARY_DIR OUTPUT_VARIABLE CMAKE_BINARY_DIR) - message(DEBUG "Setting CETLVAST_PROJECT_ROOT = ${CETLVAST_PROJECT_ROOT}") -else() - message(DEBUG "Using ${CETLVAST_PROJECT_ROOT} for CETLVAST_PROJECT_ROOT") -endif() - -if (NOT DEFINED CETLVAST_EXT_FOLDER) - cmake_path(GET CMAKE_CURRENT_BINARY_DIR FILENAME CETLVAST_BUILD_DIR_STUB) - set(EXTERNAL_PROJECT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${CETLVAST_BUILD_DIR_STUB}_ext") -else() - set(EXTERNAL_PROJECT_DIRECTORY "${CETLVAST_EXT_FOLDER}") -endif() - -message(DEBUG "Ensuring EXTERNAL_PROJECT_DIRECTORY=${EXTERNAL_PROJECT_DIRECTORY}") - -# -# Make sure we have our external project directory created. We use this -# to share external project artifacts with different build flavors. -# -file(MAKE_DIRECTORY ${EXTERNAL_PROJECT_DIRECTORY}) - -cmake_path(APPEND CETL_ROOT "include" OUTPUT_VARIABLE CETL_INCLUDE) - include_directories( - ${CETL_INCLUDE} - "${CETLVAST_PROJECT_ROOT}/include" + "${CMAKE_SOURCE_DIR}/include" ) if(DEFINED CETLVAST_CMAKE_MODULE_PATH) @@ -78,17 +57,18 @@ else() message(DEBUG "CMAKE_MODULE_PATH set to ${CMAKE_MODULE_PATH}") endif() +set(CETLVAST_EXTERNAL_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/build_external") message(STATUS "[ globals ]-----------------------------------------------\n\ - CETL_ROOT: ${CETL_ROOT}\n\ - CETL_INCLUDE: ${CETL_INCLUDE}\n\ - CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}\n\ - CETLVAST_PROJECT_ROOT: ${CETLVAST_PROJECT_ROOT}\n\ - EXTERNAL_PROJECT_DIRECTORY: ${EXTERNAL_PROJECT_DIRECTORY}\n\ - CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}\n\ - CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}\n\ - CMAKE_MESSAGE_LOG_LEVEL: ${CMAKE_MESSAGE_LOG_LEVEL}\n\ + CETL_ROOT: ${CETL_ROOT}\n\ + CETL_VERSION: ${CETL_VERSION}\n\ + CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}\n\ + CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}\n\ + CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}\n\ + CMAKE_MESSAGE_LOG_LEVEL: ${CMAKE_MESSAGE_LOG_LEVEL}\n\ + CETLVAST_EXTERNAL_ROOT: ${CETLVAST_EXTERNAL_ROOT}\n\ + FETCHCONTENT_FULLY_DISCONNECTED: ${FETCHCONTENT_FULLY_DISCONNECTED}\n\ -----------------------------------------------------------\n\ ") @@ -97,25 +77,32 @@ message(STATUS # +---------------------------------------------------------------------------+ option(CETL_ENABLE_DEBUG_ASSERT "Enable or disable runtime asserts in CETL code." OFF) -option(CETLVAST_ENABLE_COVERAGE "Enable or disable coverage data instrumentation in test builds." OFF) option(CETLVAST_STYLE_CHECK "Enable or disable a check for violations of the project's style standards." ON) set(CETLVAST_CPP_STANDARD "20" CACHE STRING "C++ standard to use when compiling.") set_property(CACHE CETLVAST_CPP_STANDARD PROPERTY STRINGS 14 17 20) +set(CMAKE_CXX_STANDARD ${CETLVAST_CPP_STANDARD}) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type.") set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release Debug) set(CETLVAST_COVERAGE_REPORT_FORMAT "html" CACHE STRING "Coverage reporting standard to use when building coverage reports.") set_property(CACHE CETLVAST_COVERAGE_REPORT_FORMAT PROPERTY STRINGS "html" "sonarqube" "json") +option(CETLVAST_DISABLE_CPP_EXCEPTIONS "Attempt to disable C++ exceptions for the current toolchain." OFF) + +if (CETL_ENABLE_DEBUG_ASSERT) +add_compile_definitions("CETL_ENABLE_DEBUG_ASSERT=1") +endif() + message(STATUS "[ Build Options ]--------------------------------------\n\ CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}\n\ CETL_ENABLE_DEBUG_ASSERT: ${CETL_ENABLE_DEBUG_ASSERT}\n\ - CETLVAST_ENABLE_COVERAGE: ${CETLVAST_ENABLE_COVERAGE}\n\ CETLVAST_COVERAGE_REPORT_FORMAT: ${CETLVAST_COVERAGE_REPORT_FORMAT}\n\ CETLVAST_STYLE_CHECK: ${CETLVAST_STYLE_CHECK}\n\ CETLVAST_CPP_STANDARD: ${CETLVAST_CPP_STANDARD}\n\ + CETLVAST_DISABLE_CPP_EXCEPTIONS: ${CETLVAST_DISABLE_CPP_EXCEPTIONS}\n\ -----------------------------------------------------------\n\ ") @@ -127,23 +114,21 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # +---------------------------------------------------------------------------+ if(NOT DEFINED CETLVAST_FLAG_SET) - message(VERBOSE "CETLVAST_FLAG_SET was not defined. Setting to \"native\" as a default") - set(CETLVAST_FLAG_SET ${CMAKE_SOURCE_DIR}/cmake/compiler_flag_sets/native.cmake) + message(VERBOSE "CETLVAST_FLAG_SET was not defined. Setting to \"default\" as a default") + set(CETLVAST_FLAG_SET ${CMAKE_SOURCE_DIR}/cmake/compiler_flag_sets/default.cmake) else() set(CETLVAST_FLAG_SET ${CETLVAST_FLAG_SET}) endif() -if(NOT DEFINED CETLVAST_TEST_SUITE) - message(VERBOSE "CETLVAST_TEST_SUITE was not defined. Setting to \"unittest\" as a default") - set(CETLVAST_TEST_SUITE ${CMAKE_SOURCE_DIR}/cmake/suites/unittest.cmake) -else() - set(CETLVAST_TEST_SUITE ${CETLVAST_TEST_SUITE}) -endif() - message(STATUS "[ Build Kit ]-------------------------------------------\n\ - CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}\n\ - CETLVAST_FLAG_SET: ${CETLVAST_FLAG_SET}\n\ - CETLVAST_TEST_SUITE: ${CETLVAST_TEST_SUITE}\n\ + CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}\n\ + CETLVAST_FLAG_SET: ${CETLVAST_FLAG_SET}\n\ + CMAKE_CXX_COMPILER_[ID-VER]: ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}\n\ + CMAKE_C_COMPILER_[ID-VER]: ${CMAKE_CXX_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}\n\ + CMAKE_AR: ${CMAKE_AR}\n\ + CMAKE_LINKER: ${CMAKE_LINKER}\n\ + CMAKE_RANLIB: ${CMAKE_RANLIB}\n\ + CMAKE_[lang]_PLATFORM_ID: ${CMAKE_CXX_PLATFORM_ID}\n\ -----------------------------------------------------------\n\ ") @@ -156,6 +141,71 @@ message(STATUS "[ Build Kit ]-------------------------------------------\n\ include(${CETLVAST_FLAG_SET}) # +---------------------------------------------------------------------------+ -# | BUILD SELECTED TEST SUITE +# | CETL +# +---------------------------------------------------------------------------+ +find_package(clangformat REQUIRED) +cmake_path(APPEND CETL_ROOT "include" OUTPUT_VARIABLE CETL_INCLUDE) + +add_library(cetl INTERFACE) +target_include_directories(cetl INTERFACE ${CETL_INCLUDE}) + +enable_clang_format_check_for_directory(DIRECTORY ${CETL_INCLUDE} GLOB_PATTERN "**/*.hpp") +enable_clang_format_in_place_for_directory(DIRECTORY ${CETL_INCLUDE} GLOB_PATTERN "**/*.hpp") + +# +---------------------------------------------------------------------------+ +# | TEST SUITES +# +---------------------------------------------------------------------------+ + +add_subdirectory(${CMAKE_SOURCE_DIR}/suites/unittest) +add_subdirectory(${CMAKE_SOURCE_DIR}/suites/docs) +add_subdirectory(${CMAKE_SOURCE_DIR}/suites/compile) + + +# +---------------------------------------------------------------------------+ +# | BUILD TARGET ALIASES # +---------------------------------------------------------------------------+ -include(${CETLVAST_TEST_SUITE}) + +add_custom_target( + docs + DEPENDS + generate_CETL_docs +) + +add_custom_target( + lint + DEPENDS + cetlvast_clang_format_check +) + +add_custom_target( + build + DEPENDS + build_unittests + build_examples +) + +if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + add_custom_target( + unittest + DEPENDS + run_unittests + gcovr_html_report_for_unittest + run_examples + gcovr_html_report_for_examples + ) +else() + add_custom_target( + unittest + DEPENDS + run_unittests + run_examples + ) +endif() + +add_custom_target( + release + DEPENDS + build + unittest + docs +) diff --git a/cetlvast/cmake/modules/clang-format-check.py b/cetlvast/cmake/bin/clang-format-check.py similarity index 57% rename from cetlvast/cmake/modules/clang-format-check.py rename to cetlvast/cmake/bin/clang-format-check.py index 08538789..c4c3b591 100755 --- a/cetlvast/cmake/modules/clang-format-check.py +++ b/cetlvast/cmake/bin/clang-format-check.py @@ -41,35 +41,42 @@ def _run(args: argparse.Namespace) -> int: for glob_result in glob_results: - completed = subprocess.run([args.clang_format_path, '-fallback-style=none', '-style=file', '-output-replacements-xml', glob_result], + run_args = [args.clang_format_path, '-fallback-style=none', '-style=file'] + if args.inplace: + run_args += ['-i', glob_result] + else: + run_args += ['-output-replacements-xml', glob_result] + + completed = subprocess.run(run_args, check=True, stdout=subprocess.PIPE) # type: subprocess.CompletedProcess - replacement_doc = completed.stdout - replacements = [] # type: List[str] - try: - replacement_dom = xml.parseString(replacement_doc) # type: Document - for replacments_element in replacement_dom.childNodes: - if replacments_element.nodeName == 'replacements': - for replacement_element in replacments_element.childNodes: - if replacement_element.nodeName == 'replacement': - replacements_text = '' - for text_element in replacement_element.childNodes: - if text_element.nodeType == Node.TEXT_NODE: - replacements_text += text_element.data - replacements.append('length={}, offset={}, replacement="{}"'.format(replacement_element.getAttribute('length'), - replacement_element.getAttribute('offset'), - replacements_text.replace('\n', ' '))) - except Exception as e: - logging.warning(e) - - if len(replacements) > 0: - result = -1 - do_clang_format = [ args.clang_format_path, '-style=file', '-i', glob_result] - logging.warning('File {} has {} formatting errors. Run clang-format:\n{}'.format( - glob_result, - len(replacements), - ' '.join(do_clang_format))) + if not args.inplace: + replacement_doc = completed.stdout + replacements = [] # type: List[str] + try: + replacement_dom = xml.parseString(replacement_doc) # type: Document + for replacements_element in replacement_dom.childNodes: + if replacements_element.nodeName == 'replacements': + for replacement_element in replacements_element.childNodes: + if replacement_element.nodeName == 'replacement': + replacements_text = '' + for text_element in replacement_element.childNodes: + if text_element.nodeType == Node.TEXT_NODE: + replacements_text += text_element.data + replacements.append('length={}, offset={}, replacement="{}"'.format(replacement_element.getAttribute('length'), + replacement_element.getAttribute('offset'), + replacements_text.replace('\n', ' '))) + except Exception as e: + logging.warning(e) + + if len(replacements) > 0: + result = -1 + do_clang_format = [ args.clang_format_path, '-style=file', '-i', glob_result] + logging.warning('File {} has {} formatting errors. Run clang-format:\n{}'.format( + glob_result, + len(replacements), + ' '.join(do_clang_format))) return result @@ -81,7 +88,7 @@ def _make_parser() -> argparse.ArgumentParser: epilog = '''**Example Usage**:: - %(prog)s path/to/cetl/include/**/*.hpp + %(prog)s path/to/include/**/*.hpp ---- ''' @@ -93,6 +100,9 @@ def _make_parser() -> argparse.ArgumentParser: parser.add_argument('--clang-format-path', default='clang-format', help='The path and executable name to use when invoking clang-format.') + parser.add_argument('-i', '--inplace', action='store_true', + help='inplace edit the files if needed.') + parser.add_argument('--verbose', '-v', action='count', help='verbosity level (-v, -vv)') diff --git a/cetlvast/cmake/bin/test_report_util.py b/cetlvast/cmake/bin/test_report_util.py new file mode 100755 index 00000000..97c5f23f --- /dev/null +++ b/cetlvast/cmake/bin/test_report_util.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# +""" + Translate a JUnit XML report into the SonarQube generic test execution format. + + Googletest can generate a JUnit XML report, but SonarQube does not support this format. + Use this script to convert the JUnit XML report into the SonarQube generic test execution + format to allow test results to be recorded in SonarQube. +""" +# cSpell: words testsuite testsuites quirksmode levelname + +import argparse +import logging +import os +import pathlib +import sys +import textwrap +import typing +import xml.etree.ElementTree as ET + +# +---------------------------------------------------------------------------+ + +def _junit_to_sonarqube_generic_execution_format(junit_report: pathlib.Path, test_executions: ET.Element) -> None: + """Append junit testsuite data to sonarqube testExecutions data. + + Input Format: http://google.github.io/googletest/advanced.html#generating-an-xml-report + Output Format: https://docs.sonarqube.org/8.9/analyzing-source-code/generic-test-data/#generic-execution + + """ + sq_files: typing.Dict[str, ET.Element] = dict() + junit_xml = ET.parse(junit_report) + + testsuite_or_testsuites = junit_xml.getroot() + + if testsuite_or_testsuites.tag == "testsuite": + testsuites: typing.Iterable[ET.Element] = [testsuite_or_testsuites] + else: + testsuites = testsuite_or_testsuites.findall("testsuite") + + for testsuite in testsuites: + logging.info("junit2sonarqube: parsing junit testsuite: {} name=\"{}\" tests=\"{}\"" + .format(testsuite.tag, + testsuite.get("name"), + testsuite.get("tests"))) + + for testcase in testsuite: + + testcase_file = testcase.get("file") + if testcase_file is not None: + quirksmode = "gtest" + else: + quirksmode = "ctest" + testcase_file = testcase.get("classname") + if testcase_file is None: + logging.warn("junit2sonarqube: Unknown tag {} (skipping)".format(testcase.tag)) + continue + + testcase_name: str = testcase.get("name", "") + if (type_param := testcase.get("type_param", None)) != None: + # mypy is not able to parse assignment expressions, apparently. + testcase_name = testcase_name + " " + type_param # type: ignore + + logging.debug("junit2sonarqube: found testcase \"{}\" (quirks={})".format(testcase_name, quirksmode)) + + try: + sq_file = sq_files[testcase_file] + except KeyError: + sq_file = ET.Element("file", attrib={"path": testcase_file}) + test_executions.append(sq_file) + sq_files[testcase_file] = sq_file + + sq_testcase_attrib: typing.Dict[str, str] = dict() + sq_testcase_attrib["name"] = testcase_name + test_duration = float(testcase.get("time", 0.0)) + sq_testcase_attrib["duration"] = str(test_duration) + + sq_test_case = ET.Element("testCase", attrib=sq_testcase_attrib) + sq_file.append(sq_test_case) + + skipped: typing.Optional[ET.Element] = testcase.find("skipped") + if skipped is not None: + sq_skipped = ET.Element("skipped", attrib={"message": skipped.get("message", "").rstrip()}) + sq_skipped.text = skipped.text.rstrip() # type: ignore + sq_test_case.append(sq_skipped) + failures = testcase.findall("failure") + if failures is not None and len(failures) > 0: + sq_failure = ET.Element("failure", attrib={"message": "failed"}) + sq_test_case.append(sq_failure) + sq_failure_text = [] + for failure in failures: + for failure_line in failure.get("message", "").split("\n"): + sq_failure_text.append(failure_line) + sq_failure.set("message", sq_failure_text[0]) + if len(sq_failure_text) > 1: + sq_failure.text = "\n".join(sq_failure_text[1:]) + + +# +---------------------------------------------------------------------------+ + + +def _handle_generate_test_report(args: argparse.Namespace, cmake_dir: pathlib.Path, test_result: int) -> int: + if (output_file := args.generate_test_report) is None: + return 0 + + output_path = pathlib.Path.cwd().joinpath(_suite_dir(args, cmake_dir) / output_file) + test_executions = ET.Element("testExecutions", attrib={"version": "1"}) + sq_report = ET.ElementTree(test_executions) + + for gtest_report in _suite_dir(args, cmake_dir).glob("*-gtest.xml"): + logging.debug("Found gtest report {}. Will combine into sonarqube report.".format(gtest_report)) + _junit_to_sonarqube_generic_execution_format(gtest_report, sq_report.getroot()) + + for ctest_report in _suite_dir(args, cmake_dir).glob("*ctest.xml"): + logging.debug("Found ctest report {}. Will combine into sonarqube report.".format(ctest_report)) + _junit_to_sonarqube_generic_execution_format(ctest_report, sq_report.getroot()) + + if args.dry_run: + logging.debug("Would have written a test report for {} files to {}".format(len(test_executions.findall("file")), output_path)) + else: + logging.debug("About to write a test report for {} files to {}".format(len(test_executions.findall("file")), output_path)) + ET.indent(sq_report) + output_path.parent.mkdir(parents=True, exist_ok=True) + sq_report.write(output_path, encoding="UTF-8") + return test_result + +# +---------------------------------------------------------------------------+ + + +def _make_parser() -> argparse.ArgumentParser: + + epilog = textwrap.dedent( + """ + + **Example Usage**:: + + ./verify.py docs + + """ + ) + + parser = argparse.ArgumentParser( + description="CMake command-line helper for running cetlvast suites.", + epilog=epilog, + formatter_class=argparse.RawTextHelpFormatter, + ) + + +# +---------------------------------------------------------------------------+ + + +def main() -> int: + """ + Main method to execute when this package/script is invoked as a command. + """ + args = _make_parser().parse_args() + + verification_dir = args.cetlvast_dir + cmake_dir = verification_dir / pathlib.Path(_create_build_dir_name(args)) + cmake_args = ["cmake"] + + logging_level = logging.WARN + + if args.verbose == 2: + logging_level = logging.INFO + elif args.verbose >= 3: + logging_level = logging.DEBUG + + logging.basicConfig(format="%(levelname)s: %(message)s", level=logging_level) + + logging.info( + textwrap.dedent( + """ + + ***************************************************************** + Command-line Arguments to {}: + + {} + + For verify version {} + ***************************************************************** + + """ + ).format(os.path.basename(__file__), str(args), _get_version_string(verification_dir)) + ) + + +# +---------------------------------------------------------------------------+ + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cetlvast/cmake/compiler_flag_sets/_native_common.cmake b/cetlvast/cmake/compiler_flag_sets/_native_common.cmake deleted file mode 100644 index 3aa20297..00000000 --- a/cetlvast/cmake/compiler_flag_sets/_native_common.cmake +++ /dev/null @@ -1,61 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# C, CXX, LD, and AS flags for native targets. -# - -set(C_FLAG_SET ) -set(EXE_LINKER_FLAG_SET ) -set(DEFINITIONS_SET ) - -# -# Diagnostics for C and C++ -# -list(APPEND C_FLAG_SET - "-pedantic" - "-Wall" - "-Wextra" - "-Werror" - "-Wfloat-equal" - "-Wconversion" - "-Wunused-parameter" - "-Wunused-variable" - "-Wunused-value" - "-Wcast-align" - "-Wmissing-declarations" - "-Wmissing-field-initializers" - "-Wdouble-promotion" - "-Wswitch-enum" - "-Wtype-limits" - "-Wno-error=array-bounds" -) - -if (CETLVAST_ENABLE_COVERAGE) -message(STATUS "Coverage is enabled. Instrumenting the code.") -list(APPEND C_FLAG_SET - "--coverage" - "$<$:-fprofile-instr-generate>" - "$<$:-fcoverage-mapping>" -) - -list(APPEND EXE_LINKER_FLAG_SET "--coverage") - -endif() - -set(CXX_FLAG_SET ${C_FLAG_SET}) -set(ASM_FLAG_SET ${C_FLAG_SET}) - -# -# C++ only diagnostics -# -list(APPEND CXX_FLAG_SET - "-Wsign-conversion" - "-Wsign-promo" - "-Wold-style-cast" - "-Wzero-as-null-pointer-constant" - "-Wnon-virtual-dtor" - "-Woverloaded-virtual" -) diff --git a/cetlvast/cmake/compiler_flag_sets/default.cmake b/cetlvast/cmake/compiler_flag_sets/default.cmake new file mode 100644 index 00000000..796d45ca --- /dev/null +++ b/cetlvast/cmake/compiler_flag_sets/default.cmake @@ -0,0 +1,93 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +# C, CXX, LD, and AS flags for building native unit tests. These flags also include +# instrumentation for code coverage. +# + +set(C_FLAG_SET ) +set(EXE_LINKER_FLAG_SET ) +set(DEFINITIONS_SET ) + +# +# Diagnostics for C and C++ +# +list(APPEND C_FLAG_SET + "-pedantic" + "-Wall" + "-Wextra" + "-Werror" + "-Wfloat-equal" + "-Wconversion" + "-Wunused-parameter" + "-Wunused-variable" + "-Wunused-value" + "-Wcast-align" + "-Wmissing-declarations" + "-Wmissing-field-initializers" + "-Wdouble-promotion" + "-Wswitch-enum" + "-Wtype-limits" + "-Wno-error=array-bounds" +) + +set(CXX_FLAG_SET ${C_FLAG_SET}) +set(ASM_FLAG_SET ${C_FLAG_SET}) + +# +# C++ only diagnostics +# +list(APPEND CXX_FLAG_SET + "-Wsign-conversion" + "-Wsign-promo" + "-Wold-style-cast" + "-Wzero-as-null-pointer-constant" + "-Wnon-virtual-dtor" + "-Woverloaded-virtual" +) + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + message(STATUS "Release build. Setting optimization flags.") + list(APPEND C_FLAG_SET + "-O1" + ) +else() + + if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + message(STATUS "Coverage is enabled. Instrumenting the code.") + list(APPEND C_FLAG_SET + "--coverage" + "$<$:-fprofile-instr-generate>" + "$<$:-fcoverage-mapping>" + ) + + list(APPEND EXE_LINKER_FLAG_SET "--coverage") + + endif() + + message(STATUS "Not a Release build. Setting debug flags.") + list(APPEND C_FLAG_SET + "-O0" + "-DDEBUG" + "-ggdb" + ) + +endif() + +if (CETLVAST_DISABLE_CPP_EXCEPTIONS) + message(STATUS "CETLVAST_DISABLE_CPP_EXCEPTIONS is true. Adding -fno-exceptions to compiler flags.") + list(APPEND C_FLAG_SET + "-fno-exceptions") +endif() + +list(APPEND CXX_FLAG_SET ${C_FLAG_SET}) +list(APPEND ASM_FLAG_SET ${C_FLAG_SET}) + +add_compile_options("$<$:${C_FLAG_SET}>") +add_compile_options("$<$:${CXX_FLAG_SET}>") +add_compile_options("$<$:${ASM_FLAG_SET}>") +add_link_options(${EXE_LINKER_FLAG_SET}) +add_definitions(${DEFINITIONS_SET}) diff --git a/cetlvast/cmake/compiler_flag_sets/native.cmake b/cetlvast/cmake/compiler_flag_sets/native.cmake deleted file mode 100644 index 48576193..00000000 --- a/cetlvast/cmake/compiler_flag_sets/native.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# C, CXX, LD, and AS flags for building native unit tests. These flags also include -# instrumentation for code coverage. -# -include(${CMAKE_SOURCE_DIR}/cmake/compiler_flag_sets/_native_common.cmake) - -if (CMAKE_BUILD_TYPE STREQUAL "Release") - message(STATUS "Release build. Setting optimization flags.") - list(APPEND C_FLAG_SET - "-O1" - ) -else() - message(STATUS "Not a Release build. Setting debug flags.") - list(APPEND C_FLAG_SET - "-O0" - "-DDEBUG" - "-ggdb" - ) - - if (CETL_ENABLE_DEBUG_ASSERT) - message(STATUS "CETL_ENABLE_DEBUG_ASSERT will be 1 enabling debug assert() in cetl code.") - list(APPEND C_FLAG_SET - "-DCETL_ENABLE_DEBUG_ASSERT=1") - endif() -endif() - -list(APPEND CXX_FLAG_SET ${C_FLAG_SET}) -list(APPEND ASM_FLAG_SET ${C_FLAG_SET}) - -add_compile_options("$<$:${C_FLAG_SET}>") -add_compile_options("$<$:${CXX_FLAG_SET}>") -add_compile_options("$<$:${ASM_FLAG_SET}>") -add_link_options(${EXE_LINKER_FLAG_SET}) -add_definitions(${DEFINITIONS_SET}) diff --git a/cetlvast/cmake/modules/CompileTest.cmake b/cetlvast/cmake/modules/CompileTest.cmake new file mode 100644 index 00000000..1ccf726c --- /dev/null +++ b/cetlvast/cmake/modules/CompileTest.cmake @@ -0,0 +1,87 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +enable_testing() + +# +# Creates a ctest that will succeed if the test fails to compile. Yep, you read that +# correctly: SUCCESS == FAILURE for this set of tests. We use these to validate +# compile-time asserts which guard against illegal template formation. +# +# This cmake-based solution was Inspired by a similar script written by +# Louis Dionne for libawful: https://github.com/ldionne/libawful +# +# :param TEST_SOURCE path: A single source file that defines the test main(). +# :param OUT_TEST_BUILD_TARGET path: The name of the target that builds the test. +# :param OUT_TEST_PRECHECK_TARGET path: The name of the target that runs a precheck +# version of the test. +# :param OUT_CTEST_NAME path: The name of the ctest that runs the test. +# +function(define_compile_failure_test) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs TEST_SOURCE OUT_TEST_BUILD_TARGET OUT_TEST_PRECHECK_TARGET OUT_CTEST_NAME) + set(multiValueArgs EXTRA_TEST_LIBS) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + cmake_path(GET ARG_TEST_SOURCE STEM LOCAL_TEST_NAME) + + #+-[body]-----------------------------------------------------------------+ + # First build with "precheck" enabled to ensure the test compiles without the negative case included... + add_executable(${LOCAL_TEST_NAME}_precheck ${ARG_TEST_SOURCE}) + + target_compile_definitions(${LOCAL_TEST_NAME}_precheck PRIVATE CETLVAST_COMPILETEST_PRECHECK=1) + target_link_libraries(${LOCAL_TEST_NAME}_precheck PUBLIC ${ARG_EXTRA_TEST_LIBS}) + + add_custom_target( + "run_${LOCAL_TEST_NAME}_precheck" + COMMAND + ${CMAKE_CURRENT_BINARY_DIR}/${LOCAL_TEST_NAME}_precheck + DEPENDS + "${LOCAL_TEST_NAME}_precheck" + ) + + # Now define the doomed version for ctest to run... + add_executable(${LOCAL_TEST_NAME} ${ARG_TEST_SOURCE}) + target_link_libraries(${LOCAL_TEST_NAME} PUBLIC ${ARG_EXTRA_TEST_LIBS}) + + set_target_properties( + ${LOCAL_TEST_NAME} + PROPERTIES + EXCLUDE_FROM_ALL ON + ) + + add_test( + NAME run_${LOCAL_TEST_NAME} + COMMAND + ${CMAKE_COMMAND} + --build ${CMAKE_CURRENT_BINARY_DIR} + --target ${LOCAL_TEST_NAME} + --config $ + ) + + set_tests_properties( + run_${LOCAL_TEST_NAME} + PROPERTIES + WILL_FAIL true + ) + + #+-[output]---------------------------------------------------------------+ + + if (NOT ARG_OUT_TEST_BUILD_TARGET STREQUAL "") + set(${ARG_OUT_TEST_BUILD_TARGET} "${LOCAL_TEST_NAME}_precheck" PARENT_SCOPE) + endif() + + if (NOT ARG_OUT_TEST_PRECHECK_TARGET STREQUAL "") + set(${ARG_OUT_TEST_PRECHECK_TARGET} "run_${LOCAL_TEST_NAME}_precheck" PARENT_SCOPE) + endif() + + if (NOT ARG_OUT_CTEST_NAME STREQUAL "") + set(${ARG_OUT_CTEST_NAME} "run_${LOCAL_TEST_NAME}" PARENT_SCOPE) + endif() + +endfunction() diff --git a/cetlvast/cmake/modules/Findclangformat.cmake b/cetlvast/cmake/modules/Findclangformat.cmake index e77a5d71..e55fceb6 100644 --- a/cetlvast/cmake/modules/Findclangformat.cmake +++ b/cetlvast/cmake/modules/Findclangformat.cmake @@ -10,69 +10,94 @@ find_program(CLANG_FORMAT clang-format) +set(LOCAL_BIN_MODULE_PATHS ${CMAKE_MODULE_PATH}) +list(TRANSFORM LOCAL_BIN_MODULE_PATHS APPEND "/../bin") +find_file(CLANG_FORMAT_PYTHON_SHIM clang-format-check.py + PATHS ${LOCAL_BIN_MODULE_PATHS} +) + +if (NOT CLANG_FORMAT_PYTHON_SHIM STREQUAL "CLANG_FORMAT_PYTHON_SHIM-NOTFOUND") + set(CLANG_FORMAT_PYTHON_SHIM_FOUND TRUE) +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(clangformat + REQUIRED_VARS CLANG_FORMAT CLANG_FORMAT_PYTHON_SHIM_FOUND +) + +# +---------------------------------------------------------------------------+ +# | clang-format helpers +# +---------------------------------------------------------------------------+ # -# :function: create_check_style_target +# :function: enable_clang_format_check_for_directory # Create a target that checks for compliance with code style rules. # -# :param str ARG_STYLE_TARGET_NAME: The name to give the target created by this function. -# :param bool ARG_ADD_TO_ALL: If true the target is added to the default build target. +# :param DIRECTORY path - If provided the directory otherwise this is +# ${CMAKE_CURRENT_SOURCE_DIR} +# :param GLOB_PATTERN glob - A pattern to match files against. +# :option ADD_TO_ALL - If set the target is added to the default build target. # -function(create_check_style_target ARG_STYLE_TARGET_NAME ARG_ADD_TO_ALL ARG_GLOB_PATTERN) - - add_custom_target(${ARG_STYLE_TARGET_NAME}-clang-format-check - COMMAND ${CMAKE_MODULE_PATH}/clang-format-check.py - --clang-format-path ${CLANG_FORMAT} - ${ARG_GLOB_PATTERN} - VERBATIM - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) +function(enable_clang_format_check_for_directory) + #+-[input]----------------------------------------------------------------+ + set(options ADD_TO_ALL) + set(singleValueArgs GLOB_PATTERN DIRECTORY) + set(multiValueArgs "") + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if(NOT ARG_DIRECTORY) + set(ARG_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + #+-[body]-----------------------------------------------------------------+ + cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LOCAL_DIRECTORY_NAME) + set(LOCAL_TARGET_NAME "${LOCAL_DIRECTORY_NAME}_clang_format_check") if (ARG_ADD_TO_ALL) - add_custom_target(${ARG_STYLE_TARGET_NAME} ALL DEPENDS ${ARG_STYLE_TARGET_NAME}-clang-format-check) + set(LOCAL_ALL "ALL") else() - add_custom_target(${ARG_STYLE_TARGET_NAME} DEPENDS ${ARG_STYLE_TARGET_NAME}-clang-format-check) + set(LOCAL_ALL "") endif() -endfunction(create_check_style_target) + add_custom_target(${LOCAL_TARGET_NAME} ${LOCAL_ALL} + COMMAND ${CLANG_FORMAT_PYTHON_SHIM} + --clang-format-path ${CLANG_FORMAT} + ${ARG_GLOB_PATTERN} + VERBATIM + WORKING_DIRECTORY ${ARG_DIRECTORY} + ) + +endfunction(enable_clang_format_check_for_directory) # -# :function: create_apply_style_target +# :function: enable_clang_format_in_place_for_directory # Create a target that reformats source, in-place, based on formatting rules. # -# :param str ARG_STYLE_TARGET_NAME: The name to give the target created by this function. -# :param bool ARG_ADD_TO_ALL: If true the target is added to the default build target. -# :param List[str] ...: A list of files to format. +# :param DIRECTORY path - If provided the directory otherwise this is +# ${CMAKE_CURRENT_SOURCE_DIR} +# :param GLOB_PATTERN glob - A pattern to match files against. # -function(create_apply_style_target ARG_STYLE_TARGET_NAME ARG_ADD_TO_ALL) - - set(LOCAL_REFORMAT_FILES "") - - if (${ARGC} GREATER 2) - foreach(ARG_N RANGE 2 ${ARGC}-1) - list(APPEND LOCAL_REFORMAT_FILES ${ARGV${ARG_N}}) - endforeach(ARG_N) - endif() +function(enable_clang_format_in_place_for_directory) - add_custom_target( ${ARG_STYLE_TARGET_NAME}-inplace - COMMAND ${CLANG_FORMAT} -style=file - -i - ${LOCAL_REFORMAT_FILES} - DEPENDS ${LOCAL_REFORMAT_FILES} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs GLOB_PATTERN DIRECTORY) + set(multiValueArgs "") + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") - if (ARG_ADD_TO_ALL) - add_custom_target(${ARG_STYLE_TARGET_NAME} ALL DEPENDS ${ARG_STYLE_TARGET_NAME}-inplace) - else() - add_custom_target(${ARG_STYLE_TARGET_NAME} DEPENDS ${ARG_STYLE_TARGET_NAME}-inplace) + if(NOT ARG_DIRECTORY) + set(ARG_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() + #+-[body]-----------------------------------------------------------------+ + cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LOCAL_DIRECTORY_NAME) + set(LOCAL_TARGET_NAME "danger-danger-${LOCAL_DIRECTORY_NAME}-clang-format-in-place") -endfunction(create_apply_style_target) - - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(clangformat - REQUIRED_VARS CLANG_FORMAT -) + add_custom_target(${LOCAL_TARGET_NAME} + COMMAND ${CLANG_FORMAT_PYTHON_SHIM} + --clang-format-path ${CLANG_FORMAT} + -i + ${ARG_GLOB_PATTERN} + VERBATIM + WORKING_DIRECTORY ${ARG_DIRECTORY} + ) +endfunction(enable_clang_format_in_place_for_directory) diff --git a/cetlvast/cmake/modules/Finddocs.cmake b/cetlvast/cmake/modules/Finddocs.cmake deleted file mode 100644 index 81fbe5e2..00000000 --- a/cetlvast/cmake/modules/Finddocs.cmake +++ /dev/null @@ -1,134 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# -# This treats the doxygen build for CETL as a standalone program. In -# reality the docs target is a doxygen build configured just for this project. -# - - -find_package(Doxygen REQUIRED) -find_program(TAR tar) - -# +---------------------------------------------------------------------------+ -# | DOXYGEN -# +---------------------------------------------------------------------------+ -# TODO: all of this is really test-suite specific and should be refactored to -# make it more reusable. -# -# :function: create_docs_target -# Create a target that generates documentation. -# -# :param str ARG_DOCS_TARGET_NAME: The name to give the target created by this function. -# This is also used as a prefix for sub-targets also -# generated by this function. -# :param bool ARG_ADD_TO_ALL: If true the target is added to the default build target. -# :param str ARG_EXAMPLES_PATH: A path to example code to be included in the docs. -# :param list[str] ARG_DEPENDS: A list of dependencies the docs build should take. -# -function (create_docs_target ARG_DOCS_TARGET_NAME ARG_ADD_TO_ALL ARG_EXAMPLES_PATH ARG_DEPENDS) - - set(DOXYGEN_SOURCE ${CETLVAST_PROJECT_ROOT}/suites/docs) - set(DOXYGEN_RDOMAIN org.opencyphal) - set(DOXYGEN_RDOMAIN_W_PROJECT org.opencyphal.cetl) - set(DOXYGEN_PROJECT_NAME "CETL") - set(DOXYGEN_PROJECT_BRIEF "Cyphal Embedded Template Library is a C++ shim library used by C++ Cyphal projects.") - set(DOXYGEN_OUTPUT_DIRECTORY_PARENT ${CMAKE_BINARY_DIR}) - set(DOXYGEN_OUTPUT_DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY_PARENT}/cetlvast/suites/docs) - set(DOXYGEN_CONFIG_FILE ${DOXYGEN_OUTPUT_DIRECTORY}/doxygen.config) - set(DOXYGEN_EXAMPLE_PATH ${ARG_EXAMPLES_PATH}) - - file(GLOB_RECURSE DOXYGEN_INPUT_LIST - LIST_DIRECTORIES false - CONFIGURE_DEPENDS - ${CETL_ROOT}/include/**/*.hpp - ) - - list(APPEND DOXYGEN_INPUT_LIST "${CETL_ROOT}/README.md") - list(APPEND DOXYGEN_INPUT_LIST "${CETL_ROOT}/SECURITY.md") - list(JOIN DOXYGEN_INPUT_LIST "\\\n " DOXYGEN_INPUT ) - set(DOXYGEN_MAINPAGE "\"${CETL_ROOT}/README.md\"") - set(DOXYGEN_CETLVAST_VERSION $ENV{GITHUB_SHA}) - set(DOXYGEN_CETLVAST_INCLUDE_PREFIX_STRIP "\"${CETL_ROOT}/include\"") - set(DOXYGEN_OUTPUT_TAGFILE "${DOXYGEN_OUTPUT_DIRECTORY}/cetl.tag.xml") - set(DOXYGEN_COMPILE_COMMANDS_DIR ${CMAKE_BINARY_DIR}) - set(DOXYGEN_CLANG_OPTIONS "-I${CETL_ROOT}/include") - - # +-----------------------------------------------------------------------+ - # | HTML - # +-----------------------------------------------------------------------+ - set(DOXYGEN_HTML_EXTRA_FILES "${DOXYGEN_SOURCE}/.nojekyll") - set(DOXYGEN_HTML_STYLESHEET ${DOXYGEN_OUTPUT_DIRECTORY}/stylesheet.css) - set(DOXYGEN_HTML_HEADER ${DOXYGEN_OUTPUT_DIRECTORY}/header.html) - set(DOXYGEN_HTML_FOOTER ${DOXYGEN_OUTPUT_DIRECTORY}/footer.html) - set(DOXYGEN_IMAGE_PATH ${DOXYGEN_SOURCE}/images) - set(DOXYGEN_LOGO ${DOXYGEN_SOURCE}/images/html/opencyphal_logo.svg) - set(DOXYGEN_TAGFILES "${DOXYGEN_SOURCE}/cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/") - - # a bit of a hack, but we copy all the images to the output to allow us to serve them - # for anything other that the docs that wants them. - file(COPY ${DOXYGEN_SOURCE}/images DESTINATION ${DOXYGEN_OUTPUT_DIRECTORY}/html) - - configure_file(${DOXYGEN_SOURCE}/header.html - ${DOXYGEN_OUTPUT_DIRECTORY}/header.html - ) - configure_file(${DOXYGEN_SOURCE}/footer.html - ${DOXYGEN_OUTPUT_DIRECTORY}/footer.html - ) - configure_file(${DOXYGEN_SOURCE}/stylesheet.css - ${DOXYGEN_OUTPUT_DIRECTORY}/stylesheet.css - ) - configure_file(${DOXYGEN_SOURCE}/doxygen.ini - ${DOXYGEN_CONFIG_FILE} - ) - add_custom_command(OUTPUT ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_FILE} - DEPENDS - ${ARG_DEPENDS} - ${DOXYGEN_CONFIG_FILE} - ${DOXYGEN_INPUT_LIST} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Generating intermediate documentation." - ) - - add_custom_target(${ARG_DOCS_TARGET_NAME} DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html) - - - -endfunction(create_docs_target) - -# -# :function: create_docs_tarball_target -# Create a target that uses the tar utility to package up all docs output. -# -# :param str ARG_DOCS_TARBALL_TARGET_NAME: The name to give the target created by this function. -# :param bool ARG_ADD_TO_ALL: If true the target is added to the default build target. -# -function (create_docs_tarball_target ARG_DOCS_TARBALL_TARGET_NAME ARG_ADD_TO_ALL) - - set(DOXYGEN_OUTPUT_DIRECTORY_PARENT ${CMAKE_BINARY_DIR}) - set(DOXYGEN_OUTPUT_DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY_PARENT}/cetlvast/suites/docs) - - add_custom_command(OUTPUT ${DOXYGEN_OUTPUT_DIRECTORY}/html.gz - COMMAND ${TAR} -vzcf html.gz ./html - DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html - WORKING_DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY} - COMMENT "Creating html tarball." - ) - - if (ARG_ADD_TO_ALL) - add_custom_target(${ARG_DOCS_TARBALL_TARGET_NAME} ALL DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html.gz) - else() - add_custom_target(${ARG_DOCS_TARBALL_TARGET_NAME} DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html.gz) - endif() - -endfunction(create_docs_tarball_target) - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(docs - REQUIRED_VARS DOXYGEN_FOUND -) diff --git a/cetlvast/cmake/modules/Findgcovr.cmake b/cetlvast/cmake/modules/Findgcovr.cmake index ada0274b..a26f39c6 100644 --- a/cetlvast/cmake/modules/Findgcovr.cmake +++ b/cetlvast/cmake/modules/Findgcovr.cmake @@ -11,3 +11,183 @@ include(FindPackageHandleStandardArgs) find_package_handle_standard_args(gcovr REQUIRED_VARS GCOVR ) + +# +---------------------------------------------------------------------------+ +# | Contributed helpers for enabling and processing coverage data +# +---------------------------------------------------------------------------+ +define_property(DIRECTORY + PROPERTY GCOV_TRACE_FILES + BRIEF_DOCS "private collection used by gcovr module." + FULL_DOCS "private collection used by gcovr module." +) + +# +# function: define_gcovr_tracefile_target +# +# param: TARGET target - The target that will run to produce TARGET_EXECUTION_DEPENDS. +# While TARGET_EXECUTION_DEPENDS is used as the dependency of +# the tracefile target created by this function the target itself +# is queried for properties that help filter the coverage data. +# +# param: TARGET_EXECUTION_DEPENDS target - A target that will be a dependency of the tracefile +# target defined by this function. +# param: EXCLUDES list[target] - A list of targets to exclude from the coverage data. +# option: EXCLUDE_TARGET - If set the target itself will be excluded from the +# coverage data. +# option: EXCLUDE_TEST_FRAMEWORKS - If set the test frameworks will be excluded from the +# coverage data. This uses the custome TEST_FRAMEWORK_LINK_LIBRARIES +# property to determine the test frameworks. +# param: OUT_TRACEFILE_VARIABLE string - The name of a variable to set to the tracefile +# that the custom command defined by this function +# will generate. +# +function(define_gcovr_tracefile_target) + #+-[input]----------------------------------------------------------------+ + set(options EXCLUDE_TARGET EXCLUDE_TEST_FRAMEWORKS) + set(singleValueArgs TARGET TARGET_EXECUTION_DEPENDS OUT_TRACEFILE_VARIABLE) + set(multiValueArgs EXCLUDES) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + #+-[body]-----------------------------------------------------------------+ + get_target_property(LOCAL_TARGET_RUNTIME_DIR ${ARG_TARGET} RUNTIME_OUTPUT_DIRECTORY) + + if (LOCAL_TARGET_RUNTIME_DIR STREQUAL "LOCAL_TARGET_RUNTIME_DIR-NOTFOUND") + set(LOCAL_TARGET_RUNTIME_DIR ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + set(LOCAL_EXCLUDE_ARGUMENTS "") + + if (ARG_EXCLUDE_TARGET) + list(APPEND ARG_EXCLUDES ${ARG_TARGET}) + endif() + + if (ARG_EXCLUDE_TEST_FRAMEWORKS) + get_target_property(LOCAL_TEST_FRAMEWORK_LINK_LIBRARIES ${ARG_TARGET} TEST_FRAMEWORK_LINK_LIBRARIES) + if(NOT LOCAL_TEST_FRAMEWORK_LINK_LIBRARIES STREQUAL "LOCAL_TEST_FRAMEWORK_LINK_LIBRARIES-NOTFOUND") + list(APPEND ARG_EXCLUDES ${LOCAL_TEST_FRAMEWORK_LINK_LIBRARIES}) + endif() + endif() + + list(REMOVE_DUPLICATES ARG_EXCLUDES) + set(LOCAL_EXCLUDE_PATHS "") + + foreach(LOCAL_EXCLUDE ${ARG_EXCLUDES}) + + if(${LOCAL_EXCLUDE} MATCHES ".*-NOTFOUND$") + message(WARNING "gcovr ${ARG_TARGET}: Invalid exclude target ${LOCAL_EXCLUDE} passed to define_gcovr_tracefile_target.") + continue() + endif() + + message(TRACE "gcovr ${ARG_TARGET}: using ${LOCAL_EXCLUDE} to calculate exclude path.") + + get_target_property(LOCAL_TARGET_SOURCE_DIR ${LOCAL_EXCLUDE} SOURCE_DIR) + + list(APPEND LOCAL_EXCLUDE_PATHS ${LOCAL_TARGET_SOURCE_DIR}) + + endforeach() + + list(REMOVE_DUPLICATES LOCAL_EXCLUDE_PATHS) + + foreach(LOCAL_EXCLUDE_PATH ${LOCAL_EXCLUDE_PATHS}) + + message(DEBUG "gcovr ${ARG_TARGET}: will exclude ${LOCAL_EXCLUDE_PATH} from coverage data.") + + list(APPEND LOCAL_EXCLUDE_ARGUMENTS "--exclude") + list(APPEND LOCAL_EXCLUDE_ARGUMENTS "\"${LOCAL_EXCLUDE_PATH}\"") + list(APPEND LOCAL_EXCLUDE_ARGUMENTS "--gcov-exclude") + list(APPEND LOCAL_EXCLUDE_ARGUMENTS "\"${LOCAL_EXCLUDE_PATH}\"") + endforeach() + + cmake_path(APPEND LOCAL_TARGET_RUNTIME_DIR "${ARG_TARGET}.json" OUTPUT_VARIABLE LOCAL_TRACEFILE_PATH) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(LOCAL_C_COVERAGE_PROCESSOR "llvm-cov gcov") + else() + set(LOCAL_C_COVERAGE_PROCESSOR "gcov") + endif() + + add_custom_command( + COMMAND # Generate tracefile from tests. + ${GCOVR} + --gcov-executable ${LOCAL_C_COVERAGE_PROCESSOR} + --r ${CMAKE_SOURCE_DIR} + --json + --output ${LOCAL_TRACEFILE_PATH} + ${LOCAL_EXCLUDE_ARGUMENTS} + ${LOCAL_TARGET_RUNTIME_DIR} + WORKING_DIRECTORY ${LOCAL_TARGET_RUNTIME_DIR} + OUTPUT ${LOCAL_TRACEFILE_PATH} + DEPENDS ${ARG_TARGET_EXECUTION_DEPENDS} + ) + + message(DEBUG "${GCOVR} will run under ${LOCAL_TARGET_RUNTIME_DIR} if ${ARG_TARGET_EXECUTION_DEPENDS}.") + + add_custom_target( + create_${ARG_TARGET}_tracefile + DEPENDS ${LOCAL_TRACEFILE_PATH} + ) + + #+-[output]---------------------------------------------------------------+ + set_property(DIRECTORY APPEND PROPERTY GCOV_TRACE_FILES ${LOCAL_TRACEFILE_PATH}) + + if (NOT ARG_OUT_TRACEFILE_VARIABLE STREQUAL "") + set(${ARG_OUT_TRACEFILE_VARIABLE} "${LOCAL_TRACEFILE_PATH}" PARENT_SCOPE) + endif() + +endfunction(define_gcovr_tracefile_target) + +# +# function: enable_html_report - Creates a custom target that will generate a html report using gcovr +# for the current directory. Be sure to call this only after all calls to define_gcovr_tracefile_target +# have been made. +# +# param: OUT_REPORT_INDEX string - The name of a variable to set to the index file of the html report. +# +function (enable_html_report) + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs OUT_REPORT_INDEX) + set(multiValueArgs "") + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + get_property(LOCAL_TRACEFILES DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} PROPERTY GCOV_TRACE_FILES) + #+-[body]-----------------------------------------------------------------+ + + cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LOCAL_DIRECTORY_NAME) + + set(LOCAL_ALL_TRACEFILES "") + set(LOCAL_ALL_GCOV_ARGS "") + + if (NOT LOCAL_TRACEFILES MATCHES ".*-NOTFOUND$") + foreach(LOCAL_TRACEFILE ${LOCAL_TRACEFILES}) + list(APPEND LOCAL_ALL_TRACEFILES ${LOCAL_TRACEFILE}) + list(APPEND LOCAL_ALL_GCOV_ARGS "--add-tracefile") + list(APPEND LOCAL_ALL_GCOV_ARGS "${LOCAL_TRACEFILE}") + endforeach() + endif() + + set(LOCAL_REPORT_INDEX "gcovr_html/coverage.html") + + add_custom_command( + OUTPUT ${LOCAL_REPORT_INDEX} + COMMAND + ${GCOVR} + ${LOCAL_ALL_GCOV_ARGS} + --r ${CMAKE_SOURCE_DIR} + --html-details ${LOCAL_REPORT_INDEX} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${LOCAL_ALL_TRACEFILES} + ) + + add_custom_target( + gcovr_html_report_for_${LOCAL_DIRECTORY_NAME} + DEPENDS ${LOCAL_REPORT_INDEX} + ) + + #+-[output]---------------------------------------------------------------+ + + if (NOT ARG_OUT_REPORT_INDEX STREQUAL "") + set(${ARG_OUT_REPORT_INDEX} "${LOCAL_REPORT_INDEX}" PARENT_SCOPE) + endif() + +endfunction(enable_html_report) diff --git a/cetlvast/cmake/modules/Findgoogletest.cmake b/cetlvast/cmake/modules/Findgoogletest.cmake new file mode 100644 index 00000000..53ea8b31 --- /dev/null +++ b/cetlvast/cmake/modules/Findgoogletest.cmake @@ -0,0 +1,408 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +if(NOT TARGET gtest_main) + +enable_testing() + +include(FetchContent) +include(FindPackageHandleStandardArgs) + +set(googletest_GIT_REPOSITORY "https://github.com/google/googletest.git") +set(googletest_GIT_TAG "b796f7d44681514f58a683a3a71ff17c94edb0c1") + +FetchContent_Declare( + googletest + GIT_REPOSITORY ${googletest_GIT_REPOSITORY} + GIT_TAG ${googletest_GIT_TAG} +) + +# The automatic management of the _POPULATED name appears to be broken in +# cmake 3.21 and earlier. This workaround may not be needed after 3.24. +get_property(googletest_POPULATED GLOBAL PROPERTY googletest_POPULATED) + +if(NOT googletest_POPULATED) + + if (NOT FETCHCONTENT_SOURCE_DIR_googletest) + set(FETCHCONTENT_SOURCE_DIR_googletest ${CETLVAST_EXTERNAL_ROOT}/googletest) + endif() + + if (NOT ${FETCHCONTENT_FULLY_DISCONNECTED}) + FetchContent_Populate( + googletest + SOURCE_DIR ${FETCHCONTENT_SOURCE_DIR_googletest} + GIT_REPOSITORY ${googletest_GIT_REPOSITORY} + GIT_TAG ${googletest_GIT_TAG} + ) + else() + set(googletest_SOURCE_DIR ${FETCHCONTENT_SOURCE_DIR_googletest}) + endif() + + # The automatic management of the _POPULATED name appears to be broken in + # cmake 3.21 and earlier. This workaround may not be needed after 3.24. + set_property(GLOBAL PROPERTY googletest_POPULATED true) + + find_package_handle_standard_args(googletest + REQUIRED_VARS googletest_SOURCE_DIR + ) + + set(INSTALL_GTEST OFF) + + add_subdirectory(${googletest_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/googletest) + + # Aparently Google doesn't care much about compiler warnings? + set(LOCAL_GTEST_COMPILE_OPTIONS + "-Wno-sign-conversion" + "-Wno-zero-as-null-pointer-constant" + "-Wno-switch-enum" + "-Wno-float-equal" + "-Wno-double-promotion" + "-Wno-conversion" + "-Wno-missing-declarations" + ) + + target_compile_options(gmock + PRIVATE + ${LOCAL_GTEST_COMPILE_OPTIONS} + ) + + target_compile_options(gmock_main + PRIVATE + ${LOCAL_GTEST_COMPILE_OPTIONS} + ) + + target_compile_options(gtest + PRIVATE + ${LOCAL_GTEST_COMPILE_OPTIONS} + ) + + target_compile_options(gtest_main + PRIVATE + ${LOCAL_GTEST_COMPILE_OPTIONS} + ) + +endif() +endif() + +# +---------------------------------------------------------------------------+ +# | Contributed helpers for building and running gtest-based unit tests. +# +---------------------------------------------------------------------------+ + +set(_PRIVATE_GOOGLETEST_OBJLIB_SUFFIX "__googletest_objlib") + + +# +# function: _get_internal_output_path_for_source - Given a source file, returns the +# output path for the object file that will be generated for it. +# +# This is a hack I don't know how to get rid of. We're not supposed to "know" about the +# CMakeFiles directory nor its internal structure but we have to list binary +# byproducts when enabling coverage to make sure the clean target works properly. +# the use of an OBJECT library at least enforces that these intermediates are available. +# +# param: SOURCEFILE path - The source file to get the output path for. +# param: SOURCEFILE_STEM_SUFFIX string - An optional suffix to append to the SOURCEFILE +# after the stem but before the extension. +# param: OUT_INTERNAL_DIRECTORY_VARIABLE path - Set to the output path for the object file. +# +function(_get_internal_output_path_for_source) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs SOURCEFILE OUT_INTERNAL_DIRECTORY_VARIABLE SOURCEFILE_STEM_SUFFIX) + set(multiValueArgs "") + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + #+-[body]-----------------------------------------------------------------+ + + cmake_path(GET ARG_SOURCEFILE PARENT_PATH LOCAL_SOURCEFILE_REL_PATH) + + cmake_path(GET ARG_SOURCEFILE STEM LOCAL_SOURCEFILE_NAME) + cmake_path(SET LOCAL_RESULT "CMakeFiles") + cmake_path(APPEND LOCAL_RESULT "${LOCAL_SOURCEFILE_NAME}${ARG_SOURCEFILE_STEM_SUFFIX}.dir") + + if (NOT "${LOCAL_SOURCEFILE_REL_PATH}" STREQUAL "") + cmake_path(APPEND LOCAL_RESULT "${LOCAL_SOURCEFILE_REL_PATH}") + endif() + + #+-[output]---------------------------------------------------------------+ + set(${ARG_OUT_INTERNAL_DIRECTORY_VARIABLE} ${LOCAL_RESULT} PARENT_SCOPE) +endfunction() + +# +# function: define_native_gtest_unittest_library - Creates a target to build a static library +# +# +# param: TEST_SOURCE path - A single source file that is the test main. +# param: RUNTIME_OUTPUT_DIRECTORY path - A path to output test binaries and coverage data under. +# param: EXTRA_TEST_LIBS targets - A list of additional test library targets to get include +# paths for. +# param: OUT_TEST_LIB_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to the test case library build target name. +# +function(define_native_gtest_unittest_library) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs TEST_SOURCE RUNTIME_OUTPUT_DIRECTORY OUT_TEST_LIB_VARIABLE) + set(multiValueArgs EXTRA_TEST_LIBS) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if (NOT ARG_RUNTIME_OUTPUT_DIRECTORY) + set(ARG_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + cmake_path(GET ARG_TEST_SOURCE STEM LOCAL_TEST_NAME) + + #+-[body]-----------------------------------------------------------------+ + + set(LOCAL_TEST_LIB_NAME ${LOCAL_TEST_NAME}${_PRIVATE_GOOGLETEST_OBJLIB_SUFFIX}) + message(DEBUG "Defining googletest library ${LOCAL_TEST_LIB_NAME} for source file ${ARG_TEST_SOURCE}") + + # Create explicit object file target so we can find it. + add_library(${LOCAL_TEST_LIB_NAME} OBJECT ${ARG_TEST_SOURCE}) + + target_link_libraries(${LOCAL_TEST_LIB_NAME} PUBLIC gtest) + target_link_libraries(${LOCAL_TEST_LIB_NAME} PUBLIC gmock) + target_link_libraries(${LOCAL_TEST_LIB_NAME} PUBLIC ${ARG_EXTRA_TEST_LIBS}) + + set_target_properties(${LOCAL_TEST_LIB_NAME} + PROPERTIES + TEST_FRAMEWORK_LINK_LIBRARIES "gmock;gtest" + ) + + if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + # Annotate the library target with the byproducts of the coverage instrumentation. + _get_internal_output_path_for_source( + SOURCEFILE ${ARG_TEST_SOURCE} + SOURCEFILE_STEM_SUFFIX "${_PRIVATE_GOOGLETEST_OBJLIB_SUFFIX}" + OUT_INTERNAL_DIRECTORY_VARIABLE LOCAL_OBJLIB_FOLDER_REL + ) + cmake_path(ABSOLUTE_PATH LOCAL_OBJLIB_FOLDER_REL + BASE_DIRECTORY ${ARG_RUNTIME_OUTPUT_DIRECTORY} + OUTPUT_VARIABLE LOCAL_OBJLIB_FOLDER) + + cmake_path(GET ARG_TEST_SOURCE EXTENSION LOCAL_TEST_EXT) + + # the generation of gcda files assumes "-fprofile-argcs" (or "-coverage" which includes this flag). + set(LOCAL_BYPRODUCTS "${LOCAL_OBJLIB_FOLDER}/${LOCAL_TEST_NAME}${LOCAL_TEST_EXT}.gcda") + # the generation of gcno files assumes "-ftest-coverage" (or "-coverage" which includes this flag) + list(APPEND LOCAL_BYPRODUCTS "${LOCAL_OBJLIB_FOLDER}/${LOCAL_TEST_NAME}${LOCAL_TEST_EXT}.gcno") + + set_target_properties(${LOCAL_TEST_LIB_NAME} + PROPERTIES + POST_BUILD_INSTRUMENTATION_BYPRODUCTS "${LOCAL_BYPRODUCTS}" + ) + endif() + + #+-[output]---------------------------------------------------------------+ + + if (NOT ARG_OUT_TEST_LIB_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_LIB_VARIABLE} "${LOCAL_TEST_LIB_NAME}" PARENT_SCOPE) + endif() + +endfunction(define_native_gtest_unittest_library) + +# +# function: define_native_gtest_unittest_executable - Creates a target to build a test executable +# +# +# param: TEST_LIB target - A test target defined by define_native_gtest_unittest_library +# param: RUNTIME_OUTPUT_DIRECTORY path - A path to output test binaries and coverage data under. +# param: EXTRA_TEST_LIBS targets - A list of additional test library targets to link with. +# param: OUT_TEST_EXE_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to the executable target name. +# +function(define_native_gtest_unittest_executable) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs TEST_LIB RUNTIME_OUTPUT_DIRECTORY OUT_TEST_EXE_VARIABLE) + set(multiValueArgs EXTRA_TEST_LIBS) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if (NOT ARG_RUNTIME_OUTPUT_DIRECTORY) + set(ARG_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + #+-[body]-----------------------------------------------------------------+ + + string(REGEX REPLACE "__googletest_[a-z0-9]+$" "" LOCAL_TEST_NAME ${ARG_TEST_LIB}) + + message(DEBUG "Defining googletest executable ${LOCAL_TEST_NAME} for source library ${ARG_TEST_LIB}") + + add_executable(${LOCAL_TEST_NAME} $) + target_link_libraries(${LOCAL_TEST_NAME} PRIVATE gmock_main) + target_link_libraries(${LOCAL_TEST_NAME} PRIVATE ${ARG_EXTRA_TEST_LIBS}) + + set(TEST_FRAMEWORK_LINK_LIBRARIES "gmock_main") + + get_target_property(LOCAL_TEST_LIB_TEST_FRAMEWORK_LINK_LIBRARIES ${ARG_TEST_LIB} TEST_FRAMEWORK_LINK_LIBRARIES) + + if (NOT LOCAL_TEST_LIB_TEST_FRAMEWORK_LINK_LIBRARIES MATCHES ".*-NOTFOUND$") + list(APPEND TEST_FRAMEWORK_LINK_LIBRARIES ${LOCAL_TEST_LIB_TEST_FRAMEWORK_LINK_LIBRARIES}) + endif() + + get_target_property(LOCAL_TEST_LIB_POST_BUILD_INSTRUMENTATION_BYPRODUCTS ${ARG_TEST_LIB} POST_BUILD_INSTRUMENTATION_BYPRODUCTS) + + if (NOT LOCAL_TEST_LIB_POST_BUILD_INSTRUMENTATION_BYPRODUCTS MATCHES ".*-NOTFOUND$") + set_target_properties(${LOCAL_TEST_NAME} + PROPERTIES + POST_BUILD_INSTRUMENTATION_BYPRODUCTS "${LOCAL_TEST_LIB_POST_BUILD_INSTRUMENTATION_BYPRODUCTS}" + ) + endif() + + set_target_properties(${LOCAL_TEST_NAME} + PROPERTIES + TEST_FRAMEWORK_LINK_LIBRARIES "${TEST_FRAMEWORK_LINK_LIBRARIES}" + ) + + set_target_properties(${LOCAL_TEST_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${ARG_RUNTIME_OUTPUT_DIRECTORY}" + ) + + #+-[output]---------------------------------------------------------------+ + if (NOT ARG_OUT_TEST_EXE_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_EXE_VARIABLE} ${LOCAL_TEST_NAME} PARENT_SCOPE) + endif() + +endfunction(define_native_gtest_unittest_executable) + +# +# function: define_native_gtest_unittest_run - Creates a cutom target to run a test executable. +# +# +# param: TEST_EXECUTABLE target - A test target defined by define_native_gtest_unittest_executable +# param: RUNTIME_OUTPUT_DIRECTORY path - A path to output test binaries and coverage data under. +# param: BYPRODUCTS - A list of byproducts expected when running the test. +# param: OUT_TEST_REPORT_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to a report artifact generated by a test run. +# +function(define_native_gtest_unittest_run) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs TEST_EXECUTABLE RUNTIME_OUTPUT_DIRECTORY OUT_TEST_REPORT_VARIABLE) + set(multiValueArgs BYPRODUCTS) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if (NOT ARG_RUNTIME_OUTPUT_DIRECTORY) + set(ARG_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + #+-[body]-----------------------------------------------------------------+ + + set(LOCAL_TESTRESULT ${ARG_TEST_EXECUTABLE}-gtest.xml) + set(LOCAL_RUNTARGET run_${ARG_TEST_EXECUTABLE}) + + message(DEBUG "Using --gtest_output=xml: expecting test to generate: ${LOCAL_TESTRESULT}") + + get_target_property(LOCAL_ARG_TEST_EXECUTABLE_POST_BUILD_INSTRUMENTATION_BYPRODUCTS ${ARG_TEST_EXECUTABLE} POST_BUILD_INSTRUMENTATION_BYPRODUCTS) + + if (NOT LOCAL_ARG_TEST_EXECUTABLE_POST_BUILD_INSTRUMENTATION_BYPRODUCTS MATCHES ".*-NOTFOUND$") + list(APPEND ARG_BYPRODUCTS ${LOCAL_ARG_TEST_EXECUTABLE_POST_BUILD_INSTRUMENTATION_BYPRODUCTS}) + endif() + + add_custom_command( + OUTPUT ${LOCAL_TESTRESULT} + COMMAND ${ARG_TEST_EXECUTABLE} --gtest_output=xml:${LOCAL_TESTRESULT} + DEPENDS ${ARG_TEST_EXECUTABLE} + WORKING_DIRECTORY ${ARG_RUNTIME_OUTPUT_DIRECTORY} + BYPRODUCTS ${ARG_BYPRODUCTS} + ) + + message(DEBUG "Defining googletest run target ${LOCAL_RUNTARGET} for source library ${ARG_TEST_EXECUTABLE}") + + add_custom_target( + ${LOCAL_RUNTARGET} + DEPENDS ${LOCAL_TESTRESULT} + ) + + #+-[output]---------------------------------------------------------------+ + if (NOT ARG_OUT_TEST_REPORT_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_REPORT_VARIABLE} ${LOCAL_TESTRESULT} PARENT_SCOPE) + endif() + +endfunction(define_native_gtest_unittest_run) + +# +# function: define_native_gtest_unittest_targets - Convenience function for succinct unittest definitions. +# +# Equivalent to calls to define_native_gtest_unittest_library, +# define_native_gtest_unittest_executable, and define_native_gtest_unittest_run +# while setting the given extra test libraries on the unittest library target. +# +# param: TEST_SOURCE path - A single source file that is the test main. +# param: RUNTIME_OUTPUT_DIRECTORY path - A path to output test binaries and coverage data under. +# param: EXTRA_TEST_LIBS targets - A list of additional test library targets to link with. +# param: OUT_TEST_LIB_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to the test case library build target name. +# param: OUT_TEST_EXE_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to the executable target name. +# param: OUT_TEST_REPORT_VARIABLE target - If set, this becomes the name of a variable set, in the parent +# context, to a report artifact generated by a test run. +# +function(define_native_gtest_unittest_targets) + + #+-[input]----------------------------------------------------------------+ + set(options "") + set(singleValueArgs TEST_SOURCE RUNTIME_OUTPUT_DIRECTORY OUT_TEST_LIB_VARIABLE OUT_TEST_EXE_VARIABLE OUT_TEST_REPORT_VARIABLE) + set(multiValueArgs EXTRA_TEST_LIBS) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if (NOT ARG_RUNTIME_OUTPUT_DIRECTORY) + set(ARG_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + #+-[body]-----------------------------------------------------------------+ + + define_native_gtest_unittest_library( + TEST_SOURCE + ${ARG_TEST_SOURCE} + RUNTIME_OUTPUT_DIRECTORY + ${ARG_RUNTIME_OUTPUT_DIRECTORY} + EXTRA_TEST_LIBS + ${ARG_EXTRA_TEST_LIBS} + OUT_TEST_LIB_VARIABLE + "LOCAL_TEST_LIB" + ) + + define_native_gtest_unittest_executable( + TEST_LIB + ${LOCAL_TEST_LIB} + RUNTIME_OUTPUT_DIRECTORY + ${ARG_RUNTIME_OUTPUT_DIRECTORY} + EXTRA_TEST_LIBS + ${ARG_EXTRA_TEST_LIBS} + OUT_TEST_EXE_VARIABLE + "LOCAL_TEST_EXE" + ) + + define_native_gtest_unittest_run( + TEST_EXECUTABLE + ${LOCAL_TEST_EXE} + RUNTIME_OUTPUT_DIRECTORY + ${ARG_RUNTIME_OUTPUT_DIRECTORY} + OUT_TEST_REPORT_VARIABLE + "LOCAL_TEST_REPORT" + ) + + #+-[output]---------------------------------------------------------------+ + + if (NOT ARG_OUT_TEST_LIB_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_LIB_VARIABLE} ${LOCAL_TEST_LIB} PARENT_SCOPE) + endif() + + if (NOT ARG_OUT_TEST_EXE_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_EXE_VARIABLE} ${LOCAL_TEST_EXE} PARENT_SCOPE) + endif() + + if (NOT ARG_OUT_TEST_REPORT_VARIABLE STREQUAL "") + set(${ARG_OUT_TEST_REPORT_VARIABLE} ${LOCAL_TEST_REPORT} PARENT_SCOPE) + endif() + +endfunction(define_native_gtest_unittest_targets) diff --git a/cetlvast/cmake/modules/Findgtest.cmake b/cetlvast/cmake/modules/Findgtest.cmake deleted file mode 100644 index b7b1750b..00000000 --- a/cetlvast/cmake/modules/Findgtest.cmake +++ /dev/null @@ -1,79 +0,0 @@ -# -# Framework : Googletest -# -# (Taken from googletest/README.md documentation) -# GTest executable -# Download and unpack googletest at configure time. We don't -# model this as a submodule because this technique properly utilizes -# the googletest cmake build. -# - -# +---------------------------------------------------------------------------+ -# | PULL GTEST SOURCE -# +---------------------------------------------------------------------------+ -configure_file(${CMAKE_MODULE_PATH}/Gtest.txt.in - ${EXTERNAL_PROJECT_DIRECTORY}/googletest-download/CMakeLists.txt) - -execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . - RESULT_VARIABLE GTEST_CMAKE_GEN_RESULT - WORKING_DIRECTORY ${EXTERNAL_PROJECT_DIRECTORY}/googletest-download ) - -if(GTEST_CMAKE_GEN_RESULT) - message(WARNING "CMake step for googletest failed: ${GTEST_CMAKE_GEN_RESULT}") -else() - execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE GTEST_CMAKE_BUILD_RESULT - WORKING_DIRECTORY ${EXTERNAL_PROJECT_DIRECTORY}/googletest-download) - - if(GTEST_CMAKE_BUILD_RESULT) - message(WARNING "Build step for googletest failed: ${GTEST_CMAKE_BUILD_RESULT}") - else() - - # Prevent overriding the parent project's compiler/linker - # settings on Windows - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - set(GTEST_FOUND ON) - set(BUILD_TESTING ON) - enable_testing() - endif() -endif() - - -# +---------------------------------------------------------------------------+ -# | gmock_main -# +---------------------------------------------------------------------------+ -if(GTEST_FOUND) - - # This is a "native" build so just add googletest directly. - # This defines the gmock_main target. - add_subdirectory(${EXTERNAL_PROJECT_DIRECTORY}/googletest-src - ${EXTERNAL_PROJECT_DIRECTORY}/googletest-build - EXCLUDE_FROM_ALL) - - # Aparently Google doesn't care much about compiler warnings? - set(LOCAL_GTEST_COMPILE_OPTIONS - "-Wno-sign-conversion" - "-Wno-zero-as-null-pointer-constant" - "-Wno-switch-enum" - "-Wno-float-equal" - "-Wno-double-promotion" - "-Wno-conversion" - "-Wno-missing-declarations" - ) - target_compile_options(gtest - PRIVATE - ${LOCAL_GTEST_COMPILE_OPTIONS} - ) - target_compile_options(gmock - PRIVATE - ${LOCAL_GTEST_COMPILE_OPTIONS} - ) - -endif() - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(gtest - REQUIRED_VARS GTEST_FOUND -) diff --git a/cetlvast/cmake/modules/Findo1heap.cmake b/cetlvast/cmake/modules/Findo1heap.cmake new file mode 100644 index 00000000..94406139 --- /dev/null +++ b/cetlvast/cmake/modules/Findo1heap.cmake @@ -0,0 +1,62 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# +if(NOT TARGET o1heap) +include(FetchContent) +include(FindPackageHandleStandardArgs) +include(ProjectLibrary) + +set(o1heap_GIT_REPOSITORY "https://github.com/pavel-kirienko/o1heap.git") +set(o1heap_GIT_TAG "aa3c253923db36eee7d73a4a5e30884a5fe7a6eb") + +FetchContent_Declare( + o1heap + GIT_REPOSITORY ${o1heap_GIT_REPOSITORY} + GIT_TAG ${o1heap_GIT_TAG} +) + +# The automatic management of the _POPULATED name appears to be broken in +# cmake 3.21 and earlier. This workaround may not be needed after 3.24. +get_property(o1heap_POPULATED GLOBAL PROPERTY o1heap_POPULATED) + +if(NOT o1heap_POPULATED) + + if (NOT FETCHCONTENT_SOURCE_DIR_o1heap) + set(FETCHCONTENT_SOURCE_DIR_o1heap ${CETLVAST_EXTERNAL_ROOT}/o1heap) + endif() + + if (NOT ${FETCHCONTENT_FULLY_DISCONNECTED}) + FetchContent_Populate( + o1heap + SOURCE_DIR ${FETCHCONTENT_SOURCE_DIR_o1heap} + GIT_REPOSITORY ${o1heap_GIT_REPOSITORY} + GIT_TAG ${o1heap_GIT_TAG} + ) + else() + set(o1heap_SOURCE_DIR ${FETCHCONTENT_SOURCE_DIR_o1heap}) + endif() + + # The automatic management of the _POPULATED name appears to be broken in + # cmake 3.21 and earlier. This workaround may not be needed after 3.24. + set_property(GLOBAL PROPERTY o1heap_POPULATED true) + + find_package_handle_standard_args(o1heap + REQUIRED_VARS o1heap_SOURCE_DIR + ) + + add_project_library( + NAME o1heap + SOURCES + ${o1heap_SOURCE_DIR}/o1heap/o1heap.c + HEADER_PATH + ${o1heap_SOURCE_DIR}/o1heap/ + HEADER_INCLUDE_PATTERNS + *.h + STATIC + FPIC + ) + +endif() +endif() diff --git a/cetlvast/cmake/modules/Gtest.txt.in b/cetlvast/cmake/modules/Gtest.txt.in deleted file mode 100644 index 2152649c..00000000 --- a/cetlvast/cmake/modules/Gtest.txt.in +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.16.0) - -project(googletest-download NONE) - -include(ExternalProject) -ExternalProject_Add(googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG b796f7d44681514f58a683a3a71ff17c94edb0c1 - SOURCE_DIR "${EXTERNAL_PROJECT_DIRECTORY}/googletest-src" - BINARY_DIR "${EXTERNAL_PROJECT_DIRECTORY}/googletest-build" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/cetlvast/cmake/modules/ProjectLibrary.cmake b/cetlvast/cmake/modules/ProjectLibrary.cmake new file mode 100644 index 00000000..c4ff6a40 --- /dev/null +++ b/cetlvast/cmake/modules/ProjectLibrary.cmake @@ -0,0 +1,174 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +# +---------------------------------------------------------------------------+ +# | SUPPORT FOR EXPORTING LIBRARIES AND HEADER FILES +# +---------------------------------------------------------------------------+ + +# Function: add_project_library +# +# Add and install a library. This method combines all the most common setup for libcyphal library definitions +# including defining install rules. Additional properties, dependencies, etc can be added after this function +# using the library target named by the `NAME` argument you passed in. +# +# By default, the library will be static, unless you pass the SHARED option. +# If no sources are passed in, and neither SHARED nor STATIC is specified, a cmake interface library will be defined +# with just headers. +# +# ## Using The HEADER_PATH Argument +# +# Given the following structure: +# +# ``` +# foo/ +# + include/ +# + bar/ +# + bar.h +# ``` +# +# ... if HEADER_PATH is `${CMAKE_CURRENT_SOURCE_DIR}/foo/include` then the output will be: +# +# ``` +# ${CMAKE_INSTALL_PREFIX}/ +# + include/ +# + include/ +# + bar/ +# + bar.h +# ``` +# +# ... which is probably not what you want. Instead do `${CMAKE_CURRENT_SOURCE_DIR}/foo/include/` (notice the trailing slash): +# +# ``` +# ${CMAKE_INSTALL_PREFIX}/ +# + include/ +# + bar/ +# + bar.h +# ``` +# +# ... which means consumers would include bar.h as `include "bar/bar.h"`. If the prefix `bar` should be omitted +# define HEADER_PATH as `${CMAKE_CURRENT_SOURCE_DIR}/include/bar/` which results in this: +# +# ``` +# ${CMAKE_INSTALL_PREFIX}/ +# + include/ +# + bar.h +# ``` +# +# Usage: +# > add_project_library(NAME SOURCES ... HEADER_PATH HEADER_INCLUDE_PATTERNS ... [LIBRARIES ...] [FPIC] [STATIC|SHARED]) +# +# Parameters: +# NAME - what to call your library (do not include prefixes (e.g. "lib") or suffixes (e.g. ".a")) +# SOURCES - list of files to build into your library +# HEADER_PATH - Directory under which all headers for the library reside. +# HEADER_INCLUDE_PATTERNS - List of posix file glob expressions to use to include files found under HEADER_PATH. If omitted +# a default set of patterns are used (e.g. *.hpp). Note, this has no effect on files available to +# the local build. +# LIBRARIES - list of additional libraries to link against. +# +# Options: +# STATIC - create a static archive +# SHARED - create a shared library +# FPIC - Build position-independent code. +# +function(add_project_library) + # parse the arguments provided to add_project_library (see cmake_parse_arguments docs) + set(options STATIC SHARED FPIC) + set(singleValueArgs NAME HEADER_PATH) + set(multiValueArgs SOURCES HEADER_INCLUDE_PATTERNS LIBRARIES) + cmake_parse_arguments(PARSE_ARGV 0 LOCAL "${options}" "${singleValueArgs}" "${multiValueArgs}") + + if(NOT LOCAL_NAME) + message(FATAL_ERROR "You must provide a name!") + endif() + + cmake_path(IS_ABSOLUTE LOCAL_HEADER_PATH HEADER_PATH_IS_ABSOLUTE) + + if(NOT HEADER_PATH_IS_ABSOLUTE) + set(LOCAL_HEADER_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${LOCAL_HEADER_PATH}") + endif() + + # Cmake doesn't have block scope but setting them before the if branches below + # makes the easier to read. + set(LOCAL_LIBRARY_INSTALL_TYPE "") + set(LOCAL_LIBRARY_SCOPE "PUBLIC") + + if(LOCAL_SHARED) + # We are creating a shared library. Neat. + set(LOCAL_LIBRARY_INSTALL_TYPE "LIBRARY") + if(LOCAL_STATIC) + message(FATAL_ERROR "SHARED defined. Cannot define both SHARED and STATIC using this function.") + endif() + add_library("${LOCAL_NAME}" SHARED ${LOCAL_SOURCES}) + else() + # We are creating some sort of archive. Either an object archive (.a) or a cmake interface archive. + set(LOCAL_LIBRARY_INSTALL_TYPE "ARCHIVE") + if(NOT LOCAL_SOURCERS AND NOT LOCAL_STATIC) + # This is a cmake concept only but it allows us to define a group of headers along with the + # dependencies you'll need to use the headers and some of the properties you should inherit + # when building objects that include these headers. No .a nor .so file will be produced for + # these, however. + message(DEBUG "Neither SHARED nor STATIC defined and no sources provided to add_project_library(${LOCAL_NAME}). Using default of INTERFACE.") + add_library("${LOCAL_NAME}" INTERFACE) + set(LOCAL_LIBRARY_SCOPE "INTERFACE") + else() + # Good old fashioned object archive! + add_library("${LOCAL_NAME}" STATIC ${LOCAL_SOURCES}) + endif() + endif() + + set(LOCAL_HEADERS_DESTINATION "include") + + if (NOT LOCAL_HEADER_INCLUDE_PATTERNS) + list(APPEND LOCAL_HEADER_INCLUDE_PATTERNS "*.h") + list(APPEND LOCAL_HEADER_INCLUDE_PATTERNS "*.hpp") + endif() + + # Recursively search for include files under LOCAL_HEADER_PATH + foreach(LOCAL_PATTERN ${LOCAL_HEADER_INCLUDE_PATTERNS}) + install( + DIRECTORY + ${LOCAL_HEADER_PATH} + DESTINATION + ${LOCAL_HEADERS_DESTINATION} + FILES_MATCHING PATTERN + ${LOCAL_PATTERN} + ) + endforeach() + + # Other options that are common enough we do it here. Anything not in this section can be done + # after this function exits using set_property + if (LOCAL_FPIC) + set_property(TARGET "${LOCAL_NAME}" PROPERTY POSITION_INDEPENDENT_CODE ON) + endif() + + target_link_libraries("${LOCAL_NAME}" ${LOCAL_LIBRARY_SCOPE} ${LOCAL_LIBRARIES}) + + # TODO: support multiple include paths using $ + target_include_directories("${LOCAL_NAME}" ${LOCAL_LIBRARY_SCOPE} + $ + $ + ) + + # Now setup the installation targets. Of course, these don't run at configuration time + # like everything else in this function does. Note that the EXPORT argument + # is a key to group all install calls together into a single export package. So, each + # call with the same key appends more export work. + install( + TARGETS "${LOCAL_NAME}" + EXPORT "${LOCAL_NAME}-export" + ${LOCAL_LIBRARY_INSTALL_TYPE} + DESTINATION lib + ) + + # We also export ${LOCAL_NAME}-export to support find_package/find_library + # in dependant packages. See + # https://cmake.org/cmake/help/latest/guide/tutorial/Adding%20Export%20Configuration.html + install(EXPORT "${LOCAL_NAME}-export" + DESTINATION include/cmake + FILE ${LOCAL_NAME}.cmake) + +endfunction(add_project_library) diff --git a/cetlvast/cmake/suites/compile.cmake b/cetlvast/cmake/suites/compile.cmake deleted file mode 100644 index 4a768b90..00000000 --- a/cetlvast/cmake/suites/compile.cmake +++ /dev/null @@ -1,95 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -enable_testing() - -# -# Creates a ctest that will succeed if the test fails to compile. Yep, you read that -# correctly: SUCCESS == FAILURE for this set of tests. We use these to validate -# compile-time asserts which guard against illegal template formation. -# -# This cmake-based solution was Inspired by a similar script written by -# Louis Dionne for libawful: https://github.com/ldionne/libawful -# -# :param ARG_TEST_NAME str: The name for the test executable. This will only be created if -# the test fails. The ctest will be named "ct_${ARG_TEST_NAME}". -# :param ARG_TEST_SOURCE path: The test source file. -# -function(define_compile_failure_test ARG_TEST_NAME ARG_TEST_SOURCE) - - # First build with "precheck" enabled to ensure the test compiles without the negative case included... - add_executable(${ARG_TEST_NAME}_precheck ${ARG_TEST_SOURCE}) - - target_compile_definitions(${ARG_TEST_NAME}_precheck PRIVATE CETLVAST_COMPILETEST_PRECHECK=1) - - add_custom_target( - "run_${ARG_TEST_NAME}_precheck" - COMMAND - ${CMAKE_CURRENT_BINARY_DIR}/${ARG_TEST_NAME}_precheck - DEPENDS - "${ARG_TEST_NAME}_precheck" - ) - - # Now define the doomed version for ctest to run... - add_executable(${ARG_TEST_NAME} ${ARG_TEST_SOURCE}) - - set_target_properties( - ${ARG_TEST_NAME} - PROPERTIES - EXCLUDE_FROM_ALL ON - ) - - add_test( - NAME ct_${ARG_TEST_NAME} - COMMAND - ${CMAKE_COMMAND} - --build ${CMAKE_CURRENT_BINARY_DIR} - --target ${ARG_TEST_NAME} - --config $ - ) - - set_tests_properties( - ct_${ARG_TEST_NAME} - PROPERTIES - WILL_FAIL true - ) - -endfunction() - -file(GLOB COMPILE_TESTS - LIST_DIRECTORIES false - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/suites/compile/test_*.cpp -) - -set(ALL_TESTS_BUILD "") -set(ALL_TESTS "") - -foreach(COMPILE_TEST ${COMPILE_TESTS}) - cmake_path(GET COMPILE_TEST STEM COMPILE_TEST_NAME) - define_compile_failure_test(${COMPILE_TEST_NAME} ${COMPILE_TEST}) - list(APPEND ALL_TESTS_BUILD "${COMPILE_TEST_NAME}_precheck") - list(APPEND ALL_TESTS "run_${COMPILE_TEST_NAME}_precheck") -endforeach() - -add_custom_target( - build_all - DEPENDS ${ALL_TESTS_BUILD} -) - -add_custom_target( - test_all - DEPENDS - ${ALL_TESTS} -) - -add_custom_target( - suite_all - COMMENT - "All CETL suites define this target as a default action scripts can rely on." - DEPENDS - test_all -) diff --git a/cetlvast/cmake/suites/docs.cmake b/cetlvast/cmake/suites/docs.cmake deleted file mode 100644 index 774b2611..00000000 --- a/cetlvast/cmake/suites/docs.cmake +++ /dev/null @@ -1,100 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# +---------------------------------------------------------------------------+ -# | BUILD NATIVE EXAMPLE CODE -# +---------------------------------------------------------------------------+ -# All example binaries will be created under this directory. -set(CETLVAST_NATIVE_EXAMPLE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/cetlvast/suites/examples) - - -# -# function: define_native_example_build - creates an rule to build an executable for -# a given example. -# -# param: ARG_EXAMPLE_NAME string - The name to give the executable binary. -# param: ARG_EXAMPLE_SOURCE List[path] - A list of source files to compile into -# the example binary. -# param: ARG_OUTDIR path - A path to output example binaries. -# -function(define_native_example_build ARG_EXAMPLE_NAME ARG_EXAMPLE_SOURCE ARG_OUTDIR) - - add_executable(${ARG_EXAMPLE_NAME} ${ARG_EXAMPLE_SOURCE}) - set_target_properties(${ARG_EXAMPLE_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${ARG_OUTDIR}" - ) - -endfunction() - - -# -# function: define_native_example_run - creates a rule that will and run individual -# examples. -# -# param: ARG_EXAMPLE_NAME string - The name of the example to run. A target will be created -# with the name run_${ARG_EXAMPLE_NAME} -# param: ARG_OUTDIR path - The path where the example binaries live. -# -function(define_native_example_run ARG_EXAMPLE_NAME ARG_OUTDIR) - add_custom_target( - run_${ARG_EXAMPLE_NAME} - COMMAND - ${ARG_OUTDIR}/${ARG_EXAMPLE_NAME} - DEPENDS - ${ARG_EXAMPLE_NAME} - ) - -endfunction() - -file(GLOB NATIVE_EXAMPLES - LIST_DIRECTORIES false - RELATIVE ${CETLVAST_PROJECT_ROOT} - suites/docs/examples/example_*.cpp -) - -set(ALL_EXAMPLES "") -set(ALL_EXAMPLE_RUNS "") - -foreach(NATIVE_EXAMPLE ${NATIVE_EXAMPLES}) - cmake_path(GET NATIVE_EXAMPLE STEM NATIVE_EXAMPLE_NAME) - message(STATUS "Defining native example binary ${NATIVE_EXAMPLE_NAME} for source file ${NATIVE_EXAMPLE}") - define_native_example_build(${NATIVE_EXAMPLE_NAME} ${NATIVE_EXAMPLE} ${CETLVAST_NATIVE_EXAMPLE_BINARY_DIR}) - define_native_example_run(${NATIVE_EXAMPLE_NAME} ${CETLVAST_NATIVE_EXAMPLE_BINARY_DIR}) - list(APPEND ALL_EXAMPLES "${NATIVE_EXAMPLE_NAME}") - list(APPEND ALL_EXAMPLE_RUNS "run_${NATIVE_EXAMPLE_NAME}") -endforeach() - -add_custom_target( - build_all - DEPENDS - ${ALL_EXAMPLES} -) - -add_custom_target( - run_all_examples - DEPENDS - ${ALL_EXAMPLE_RUNS} -) - -# +---------------------------------------------------------------------------+ -# | DOXYGEN -# +---------------------------------------------------------------------------+ -# -# Finds programs needed to build the CETL documentation -# -find_package(docs REQUIRED) - -create_docs_target(docs ON ${CETLVAST_PROJECT_ROOT}/suites/docs/examples "${ALL_EXAMPLES}") -create_docs_tarball_target(docs_tarball OFF) - -add_custom_target( - suite_all - COMMENT - "All CETL suites define this target as a default action scripts can rely on." - DEPENDS - docs -) diff --git a/cetlvast/cmake/suites/lint.cmake b/cetlvast/cmake/suites/lint.cmake deleted file mode 100644 index f65e2e31..00000000 --- a/cetlvast/cmake/suites/lint.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# +---------------------------------------------------------------------------+ -# | STYLE -# +---------------------------------------------------------------------------+ -find_package(clangformat REQUIRED) - -create_check_style_target(format-check ${CETLVAST_STYLE_CHECK} "${CETL_INCLUDE}/**/*.hpp") - -add_custom_target( - suite_all - COMMENT - "All CETL suites define this target as a default action scripts can rely on." - DEPENDS - format-check -) diff --git a/cetlvast/cmake/suites/unittest.cmake b/cetlvast/cmake/suites/unittest.cmake deleted file mode 100644 index fd29f444..00000000 --- a/cetlvast/cmake/suites/unittest.cmake +++ /dev/null @@ -1,261 +0,0 @@ -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# - -# unit tests compiled for whatever environment the build is running on. They assume they are running -# on a fairly robust POSIX environment and use googletest/googlemock to organize the tests. Native tests should work -# on linux, osx, or Windows hosts and should work on any popular architecture including 32-bit and 64-bit ARM and x86. -# Finally, native tests assume the available toolchain can compile and run executables as part of the build process. -# - -# All test binaries and reports will be created under this directory. -set(CETLVAST_NATIVE_TEST_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/cetlvast/suites/unittest) - -# -# googletest (and googlemock) external project. -# -find_package(gtest REQUIRED) - -# +---------------------------------------------------------------------------+ -# | BUILD NATIVE UNIT TESTS -# +---------------------------------------------------------------------------+ -function(get_internal_output_path_for_source ARG_SOURCEFILE ARG_ARTIFACT_SUFFIX OUTARG_INTERNAL_DIR) - # This is a hack I don't know how to get rid of. We're not supposed to "know" about the - # CMakeFiles directory nor its internal structure but we have to list the test binary - # byproducts when enabling coverage to make sure the clean target works properly. - # the use of a OBJECT library at least enforces that these intermediates are available. - cmake_path(GET ARG_SOURCEFILE PARENT_PATH LOCAL_SOURCEFILE_REL_PATH) - cmake_path(SET LOCAL_SOURCFILE_REL_PATH_NORMAL NORMALIZE ${LOCAL_SOURCEFILE_REL_PATH}) - cmake_path(GET ARG_SOURCEFILE STEM LOCAL_SOURCEFILE_NAME) - cmake_path(SET LOCAL_RESULT "CMakeFiles") - cmake_path(APPEND LOCAL_RESULT "${LOCAL_SOURCEFILE_NAME}${ARG_ARTIFACT_SUFFIX}.dir") - cmake_path(APPEND LOCAL_RESULT "${LOCAL_SOURCFILE_REL_PATH_NORMAL}") - set(${OUTARG_INTERNAL_DIR} ${LOCAL_RESULT} PARENT_SCOPE) -endfunction() - -# -# function: define_native_unit_test - Creates rules and targets to build and run gtest-based -# unit tests. -# -# param: ARG_TEST_SOURCE path - A single source file that is the test main. -# param: ARG_OUTDIR path - A path to output test binaries and coverage data under. -# param: OUTARG_TESTNAME path - Set to the name of the test derived from the given source -# file. -# param: OUTARG_TESTRESULT path - Set to the path for the test result file produced by a -# successful test run. -# -function(define_native_gtest_unit_test ARG_TEST_SOURCE ARG_OUTDIR OUTARG_TESTNAME OUTARG_TESTRESULT) - - cmake_path(GET ARG_TEST_SOURCE STEM LOCAL_TEST_NAME) - set(LOCAL_TESTRESULT ${ARG_OUTDIR}/${LOCAL_TEST_NAME}-gtest.xml) - - message(STATUS "Defining googletest binary ${LOCAL_TEST_NAME} for source file ${ARG_TEST_SOURCE}") - - # Create explicit object file target so we can find it. - add_library(${LOCAL_TEST_NAME}_objlib OBJECT ${ARG_TEST_SOURCE}) - # This gets the includes from the gmock_main interface library. - target_link_libraries(${LOCAL_TEST_NAME}_objlib gmock_main) - - add_executable(${LOCAL_TEST_NAME} $) - target_link_libraries(${LOCAL_TEST_NAME} gmock_main) - set_target_properties(${LOCAL_TEST_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${ARG_OUTDIR}" - ) - - add_custom_command( - OUTPUT ${LOCAL_TESTRESULT} - COMMAND ${ARG_OUTDIR}/${LOCAL_TEST_NAME} --gtest_output=xml:${LOCAL_TESTRESULT} - DEPENDS ${ARG_OUTDIR}/${LOCAL_TEST_NAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - - message(DEBUG "Using --getst_output=xml: expecting test to generate: ${LOCAL_TESTRESULT}") - - set(LOCAL_BYPRODUCTS "") - - if (CETLVAST_ENABLE_COVERAGE) - - get_internal_output_path_for_source(${ARG_TEST_SOURCE} "_objlib" LOCAL_OBJLIB_FOLDER_REL) - cmake_path(ABSOLUTE_PATH LOCAL_OBJLIB_FOLDER_REL - BASE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - OUTPUT_VARIABLE LOCAL_OBJLIB_FOLDER) - - cmake_path(GET ARG_TEST_SOURCE EXTENSION LOCAL_TEST_EXT) - # the generation of gcda files assumes "-fprofile-argcs" (or "-coverage" which includes this flag). - set(LOCAL_GCDA_FILEPATH "${LOCAL_OBJLIB_FOLDER}/${LOCAL_TEST_NAME}${LOCAL_TEST_EXT}.gcda") - # the generation of gcno files assumes "-ftest-coverage" (or "-coverage" which includes this flag) - set(LOCAL_GCNO_FILEPATH "${LOCAL_OBJLIB_FOLDER}/${LOCAL_TEST_NAME}${LOCAL_TEST_EXT}.gcno") - - list(APPEND LOCAL_BYPRODUCTS ${LOCAL_GCDA_FILEPATH}) - list(APPEND LOCAL_BYPRODUCTS ${LOCAL_GCNO_FILEPATH}) - - message(DEBUG "Coverage is enabled: expecting test run byproduct: ${LOCAL_GCDA_FILEPATH}") - message(DEBUG "Coverage is enabled: expecting test run byproduct: ${LOCAL_GCNO_FILEPATH}") - endif() - - add_custom_target( - run_${LOCAL_TEST_NAME} - COMMAND ${ARG_OUTDIR}/${LOCAL_TEST_NAME} - DEPENDS ${ARG_OUTDIR}/${LOCAL_TEST_NAME} - BYPRODUCTS ${LOCAL_BYPRODUCTS} - ) - - set(${OUTARG_TESTNAME} ${LOCAL_TEST_NAME} PARENT_SCOPE) - set(${OUTARG_TESTRESULT} ${LOCAL_TESTRESULT} PARENT_SCOPE) - -endfunction() - -# +---------------------------------------------------------------------------+ -# We generate individual test binaires so we can record which test generated -# what coverage. We also allow test authors to generate coverage reports for -# just one test allowing for faster iteration. -file(GLOB NATIVE_TESTS - LIST_DIRECTORIES false - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/suites/unittest/test_*.cpp -) - -set(ALL_TESTS_BUILD "") -set(ALL_TESTS "") -set(LOCAL_TEST_NAME "") -set(LOCAL_TEST_REPORT "") - -foreach(NATIVE_TEST ${NATIVE_TESTS}) - define_native_gtest_unit_test(${NATIVE_TEST} - ${CETLVAST_NATIVE_TEST_BINARY_DIR} - LOCAL_TEST_NAME - LOCAL_TEST_REPORT) - list(APPEND ALL_TESTS_BUILD "${CETLVAST_NATIVE_TEST_BINARY_DIR}/${LOCAL_TEST_NAME}") - list(APPEND ALL_TESTS "${LOCAL_TEST_REPORT}") -endforeach() - -add_custom_target( - build_all - DEPENDS ${ALL_TESTS_BUILD} -) - -if (CETLVAST_ENABLE_COVERAGE) - -message(STATUS "Coverage is enabled: adding coverage targets.") - -# +---------------------------------------------------------------------------+ -# If coverage is enabled we have more work to do... -# +---------------------------------------------------------------------------+ - -# we use gcovr to support standard coverage reporting tools like coveralls or sonarqube. -find_package(gcovr REQUIRED) - -function(define_gcovr_tracefile_for_test ARG_TEST_SOURCE ARG_OUT_TRACEFILE) - # We're not supposed to know what the test name is given the source file. This - # needs to be cleaned up if we want to reuse it. - cmake_path(GET ARG_TEST_SOURCE STEM LOCAL_TEST_NAME) - cmake_path(GET ARG_TEST_SOURCE EXTENSION LOCAL_TEST_EXT) - - get_internal_output_path_for_source(${ARG_TEST_SOURCE} "_objlib" LOCAL_OBJLIB_REL_FOLDER) - - set(LOCAL_TRACEFILE_NAME "${LOCAL_TEST_NAME}-gcovr.json") - set(LOCAL_TRACEFILE_PATH "${CETLVAST_NATIVE_TEST_BINARY_DIR}/${LOCAL_TRACEFILE_NAME}") - set(LOCAL_TESTRESULT "${CETLVAST_NATIVE_TEST_BINARY_DIR}/${LOCAL_TEST_NAME}-gtest.xml") - - add_custom_command( - COMMAND # Generate tracefile from tests. - ${GCOVR} - --gcov-executable ${CMAKE_C_COVERAGE_PROCESSOR} - --r ${CETL_ROOT} - --json ${LOCAL_TRACEFILE_PATH} - --exclude "\"${EXTERNAL_PROJECT_DIRECTORY}\"" - --gcov-exclude "\"${EXTERNAL_PROJECT_DIRECTORY}\"" - --object-directory ${LOCAL_OBJLIB_REL_FOLDER} - ${LOCAL_OBJLIB_REL_FOLDER} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - OUTPUT ${LOCAL_TRACEFILE_PATH} - DEPENDS ${LOCAL_TESTRESULT} - ) - - message(DEBUG "Will generate tracefile \"${LOCAL_TRACEFILE_PATH}\" for instrumentation found under \"${CMAKE_CURRENT_BINARY_DIR}/${LOCAL_OBJLIB_REL_FOLDER}\"") - set(${ARG_OUT_TRACEFILE} ${LOCAL_TRACEFILE_PATH} PARENT_SCOPE) - - add_custom_target( - create_${LOCAL_TEST_NAME}_tracefile - DEPENDS ${LOCAL_TRACEFILE_PATH} - ) -endfunction() - -set(ALL_TEST_TRACEFILES) -set(ALL_TEST_COVERAGE "") -set(LOCAL_TRACEFILE "") -# we reset the tests to run by way of the coverage report. -set(ALL_TESTS "") - -foreach(NATIVE_TEST ${NATIVE_TESTS}) - define_gcovr_tracefile_for_test(${NATIVE_TEST} NATIVE_TEST_TRACEFILE) - list(APPEND ALL_TEST_TRACEFILES "${NATIVE_TEST_TRACEFILE}") - list(APPEND ALL_TEST_COVERAGE "--add-tracefile") - list(APPEND ALL_TEST_COVERAGE "${NATIVE_TEST_TRACEFILE}") -endforeach() - -add_custom_command( - OUTPUT ${CETLVAST_NATIVE_TEST_BINARY_DIR}/gcovr_html/coverage.html - COMMAND - ${GCOVR} - ${ALL_TEST_COVERAGE} - --r ${CETL_ROOT} - --html-details ${CETLVAST_NATIVE_TEST_BINARY_DIR}/gcovr_html/coverage.html - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${ALL_TEST_TRACEFILES} -) - -add_custom_target( - gcovr_html_report - DEPENDS ${CETLVAST_NATIVE_TEST_BINARY_DIR}/gcovr_html/coverage.html -) - -add_custom_command( - OUTPUT ${CETLVAST_NATIVE_TEST_BINARY_DIR}/coverage.xml - COMMAND - ${GCOVR} - ${ALL_TEST_COVERAGE} - --r ${CETL_ROOT} - --sonarqube ${CETLVAST_NATIVE_TEST_BINARY_DIR}/coverage.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${ALL_TEST_TRACEFILES} -) - -add_custom_target( - gcovr_sonarqube_report - DEPENDS ${CETLVAST_NATIVE_TEST_BINARY_DIR}/coverage.xml -) - -if (${CETLVAST_COVERAGE_REPORT_FORMAT} STREQUAL "html") - # Write a README to create the gcovr_html folder. - file(WRITE ${CETLVAST_NATIVE_TEST_BINARY_DIR}/gcovr_html/README.txt - "gcovr html coverage report.") - list(APPEND ALL_TESTS "${CETLVAST_NATIVE_TEST_BINARY_DIR}/gcovr_html/coverage.html") -else() - list(APPEND ALL_TESTS "${CETLVAST_NATIVE_TEST_BINARY_DIR}/coverage.xml") -endif() - -endif() # endif coverage is enabled - -# +---------------------------------------------------------------------------+ - -add_custom_target( - test_all - DEPENDS - ${ALL_TESTS} -) - -add_custom_target( - suite_all - COMMENT - "All CETL suites define this target as a default action scripts can rely on." - DEPENDS - ${ALL_TESTS} -) - -# Write a README to create the tests folder. -file(WRITE ${CETLVAST_NATIVE_TEST_BINARY_DIR}/README.txt - "All test binaries and output will appear under here.") diff --git a/cetlvast/include/cetlvast/datasets.h b/cetlvast/include/cetlvast/datasets.h new file mode 100644 index 00000000..3878901c --- /dev/null +++ b/cetlvast/include/cetlvast/datasets.h @@ -0,0 +1,239 @@ +/// @file +/// CETL VerificAtion SuiTe – Data used for testing. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETLVAST_DATASETS_H_INCLUDED +#define CETLVAST_DATASETS_H_INCLUDED + +#include + +namespace cetlvast +{ + +static constexpr std::size_t large_array_of_integers_size = 4096; +static constexpr int large_array_of_integers[large_array_of_integers_size] = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, + 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, + 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, + 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, + 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, + 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, + 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, + 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, + 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, + 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, + 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, + 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, + 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, + 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, + 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, + 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, + 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, + 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, + 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, + 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, + 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, + 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, + 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, + 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, + 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, + 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, + 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, + 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, + 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, + 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, + 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, + 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, + 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, + 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, + 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, + 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, + 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, + 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, + 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, + 1159, 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, + 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, + 1197, 1198, 1199, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, + 1216, 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, + 1235, 1236, 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, + 1254, 1255, 1256, 1257, 1258, 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, + 1273, 1274, 1275, 1276, 1277, 1278, 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, + 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, + 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, 1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, + 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, + 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, + 1368, 1369, 1370, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, + 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, + 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416, 1417, 1418, 1419, 1420, 1421, 1422, 1423, 1424, + 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, + 1444, 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, + 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 1474, 1475, 1476, 1477, 1478, 1479, 1480, 1481, + 1482, 1483, 1484, 1485, 1486, 1487, 1488, 1489, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1497, 1498, 1499, 1500, + 1501, 1502, 1503, 1504, 1505, 1506, 1507, 1508, 1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516, 1517, 1518, 1519, + 1520, 1521, 1522, 1523, 1524, 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, 1534, 1535, 1536, 1537, 1538, + 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1553, 1554, 1555, 1556, 1557, + 1558, 1559, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, + 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1594, 1595, + 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, + 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628, 1629, 1630, 1631, 1632, 1633, + 1634, 1635, 1636, 1637, 1638, 1639, 1640, 1641, 1642, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, + 1653, 1654, 1655, 1656, 1657, 1658, 1659, 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, 1670, 1671, + 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681, 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1689, 1690, + 1691, 1692, 1693, 1694, 1695, 1696, 1697, 1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709, + 1710, 1711, 1712, 1713, 1714, 1715, 1716, 1717, 1718, 1719, 1720, 1721, 1722, 1723, 1724, 1725, 1726, 1727, 1728, + 1729, 1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739, 1740, 1741, 1742, 1743, 1744, 1745, 1746, 1747, + 1748, 1749, 1750, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1761, 1762, 1763, 1764, 1765, 1766, + 1767, 1768, 1769, 1770, 1771, 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1785, + 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1794, 1795, 1796, 1797, 1798, 1799, 1800, 1801, 1802, 1803, 1804, + 1805, 1806, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, 1822, 1823, + 1824, 1825, 1826, 1827, 1828, 1829, 1830, 1831, 1832, 1833, 1834, 1835, 1836, 1837, 1838, 1839, 1840, 1841, 1842, + 1843, 1844, 1845, 1846, 1847, 1848, 1849, 1850, 1851, 1852, 1853, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, + 1862, 1863, 1864, 1865, 1866, 1867, 1868, 1869, 1870, 1871, 1872, 1873, 1874, 1875, 1876, 1877, 1878, 1879, 1880, + 1881, 1882, 1883, 1884, 1885, 1886, 1887, 1888, 1889, 1890, 1891, 1892, 1893, 1894, 1895, 1896, 1897, 1898, 1899, + 1900, 1901, 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, 1913, 1914, 1915, 1916, 1917, 1918, + 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, 1931, 1932, 1933, 1934, 1935, 1936, 1937, + 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956, + 1957, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, + 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, + 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, + 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, + 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, + 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, + 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, + 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107, 2108, + 2109, 2110, 2111, 2112, 2113, 2114, 2115, 2116, 2117, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2127, + 2128, 2129, 2130, 2131, 2132, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2146, + 2147, 2148, 2149, 2150, 2151, 2152, 2153, 2154, 2155, 2156, 2157, 2158, 2159, 2160, 2161, 2162, 2163, 2164, 2165, + 2166, 2167, 2168, 2169, 2170, 2171, 2172, 2173, 2174, 2175, 2176, 2177, 2178, 2179, 2180, 2181, 2182, 2183, 2184, + 2185, 2186, 2187, 2188, 2189, 2190, 2191, 2192, 2193, 2194, 2195, 2196, 2197, 2198, 2199, 2200, 2201, 2202, 2203, + 2204, 2205, 2206, 2207, 2208, 2209, 2210, 2211, 2212, 2213, 2214, 2215, 2216, 2217, 2218, 2219, 2220, 2221, 2222, + 2223, 2224, 2225, 2226, 2227, 2228, 2229, 2230, 2231, 2232, 2233, 2234, 2235, 2236, 2237, 2238, 2239, 2240, 2241, + 2242, 2243, 2244, 2245, 2246, 2247, 2248, 2249, 2250, 2251, 2252, 2253, 2254, 2255, 2256, 2257, 2258, 2259, 2260, + 2261, 2262, 2263, 2264, 2265, 2266, 2267, 2268, 2269, 2270, 2271, 2272, 2273, 2274, 2275, 2276, 2277, 2278, 2279, + 2280, 2281, 2282, 2283, 2284, 2285, 2286, 2287, 2288, 2289, 2290, 2291, 2292, 2293, 2294, 2295, 2296, 2297, 2298, + 2299, 2300, 2301, 2302, 2303, 2304, 2305, 2306, 2307, 2308, 2309, 2310, 2311, 2312, 2313, 2314, 2315, 2316, 2317, + 2318, 2319, 2320, 2321, 2322, 2323, 2324, 2325, 2326, 2327, 2328, 2329, 2330, 2331, 2332, 2333, 2334, 2335, 2336, + 2337, 2338, 2339, 2340, 2341, 2342, 2343, 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2351, 2352, 2353, 2354, 2355, + 2356, 2357, 2358, 2359, 2360, 2361, 2362, 2363, 2364, 2365, 2366, 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, + 2375, 2376, 2377, 2378, 2379, 2380, 2381, 2382, 2383, 2384, 2385, 2386, 2387, 2388, 2389, 2390, 2391, 2392, 2393, + 2394, 2395, 2396, 2397, 2398, 2399, 2400, 2401, 2402, 2403, 2404, 2405, 2406, 2407, 2408, 2409, 2410, 2411, 2412, + 2413, 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2421, 2422, 2423, 2424, 2425, 2426, 2427, 2428, 2429, 2430, 2431, + 2432, 2433, 2434, 2435, 2436, 2437, 2438, 2439, 2440, 2441, 2442, 2443, 2444, 2445, 2446, 2447, 2448, 2449, 2450, + 2451, 2452, 2453, 2454, 2455, 2456, 2457, 2458, 2459, 2460, 2461, 2462, 2463, 2464, 2465, 2466, 2467, 2468, 2469, + 2470, 2471, 2472, 2473, 2474, 2475, 2476, 2477, 2478, 2479, 2480, 2481, 2482, 2483, 2484, 2485, 2486, 2487, 2488, + 2489, 2490, 2491, 2492, 2493, 2494, 2495, 2496, 2497, 2498, 2499, 2500, 2501, 2502, 2503, 2504, 2505, 2506, 2507, + 2508, 2509, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2518, 2519, 2520, 2521, 2522, 2523, 2524, 2525, 2526, + 2527, 2528, 2529, 2530, 2531, 2532, 2533, 2534, 2535, 2536, 2537, 2538, 2539, 2540, 2541, 2542, 2543, 2544, 2545, + 2546, 2547, 2548, 2549, 2550, 2551, 2552, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561, 2562, 2563, 2564, + 2565, 2566, 2567, 2568, 2569, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2583, + 2584, 2585, 2586, 2587, 2588, 2589, 2590, 2591, 2592, 2593, 2594, 2595, 2596, 2597, 2598, 2599, 2600, 2601, 2602, + 2603, 2604, 2605, 2606, 2607, 2608, 2609, 2610, 2611, 2612, 2613, 2614, 2615, 2616, 2617, 2618, 2619, 2620, 2621, + 2622, 2623, 2624, 2625, 2626, 2627, 2628, 2629, 2630, 2631, 2632, 2633, 2634, 2635, 2636, 2637, 2638, 2639, 2640, + 2641, 2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 2651, 2652, 2653, 2654, 2655, 2656, 2657, 2658, 2659, + 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, 2677, 2678, + 2679, 2680, 2681, 2682, 2683, 2684, 2685, 2686, 2687, 2688, 2689, 2690, 2691, 2692, 2693, 2694, 2695, 2696, 2697, + 2698, 2699, 2700, 2701, 2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2710, 2711, 2712, 2713, 2714, 2715, 2716, + 2717, 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725, 2726, 2727, 2728, 2729, 2730, 2731, 2732, 2733, 2734, 2735, + 2736, 2737, 2738, 2739, 2740, 2741, 2742, 2743, 2744, 2745, 2746, 2747, 2748, 2749, 2750, 2751, 2752, 2753, 2754, + 2755, 2756, 2757, 2758, 2759, 2760, 2761, 2762, 2763, 2764, 2765, 2766, 2767, 2768, 2769, 2770, 2771, 2772, 2773, + 2774, 2775, 2776, 2777, 2778, 2779, 2780, 2781, 2782, 2783, 2784, 2785, 2786, 2787, 2788, 2789, 2790, 2791, 2792, + 2793, 2794, 2795, 2796, 2797, 2798, 2799, 2800, 2801, 2802, 2803, 2804, 2805, 2806, 2807, 2808, 2809, 2810, 2811, + 2812, 2813, 2814, 2815, 2816, 2817, 2818, 2819, 2820, 2821, 2822, 2823, 2824, 2825, 2826, 2827, 2828, 2829, 2830, + 2831, 2832, 2833, 2834, 2835, 2836, 2837, 2838, 2839, 2840, 2841, 2842, 2843, 2844, 2845, 2846, 2847, 2848, 2849, + 2850, 2851, 2852, 2853, 2854, 2855, 2856, 2857, 2858, 2859, 2860, 2861, 2862, 2863, 2864, 2865, 2866, 2867, 2868, + 2869, 2870, 2871, 2872, 2873, 2874, 2875, 2876, 2877, 2878, 2879, 2880, 2881, 2882, 2883, 2884, 2885, 2886, 2887, + 2888, 2889, 2890, 2891, 2892, 2893, 2894, 2895, 2896, 2897, 2898, 2899, 2900, 2901, 2902, 2903, 2904, 2905, 2906, + 2907, 2908, 2909, 2910, 2911, 2912, 2913, 2914, 2915, 2916, 2917, 2918, 2919, 2920, 2921, 2922, 2923, 2924, 2925, + 2926, 2927, 2928, 2929, 2930, 2931, 2932, 2933, 2934, 2935, 2936, 2937, 2938, 2939, 2940, 2941, 2942, 2943, 2944, + 2945, 2946, 2947, 2948, 2949, 2950, 2951, 2952, 2953, 2954, 2955, 2956, 2957, 2958, 2959, 2960, 2961, 2962, 2963, + 2964, 2965, 2966, 2967, 2968, 2969, 2970, 2971, 2972, 2973, 2974, 2975, 2976, 2977, 2978, 2979, 2980, 2981, 2982, + 2983, 2984, 2985, 2986, 2987, 2988, 2989, 2990, 2991, 2992, 2993, 2994, 2995, 2996, 2997, 2998, 2999, 3000, 3001, + 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012, 3013, 3014, 3015, 3016, 3017, 3018, 3019, 3020, + 3021, 3022, 3023, 3024, 3025, 3026, 3027, 3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038, 3039, + 3040, 3041, 3042, 3043, 3044, 3045, 3046, 3047, 3048, 3049, 3050, 3051, 3052, 3053, 3054, 3055, 3056, 3057, 3058, + 3059, 3060, 3061, 3062, 3063, 3064, 3065, 3066, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076, 3077, + 3078, 3079, 3080, 3081, 3082, 3083, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094, 3095, 3096, + 3097, 3098, 3099, 3100, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109, 3110, 3111, 3112, 3113, 3114, 3115, + 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3123, 3124, 3125, 3126, 3127, 3128, 3129, 3130, 3131, 3132, 3133, 3134, + 3135, 3136, 3137, 3138, 3139, 3140, 3141, 3142, 3143, 3144, 3145, 3146, 3147, 3148, 3149, 3150, 3151, 3152, 3153, + 3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166, 3167, 3168, 3169, 3170, 3171, 3172, + 3173, 3174, 3175, 3176, 3177, 3178, 3179, 3180, 3181, 3182, 3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, + 3192, 3193, 3194, 3195, 3196, 3197, 3198, 3199, 3200, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, + 3211, 3212, 3213, 3214, 3215, 3216, 3217, 3218, 3219, 3220, 3221, 3222, 3223, 3224, 3225, 3226, 3227, 3228, 3229, + 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3239, 3240, 3241, 3242, 3243, 3244, 3245, 3246, 3247, 3248, + 3249, 3250, 3251, 3252, 3253, 3254, 3255, 3256, 3257, 3258, 3259, 3260, 3261, 3262, 3263, 3264, 3265, 3266, 3267, + 3268, 3269, 3270, 3271, 3272, 3273, 3274, 3275, 3276, 3277, 3278, 3279, 3280, 3281, 3282, 3283, 3284, 3285, 3286, + 3287, 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, 3296, 3297, 3298, 3299, 3300, 3301, 3302, 3303, 3304, 3305, + 3306, 3307, 3308, 3309, 3310, 3311, 3312, 3313, 3314, 3315, 3316, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, + 3325, 3326, 3327, 3328, 3329, 3330, 3331, 3332, 3333, 3334, 3335, 3336, 3337, 3338, 3339, 3340, 3341, 3342, 3343, + 3344, 3345, 3346, 3347, 3348, 3349, 3350, 3351, 3352, 3353, 3354, 3355, 3356, 3357, 3358, 3359, 3360, 3361, 3362, + 3363, 3364, 3365, 3366, 3367, 3368, 3369, 3370, 3371, 3372, 3373, 3374, 3375, 3376, 3377, 3378, 3379, 3380, 3381, + 3382, 3383, 3384, 3385, 3386, 3387, 3388, 3389, 3390, 3391, 3392, 3393, 3394, 3395, 3396, 3397, 3398, 3399, 3400, + 3401, 3402, 3403, 3404, 3405, 3406, 3407, 3408, 3409, 3410, 3411, 3412, 3413, 3414, 3415, 3416, 3417, 3418, 3419, + 3420, 3421, 3422, 3423, 3424, 3425, 3426, 3427, 3428, 3429, 3430, 3431, 3432, 3433, 3434, 3435, 3436, 3437, 3438, + 3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448, 3449, 3450, 3451, 3452, 3453, 3454, 3455, 3456, 3457, + 3458, 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, 3467, 3468, 3469, 3470, 3471, 3472, 3473, 3474, 3475, 3476, + 3477, 3478, 3479, 3480, 3481, 3482, 3483, 3484, 3485, 3486, 3487, 3488, 3489, 3490, 3491, 3492, 3493, 3494, 3495, + 3496, 3497, 3498, 3499, 3500, 3501, 3502, 3503, 3504, 3505, 3506, 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3514, + 3515, 3516, 3517, 3518, 3519, 3520, 3521, 3522, 3523, 3524, 3525, 3526, 3527, 3528, 3529, 3530, 3531, 3532, 3533, + 3534, 3535, 3536, 3537, 3538, 3539, 3540, 3541, 3542, 3543, 3544, 3545, 3546, 3547, 3548, 3549, 3550, 3551, 3552, + 3553, 3554, 3555, 3556, 3557, 3558, 3559, 3560, 3561, 3562, 3563, 3564, 3565, 3566, 3567, 3568, 3569, 3570, 3571, + 3572, 3573, 3574, 3575, 3576, 3577, 3578, 3579, 3580, 3581, 3582, 3583, 3584, 3585, 3586, 3587, 3588, 3589, 3590, + 3591, 3592, 3593, 3594, 3595, 3596, 3597, 3598, 3599, 3600, 3601, 3602, 3603, 3604, 3605, 3606, 3607, 3608, 3609, + 3610, 3611, 3612, 3613, 3614, 3615, 3616, 3617, 3618, 3619, 3620, 3621, 3622, 3623, 3624, 3625, 3626, 3627, 3628, + 3629, 3630, 3631, 3632, 3633, 3634, 3635, 3636, 3637, 3638, 3639, 3640, 3641, 3642, 3643, 3644, 3645, 3646, 3647, + 3648, 3649, 3650, 3651, 3652, 3653, 3654, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662, 3663, 3664, 3665, 3666, + 3667, 3668, 3669, 3670, 3671, 3672, 3673, 3674, 3675, 3676, 3677, 3678, 3679, 3680, 3681, 3682, 3683, 3684, 3685, + 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694, 3695, 3696, 3697, 3698, 3699, 3700, 3701, 3702, 3703, 3704, + 3705, 3706, 3707, 3708, 3709, 3710, 3711, 3712, 3713, 3714, 3715, 3716, 3717, 3718, 3719, 3720, 3721, 3722, 3723, + 3724, 3725, 3726, 3727, 3728, 3729, 3730, 3731, 3732, 3733, 3734, 3735, 3736, 3737, 3738, 3739, 3740, 3741, 3742, + 3743, 3744, 3745, 3746, 3747, 3748, 3749, 3750, 3751, 3752, 3753, 3754, 3755, 3756, 3757, 3758, 3759, 3760, 3761, + 3762, 3763, 3764, 3765, 3766, 3767, 3768, 3769, 3770, 3771, 3772, 3773, 3774, 3775, 3776, 3777, 3778, 3779, 3780, + 3781, 3782, 3783, 3784, 3785, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 3794, 3795, 3796, 3797, 3798, 3799, + 3800, 3801, 3802, 3803, 3804, 3805, 3806, 3807, 3808, 3809, 3810, 3811, 3812, 3813, 3814, 3815, 3816, 3817, 3818, + 3819, 3820, 3821, 3822, 3823, 3824, 3825, 3826, 3827, 3828, 3829, 3830, 3831, 3832, 3833, 3834, 3835, 3836, 3837, + 3838, 3839, 3840, 3841, 3842, 3843, 3844, 3845, 3846, 3847, 3848, 3849, 3850, 3851, 3852, 3853, 3854, 3855, 3856, + 3857, 3858, 3859, 3860, 3861, 3862, 3863, 3864, 3865, 3866, 3867, 3868, 3869, 3870, 3871, 3872, 3873, 3874, 3875, + 3876, 3877, 3878, 3879, 3880, 3881, 3882, 3883, 3884, 3885, 3886, 3887, 3888, 3889, 3890, 3891, 3892, 3893, 3894, + 3895, 3896, 3897, 3898, 3899, 3900, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3909, 3910, 3911, 3912, 3913, + 3914, 3915, 3916, 3917, 3918, 3919, 3920, 3921, 3922, 3923, 3924, 3925, 3926, 3927, 3928, 3929, 3930, 3931, 3932, + 3933, 3934, 3935, 3936, 3937, 3938, 3939, 3940, 3941, 3942, 3943, 3944, 3945, 3946, 3947, 3948, 3949, 3950, 3951, + 3952, 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 3970, + 3971, 3972, 3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 3983, 3984, 3985, 3986, 3987, 3988, 3989, + 3990, 3991, 3992, 3993, 3994, 3995, 3996, 3997, 3998, 3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, + 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4026, 4027, + 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, + 4047, 4048, 4049, 4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061, 4062, 4063, 4064, 4065, + 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4075, 4076, 4077, 4078, 4079, 4080, 4081, 4082, 4083, 4084, + 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, 4094, 4095}; + +} // namespace cetlvast + +#endif // CETLVAST_DATASETS_H_INCLUDED diff --git a/cetlvast/include/cetlvast/helpers.hpp b/cetlvast/include/cetlvast/helpers.hpp index 0763876c..ce31f057 100644 --- a/cetlvast/include/cetlvast/helpers.hpp +++ b/cetlvast/include/cetlvast/helpers.hpp @@ -12,8 +12,45 @@ #include #include +#include -#include "gtest/gtest.h" -#include "gmock/gmock.h" +#if defined(__clang__) +# if __has_feature(cxx_rtti) +# define CETLVAST_RTTI_ENABLED +# endif +#elif defined(__GNUC__) +# if defined(__GXX_RTTI) +# define CETLVAST_RTTI_ENABLED +# endif +#elif defined(_MSC_VER) +# if defined(_CPPRTTI) +# define CETLVAST_RTTI_ENABLED +# endif +#endif + +namespace cetlvast +{ + +template +constexpr bool is_power_of_two(const T& value) +{ + return (value && !static_cast(value & (value - 1))); +} + +template +constexpr bool is_aligned(T* object, std::size_t alignment) +{ + void* object_ptr_variable = object; + std::size_t sizeof_t_variable = sizeof(T); + return (nullptr != std::align(alignment, sizeof(T), object_ptr_variable, sizeof_t_variable)); +} + +template +constexpr bool is_aligned(T* object) +{ + return is_aligned(object, alignof(T)); +} + +} // namespace cetlvast #endif // CETLVAST_HELPERS_H_INCLUDED diff --git a/cetlvast/include/cetlvast/helpers_gtest.hpp b/cetlvast/include/cetlvast/helpers_gtest.hpp new file mode 100644 index 00000000..34b7eb02 --- /dev/null +++ b/cetlvast/include/cetlvast/helpers_gtest.hpp @@ -0,0 +1,19 @@ +/// @file +/// CETL VerificAtion SuiTe – Google test helpers. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETLVAST_HELPERS_GTEST_H_INCLUDED +#define CETLVAST_HELPERS_GTEST_H_INCLUDED + +#include "cetlvast/helpers.hpp" + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + + +#endif // CETLVAST_HELPERS_GTEST_H_INCLUDED diff --git a/cetlvast/include/cetlvast/helpers_gtest_memory_resource.hpp b/cetlvast/include/cetlvast/helpers_gtest_memory_resource.hpp new file mode 100644 index 00000000..c08d5693 --- /dev/null +++ b/cetlvast/include/cetlvast/helpers_gtest_memory_resource.hpp @@ -0,0 +1,439 @@ +/// @file +/// CETL VerificAtion SuiTe – Google test helpers that includes memory_resource. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +// cSpell: words pocca pocma soccc + +#ifndef CETLVAST_HELPERS_GTEST_MEMORY_RESOURCE_H_INCLUDED +#define CETLVAST_HELPERS_GTEST_MEMORY_RESOURCE_H_INCLUDED + +#include "cetlvast/helpers_gtest.hpp" + +#include "cetl/pf17/memory_resource.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" +#if (__cplusplus >= CETL_CPP_STANDARD_17) +// As of xcode 14, Apple clang doesn't support PMR yet. It's available under +// an experimental namespace but it isn't fully integrated into Apple's +// standard library. +# include +#endif + +namespace cetlvast +{ + +/// Pf17 Mock of memory_resource. +class MockPf17MemoryResource : public cetl::pf17::pmr::memory_resource +{ +public: + MOCK_METHOD(void*, do_allocate, (std::size_t size_bytes, std::size_t alignment)); + MOCK_METHOD(void, do_deallocate, (void* p, std::size_t size_bytes, std::size_t alignment)); + MOCK_METHOD(bool, do_is_equal, (const cetl::pf17::pmr::memory_resource& rhs), (const, noexcept)); + + static constexpr bool ReturnsNullWhenFNoExceptions = true; + + static cetl::pf17::pmr::memory_resource* get() + { + return cetl::pf17::pmr::null_memory_resource(); + } +}; + +#if (__cplusplus >= CETL_CPP_STANDARD_17) +/// Std Mock of memory_resource. +class MockStdMemoryResource : public std::pmr::memory_resource +{ +public: + MOCK_METHOD(void*, do_allocate, (std::size_t size_bytes, std::size_t alignment)); + MOCK_METHOD(void, do_deallocate, (void* p, std::size_t size_bytes, std::size_t alignment)); + MOCK_METHOD(bool, do_is_equal, (const std::pmr::memory_resource& rhs), (const, noexcept)); + + static constexpr bool ReturnsNullWhenFNoExceptions = false; + + static std::pmr::memory_resource* get() + { + return std::pmr::null_memory_resource(); + } +}; +#endif + +// +----------------------------------------------------------------------+ + +/// Memory Resource Helper (MRH) +/// Support for parameterized tests that use both std::pmr::memory_resource and cetl::pf17::pmr::memory_resource. +struct MRH final +{ +#if (__cplusplus >= CETL_CPP_STANDARD_17) + + template + constexpr static std::enable_if_t::value, std::pmr::memory_resource*> + null_memory_resource() noexcept + { + return std::pmr::null_memory_resource(); + } + + template + using MockMemoryResourceType = std::conditional_t< + std::is_base_of::value, + MockPf17MemoryResource, + std::conditional_t::value, MockStdMemoryResource, void>>; + +#else + template + using MockMemoryResourceType = + std::conditional_t::value, MockPf17MemoryResource, void>; +#endif + + template + constexpr static std::enable_if_t::value, + cetl::pf17::pmr::memory_resource*> + null_memory_resource() noexcept + { + return cetl::pf17::pmr::null_memory_resource(); + } + + MRH() = delete; +}; + +// +----------------------------------------------------------------------+ +// | TYPED CONTAINER ALLOCATOR FACTORIES +// +----------------------------------------------------------------------+ + +/// Creates std::allocator instances +struct DefaultAllocatorFactory +{ + DefaultAllocatorFactory() = delete; + + template + using allocator_type = std::allocator; + + template + static allocator_type make_allocator() + { + return std::allocator{}; + } + + template + static void reset() + { + // no-op + } +}; + +// +---------------------------------------------------------------------------+ + +/// Creates cetl::pf17::pmr::polymorphic_allocator instances that use new and delete +struct PolymorphicAllocatorNewDeleteFactory +{ + PolymorphicAllocatorNewDeleteFactory() = delete; + + template + using allocator_type = cetl::pf17::pmr::polymorphic_allocator; + + template + static allocator_type make_allocator() + { + return allocator_type{cetl::pf17::pmr::new_delete_resource()}; + } + + template + static void reset() + { + // no-op + } +}; + +// +---------------------------------------------------------------------------+ + +/// Creates cetl::pf17::pmr::polymorphic_allocator instances that a monotonic buffer +/// with new and delete when the buffer is exhausted. +template +struct PolymorphicAllocatorNewDeleteBackedMonotonicFactory +{ + template + using allocator_type = cetl::pf17::pmr::polymorphic_allocator; + + template + struct MonotonicArray + { + std::array storage; + cetl::pf17::pmr::monotonic_buffer_resource resource{storage.data(), + storage.size(), + cetl::pf17::pmr::new_delete_resource()}; + }; + + template + static std::vector>>& buffers() + { + static std::vector>> buffers; + return buffers; + } + + template + static allocator_type make_allocator() + { + auto& buffers_ref = buffers(); + buffers_ref.push_back(std::make_unique>()); + return allocator_type{&buffers_ref.back()->resource}; + } + + template + static void reset() + { + buffers().clear(); + } +}; + +// +---------------------------------------------------------------------------+ +// | TYPED CONTAINER ALLOCATOR PROTOCOL +// +---------------------------------------------------------------------------+ + +/// Pretty-typing for parameterized tests that use allocator factories. +/// +/// Given an allocator factory type, creates a type for parameterized unit-tests that both defines the allocator type as +/// allocator_type and provides a static make_allocator() method that delegates to the allocator factory's +/// make_allocator method. A static reset() method is also provided which delegates to the allocator factory's reset +/// method. +template +struct AllocatorTypeParamDef +{ + AllocatorTypeParamDef() = delete; + + using allocator_type = typename AllocatorFactoryType::template allocator_type; + using allocator_factory = AllocatorFactoryType; + + static constexpr allocator_type make_allocator() + { + return allocator_factory::template make_allocator(); + } + + static constexpr void reset() + { + allocator_factory::template reset(); + } +}; + +// +---------------------------------------------------------------------------+ +// | INSTRUMENTED ALLOCATOR +// +---------------------------------------------------------------------------+ +/// Used by InstrumentedNewDeleteAllocator to track allocations and deallocations. +struct InstrumentedAllocatorStatistics +{ +private: + InstrumentedAllocatorStatistics() + : outstanding_allocated_memory{0} + , allocations{0} + , deallocations{0} + , allocated_bytes{0} + , deallocated_bytes{0} + , last_allocation_size_bytes{0} + , last_deallocation_size_bytes{0} + { + } + + InstrumentedAllocatorStatistics& operator=(const InstrumentedAllocatorStatistics&) = default; + InstrumentedAllocatorStatistics& operator=(InstrumentedAllocatorStatistics&&) = default; + +public: + InstrumentedAllocatorStatistics(const InstrumentedAllocatorStatistics&) = delete; + InstrumentedAllocatorStatistics(InstrumentedAllocatorStatistics&&) = delete; + + std::size_t outstanding_allocated_memory; + std::size_t allocations; + std::size_t deallocations; + std::size_t allocated_bytes; + std::size_t deallocated_bytes; + std::size_t last_allocation_size_bytes; + std::size_t last_deallocation_size_bytes; + + static ::testing::AssertionResult subtract_or_assert(std::size_t& lhs, const std::size_t rhs) + { + if (rhs > lhs) + { + return ::testing::AssertionFailure() << "Attempted to subtract " << rhs << " from " << lhs << std::endl; + } + lhs -= rhs; + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult record_deallocation(const std::size_t amount_bytes) + { + subtract_or_assert(outstanding_allocated_memory, amount_bytes) + << "Attempted to deallocate " << deallocated_bytes << " bytes, but only " << outstanding_allocated_memory + << " bytes were allocated." << std::endl; + deallocations += 1; + deallocated_bytes += amount_bytes; + last_deallocation_size_bytes = amount_bytes; + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult record_allocation(const std::size_t amount_bytes) + { + outstanding_allocated_memory += amount_bytes; + allocations += 1; + allocated_bytes += amount_bytes; + last_allocation_size_bytes = amount_bytes; + return ::testing::AssertionSuccess(); + } + + static InstrumentedAllocatorStatistics& get() + { + static InstrumentedAllocatorStatistics stats; + return stats; + } + + static void reset() + { + get() = InstrumentedAllocatorStatistics{}; + } +}; + +/// Allocator that uses the standard heap but which can mimic the behavior of +/// polymorphic allocators. This allocator also collects statistics about +/// allocations and deallocations. +/// @tparam IsAlwaysEqual Mimic the is_always_equal property of polymorphic allocators. +/// @tparam IsEqual Pretend to be equal if IsAlwaysEqual is false. +/// @tparam IsPropOnMove Mimic the propagate_on_container_move_assignment property of polymorphic allocators. +/// @tparam IsPropOnCopy Mimic the propagate_on_container_copy_assignment property of polymorphic allocators. +template +struct InstrumentedNewDeleteAllocator +{ + using value_type = T; + using pointer = T*; + using size_type = std::size_t; + + InstrumentedNewDeleteAllocator() + : is_invalid{false} + , was_from_soccc{false} + , allocated_bytes{0} + { + } + + InstrumentedNewDeleteAllocator(const InstrumentedNewDeleteAllocator& rhs, + bool is_soccc) noexcept(IsPropOnCopy::value) + : is_invalid{false} + , was_from_soccc{is_soccc} + , allocated_bytes{0} + { + (void) rhs; + } + + InstrumentedNewDeleteAllocator(const InstrumentedNewDeleteAllocator& rhs) noexcept(IsPropOnCopy::value) + : InstrumentedNewDeleteAllocator(rhs, rhs.was_from_soccc) + { + } + + InstrumentedNewDeleteAllocator(InstrumentedNewDeleteAllocator&& rhs) noexcept + : is_invalid{rhs.is_invalid} + , was_from_soccc{rhs.was_from_soccc} + , allocated_bytes{rhs.allocated_bytes} + { + rhs.allocated_bytes = 0; + rhs.is_invalid = true; + } + + ~InstrumentedNewDeleteAllocator() {} + + InstrumentedNewDeleteAllocator& operator=(const InstrumentedNewDeleteAllocator& rhs) noexcept(IsPropOnCopy::value) + { + EXPECT_FALSE(rhs.is_invalid) << "Attempted to copy from an invalid allocator." << std::endl; + EXPECT_FALSE(is_invalid) << "Attempted to copy to an invalid allocator." << std::endl; + if (!IsAlwaysEqual::value && !IsEqual::value) + { + EXPECT_EQ(allocated_bytes, 0) << "leaked " << allocated_bytes << " bytes in copy assignment." << std::endl; + } + allocated_bytes = rhs.allocated_bytes; + return *this; + } + + InstrumentedNewDeleteAllocator& operator=(InstrumentedNewDeleteAllocator&& rhs) noexcept(IsPropOnMove::value) + { + EXPECT_FALSE(rhs.is_invalid) << "Attempted to move from an invalid allocator." << std::endl; + EXPECT_FALSE(is_invalid) << "Attempted to move to an invalid allocator." << std::endl; + if (IsPropOnMove::value) + { + allocated_bytes += rhs.allocated_bytes; + } + else + { + EXPECT_TRUE(IsAlwaysEqual::value || IsEqual::value) + << "Attempted to move from an allocator that is neither equal nor marked for propagation on move." + << std::endl; + EXPECT_EQ(allocated_bytes, 0) << "leaked " << allocated_bytes << " bytes in move assignment." << std::endl; + allocated_bytes = rhs.allocated_bytes; + } + rhs.allocated_bytes = 0; + rhs.is_invalid = true; + return *this; + } + + bool operator==(const InstrumentedNewDeleteAllocator& rhs) + { + (void) rhs; + return (IsAlwaysEqual::value || IsEqual::value); + } + + bool operator!=(const InstrumentedNewDeleteAllocator& rhs) + { + (void) rhs; + return (!IsAlwaysEqual::value && !IsEqual::value); + } + + using is_always_equal = IsAlwaysEqual; + using is_equal = IsEqual; + using propagate_on_container_move_assignment = IsPropOnMove; + using propagate_on_container_copy_assignment = IsPropOnCopy; + + InstrumentedNewDeleteAllocator select_on_container_copy_construction() const + { + return InstrumentedNewDeleteAllocator(*this, true); + } + + template + struct rebind + { + typedef InstrumentedNewDeleteAllocator other; + }; + + pointer allocate(size_type n, const void* hint = nullptr) + { + EXPECT_FALSE(is_invalid) << "Attempted to allocate from an invalid allocator." << std::endl; + (void) hint; + const std::size_t bytes_to_allocate = (n * sizeof(T)); + InstrumentedAllocatorStatistics::get().record_allocation(bytes_to_allocate); + allocated_bytes += bytes_to_allocate; + return reinterpret_cast(::operator new(bytes_to_allocate)); + } + + void deallocate(T* p, std::size_t n) + { + EXPECT_FALSE(is_invalid) << "Attempted to deallocate from an invalid allocator." << std::endl; + const std::size_t bytes_to_deallocate = (n * sizeof(T)); + allocated_bytes -= bytes_to_deallocate; + InstrumentedAllocatorStatistics::get().record_deallocation(bytes_to_deallocate); + ::operator delete(p); + } + + template + void construct(U* p, Args&&... args) + { + EXPECT_FALSE(is_invalid) << "Attempted to construct from an invalid allocator." << std::endl; + ::new (static_cast(p)) U(std::forward(args)...); + } + + bool is_invalid; + const bool was_from_soccc; + +private: + std::size_t allocated_bytes; +}; + +} // namespace cetlvast + +#endif // CETLVAST_HELPERS_GTEST_MEMORY_RESOURCE_H_INCLUDED diff --git a/cetlvast/suites/compile/CMakeLists.txt b/cetlvast/suites/compile/CMakeLists.txt new file mode 100644 index 00000000..115b4c3c --- /dev/null +++ b/cetlvast/suites/compile/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# +cmake_minimum_required(VERSION 3.22.0) + +project(cetlvast_compile_tests CXX) + +include(CompileTest) + +file(GLOB COMPILE_TESTS + LIST_DIRECTORIES false + CONFIGURE_DEPENDS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp +) + +set(ALL_TESTS_BUILD "") +set(ALL_TESTS_RUN "") + +foreach(COMPILE_TEST ${COMPILE_TESTS}) + define_compile_failure_test(TEST_SOURCE ${COMPILE_TEST} + EXTRA_TEST_LIBS cetl + OUT_TEST_BUILD_TARGET COMPILE_TEST_BUILD_TARGET + OUT_TEST_PRECHECK_TARGET COMPILE_TEST_PRECHECK_TARGET) + list(APPEND ALL_TESTS_BUILD "${COMPILE_TEST_BUILD_TARGET}") + list(APPEND ALL_TESTS_RUN "${COMPILE_TEST_PRECHECK_TARGET}") +endforeach() + +add_custom_target( + build_compile_tests + DEPENDS ${ALL_TESTS_BUILD} +) + +add_custom_target( + run_compile_precheck_tests + DEPENDS ${ALL_TESTS_RUN} +) diff --git a/cetlvast/suites/docs/CMakeLists.txt b/cetlvast/suites/docs/CMakeLists.txt new file mode 100644 index 00000000..67b6d79d --- /dev/null +++ b/cetlvast/suites/docs/CMakeLists.txt @@ -0,0 +1,145 @@ +# +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +# +---------------------------------------------------------------------------+ +# | PROJECT +# +---------------------------------------------------------------------------+ + +project(cetlvast_docs C CXX) +add_subdirectory(examples) + +# +---------------------------------------------------------------------------+ +# | DOXYGEN +# +---------------------------------------------------------------------------+ + +find_package(Doxygen REQUIRED) +find_program(TAR tar) + +# :function: create_docs_target +# Create a target that generates documentation. +# +# :param str ARG_DOCS_DOXY_ROOT: The folder under which all doxygen configuration, templates, +# styles, and examples can be found. +# :param str ARG_OPEN_CYPHAL_PROJECT_NAME: The name of the org.opencyphal project this documentation is for. +# :param str ARG_PROJECT_VERSION A string that contains the version of the cyphal project to display +# in the generated documentation. +# :param str ARG_OPEN_CYPHAL_PROJECT_BRIEF: A one-line description of the project to be included in the generated +# documents. +# :param bool ARG_W_ERROR If true then warnings are treated as errors. +# :param path ARG_EXAMPLES_PATH: A path to example code to be included in the docs. +# :param path ARG_MAIN_PAGE_PATH: A file to use as the main page (e.g. ${PROJECT_ROOT}/README.md). +# :param list[path] ARG_INPUT_LIST A list of source files to generate documentation for. +# :param list[str] ARG_DEPENDS: A list of dependencies the docs build should take. +# +# :return : A build target named ${ARG_OPEN_CYPHAL_PROJECT_NAME}_docs is defined. +# +function (create_docs_target ARG_DOCS_DOXY_ROOT + ARG_OPEN_CYPHAL_PROJECT_NAME + ARG_PROJECT_VERSION + ARG_OPEN_CYPHAL_PROJECT_BRIEF + ARG_W_ERROR + ARG_EXAMPLES_PATH + ARG_MAIN_PAGE_PATH + ARG_INPUT_LIST + ARG_DEPENDS) + + set(DOXYGEN_SOURCE ${ARG_DOCS_DOXY_ROOT}) + set(DOXYGEN_RDOMAIN org.opencyphal) + set(DOXYGEN_RDOMAIN_W_PROJECT org.opencyphal.${ARG_OPEN_CYPHAL_PROJECT_NAME}) + set(DOXYGEN_PROJECT_NAME "${ARG_OPEN_CYPHAL_PROJECT_NAME}") + set(DOXYGEN_PROJECT_VERSION "${ARG_PROJECT_VERSION}") + set(DOXYGEN_PROJECT_BRIEF "${ARG_OPEN_CYPHAL_PROJECT_BRIEF}") + if (${ARG_W_ERROR}) + set(DOXYGEN_WARN_AS_ERROR "YES") + else() + set(DOXYGEN_WARN_AS_ERROR "NO") + endif() + set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(DOXYGEN_CONFIG_FILE ${DOXYGEN_OUTPUT_DIRECTORY}/doxygen.config) + set(DOXYGEN_EXAMPLE_PATH ${ARG_EXAMPLES_PATH}) + + list(APPEND DOXYGEN_INPUT_LIST ${ARG_INPUT_LIST}) + list(JOIN DOXYGEN_INPUT_LIST "\\\n " DOXYGEN_INPUT ) + set(DOXYGEN_MAINPAGE "${ARG_MAIN_PAGE_PATH}") + set(DOXYGEN_OUTPUT_TAGFILE "${DOXYGEN_OUTPUT_DIRECTORY}/${ARG_OPEN_CYPHAL_PROJECT_NAME}.tag.xml") + set(DOXYGEN_COMPILE_COMMANDS_DIR ${CMAKE_BINARY_DIR}) + + # +-----------------------------------------------------------------------+ + # | HTML + # +-----------------------------------------------------------------------+ + set(DOXYGEN_HTML_EXTRA_FILES "${DOXYGEN_SOURCE}/.nojekyll") + set(DOXYGEN_HTML_STYLESHEET ${DOXYGEN_OUTPUT_DIRECTORY}/stylesheet.css) + set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_OUTPUT_DIRECTORY}/doxygen-flat-style.css) + set(DOXYGEN_HTML_HEADER ${DOXYGEN_OUTPUT_DIRECTORY}/header.html) + set(DOXYGEN_HTML_FOOTER ${DOXYGEN_OUTPUT_DIRECTORY}/footer.html) + set(DOXYGEN_IMAGE_PATH ${DOXYGEN_SOURCE}/images) + set(DOXYGEN_LOGO ${DOXYGEN_SOURCE}/images/html/opencyphal_logo.svg) + set(DOXYGEN_TAGFILES "${DOXYGEN_SOURCE}/cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/") + + # a bit of a hack, but we copy all the images to the output to allow us to serve them + # for anything other that the docs that wants them. + file(COPY ${DOXYGEN_SOURCE}/images/html DESTINATION ${DOXYGEN_OUTPUT_DIRECTORY}) + + configure_file(${DOXYGEN_SOURCE}/header.html + ${DOXYGEN_OUTPUT_DIRECTORY}/header.html + ) + configure_file(${DOXYGEN_SOURCE}/footer.html + ${DOXYGEN_OUTPUT_DIRECTORY}/footer.html + ) + configure_file(${DOXYGEN_SOURCE}/stylesheet.css + ${DOXYGEN_OUTPUT_DIRECTORY}/stylesheet.css + ) + configure_file(${DOXYGEN_SOURCE}/doxygen-flat-style.css + ${DOXYGEN_OUTPUT_DIRECTORY}/doxygen-flat-style.css + ) + configure_file(${DOXYGEN_SOURCE}/doxygen.ini + ${DOXYGEN_CONFIG_FILE} + ) + add_custom_command(OUTPUT ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_FILE} + DEPENDS + ${ARG_DEPENDS} + ${DOXYGEN_CONFIG_FILE} + ${DOXYGEN_INPUT_LIST} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating intermediate documentation." + ) + add_custom_target(generate_${ARG_OPEN_CYPHAL_PROJECT_NAME}_docs DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html) + + if (TAR) + add_custom_command(OUTPUT ${DOXYGEN_OUTPUT_DIRECTORY}/html.gz + COMMAND ${TAR} -vzcf html.gz ./html + DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html/index.html + WORKING_DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY} + COMMENT "Creating html tarball." + ) + + add_custom_target(archive_${ARG_OPEN_CYPHAL_PROJECT_NAME}_docs DEPENDS ${DOXYGEN_OUTPUT_DIRECTORY}/html.gz) + endif() + +endfunction(create_docs_target) + +file(GLOB_RECURSE DOXYGEN_INPUT_LIST + LIST_DIRECTORIES false + CONFIGURE_DEPENDS + ${CETL_ROOT}/include/**/*.hpp +) + +get_property(LOCAL_EXAMPLES DIRECTORY "examples" PROPERTY IN_BUILD_TESTS) + +list(TRANSFORM LOCAL_EXAMPLES PREPEND "examples/") + +create_docs_target( ${CMAKE_CURRENT_SOURCE_DIR} + "CETL" + ${CETL_VERSION} + "Cyphal Embedded Template Library" + OFF + ${CMAKE_CURRENT_SOURCE_DIR}/examples + "${CETL_ROOT}/README.md" + "${DOXYGEN_INPUT_LIST}" + "${LOCAL_EXAMPLES}" +) diff --git a/cetlvast/suites/docs/doxygen-flat-style.css b/cetlvast/suites/docs/doxygen-flat-style.css new file mode 100644 index 00000000..d33f61a1 --- /dev/null +++ b/cetlvast/suites/docs/doxygen-flat-style.css @@ -0,0 +1,638 @@ +/* This doxygen theme is free to use. If you like this, please Star https://github.com/kcwongjoe/doxygen_theme_flat_design */ + +/* Color Pattern. You can change this pattern to design your theme. */ + +:root { + /* Content */ + --bgcolor: #ffffff; + --bgfont: #303030; + --bgfont2: #fff2c0; + --bgfont-hover: #c5a52e; + --bgfont-hover-text-decoration: none; + --bgborder: #7d7d7d; + --bgborder2: #f6f6f6; + /* Main Header */ + --bg1color: #303030; + --bg1font: #ffffff; + --bg1font2: #fff2c0; + /* Second header */ + --bg2color: #E2E2E2; + --bg2font: #7D7D7D; + --bg2-hover-bg: #ffffff; + --bg2-hover-font: #303030; + --bg2-hover-topborder: #fff2c0; + /* Third header */ + --bg3color: #f6f6f6; + --bg3font: #303030; + --bg3font2: #7D7D7D; + /* Code */ + --code-bg: #f6f6f6; + --code-comment: #7D7D7D; + --code-keyword: #d73a49; + --code-preprocessor: #d73a49; + --code-keywordtype: #d73a49; + --code-text: #303030; + --code-code: #6f42c1; + --code-line: #7D7D7D; + --code-line-bg: #D8D8D8; + /* Namespace List, Class List icon */ + --icon-bg: #303030; + --icon-font: #fff2c0; + /* Class Index */ + --qindex-menu-bg: #303030; + --qindex-menu-font: #ffffff; + --qindex-menu-font-hover: #fff2c0; + --qindex-icon-bg: #ffefaf; + --qindex-icon-font: #303030; + /* Member table */ + --mem-title-bg: #303030; + --mem-title-font: #ffffff; + --mem-subtitle-bg: #fff2c0; + --mem-subtitle-font: #303030; + --mem-subtitle-font-hover: #303030; + --mem-content-bg: #ffffff; + --mem-content-font: #303030; + --mem-content-border: #f6f6f6; + /* Nav Tree */ + --nav-tree-bg: #E2E2E2; + --nav-tree-bg-hover: #ffffff; + --nav-tree-font: #7D7D7D; + --nav-tree-font-hover: #303030; + --nav-tree-bg-selected: #fff2c0; + --nav-tree-font-selected: #303030; +} + +body, table, div, p, dl { + color: var(--bgfont); + background-color: var(--bgcolor); + line-height: 150%; + font: 14px/22px, Roboto, Arial; +} + +div.contents { + margin: 20px 40px; +} + +div.contents ul { + line-height: 200%; +} + +/***********************************/ + +/********** Project header *********/ + +/***********************************/ + +#titlearea { + border-bottom: none; + padding-bottom: 20px; + padding-top: 20px; +} + +#titlearea, #titlearea * { + color: var(--bg1font); + background-color: var(--bg1color); +} + +#projectname { + padding: 0px 40px !important; +} + +#projectbrief { + padding: 0px 40px !important; +} + +#projectalign { + padding: 0px !important; +} + +/***********************************/ + +/************ Main Menu ************/ + +/***********************************/ + +/* Margin */ + +#main-menu { + padding: 0px 30px; +} + +#main-menu a, #main-menu a:hover { + padding-top: 10px; + padding-bottom: 10px; +} + +/* Menu button */ + +#main-menu li a { + background-image: none; + font-family: Arial; + text-transform: uppercase; + text-shadow: none; + font-size: 14px; + font-weight: 700; +} + +#main-menu, #main-menu>li>a { + background-image: none; + background-color: var(--bg2color); + color: var(--bg2font); + transition: 0.2s; +} + +/* hover Effect */ + +#main-menu>li { + border-top: 5px solid var(--bg2color); +} + +#main-menu>li:hover { + color: var(--bg2-hover-font); + background-color: var(--bg2-hover-bg); + border-top: 5px solid var(--bg2-hover-topborder); +} + +#main-menu>li:hover, #main-menu>li>a:hover, #main-menu>li>a.highlighted { + color: var(--bg2-hover-font); + background-color: var(--bg2-hover-bg); +} + +/* Search Bar */ + +#MSearchBox { + border-radius: 0; + box-shadow: none; +} + +#MSearchBox>span { + margin: 10px; +} + +#main-menu>li:last-child { + padding: 25px 0px; +} + +/* Reset search hover color*/ + +#main-menu>li:last-child:hover { + color: var(--bg2font); + background-color: var(--bg2color); + border-top: 5px solid var(--bg2color); +} + +#MSearchResultsWindow { + border: 1px solid var(--bg3font2); + background-color: var(--bg3color); + padding: 10px; +} + +body.SRPage, body.SRPage * { + font-family: Arial; +} + +/* Sub Menu */ + +#main-menu>li ul { + transition: max-height 0.2s ease-in-out; + padding: 0px; + border-radius: 0px !important; +} + +#main-menu>li ul:before, #main-menu>li ul:after { + border-width: 0px; +} + +#main-menu>li>ul li a, #main-menu>li>ul li { + background-color: var(--bgcolor); + color: var(--bgfont); + background-image: none; +} + +#main-menu>li>ul li a:hover, #main-menu>li>ul li:hover { + background-color: var(--bgfont2); + color: var(--bgfont); +} + +/***********************************/ + +/************** Header *************/ + +/***********************************/ + +div.headertitle { + padding: 5px 40px; +} + +div.header, div.header * { + color: var(--bg3font); + background-color: var(--bg3color); + border-bottom: none; +} + +div.summary { + padding-right: 40px; +} + +/***********************************/ + +/************** Link *************/ + +/***********************************/ + +a, a:visited, a:active, .contents a:visited, body.SRPage a, body.SRPage a:visited, body.SRPage a:active { + color: var(--bgfont); + text-decoration: none; +} + +a:hover, .contents a:hover, body.SRPage a:hover { + color: var(--bgfont-hover); + text-decoration: var(--bgfont-hover-text-decoration); +} + +/***********************************/ + +/************ Nav-path ************/ + +/***********************************/ + +#nav-path, #nav-path ul { + background-image: none; +} + +#nav-path ul { + padding: 5px 30px; +} + +#nav-path, #nav-path * { + color: var(--bg3font2); + background-color: var(--bg3color); + border: none; + font-family: Arial; +} + +li.navelem { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnN2Z2pzPSJodHRwOi8vc3ZnanMuY29tL3N2Z2pzIiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDI5Mi4zNTkgMjkyLjM1OSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEyIDUxMiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgY2xhc3M9IiI+PGc+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+Cgk8cGF0aCBkPSJNMjIyLjk3OSwxMzMuMzMxTDk1LjA3Myw1LjQyNEM5MS40NTYsMS44MDcsODcuMTc4LDAsODIuMjI2LDBjLTQuOTUyLDAtOS4yMzMsMS44MDctMTIuODUsNS40MjQgICBjLTMuNjE3LDMuNjE3LTUuNDI0LDcuODk4LTUuNDI0LDEyLjg0N3YyNTUuODEzYzAsNC45NDgsMS44MDcsOS4yMzIsNS40MjQsMTIuODQ3YzMuNjIxLDMuNjE3LDcuOTAyLDUuNDI4LDEyLjg1LDUuNDI4ICAgYzQuOTQ5LDAsOS4yMy0xLjgxMSwxMi44NDctNS40MjhsMTI3LjkwNi0xMjcuOTA3YzMuNjE0LTMuNjEzLDUuNDI4LTcuODk3LDUuNDI4LTEyLjg0NyAgIEMyMjguNDA3LDE0MS4yMjksMjI2LjU5NCwxMzYuOTQ4LDIyMi45NzksMTMzLjMzMXoiIGZpbGw9IiM3ZDdkN2QiIGRhdGEtb3JpZ2luYWw9IiMwMDAwMDAiIHN0eWxlPSIiIGNsYXNzPSIiPjwvcGF0aD4KPC9nPgo8ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8L2c+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjwvZz4KPGcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPC9nPgo8ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8L2c+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjwvZz4KPGcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPC9nPgo8ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8L2c+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjwvZz4KPGcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPC9nPgo8ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8L2c+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjwvZz4KPGcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPC9nPgo8ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8L2c+CjxnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjwvZz4KPGcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPC9nPgo8L2c+PC9zdmc+); + background-size: 9px; +} + +li.navelem a { + margin-right: 20px; +} + +/***********************************/ + +/*************** mem ***************/ + +/***********************************/ + +.memtitle { + padding: 15px; + margin-top: 30px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.memtitle, .memtitle *, .memtitle a:visited { + border: none; + background-image: none; + color: var(--mem-title-font); + background-color: var(--mem-title-bg); +} + +.memproto { + padding: 10px; + text-shadow: none; + padding: 20px; + border-top-right-radius: 0px; + -moz-border-radius-topright: 0px; + -webkit-border-top-right-radius: 0px; +} + +.memproto, .memproto *, .memproto a:visited { + border: none; + background-image: none; + background-color: var(--mem-subtitle-bg); + color: var(--mem-subtitle-font); + font-size: inherit; + line-height: 100% +} + +.memproto a:hover { + color: var(--mem-subtitle-font-hover); +} + +.memdoc { + border-bottom: 1px solid var(--mem-content-border); + border-left: 1px solid var(--mem-content-border); + border-right: 1px solid var(--mem-content-border); + background-color: var(--mem-content-bg); + color: var(--mem-content-font); + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-bottomleft: 0px; + -moz-border-radius-bottomright: 0px; + -webkit-border-bottom-left-radius: 0px; + -webkit-border-bottom-right-radius: 0px; +} + +.memdoc p, .memdoc dt { + padding: 0px 20px; +} + +/***********************************/ + +/************* Contents ************/ + +/***********************************/ + +a.anchor { + padding-top: 20px; +} + +/***********************************/ + +/************* fragment ************/ + +/***********************************/ + +h2.groupheader { + color: #303030; + font-size: 200%; + font-weight: bold; + border-bottom: none; + padding-top: 20px; + padding-bottom: 20px; +} + +div.fragment, pre.fragment { + border: none; + padding: 20px; + margin: none; + background-color: var(--code-bg); +} + +div.line { + background-color: var(--code-bg); +} + +span.comment { + color: var(--code-comment); +} + +span.keyword { + color: var(--code-keyword); +} + +span.preprocessor { + color: var(--code-preprocessor); +} + +span.keywordtype { + color: var(--code-keywordtype); +} + +span.mlabel { + background-color: var(--code-text); + color: var(--code-bg); + border-top: none; + border-left: none; + border-right: none; + border-bottom: none; + padding: 10px; + border-radius: 0px; +} + +a.code { + color: var(--code-code); +} + +span.lineno, span.lineno>* { + color: var(--code-line); + border-right: none; + background-color: var(--code-bg); +} + +span.lineno a { + background-color: var(--code-line-bg); +} + +span.lineno a:hover { + color: var(--bg3font); + background-color: var(--code-line-bg); +} + +/***********************************/ + +/************* directory ***********/ + +/***********************************/ + +.directory tr.even { + background-color: inherit; +} + +.iconfclosed { + background-image: url(closed-folder.png); + margin-right: 10px; +} + +.iconfopen { + background-image: url(opened-folder.png); + margin-right: 10px; +} + +.icondoc { + background-image: url(document.png); + margin-right: 10px; +} + +.arrow { + color: #7d7d7d; +} + +.icona { + vertical-align: middle; + margin-right: 5px; +} + +.icon { + background-color: var(--icon-bg); + color: var(--icon-font); + display: table-cell; + vertical-align: middle; + height: 20px; + width: 20px; +} + +div.ah { + background-color: var(--qindex-icon-bg); + color: var(--qindex-icon-font); + text-align: center; + background-image: none; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-border-radius: 0px; + border-radius: 0px; + border: none; +} + +div.qindex { + background-color: var(--qindex-menu-bg); + border: none; + padding: 20px; +} + +a.qindex { + color: var(--qindex-menu-font); + font-weight: normal; + font-size: 20px; +} + +a:hover.qindex { + color: var(--qindex-menu-font-hover); +} + +a:visited.qindex { + color: var(--qindex-menu-font); +} + +table.classindex { + margin-top: 30px; + margin-bottom: 30px; +} + +table.classindex a.el { + font-weight: normal; +} + +/***********************************/ + +/************** footer *************/ + +/***********************************/ + +div.directory { + border-top: 1px solid var(--bgborder); + border-bottom: none; + margin: 20px 0px; +} + +div.directory a.el { + font-weight: normal; +} + +div.directory>table { + margin: 20px 0px; +} + +hr.footer { + border: none; +} + +.contents>hr { + border-top: 0px; +} + +/***********************************/ + +/*********** memberdecls ***********/ + +/***********************************/ + +.memItemLeft, .memItemRight { + padding: 15px 30px; + background-color: inherit; +} + +.mdescRight { + padding: 0px 30px 10px 30px; +} + +.memberdecls * { + background-color: inherit; +} + +.memSeparator { + border-bottom: 1px solid var(--bgborder2); +} + +.memTemplParams { + color: var(--bgfont); +} + +/***********************************/ + +/*********** nav-tree ***********/ + +/***********************************/ + +#nav-tree-contents { + background-color: var(--nav-tree-bg); + margin: 0px; +} + +#side-nav, #nav-tree { + background-image: none; + background-color: var(--nav-tree-bg); +} + +#nav-tree .item { + background-color: var(--nav-tree-bg); + font-family: Arial; + text-transform: uppercase; + text-shadow: none; + font-size: 14px; + font-weight: 700; + padding: 10px; + color: var(--nav-tree-font); +} + +#nav-tree .arrow { + color: var(--nav-tree-font); +} + +#nav-tree .selected { + background-image: none; + background-color: var(--nav-tree-bg-selected); +} + +#nav-tree .selected a { + color: var(--nav-tree-font-selected); +} + +#nav-tree .item:hover { + background-color: var(--nav-tree-bg-hover); + color: var(--nav-tree-font-hover); +} + +#nav-tree .item a:hover { + color: var(--nav-tree-font-hover); +} + +#side-nav .ui-resizable-e { + background-image: none; + background-color: var(--nav-tree-bg); +} + +#nav-sync { + background-color: transparent; +} + +#nav-sync>img { + content: url(off_sync.png); +} + +#nav-sync.sync>img { + content: url(on_sync.png); +} + +/***********************************/ + +/*********** Plant UML ***********/ + +/***********************************/ + +.plantumlgraph > img { + width: 80%; +} diff --git a/cetlvast/suites/docs/doxygen.ini b/cetlvast/suites/docs/doxygen.ini index d70ac894..f6633e93 100644 --- a/cetlvast/suites/docs/doxygen.ini +++ b/cetlvast/suites/docs/doxygen.ini @@ -48,7 +48,7 @@ PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = @DOXYGEN_CETLVAST_VERSION@ +PROJECT_NUMBER = @DOXYGEN_PROJECT_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -193,7 +193,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = @DOXYGEN_CETLVAST_INCLUDE_PREFIX_STRIP@ +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -833,7 +833,7 @@ WARNINGS = YES # will automatically be disabled. # The default value is: YES. -WARN_IF_UNDOCUMENTED = YES +WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in @@ -1254,7 +1254,7 @@ CLANG_ADD_INC_PATHS = NO # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. -CLANG_OPTIONS = @DOXYGEN_CLANG_OPTIONS@ +CLANG_OPTIONS = # If clang assisted parsing is enabled you can provide the clang parser with the # path to the directory containing a file called compile_commands.json. This @@ -1371,7 +1371,7 @@ HTML_STYLESHEET = @DOXYGEN_HTML_STYLESHEET@ # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = @DOXYGEN_HTML_EXTRA_STYLESHEET@ # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1394,7 +1394,7 @@ HTML_EXTRA_FILES = @DOXYGEN_HTML_EXTRA_FILES@ # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE = LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1405,7 +1405,7 @@ HTML_COLORSTYLE = AUTO_LIGHT # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_HUE = 46 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A @@ -1413,7 +1413,7 @@ HTML_COLORSTYLE_HUE = 220 # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_SAT = 0 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 @@ -1424,7 +1424,7 @@ HTML_COLORSTYLE_SAT = 100 # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_GAMMA = 80 +HTML_COLORSTYLE_GAMMA = 100 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this @@ -1677,7 +1677,7 @@ ECLIPSE_DOC_ID = @DOXYGEN_RDOMAIN_W_PROJECT@ # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -DISABLE_INDEX = NO +DISABLE_INDEX = YES # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag @@ -1694,7 +1694,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview @@ -1706,7 +1706,7 @@ GENERATE_TREEVIEW = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -FULL_SIDEBAR = NO +FULL_SIDEBAR = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -2371,7 +2371,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = CETL_DOXYGEN # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/cetlvast/suites/docs/examples/CMakeLists.txt b/cetlvast/suites/docs/examples/CMakeLists.txt new file mode 100644 index 00000000..4773d807 --- /dev/null +++ b/cetlvast/suites/docs/examples/CMakeLists.txt @@ -0,0 +1,73 @@ +# +# Copyright (C) OpenCyphal Development Team +# Copyright Amazon.com Inc. or its affiliates. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +# +---------------------------------------------------------------------------+ +# | PROJECT +# +---------------------------------------------------------------------------+ + +project(cetlvast_docs_examples C CXX) + +find_package(googletest REQUIRED) +find_package(o1heap REQUIRED) +if (CMAKE_BUILD_TYPE STREQUAL "Coverage") +find_package(gcovr REQUIRED) +endif() + +# +---------------------------------------------------------------------------+ +# | BUILD NATIVE EXAMPLE CODE +# +---------------------------------------------------------------------------+ + +file(GLOB NATIVE_EXAMPLES + LIST_DIRECTORIES false + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + example_*.cpp +) + +set(ALL_EXAMPLES "") +set(ALL_EXAMPLE_RUNS "") + +foreach(NATIVE_EXAMPLE ${NATIVE_EXAMPLES}) + define_native_gtest_unittest_targets( + TEST_SOURCE ${NATIVE_EXAMPLE} + EXTRA_TEST_LIBS cetl o1heap + OUT_TEST_LIB_VARIABLE LOCAL_TEST_LIB + OUT_TEST_EXE_VARIABLE LOCAL_TEST_TARGET + OUT_TEST_REPORT_VARIABLE LOCAL_TEST_REPORT + ) + if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + define_gcovr_tracefile_target( + TARGET ${LOCAL_TEST_TARGET} + TARGET_EXECUTION_DEPENDS ${LOCAL_TEST_REPORT} + EXCLUDE_TEST_FRAMEWORKS + EXCLUDE_TARGET + OUT_TRACEFILE_VARIABLE LOCAL_TEST_TRACEFILE + ) + endif() + list(APPEND ALL_EXAMPLES "${LOCAL_TEST_TARGET}") + list(APPEND ALL_EXAMPLE_RUNS "${LOCAL_TEST_REPORT}") +endforeach() + +add_custom_target( + build_examples + DEPENDS + ${ALL_EXAMPLES} +) + +add_custom_target( + run_examples + DEPENDS + ${ALL_EXAMPLE_RUNS} +) + +set_directory_properties(PROPERTIES + IN_BUILD_TESTS "${ALL_EXAMPLE_RUNS}" +) + +if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + enable_html_report() +endif() diff --git a/cetlvast/suites/docs/examples/example_01_polyfill.cpp b/cetlvast/suites/docs/examples/example_01_polyfill.cpp index ee3101f9..c42e7ca9 100644 --- a/cetlvast/suites/docs/examples/example_01_polyfill.cpp +++ b/cetlvast/suites/docs/examples/example_01_polyfill.cpp @@ -12,11 +12,13 @@ #include #include +#include -int main() +TEST(example_01_polyfill, main) { +//! [main] std::string greeting{"Hello Dynamic World."}; - std::span dynamic{greeting.c_str(), 13}; + cetl::span dynamic{greeting.c_str(), 13}; auto print = [](const char c) { std::cout << c; }; // Print just the characters in the span... @@ -26,6 +28,5 @@ int main() // or... std::string substring{dynamic.begin(), dynamic.end()}; std::cout << substring << std::endl; - - return 0; +//! [main] } diff --git a/cetlvast/suites/docs/examples/example_01_span_dynamic.cpp b/cetlvast/suites/docs/examples/example_01_span_dynamic.cpp index de6948d2..1143e5b3 100644 --- a/cetlvast/suites/docs/examples/example_01_span_dynamic.cpp +++ b/cetlvast/suites/docs/examples/example_01_span_dynamic.cpp @@ -11,7 +11,9 @@ #include #include -int main() +#include + +TEST(example_01_span_dynamic, main) { //! [main] std::string greeting{"Hello Dynamic World."}; @@ -26,6 +28,4 @@ int main() std::string substring{dynamic.begin(), dynamic.size()}; std::cout << substring << std::endl; //! [main] - - return 0; } diff --git a/cetlvast/suites/docs/examples/example_01_span_static.cpp b/cetlvast/suites/docs/examples/example_01_span_static.cpp index 4ff4ebd5..45c638e8 100644 --- a/cetlvast/suites/docs/examples/example_01_span_static.cpp +++ b/cetlvast/suites/docs/examples/example_01_span_static.cpp @@ -11,6 +11,8 @@ #include #include +#include + //! [global] template std::ostream& operator<<(std::ostream& os, const cetl::pf20::span& sp) @@ -20,11 +22,11 @@ std::ostream& operator<<(std::ostream& os, const cetl::pf20::span& sp } //! [global] -int main() + +TEST(example_01_span_static, main) { //! [main] constexpr const char* greeting = "Hello Static World"; std::cout << cetl::pf20::span{greeting, 12} << std::endl; //! [main] - return 0; } diff --git a/cetlvast/suites/docs/examples/example_02_polyfill.cpp b/cetlvast/suites/docs/examples/example_02_polyfill.cpp index 7d6fe435..2081dc5c 100644 --- a/cetlvast/suites/docs/examples/example_02_polyfill.cpp +++ b/cetlvast/suites/docs/examples/example_02_polyfill.cpp @@ -6,12 +6,16 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT /// +// cSpell: words myspan + #include "cetl/pf20/span.hpp" #include #include #include #include +#include + namespace { @@ -24,8 +28,9 @@ using myspan = cetl::pf20::span; } // end anonymous namespace -int main() +TEST(example_02_polyfill, main) { +//! [main] std::string greeting{"Hello Dynamic World."}; myspan dynamic{greeting.c_str(), 13}; auto print = [](const char c) { std::cout << c; }; @@ -38,5 +43,5 @@ int main() std::string substring{dynamic.begin(), dynamic.end()}; std::cout << substring << std::endl; - return 0; +//! [main] } diff --git a/cetlvast/suites/docs/examples/example_03_memory_resource.cpp b/cetlvast/suites/docs/examples/example_03_memory_resource.cpp new file mode 100644 index 00000000..d7655d77 --- /dev/null +++ b/cetlvast/suites/docs/examples/example_03_memory_resource.cpp @@ -0,0 +1,392 @@ +/// @file +/// Example of using CETL memory_resource types. +/// +/// This file implements two cetl::pf17::pmr::memory_resource specializations to demonstrate the +/// utility and requirements of the type. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +#include "cetl/cetl.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" + +#if (__cplusplus >= CETL_CPP_STANDARD_17 && !defined(CETL_DOXYGEN)) +#include +#include "cetl/pf17/byte.hpp" +#include +#include +#include +#include +#include + +#include + + +namespace cetl +{ +namespace pf17 +{ + +/// Implements a memory resource that over-allocates memory from an upstream memory resource to support +/// over-aligning allocations without C++17 or platform-specific system calls. +class OverAlignedMemoryResource : public pmr::memory_resource +{ + /// A control-block, of sorts. + /// The MemoryBlock provides a map between the system aligned memory returned by the + /// upstream memory_resource to the over-aligned region provided to the caller + /// of this specialization. + struct MemoryBlock + { + void* aligned_memory{nullptr}; + std::size_t aligned_memory_size_bytes{0}; + std::size_t memory_block_size_bytes; + MemoryBlock* next{nullptr}; + }; + + /// We use a single unique_ptr as the head of a linked list of MemoryBlocks. + /// When the OverAlignedMemoryResource is deleted this deleter will deconstruct all memory blocks. + struct MemoryBlockDeleter + { + MemoryBlockDeleter(pmr::memory_resource* upstream) + : upstream_(upstream) + { + } + + void operator()(MemoryBlock* cb) + { + CETL_DEBUG_ASSERT(nullptr != upstream_, "null memory_resource stored in MemoryBlockDeleter!"); + MemoryBlock* n = cb; + while (n != nullptr) + { + MemoryBlock* nn = n->next; + const std::size_t memory_block_size_bytes = n->memory_block_size_bytes; + n->~MemoryBlock(); + upstream_->deallocate(n, memory_block_size_bytes); + n = nn; + } + }; + + private: + pmr::memory_resource* upstream_; + }; + + /// A helper type definition for the root block smart pointer. + using MemoryBlockPointer = std::unique_ptr; + +public: + /// Required constructor. + /// + /// While STL prefers using std::pmr::new_delete_resource as a default thus allowing + /// for default constructors, CETL does not provide this API since it cannot be properly + /// implemented using C++14 without using non-standard and platform-specific APIs. + /// @param upstream The memory_resource this class uses to allocate default-aligned memory. + OverAlignedMemoryResource(pmr::memory_resource* upstream = cetl::pf17::pmr::new_delete_resource()) + : head_(nullptr, MemoryBlockDeleter(upstream)) + , tail_(nullptr) + , upstream_(upstream) + { + } + + // Remember, this is a polymorphic type and relies on virtual methods. + virtual ~OverAlignedMemoryResource() = default; + + // we could implement the move constructors but this is just a example so we'll + // keep it simple. The copy constructors should not be implemented since that would + // require an additional layer of abstraction needed to share the internal + // memory blocks. + OverAlignedMemoryResource(const OverAlignedMemoryResource&) = delete; + OverAlignedMemoryResource& operator=(const OverAlignedMemoryResource&) = delete; + OverAlignedMemoryResource(OverAlignedMemoryResource&& rhs) = delete; + OverAlignedMemoryResource& operator=(OverAlignedMemoryResource&& rhs) = delete; + +protected: + //! [do_allocate] + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + // The standard specifies a pre-condition that alignment is a power of two. + CETL_DEBUG_ASSERT(alignment && !(alignment & (alignment - 1)), "Alignment must be a power of 2."); + + // This class has a precondition that the memory_resource pointer is not-null. We use a raw pointer + // since the C++17 standard uses a similar pattern for an "upstream" allocator for the + // std::pmr::monotonic_buffer_resource. + if (nullptr == upstream_) + { +#if __cpp_exceptions + throw std::bad_alloc(); +#endif + return nullptr; + } + + // Optimization here: if we are not over-aligning then just use the upstream allocator. + if (alignment <= alignof(std::max_align_t)) + { + return upstream_->allocate(size_bytes, alignment); + } + + // This method will demonstrate an implementation of do_allocate that handles the alignment parameter using + // only portable APIs available in C++14. We'll over-allocate from the system to store a MemoryBlock and + // to ensure we can locate a starting pointer within the memory that is aligned to the requested power of 2. + + // We'll allocate the size of the MemoryBlock and also enforce that the size plus the padding added by the + // compiler provides an alignof(std::max_align_t) aligned start to our aligned memory area. + const std::size_t control_block_size_bytes = + sizeof(MemoryBlock) + (sizeof(MemoryBlock) % alignof(std::max_align_t)); + + // Now we adjust our allocation to account for over-alignment. For under-alignment we just waste memory. + const std::size_t over_alignment = (alignment > alignof(std::max_align_t)) ? alignment : 0; + const std::size_t upstream_size = control_block_size_bytes + over_alignment + size_bytes; + + byte* const max_aligned_memory = + static_cast(upstream_->allocate(upstream_size, alignof(std::max_align_t))); + + if (nullptr == max_aligned_memory) + { + // we don't have to throw here because, if exceptions are enabled, the upstream call would have thrown. + return nullptr; + } + + // Time to setup RAII for our raw memory. We can reuse the MemoryBlockDeleter as long as we haven't yet + // set the next_block pointer. + MemoryBlockPointer cb(new (max_aligned_memory) MemoryBlock{nullptr, size_bytes, upstream_size, nullptr}, + MemoryBlockDeleter(upstream_)); + + // We expect all malloc implementations to return memory aligned to std::max_align_t. + CETL_DEBUG_ASSERT((reinterpret_cast(max_aligned_memory) % alignof(std::max_align_t)) == 0, + "The upstream allocator must provide alignof(std::max_align_t) aligned memory!?"); + + // Now we give the area after the MemoryBlock to std::align to do it's thing. + void* aligned_memory = &static_cast(max_aligned_memory)[control_block_size_bytes]; + std::size_t max_aligned_memory_size = upstream_size - control_block_size_bytes; + aligned_memory = std::align(alignment, size_bytes, aligned_memory, max_aligned_memory_size); + + // If we get here then something about our expectations for over-allocation memory was wrong. Given this + // is just example code we haven't rigorously proven that it will always be correct. UMMV. + if (nullptr == aligned_memory || max_aligned_memory_size < size_bytes) + { +#if __cpp_exceptions + throw std::bad_alloc(); +#endif + return nullptr; + } + + // One last sanity check but this failing would mean std::align was broken so we don't expect + // this to ever fail given a correctly implemented C++14 library. + CETL_DEBUG_ASSERT((reinterpret_cast(aligned_memory) % static_cast(alignment)) == + 0, + "Internal alignment math was incorrect and did not result in a properly aligned memory block " + "block."); + + // All that's left is the linked-list business. + cb->aligned_memory = aligned_memory; + + if (nullptr == head_.get()) + { + CETL_DEBUG_ASSERT(nullptr == tail_, "Tail must be null when head is null."); + // This is the first. We establish the head of our linked list of + // allocations here. + head_.swap(cb); + tail_ = head_.get(); + } + else if (nullptr == tail_) + { + head_->next = cb.release(); + tail_ = head_->next; + } + else + { + tail_->next = cb.release(); + tail_ = tail_->next; + } + + // We return the aligned region to the call. They must use this object to de_allocate or the behaviour + // is undefined. + return aligned_memory; + } + //! [do_allocate] + //! [do_deallocate] + void do_deallocate(void* p, std::size_t size_bytes, std::size_t alignment) override + { + // The standard does not actually specify what to do about size_bytes nor alignment in the deallocate method. + // It does require that this method does not throw and, since there's no return value, + // this means any failures to find and deallocate memory are silent. + // We use alignment to detect which allocator was used and dispatch the deallocate call accordingly. + if (alignment <= alignof(std::max_align_t)) + { + upstream_->deallocate(p, size_bytes, alignment); + return; + } + MemoryBlock* previous; + + MemoryBlock* cb = find(p, previous); + + // In a debug mode you might want to assert on conditions that shouldn't occur + // in a healthy program. + // Here we maintain the standard behaviour of C that deallocating nullptr is + // not an error. We therefore only abort if p is not null AND we did not find a + // control block OR p was null AND we somehow did find a control block + // (that would be weird). + CETL_DEBUG_ASSERT((p && cb) || (!p && !cb), "Unknown pointer provided to do_deallocate."); + + // Here we assert that, if we are deallocating a valid block, the size requested + // by the call to do_allocate is the same size provided to this method. + CETL_DEBUG_ASSERT(!p || cb->aligned_memory_size_bytes == size_bytes, + "Control Block size did not match size argument for do_deallocate."); + + if (nullptr != cb) + { + if (nullptr == previous) + { + // there was no previous block. This is the head we are deleting. + CETL_DEBUG_ASSERT(cb == head_.get(), "find logic is incorrect"); + (void) head_.release(); + head_.reset(cb->next); + } + else + { + previous->next = cb->next; + } + if (cb == tail_) + { + tail_ = cb->next; + } + const std::size_t memory_block_size_bytes = cb->memory_block_size_bytes; + cb->~MemoryBlock(); + upstream_->deallocate(cb, memory_block_size_bytes); + } + } + //! [do_deallocate] + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + // As our heap is stored in a unique pointer we cannot share it with + // another instance of this class or any other memory_resource. As such, + // Simple pointer comparison should suffice. + return (&rhs == this); + } + +private: + MemoryBlock* find(void* p, MemoryBlock*& out_previous) + { + // this is, of course, really naive and O(n). To implement this class for heavy loads + // some sort of logarithmic search algorithm should be supported. + out_previous = nullptr; + MemoryBlock* cb = head_.get(); + while (cb != nullptr) + { + if (cb->aligned_memory == p) + { + break; + } + out_previous = cb; + cb = cb->next; + } + return cb; + } + + MemoryBlockPointer head_; + MemoryBlock* tail_; + memory_resource* upstream_; +}; + +// +--------------------------------------------------------------------------+ +// everything after this point is just RAII and fake machinery used to build +// an executable example. You don't need to look at it to understand what +// this file is trying to demonstrate. +class FakeDmaTransfer +{ +public: + FakeDmaTransfer(byte* buffer, std::size_t buffer_size) + : buffer_{buffer} + , buffer_size_{buffer_size} + , make_believe_progress_{0} + { + } + + bool is_complete() const + { + const std::size_t run_until = std::min(buffer_size_, make_believe_progress_ + (buffer_size_ / 12)); + for (; make_believe_progress_ < run_until; ++make_believe_progress_) + { + buffer_[make_believe_progress_] = static_cast(make_believe_progress_ % sizeof(byte)); + } + + return (make_believe_progress_ >= buffer_size_); + } + +private: + byte* buffer_; + std::size_t buffer_size_; + mutable std::size_t make_believe_progress_; +}; + +template +struct MemoryResourceDeleter +{ + MemoryResourceDeleter(pmr::memory_resource* resource, std::size_t allocation_size, std::size_t buffer_alignment) + : resource_(resource) + , allocation_size_(allocation_size) + , buffer_alignment_(buffer_alignment) + { + } + + void operator()(T* p) + { + CETL_DEBUG_ASSERT(nullptr != resource_, "null memory_resource stored in MemoryResourceDeleter!"); + resource_->deallocate(p, allocation_size_, buffer_alignment_); + }; + +private: + pmr::memory_resource* resource_; + std::size_t allocation_size_; + std::size_t buffer_alignment_; +}; + +template +std::unique_ptr> allocate_buffer(pmr::memory_resource& allocator, + std::size_t buffer_size, + std::size_t buffer_alignment) +{ + return std::unique_ptr>{static_cast( + allocator.allocate(buffer_size, buffer_alignment)), + MemoryResourceDeleter{&allocator, + buffer_size, + buffer_alignment}}; +} + +// +--------------------------------------------------------------------------+ +} // namespace pf17 +} // namespace cetl + +TEST(example_03_memory_resource, main) +{ +//! [main] + cetl::pf17::OverAlignedMemoryResource over_aligned_new_delete_resource{}; + + // let's pretend we have dma that must be aligned to a 128-byte (1024-bit) boundary: + std::cout << "About to allocate a big ol' buffer." << std::endl; + auto buffer = cetl::pf17::allocate_buffer(over_aligned_new_delete_resource, 0x100000, 128); + cetl::pf17::FakeDmaTransfer transfer(buffer.get(), 0x100000); + std::cout << "About to pretend we're waiting on hardware." << std::endl; + while (!transfer.is_complete()) + { + std::cout << "Fake waiting..." << std::endl; + } + std::cout << "Our fake DMA transfer is complete!" << std::endl; + + // Just to prove a point, we can also use this as a regular old memory resource using the CETL deviant + // set_new_delete_resource() method to transparently provide our new implementation to the program: + cetl::pf17::pmr::deviant::set_new_delete_resource(&over_aligned_new_delete_resource); + + auto string_buffer = + cetl::pf17::allocate_buffer(*cetl::pf17::pmr::new_delete_resource(), sizeof(char) * 12, alignof(char)); + strncpy(string_buffer.get(), "hello world", 12); + std::cout << string_buffer.get() << std::endl; + + // Do remember that memory_resource does not construct and delete objects. That is the job of allocators like + // cetl::pf17::pmr::polymorphic_allocator. Because this example only used trivially constructable, copyable, and + // destructible types we didn't have to build that machinery. +//! [main] +} + +#endif diff --git a/cetlvast/suites/docs/examples/example_05_array_memory_resource_alignment.cpp b/cetlvast/suites/docs/examples/example_05_array_memory_resource_alignment.cpp new file mode 100644 index 00000000..1a749ea0 --- /dev/null +++ b/cetlvast/suites/docs/examples/example_05_array_memory_resource_alignment.cpp @@ -0,0 +1,124 @@ +/// @file +/// Demonstration of memory alignment when using the cetl::pmr::UnsynchronizedArrayMemoryResource in +/// cetl/pmr/array_memory_resource.hpp. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include + +#include + +//![example_delegate] +#include "cetl/pf17/memory_resource.hpp" +#include "cetl/pmr/array_memory_resource.hpp" +#include "cetl/pf17/byte.hpp" + +namespace cetl +{ + +namespace pmr +{ + +/// Implementation of cetl::pmr::memory_resource that implements cetl::pf17::pmr::memory_resource using +/// cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate and cetl::pf17::pmr::memory_resource as the upstream memory +/// resource. +class UnsynchronizedArrayMemoryResource final : public cetl::pf17::pmr::memory_resource +{ +public: + using MemoryResourceType = cetl::pf17::pmr::memory_resource; + + /// See cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate for details.. + UnsynchronizedArrayMemoryResource(void* buffer, + std::size_t buffer_size_bytes, + MemoryResourceType* upstream = cetl::pf17::pmr::null_memory_resource()) + : delegate_{buffer, + buffer_size_bytes, + upstream, + cetl::pf17::pmr::deviant::memory_resource_traits::max_size(*upstream)} + { + } + + ~UnsynchronizedArrayMemoryResource() = default; + UnsynchronizedArrayMemoryResource(const UnsynchronizedArrayMemoryResource&) = delete; + UnsynchronizedArrayMemoryResource& operator=(const UnsynchronizedArrayMemoryResource&) = delete; + UnsynchronizedArrayMemoryResource(UnsynchronizedArrayMemoryResource&&) = delete; + UnsynchronizedArrayMemoryResource& operator=(UnsynchronizedArrayMemoryResource&&) = delete; + +private: + void* do_allocate(std::size_t bytes, std::size_t alignment) override + { + return delegate_.allocate(bytes, alignment); + } + + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { + delegate_.deallocate(p, bytes, alignment); + } + + bool do_is_equal(const MemoryResourceType& other) const noexcept override + { + return (this == &other); + } + + std::size_t do_max_size() const noexcept override + { + return delegate_.max_size(); + } + + void* do_reallocate(void* p, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) override + { + return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); + } + + cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate delegate_; +}; +} // namespace pmr +} // namespace cetl +//![example_delegate] + +TEST(example_05_array_memory_resource_alignment, example_0) +{ + static_assert(alignof(std::max_align_t) < 128, "Wow, what hardware are you running on?"); + //![example_0] + constexpr std::size_t BufferSizeBytes = 64; + cetl::pf17::byte buffer[BufferSizeBytes]; + cetl::pmr::UnsynchronizedArrayMemoryResource resource{buffer, BufferSizeBytes}; + + // let's say we have a buffer that must be aligned to a 128-byte (1024-bit) boundary. If we tried to use + // UnsynchronizedArrayMemoryResource with a 64-byte buffer, on a typical system, the allocation would fail. + + void* r = nullptr; +#if __cpp_exceptions + try + { +#endif + r = resource.allocate(64, 128); +#if __cpp_exceptions + } catch (const std::bad_alloc&) + { + // This is expected. + } +#endif + std::cout << "Over-aligned attempt failed: " << r << std::endl; + + //![example_0] +} +TEST(example_05_array_memory_resource_alignment, example_1) +{ + //![example_1] + // By over-provisioning the buffer you can now get the alignment you want: + constexpr std::size_t BufferSizeBytes = 64 + 128; + cetl::pf17::byte buffer[BufferSizeBytes]; + cetl::pmr::UnsynchronizedArrayMemoryResource resource{buffer, BufferSizeBytes}; + + void* r = resource.allocate(64, 128); + + std::cout << "Over-aligned address at: " << r << std::endl; + + resource.deallocate(r, 64, 128); + //![example_1] +} diff --git a/cetlvast/suites/docs/examples/example_05_array_memory_resource_array.cpp b/cetlvast/suites/docs/examples/example_05_array_memory_resource_array.cpp new file mode 100644 index 00000000..9840a345 --- /dev/null +++ b/cetlvast/suites/docs/examples/example_05_array_memory_resource_array.cpp @@ -0,0 +1,128 @@ +/// @file +/// Example of using the cetl::pmr::UnsynchronizedArrayMemoryResource in cetl/pmr/array_memory_resource.hpp. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +//![example_include] +#include "cetl/pmr/array_memory_resource.hpp" +#include "cetl/pf17/cetlpf.hpp" +//![example_include] + +#include +#include + +#include +#include + +//![example_setup] +struct Message +{ + explicit Message(const cetl::pmr::polymorphic_allocator& allocator) + : data{allocator} + { + } + std::vector> data; +}; + +// Let's say we have a data structure that contains a Message with variable-length data in it. +// We can use UnsynchronizedArrayMemoryResource to allocate a buffer large enough to hold all of this data at once +// but if there is less data the std::vector in the message will return the size() of that data (i.e. where an +// std::array would not). +static constexpr std::size_t SmallMessageSizeBytes = 64 * 8; +static cetl::byte small_message_buffer_[SmallMessageSizeBytes]; + +//![example_setup] + +TEST(Example_05_array_memory_resource_array, example_a) +{ + //![example_a] + cetl::pmr::UnsynchronizedArrayMemoryResource + aResource{small_message_buffer_, SmallMessageSizeBytes, cetl::pmr::null_memory_resource(), 0}; + cetl::pmr::polymorphic_allocator aAlloc{&aResource}; + Message a{aAlloc}; + + // The big "gotcha" when using UnsynchronizedArrayMemoryResource with STL containers is that you must reserve + // the size needed before you insert data into them. This is because UnsynchronizedArrayMemoryResource only + // allows one allocation at a time and vector, for example, will have two allocations outstanding as it + // geometrically grows its capacity as new items are added. + const std::size_t item_cout = SmallMessageSizeBytes / sizeof(decltype(aAlloc)::value_type); + a.data.reserve(item_cout); + + std::cout << "BEFORE -> data size = " << a.data.size() << ", data capacity : " << a.data.capacity() << std::endl; + + for (std::size_t i = 0; i < item_cout; ++i) + { + a.data.push_back(i); + } + + std::cout << "AFTER -> data size = " << a.data.size() << ", data capacity : " << a.data.capacity() << std::endl; + //![example_a] +} + +TEST(Example_05_array_memory_resource_array, example_b) +{ + //![example_b] + // BUT WAIT! THERE'S MORE! The UnsynchronizedArrayMemoryResource both slices and dices! That is, you can provide + // an upstream allocator to turn this into a "small buffer optimization" resource where the internal allocation + // is the small buffer and the upstream allocator becomes the larger allocator. + + cetl::pmr::UnsynchronizedArrayMemoryResource + bResource{small_message_buffer_, + SmallMessageSizeBytes, + cetl::pmr::new_delete_resource(), + std::numeric_limits::max()}; + + cetl::pmr::polymorphic_allocator bAlloc{&bResource}; + Message b{bAlloc}; + + // This time we won't reserve which should cause vector to do multiple allocations. We'll also insert + // a bunch more items than there is space in the small message buffer. + std::cout << "BEFORE -> data size = " << b.data.size() << ", data capacity : " << b.data.capacity() << std::endl; + + const std::size_t item_cout = (SmallMessageSizeBytes / sizeof(decltype(bAlloc)::value_type)) * 100; + for (std::size_t i = 0; i < item_cout; ++i) + { + b.data.push_back(i); + } + + std::cout << "AFTER -> data size = " << b.data.size() << ", data capacity : " << b.data.capacity() << std::endl; + //![example_b] +} + +TEST(Example_05_array_memory_resource_array, example_c) +{ + //![example_c] + // One more example: by using another UnsynchronizedArrayMemoryResource as an upstream for another + // UnsynchronizedArrayMemoryResource with the same-sized buffer you can use vector push_back without reserve up + // to the size of these buffers. + static cetl::byte upstream_buffer[SmallMessageSizeBytes]; + cetl::pmr::UnsynchronizedArrayMemoryResource + cUpstreamResource{&upstream_buffer, SmallMessageSizeBytes, cetl::pmr::null_memory_resource(), 0}; + cetl::pmr::UnsynchronizedArrayMemoryResource + cResource{small_message_buffer_, + SmallMessageSizeBytes, + &cUpstreamResource, + std::numeric_limits::max()}; + cetl::pmr::polymorphic_allocator cAlloc{&cResource}; + Message c{cAlloc}; + + // We also won't reserve in this example which should cause vector to do multiple allocations. We'll insert + // exactly the number of items that will fit in the small message buffer. Because containers like vector use a + // geometric growth strategy for capacity we can't insert more than the capacity of one of the small message + // buffers but with two of them we can insert up to that capacity whereas with only one we would run out of + // memory well before we reached `SmallMessageSizeBytes / sizeof(decltype(cAlloc)::value_type)`. + std::cout << "BEFORE -> data size = " << c.data.size() << ", data capacity : " << c.data.capacity() << std::endl; + + const std::size_t item_cout = SmallMessageSizeBytes / sizeof(decltype(cAlloc)::value_type); + for (std::size_t i = 0; i < item_cout; ++i) + { + c.data.push_back(i); + } + + std::cout << "AFTER -> data size = " << c.data.size() << ", data capacity : " << c.data.capacity() << std::endl; + //![example_c] +} diff --git a/cetlvast/suites/docs/examples/example_05_variable_length_array_vs_vector.cpp b/cetlvast/suites/docs/examples/example_05_variable_length_array_vs_vector.cpp new file mode 100644 index 00000000..79ec64fd --- /dev/null +++ b/cetlvast/suites/docs/examples/example_05_variable_length_array_vs_vector.cpp @@ -0,0 +1,202 @@ +/// @file +/// Demonstration of the differences between std::vector and cetl::variable_length_array type. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +// CSpell: ignore sayin + +#include "cetl/pmr/array_memory_resource.hpp" +#include "cetl/pf17/cetlpf.hpp" +#include "cetl/variable_length_array.hpp" +#include +#include + +#include + + +template +void add_hello_world(T& container) +{ + container.reserve(12); + container.push_back('H'); + container.push_back('e'); + container.push_back('l'); + container.push_back('l'); + container.push_back('o'); + container.push_back(' '); + container.push_back('W'); + container.push_back('o'); + container.push_back('r'); + container.push_back('l'); + container.push_back('d'); +} + +template +void print_container(const T& container) +{ + for (auto c : container) + { + std::cout << c; + } + + std::cout << std::endl; +} + +#if __cpp_exceptions + +TEST(example_05_variable_length_array_vs_vector, example_tight_fit_0) +{ + //! [example_tight_fit_0] + /// while a VariableLengthArray will attempt to grow its capacity using a geometric sequence it will also fit + /// tightly inside an array. For example, where std::vector, given an allocator with a maximum_size of + /// 56 bytes, and pushing back 8-byte elements one at at time, some vector implementations will allocate + /// 8, 16, 32, then 64 bytes. This last allocation, obviously, will fail. A VariableLengthArray allocation + /// may start with a similar sequence but would never attempt to allocate more than max_size; 8, 16, 32, 56. + + std::array storage_0{}; + std::array storage_1{}; + cetl::pmr::UnsynchronizedArrayMemoryResource + array_storage_1{storage_1.data(), storage_1.size(), cetl::pmr::null_memory_resource(), 0U}; + cetl::pmr::UnsynchronizedArrayMemoryResource + array_storage_0{storage_0.data(), storage_0.size(), &array_storage_1, array_storage_1.max_size()}; + std::vector> space_waster{{&array_storage_0}}; + + try + { + for (std::size_t i = 0; i < 56; ++i) + { + std::cout << i << ", "; + space_waster.push_back(static_cast(i + 46)); + } + } catch (const std::bad_alloc&) + { + std::cout << "<- vector claimed to have run out of memory but we know better." << std::endl; + } + //! [example_tight_fit_0] +} + +TEST(example_05_variable_length_array_vs_vector, example_tight_fit_1) +{ + //! [example_tight_fit_1] + // The problem with the C++17 standard is the lack of support for max_size in pmr types. + // VariableLengthArray provides the "max_size_max" argument that lets the user limit the amount of memory + // the container will use. + std::array storage_0{}; + std::array storage_1{}; + cetl::pmr::UnsynchronizedArrayMemoryResource + array_storage_1{storage_1.data(), storage_1.size(), cetl::pmr::null_memory_resource(), 0}; + cetl::pmr::UnsynchronizedArrayMemoryResource + array_storage_0{storage_0.data(), storage_0.size(), &array_storage_1, array_storage_1.max_size()}; + cetl::VariableLengthArray> tight_fit{{&array_storage_0}, + storage_0.size()}; + for (std::size_t i = 0; i < 56; ++i) + { + std::cout << i << ", "; + tight_fit.push_back(static_cast(i + 46)); + } + + std::cout << "<- The VLA was able to fit tightly inside of the maximum size it was given." << std::endl; + //! [example_tight_fit_1] +} + +TEST(example_05_variable_length_array_vs_vector, example_exact_fit) +{ + //! [example_exact_fit] + /// Using the reserve function a VariableLengthArray can be made to fit exactly inside of a given memory + /// resource. + + std::array storage_0{}; + cetl::pmr::UnsynchronizedArrayMemoryResource + array_storage_0{storage_0.data(), storage_0.size(), cetl::pmr::null_memory_resource(), 0}; + cetl::VariableLengthArray> exact_fit{{&array_storage_0}, + storage_0.size()}; + exact_fit.reserve(56); + for (std::size_t i = 0; i < 56; ++i) + { + std::cout << i << ", "; + exact_fit.push_back(static_cast(i + 46)); + } + + std::cout << "<- The VLA only used the 56 chars we gave it and no more." << std::endl; + //! [example_exact_fit] +} + +TEST(example_05_variable_length_array_vs_vector, example_no_exceptions) +{ + // compile with -fno-exceptions to enable this example. +} + +#else + +TEST(example_05_variable_length_array_vs_vector, example_tight_fit_0) +{ + // compile with exceptions to enable this example. +} + +TEST(example_05_variable_length_array_vs_vector, example_tight_fit_1) +{ + // compile with exceptions to enable this example. +} + +TEST(example_05_variable_length_array_vs_vector, example_no_exceptions) +{ + //! [example_no_exceptions] + + // Using the cetlpf.hpp header we can create a polymorphic allocator that is aliased to + // std::pmr::polymorphic_allocator when compiling with C++17 or newer and cetl::pf17::pmr::polymorphic_allocator + // when compiling with C++14. + cetl::pmr::polymorphic_allocator alloc{cetl::pmr::new_delete_resource()}; + + // This allows us to demonstrate that the cetl::variable_length_array behaves like a std::vector... + std::vector> a{alloc}; + cetl::VariableLengthArray> b{alloc}; + + add_hello_world(a); + add_hello_world(b); + + print_container(a); + print_container(b); + + // So why not just use vector? + // The primary reason is std::vector has some edge cases where it cannot be used when exceptions are disabled. + // Here we are giving a vector and a VariableLengthArray the same allocator CETL pf17 allocator. This allocator + // returns nullptr when out of memory and exceptions are disabled. + + cetl::pf17::pmr::polymorphic_allocator bad_alloc{cetl::pf17::pmr::null_memory_resource()}; + + std::vector> bad_a{bad_alloc}; + cetl::VariableLengthArray> bad_b{bad_alloc}; + + // In this case, the VariableLengthArray will fail gracefully when it failed to allocate. + bad_b.push_back('H'); + + // But this would cause undefined behavior in the vector. Some implementations might cause an abort() whereas + // others may cause a segfault (or worse). + + // of course, we commented out the following line but feel free to uncomment and try yourself. + // bad_a.push_back('H'); + + // You can detect that VariableLengthArray was not able to allocate memory for push_back using the following + // technique: + const std::size_t size_before = bad_b.size(); + bad_b.push_back('H'); + if (bad_b.size() == size_before) + { + if (size_before == bad_b.max_size()) + { + std::cout + << "bad_b was not able to allocate memory because it reached its max_size. You probably should " + "have checked this first? Just sayin'." + << std::endl; + } + else + { + std::cout << "bad_b allocator is out of memory." << std::endl; + } + } + //! [example_no_exceptions] +} +#endif // __cpp_exceptions diff --git a/cetlvast/suites/docs/examples/example_06_memory_resource_deleter.cpp b/cetlvast/suites/docs/examples/example_06_memory_resource_deleter.cpp new file mode 100644 index 00000000..9f542dc2 --- /dev/null +++ b/cetlvast/suites/docs/examples/example_06_memory_resource_deleter.cpp @@ -0,0 +1,57 @@ +/// @file +/// Example of using the cetl::pmr::MemoryResourceDeleter from cetl/pmr/memory.hpp. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +//![example_include] +#include "cetl/pf17/cetlpf.hpp" +#include "cetl/pmr/memory.hpp" +//![example_include] + +#include +#include +#include + +#include + + +TEST(example_06_memory_resource_deleter, example_usage) +{ + //![example_usage] + // Let's say you wanted to store a bunch of buffers in a heap so you can get the largest one quickly. + // You could do something like this: + + using MemoryResourcePointer = std::unique_ptr; + + struct ByteBuffer + { + MemoryResourcePointer data; + std::size_t size; + }; + + cetl::pmr::memory_resource* resource = cetl::pmr::new_delete_resource(); + auto buffer_0 = ByteBuffer{{resource->allocate(256), {resource, 256}}, 256}; + auto buffer_1 = ByteBuffer{{resource->allocate(512), {resource, 512}}, 512}; + auto buffer_2 = ByteBuffer{{resource->allocate(1024), {resource, 1024}}, 1024}; + std::vector buffers; + buffers.push_back(std::move(buffer_0)); + buffers.push_back(std::move(buffer_1)); + buffers.push_back(std::move(buffer_2)); + + std::make_heap(buffers.begin(), buffers.end(), [](const auto& lhs, const auto& rhs) { + return lhs.size < rhs.size; + }); + + auto& largest_buffer = buffers.front(); + + std::cout << "Largest buffer size: " << largest_buffer.size << std::endl; + + // Now as long as the vector "buffers" is in scope, the buffers will be valid. When the vector is destroyed, + // the buffers will be freed using the correct memory resource. + + //![example_usage] +} diff --git a/cetlvast/suites/docs/examples/example_06_polymorphic_alloc_deleter.cpp b/cetlvast/suites/docs/examples/example_06_polymorphic_alloc_deleter.cpp new file mode 100644 index 00000000..5c5b3733 --- /dev/null +++ b/cetlvast/suites/docs/examples/example_06_polymorphic_alloc_deleter.cpp @@ -0,0 +1,152 @@ +/// @file +/// Example of using the cetl::pmr::PolymorphicAllocatorDeleter from cetl/pmr/memory.hpp. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +/// cSpell: words emplacer + +//![example_include] +#include "cetl/pf17/cetlpf.hpp" +#include "cetl/pmr/memory.hpp" +//![example_include] + +#include +#include +#include + +#include + + +class MyObject final +{ +public: + MyObject(const char* name, std::size_t name_length) + : name_(nullptr) + { + name_ = static_cast(malloc(name_length + 1)); + strncpy(name_, name, name_length); + name_[name_length] = '\0'; + } + + MyObject(const MyObject&) = delete; + MyObject& operator=(const MyObject&) = delete; + MyObject& operator=(MyObject&&) = delete; + + MyObject(MyObject&& rhs) noexcept + : name_(rhs.name_) + { + rhs.name_ = nullptr; + } + + ~MyObject() + { + std::cout << "MyObject destructor called : " << name_ << std::endl; + free(name_); + } + + std::string name() const + { + if (name_ == nullptr) + { + return std::string{}; + } + else + { + return std::string{name_}; + } + } + +private: + char* name_; +}; + +TEST(example_06_polymorphic_alloc_deleter, example_usage_0) +{ + //![example_usage_0] + // Let's say you wanted to store a bunch of objects in a container of some sort. You can use the + // cetl::pmr::PolymorphicDeleter to help you build unique_ptr's like this: + + using MyAllocator = cetl::pmr::polymorphic_allocator; + using MyDeleter = cetl::pmr::PolymorphicDeleter; + MyAllocator alloc{cetl::pmr::new_delete_resource()}; + + std::unordered_map> objects; + objects.reserve(3); + + // where "MyObject" stores a c string internally so we're creating MyObject and MyObject is malloc'ing an internal + // buffer to copy the string "object_0" (which is 8-characters long) into. If you run this example you'll see that + // MyObject's destructor is called properly by the the deleter before it deallocate's the memory using the correct + // allocator. + std::unique_ptr object_0{alloc.allocate(1), MyDeleter{alloc, 1}}; + if (nullptr != object_0) + { + alloc.construct(object_0.get(), "object_0", 8U); + objects.emplace(object_0->name(), std::move(object_0)); + } // else, if we're here then exceptions are turned off, but deallocation is always null-safe. + + std::unique_ptr object_1{alloc.allocate(1), MyDeleter{alloc, 1}}; + if (nullptr != object_1) + { + alloc.construct(object_1.get(), "object_1", 8U); + objects.emplace(object_1->name(), std::move(object_1)); + } + + std::unique_ptr object_2{alloc.allocate(1), MyDeleter{alloc, 1}}; + if (nullptr != object_2) + { + alloc.construct(object_2.get(), "object_2", 8U); + objects.emplace(object_2->name(), std::move(object_2)); + } + + for (const auto& pair : objects) + { + std::cout << "Object name: " << pair.first << std::endl; + } + // But this is a lot of boilerplate code. You can use the cetl::pmr::Factory to help you do this more easily + // (see next example). + + //![example_usage_0] +} + +TEST(example_06_polymorphic_alloc_deleter, example_usage_1) +{ + //![example_usage_1] + // By using the cetl::pmr::Factory, you can simplify the code from the previous example: + + cetl::pmr::polymorphic_allocator alloc{cetl::pmr::new_delete_resource()}; + + std::unordered_map> objects; + objects.reserve(6); + + auto object_0 = cetl::pmr::Factory::make_unique(alloc, "object_0", 8U); + objects.emplace(object_0->name(), std::move(object_0)); + + auto object_1 = cetl::pmr::Factory::make_unique(alloc, "object_1", 8U); + objects.emplace(object_1->name(), std::move(object_1)); + + auto object_2 = cetl::pmr::Factory::make_unique(alloc, "object_2", 8U); + objects.emplace(object_2->name(), std::move(object_2)); + + // or even simpler: + auto emplacer = [&objects, &alloc](const char* name, std::size_t name_length) + { + auto object = cetl::pmr::Factory::make_unique(alloc, name, name_length); + objects.emplace(object->name(), std::move(object)); + }; + + emplacer("object_3", 8U); + emplacer("object_4", 8U); + emplacer("object_5", 8U); + + for (const auto& pair : objects) + { + std::cout << "Object name: " << pair.first << std::endl; + } + // Now as long as the map "objects" is in scope, the MyObject instances will be valid. When the map is destroyed, + // the objects will be deconstructed using the correct allocator. + + //![example_usage_1] +} diff --git a/cetlvast/suites/docs/footer.html b/cetlvast/suites/docs/footer.html index 8bbda556..d194ca0a 100644 --- a/cetlvast/suites/docs/footer.html +++ b/cetlvast/suites/docs/footer.html @@ -4,7 +4,11 @@ @@ -12,8 +16,7 @@ diff --git a/cetlvast/suites/docs/images/html/closed-folder.png b/cetlvast/suites/docs/images/html/closed-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae0df05f27725eb31bc9620e6eeb345245f277a GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4NRUcjv*f2$r7y0d=d*y*jB7unVFQ5a)s6C zf8!5_Z~yE6{|nu|Wy>9wkDaV%0;E_?OiUygR=S=!b0*5i$7hzpnNz1$eSLmj{+)9> zUvA3^CB6ke7A#uiG@-TCmEpy$JO$Z5zrW{CIK6OF!iTs2|Np<|u+u%jj*Y?Uk^P0~ z-xtQPscG=CIf$n-ul(%b+}y|&$ipthvuH;{QuV)of2&11)+Ziz_^F-18_CuW`^yY5#|0e{7S$eV(@hJb6Mw< G&;$Uc)r*7x literal 0 HcmV?d00001 diff --git a/cetlvast/suites/docs/images/html/docs-ok-green.svg b/cetlvast/suites/docs/images/html/docs-ok-green.svg deleted file mode 100644 index bb7610a0..00000000 --- a/cetlvast/suites/docs/images/html/docs-ok-green.svg +++ /dev/null @@ -1 +0,0 @@ -docs: okdocsok \ No newline at end of file diff --git a/cetlvast/suites/docs/images/html/document.png b/cetlvast/suites/docs/images/html/document.png new file mode 100644 index 0000000000000000000000000000000000000000..be6256cb9e1b4a37c716744d5b86a1e8af032622 GIT binary patch literal 535 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk`$4w*`%y80-9z6g`Rr4IEGl9PEL?0X%IZPW?FiY z`_zsGILMba__%b8?#4IWBqzF789ETD;padA7&LxGzI_eRQ&&bQ`?NT>Jwfw zKJ1+S^V|O)_1@A`I<9?i@1OYo|C9Z39k=d(%}{Er_VI)cS7l3B875q|*W8nQ`YzDJswJ)wB`Jv|saDBFsX&Us$iT=@*T7iU&@jZn z%*x2j%G5&Jz`)ADKz>VyAc}_E{FKbJO57UqUK*?gYLEok5S*V@Ql40p%1~Zju9umY dU7Va)kgAtols@~NjTBH3gQu&X%Q~loCIGENS%G|oWRD45dJ|uM!v-tY$DUh!@P+6=(yLU`q0KcVS>y)vIg-2>Spei8#knMCH$Y3w=^mS!_%EH4eVO1P;ESiCVIm*+;F~sBe)v37|!RaE$|NH++ z+v>UXO4sI&t*nM=m3~5ESqqO?WFAdQTwN#mB{g+TomNi7RNrU1KMqD(2MEX{zu9ec ztLv&*=D{t0FO<%@X`U#%CgRjACsV@(y}m_%1o9`V--~KJc%zZ~`@H8*%g_C{IluG3 z|GO@)+AqH>vdx|~=g(?WAslin%czqVGws{UR^@a3ng!{v4Q z`nCNk54~Nstzbr1RkiJ&#KW&ws{MR>bMvHT$E^GupSkagrcM(wTy8(f)VJ}a{>Scx z0^3^-|9Eg>;)S#C-hJA6Cq_Z;`1i&+qk6!oiFf^=RmG!g0|9q4HLqdIZwOi+u6Z7VZ#z*KRy*Sjm{d7f1(WH)< zt9I=w*_ra-UF`Ji$H%_#1aW(P`tdPzrBzqgwF%3YxcJ|`m$=#TQ1$n9*N&JQEO7Gi z`lgp2CiZvB7MCM$PGn^mO!4@$Ctsdnfu4|L{_I(wX3N_O#aHcJ!OwD`>O;bsZ`anU zsL1)pR{uYEifi`YI);w$X;USyKk!L1oNimoWx(9P^zu_x^0Rw!)yJ3lwh8I5H!~>Y zu(BR&ICj*P;lRT~PWA7i{pyBdwN~}TVHhY%e%wc1=b@$q{ z6R+MJsZ#sDv#_~irrzYB22{dwEujdz4kpMK30Co&JiP4u*#_w#S7`CYxn#PDCB zyYu|F_x(IyzVDus^}Lo@A@Xv|<2yS`OIaGS_RqJ^%zO6R1sJSHwto6_BV5bn=i;5| zI@6{f?}^s?Z)o-{;LN#oN0sJZ?b>+I@z2b;vbP_8^rNS%G|oWRD45dJ|uM!v-tY$DUh!@P+6=(yLU`q0KcVS>y)vIg-2>Spei8#knMCH$Y3w=^mS!_%EH6Uqi*-9_Bv2gyr+v}h{y4*(+oYN6Ge{yPmh`8 z&9+e>+H+wOSJ-FUlS&;*78%aYfjhXCHt$%{y{MT_RaITpd-KheCH*S`UI<-i@VKiT z8a<0kl83MH35#K&$EE9zdcIkDd%1tz-}n9A_qsjh@AtiD*74Y|Dynn4%E47$M{1Uz zoXvRSjLLMj!-=(bH|n(P&aJqyH*x!J`MLAbgpMXH{u`wH{D4Dv`03}5lQVDSt$+T! ze5cH$i5};tZ{50fZPCt^-+1zR`@2IHF4zt|1k;b*G6R0vE;IfO_ zM1!U)x0n0Rym2yZZ(yVuOGmY_vCEk)g@qTlzbu)RyU<+gw8#|`rL>P(S*vbc5868+ zuBV5A;ob4a_VeQ7X0giXJ^%ctpjw*2z|Jg<bnW3R3eR&}( zzn3b*gS^+qx~);sj1FB_{ep5?^Ut3H#*D4)G;NLYa%P6@OD`X5Ft4s=P^g_T=gUtS zzRf#-o6Yr`^0$s*LfyOf(ri_WH|dmZid0IQ^|$(8$m)A5_?Esco9L->#Lp}`e^0i` zJ$+q4!|u;X7o~Yw@_q!jHVL+fty`U;;kxnWkwhE6BRXZ?ss~p^{rxOpDDCL88W>`i z6kW~$eI^(gzkZ+i87H3pY@ph@+;iuZ1-U%6-@g5t^yBi?tMAkQ{&ja>IVH70FF$13 z$CM2OC7#SED>Kd5n8XAQd7+V<{Ss7bu8yHv_7)-1=@EJuzZhlH; YS|x4`4-_l>ff^V*UHx3vIVCg!0GG08<^TWy literal 0 HcmV?d00001 diff --git a/cetlvast/suites/docs/images/html/opened-folder.png b/cetlvast/suites/docs/images/html/opened-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec26ab4b184f328fd24b04d73a1cdf0cb933656 GIT binary patch literal 562 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk`$4w*|;@M7+g5az`)4h>Eak-aXL9c;#Y&HeDu$D2cgv+yNcM@GTQ<|c-Q#yXhWdD$48`Imbrp+num+#zE| z+x>cRqh${b=Rf@K7Q!q2uO{(`a4gUHS^f(%66TzhJAI)0xTCb>KjrKl=eLNjZ$+-x{mupI}5gNz~E6W zag8WRNi0dVN-jzTQVd20Muxfu#=3@vAqHkvhK5!q2HFM&Rt5&M&+ndqq9HdwB{QuO zw}w}fn00^}BtbR==ckpFCl;kLl$V$5W#(lUCnpx9>g5-u&wghk1ysb~>FVdQ&MBb@ E02BniPXGV_ literal 0 HcmV?d00001 diff --git a/cetlvast/suites/docs/stylesheet.css b/cetlvast/suites/docs/stylesheet.css index 8fee6182..4d25cb3b 100644 --- a/cetlvast/suites/docs/stylesheet.css +++ b/cetlvast/suites/docs/stylesheet.css @@ -161,169 +161,6 @@ html { } -@media (prefers-color-scheme: dark) { - html:not(.dark-mode) { - color-scheme: dark; - -/* page base colors */ ---page-background-color: black; ---page-foreground-color: #C9D1D9; ---page-link-color: #90A5CE; ---page-visited-link-color: #A3B4D7; - -/* index */ ---index-odd-item-bg-color: #0B101A; ---index-even-item-bg-color: black; ---index-header-color: #C4CFE5; ---index-separator-color: #334975; - -/* header */ ---header-background-color: #070B11; ---header-separator-color: #141C2E; ---header-gradient-image: url('nav_hd.png'); ---group-header-separator-color: #283A5D; ---group-header-color: #90A5CE; ---inherit-header-color: #A0A0A0; - ---footer-foreground-color: #5B7AB7; ---footer-logo-width: 60px; ---citation-label-color: #90A5CE; ---glow-color: cyan; - ---title-background-color: #090D16; ---title-separator-color: #354C79; ---directory-separator-color: #283A5D; ---separator-color: #283A5D; - ---blockquote-background-color: #101826; ---blockquote-border-color: #283A5D; - ---scrollbar-thumb-color: #283A5D; ---scrollbar-background-color: #070B11; - ---icon-background-color: #334975; ---icon-foreground-color: #C4CFE5; ---icon-doc-image: url('docd.png'); - -/* brief member declaration list */ ---memdecl-background-color: #0B101A; ---memdecl-separator-color: #2C3F65; ---memdecl-foreground-color: #BBB; ---memdecl-template-color: #7C95C6; - -/* detailed member list */ ---memdef-border-color: #233250; ---memdef-title-background-color: #1B2840; ---memdef-title-gradient-image: url('nav_fd.png'); ---memdef-proto-background-color: #19243A; ---memdef-proto-text-color: #9DB0D4; ---memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); ---memdef-doc-background-color: black; ---memdef-param-name-color: #D28757; ---memdef-template-color: #7C95C6; - -/* tables */ ---table-cell-border-color: #283A5D; ---table-header-background-color: #283A5D; ---table-header-foreground-color: #C4CFE5; - -/* labels */ ---label-background-color: #354C7B; ---label-left-top-border-color: #4665A2; ---label-right-bottom-border-color: #283A5D; ---label-foreground-color: #CCCCCC; - -/** navigation bar/tree/menu */ ---nav-background-color: #101826; ---nav-foreground-color: #364D7C; ---nav-gradient-image: url('tab_bd.png'); ---nav-gradient-hover-image: url('tab_hd.png'); ---nav-gradient-active-image: url('tab_ad.png'); ---nav-gradient-active-image-parent: url("../tab_ad.png"); ---nav-separator-image: url('tab_sd.png'); ---nav-breadcrumb-image: url('bc_sd.png'); ---nav-breadcrumb-border-color: #2A3D61; ---nav-splitbar-image: url('splitbard.png'); ---nav-font-size-level1: 13px; ---nav-font-size-level2: 10px; ---nav-font-size-level3: 9px; ---nav-text-normal-color: #B6C4DF; ---nav-text-hover-color: #DCE2EF; ---nav-text-active-color: #DCE2EF; ---nav-text-normal-shadow: 0px 1px 1px black; ---nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); ---nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); ---nav-menu-button-color: #B6C4DF; ---nav-menu-background-color: #05070C; ---nav-menu-foreground-color: #BBBBBB; ---nav-menu-toggle-color: rgba(255, 255, 255, 0.2); ---nav-arrow-color: #334975; ---nav-arrow-selected-color: #90A5CE; - -/* table of contents */ ---toc-background-color: #151E30; ---toc-border-color: #202E4A; ---toc-header-color: #A3B4D7; - -/** search field */ ---search-background-color: black; ---search-foreground-color: #C5C5C5; ---search-magnification-image: url('mag_d.svg'); ---search-magnification-select-image: url('mag_seld.svg'); ---search-active-color: #C5C5C5; ---search-filter-background-color: #101826; ---search-filter-foreground-color: #90A5CE; ---search-filter-border-color: #7C95C6; ---search-filter-highlight-text-color: #BCC9E2; ---search-filter-highlight-bg-color: #283A5D; ---search-results-background-color: #101826; ---search-results-foreground-color: #90A5CE; ---search-results-border-color: #7C95C6; ---search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; - -/** code fragments */ ---code-keyword-color: #CC99CD; ---code-type-keyword-color: #AB99CD; ---code-flow-keyword-color: #E08000; ---code-comment-color: #717790; ---code-preprocessor-color: #65CABE; ---code-string-literal-color: #7EC699; ---code-char-literal-color: #00E0F0; ---code-vhdl-digit-color: #FF00FF; ---code-vhdl-char-color: #000000; ---code-vhdl-keyword-color: #700070; ---code-vhdl-logic-color: #FF0000; ---code-link-color: #79C0FF; ---code-external-link-color: #79C0FF; ---fragment-foreground-color: #C9D1D9; ---fragment-background-color: black; ---fragment-border-color: #30363D; ---fragment-lineno-border-color: #30363D; ---fragment-lineno-background-color: black; ---fragment-lineno-foreground-color: #6E7681; ---fragment-lineno-link-fg-color: #6E7681; ---fragment-lineno-link-bg-color: #303030; ---fragment-lineno-link-hover-fg-color: #8E96A1; ---fragment-lineno-link-hover-bg-color: #505050; ---tooltip-foreground-color: #C9D1D9; ---tooltip-background-color: #202020; ---tooltip-border-color: #C9D1D9; ---tooltip-doc-color: #D9E1E9; ---tooltip-declaration-color: #20C348; ---tooltip-link-color: #79C0FF; ---tooltip-shadow: none; - -/** font-family */ ---font-family-normal: Roboto,sans-serif; ---font-family-monospace: monospace,fixed; ---font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; ---font-family-title: Tahoma,Arial,sans-serif; ---font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; ---font-family-search: Arial,Verdana,sans-serif; ---font-family-icon: Arial,Helvetica; ---font-family-tooltip: Roboto,sans-serif; - -}} body { background-color: var(--page-background-color); color: var(--page-foreground-color); @@ -743,7 +580,7 @@ img.light-mode-visible { } img.formulaDsp { - + } img.formulaInl, img.inline { @@ -764,6 +601,7 @@ div.center img { address.footer { text-align: right; padding-right: 12px; + padding-left: 12px; } img.footer { @@ -808,19 +646,19 @@ span.charliteral { color: var(--code-char-literal-color); } -span.vhdldigit { +span.vhdldigit { color: var(--code-vhdl-digit-color); } -span.vhdlchar { +span.vhdlchar { color: var(--code-vhdl-char-color); } -span.vhdlkeyword { +span.vhdlkeyword { color: var(--code-vhdl-keyword-color); } -span.vhdllogic { +span.vhdllogic { color: var(--code-vhdl-logic-color); } @@ -1547,12 +1385,12 @@ dl.section dd { vertical-align: bottom; border-collapse: separate; } - + #projectlogo img -{ +{ border: 0px none; } - + #projectalign { vertical-align: middle; @@ -1986,4 +1824,3 @@ details[open]>summary::before { padding-right:4px; font-size: 80%; } - diff --git a/cetlvast/suites/unittest/CMakeLists.txt b/cetlvast/suites/unittest/CMakeLists.txt new file mode 100644 index 00000000..513609da --- /dev/null +++ b/cetlvast/suites/unittest/CMakeLists.txt @@ -0,0 +1,73 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +project(cetlvast_unittest CXX) + +find_package(googletest REQUIRED) +find_package(o1heap REQUIRED) +if (CMAKE_BUILD_TYPE STREQUAL "Coverage") +find_package(gcovr REQUIRED) +endif() + +# +---------------------------------------------------------------------------+ +# We generate individual test binaires so we can record which test generated +# what coverage. We also allow test authors to generate coverage reports for +# just one test allowing for faster iteration. +file(GLOB NATIVE_TESTS + LIST_DIRECTORIES false + CONFIGURE_DEPENDS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + test_*.cpp +) + +set(ALL_TESTS_BUILD "") +set(ALL_TESTS_RUN "") + +foreach(NATIVE_TEST ${NATIVE_TESTS}) + define_native_gtest_unittest_targets( + TEST_SOURCE ${NATIVE_TEST} + EXTRA_TEST_LIBS + cetl + o1heap + OUT_TEST_LIB_VARIABLE LOCAL_TEST_LIB + OUT_TEST_EXE_VARIABLE LOCAL_TEST_TARGET + OUT_TEST_REPORT_VARIABLE LOCAL_TEST_REPORT + ) + + list(APPEND ALL_TESTS_BUILD ${LOCAL_TEST_TARGET}) + list(APPEND ALL_TESTS_RUN ${LOCAL_TEST_REPORT}) + + if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + define_gcovr_tracefile_target( + TARGET ${LOCAL_TEST_TARGET} + TARGET_EXECUTION_DEPENDS ${LOCAL_TEST_REPORT} + EXCLUDE_TEST_FRAMEWORKS + EXCLUDE_TARGET + OUT_TRACEFILE_VARIABLE LOCAL_TEST_TRACEFILE + ) + endif() +endforeach() + +add_custom_target( + build_unittests + DEPENDS + ${ALL_TESTS_BUILD} +) + +add_custom_target( + run_unittests + DEPENDS + ${ALL_TESTS_RUN} +) + +set_directory_properties(PROPERTIES + IN_BUILD_TESTS "${ALL_TESTS_RUN}" +) + +if (CMAKE_BUILD_TYPE STREQUAL "Coverage") + enable_html_report() +endif() diff --git a/cetlvast/suites/unittest/test_o1heap_memory_resource.cpp b/cetlvast/suites/unittest/test_o1heap_memory_resource.cpp new file mode 100644 index 00000000..a1d1e753 --- /dev/null +++ b/cetlvast/suites/unittest/test_o1heap_memory_resource.cpp @@ -0,0 +1,33 @@ +/// @file +/// Unit tests for cetl::pmr::O1heapResrouce +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/cetl.hpp" +#include "cetlvast/helpers_gtest.hpp" + +#include "cetl/pmr/o1heap_memory_resource.hpp" + +constexpr std::size_t TestBufferSize = 0x100000; +static cetl::pmr::O1HeapAlignedStorage large_buffer{}; + +TEST(UnsynchronizedO1HeapMemoryResourceTest, TestDefault) +{ + cetl::pmr::UnsynchronizedO1HeapMemoryResourceDelegate test_subject{large_buffer}; + void* mem = test_subject.allocate(8); + ASSERT_NE(nullptr, mem); + test_subject.deallocate(mem, 8); +} + +TEST(UnsynchronizedO1HeapMemoryResourceTest, O1HeapAlignedStorageTest) +{ + cetl::pmr::O1HeapAlignedStorage<4096> aligned_storage{}; + cetl::pmr::UnsynchronizedO1HeapMemoryResourceDelegate test_subject{aligned_storage}; + void* mem = test_subject.allocate(16); + ASSERT_NE(nullptr, mem); + test_subject.deallocate(mem, 16); +} diff --git a/cetlvast/suites/unittest/test_pf17_memory_resource.cpp b/cetlvast/suites/unittest/test_pf17_memory_resource.cpp new file mode 100644 index 00000000..52ca052a --- /dev/null +++ b/cetlvast/suites/unittest/test_pf17_memory_resource.cpp @@ -0,0 +1,126 @@ +/// @file +/// Unit tests for memory_resource.hpp +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/cetl.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" + +using ::testing::Return; +using ::testing::Sequence; +using ::testing::Ref; +using ::testing::_; +using ::testing::NiceMock; + +// +----------------------------------------------------------------------+ +// | Test Suite :: TestMemoryResourceABC +// +----------------------------------------------------------------------+ +/** + * Test suite for testing the Abstract Base Class, cetl::pf17::pmr::memory_resource + * against std::pmr::memory_resource. + */ +template +class TestMemoryResourceABC : public ::testing::Test +{}; + +// clang-format off +using MemoryResourceMocks = ::testing::Types< + cetlvast::MockPf17MemoryResource +#if (__cplusplus >= CETL_CPP_STANDARD_17) + , cetlvast::MockStdMemoryResource +#endif +>; +// clang-format on + +TYPED_TEST_SUITE(TestMemoryResourceABC, MemoryResourceMocks, ); + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestMemoryResourceABC, TestAllocation) +{ + TypeParam subject; + void* stack_ptr = &subject; + + Sequence s1; + EXPECT_CALL(subject, do_allocate(56, alignof(std::max_align_t))) + .Times(1) + .InSequence(s1) + .WillOnce(Return(stack_ptr)); + + void* allocated = subject.allocate(56); + ASSERT_EQ(stack_ptr, allocated); + + EXPECT_CALL(subject, do_deallocate(allocated, 56, alignof(std::max_align_t))).Times(1).InSequence(s1); + + subject.deallocate(allocated, 56); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestMemoryResourceABC, TestMemberEquality) +{ + TypeParam subject; + + EXPECT_CALL(subject, do_is_equal(Ref(subject))).Times(1).WillOnce(Return(true)); + + ASSERT_TRUE(subject.is_equal(subject)); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestMemoryResourceABC, TestGlobalEquality) +{ + NiceMock subject0; + TypeParam subject1; + + ON_CALL(subject0, do_is_equal(Ref(subject0))).WillByDefault(Return(true)); + ON_CALL(subject0, do_is_equal(_)).WillByDefault(Return(false)); + + ASSERT_FALSE(subject0 == subject1); + ASSERT_TRUE(subject0 == subject0); + + ASSERT_TRUE(subject0 != subject1); + ASSERT_FALSE(subject0 != subject0); +} + + +// +----------------------------------------------------------------------+ + + +TYPED_TEST(TestMemoryResourceABC, TestNullMemoryResourceAllocation) +{ + auto* subject = TypeParam::get(); + ASSERT_NE(nullptr, subject); + int dummy = 1; + void* dummy_memory = &dummy; +#if __cpp_exceptions + EXPECT_THROW((void)subject->allocate(1), std::bad_alloc); +#else + if(TypeParam::ReturnsNullWhenFNoExceptions) + { + EXPECT_EQ(nullptr, subject->allocate(1)); + } + // else the behavior is undefined and cannot be tested. +#endif + + // nothing should happen. + subject->deallocate(dummy_memory, sizeof(dummy)); + ASSERT_EQ(1, dummy); +} + +// +----------------------------------------------------------------------+ + + +TYPED_TEST(TestMemoryResourceABC, TestNullMemoryResourceMemberEquality) +{ + ASSERT_TRUE(TypeParam::get()->is_equal(*TypeParam::get())); + + TypeParam fixture; + + ASSERT_FALSE(TypeParam::get()->is_equal(fixture)); + +} diff --git a/cetlvast/suites/unittest/test_pf17_monotonic_buffer_resource.cpp b/cetlvast/suites/unittest/test_pf17_monotonic_buffer_resource.cpp new file mode 100644 index 00000000..8bac7e70 --- /dev/null +++ b/cetlvast/suites/unittest/test_pf17_monotonic_buffer_resource.cpp @@ -0,0 +1,105 @@ +/// @file +/// Unit tests for cetl::pf17::pmr::monotonic_buffer_resource +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetlvast/helpers_gtest_memory_resource.hpp" +#include "cetl/pf17/cetlpf.hpp" + +using ::testing::_; +using ::testing::Return; +using ::testing::Sequence; +using ::testing::Ge; + +// +----------------------------------------------------------------------+ +// | TEST SUITE :: TestMonotonicBufferResource +// +----------------------------------------------------------------------+ +template +class TestMonotonicBufferResource : public ::testing::Test +{}; + +// clang-format off +using MonotonicBufferResourceTypes = ::testing::Types< + cetl::pf17::pmr::monotonic_buffer_resource +#if (__cplusplus >= CETL_CPP_STANDARD_17) + , std::pmr::monotonic_buffer_resource +#endif +>; +// clang-format on + +TYPED_TEST_SUITE(TestMonotonicBufferResource, MonotonicBufferResourceTypes, ); + +// +----------------------------------------------------------------------+ + +/// Test that the default constructor provides a viable, upstream, memory resource. +TYPED_TEST(TestMonotonicBufferResource, TestDefaultConstruction) +{ + TypeParam subject{}; + void* memory = subject.allocate(1024); + ASSERT_NE(nullptr, memory); + subject.release(); +} + +// +----------------------------------------------------------------------+ + +/// Ensure deallocate has no effect on the monotonic resource. +TYPED_TEST(TestMonotonicBufferResource, TestDeallocateHasNoEffect) +{ + static_assert(alignof(cetl::byte) <= alignof(std::max_align_t), + "Assumptions about the alignment of cetl::byte are wrong"); + constexpr std::size_t size_bytes = 1024; + std::array buffer{}; + TypeParam subject{buffer.data(), buffer.size(), cetlvast::MRH::null_memory_resource()}; + ASSERT_NE(nullptr, subject.upstream_resource()); + void* memory = subject.allocate(size_bytes / 2); + ASSERT_NE(nullptr, memory); + // first we try to allocate more then the buffer can hold. This should + // cause the resource to try using the null_memory_resource, which will + // throw an exception or return null (depending on __cpp_exceptions). + // TODO: test helper that is expect-throw-if-except-else-nullptr-if-cetl-else-dont-do-test. +#if __cpp_exceptions + EXPECT_THROW((void) subject.allocate(size_bytes), std::bad_alloc); +#elif (__cplusplus == CETL_CPP_STANDARD_14) + ASSERT_EQ(nullptr, subject.allocate(size_bytes)); +#else + GTEST_SKIP() << "C++17 pmr does not support defined out of memory behaviour without exceptions."; +#endif + // now we deallocate the memory we allocated. This should have no effect. + subject.deallocate(memory, size_bytes / 2); +#if __cpp_exceptions + EXPECT_THROW((void) subject.allocate(size_bytes), std::bad_alloc); +#else + ASSERT_EQ(nullptr, subject.allocate(size_bytes)); +#endif + // finally, we call release which should reset the resource. + subject.release(); + // now we should be able to allocate again. + ASSERT_NE(nullptr, subject.allocate(size_bytes / 2)); +} + +// +----------------------------------------------------------------------+ + +/// Test that the upstream is used only after the internal buffer is exhausted. +TYPED_TEST(TestMonotonicBufferResource, TestAllocationOrder) +{ + static_assert(alignof(cetl::byte) <= alignof(std::max_align_t), + "Assumptions about the alignment of cetl::byte are wrong"); + constexpr std::size_t size_bytes = 1024; + std::array buffer{}; + std::array upstream_buffer{}; + cetlvast::MRH::MockMemoryResourceType mock{}; + Sequence s1; + EXPECT_CALL(mock, do_allocate(Ge(size_bytes), _)).Times(1).InSequence(s1).WillOnce(Return(upstream_buffer.data())); + EXPECT_CALL(mock, do_deallocate(upstream_buffer.data(), Ge(size_bytes), _)).Times(1).InSequence(s1); + + TypeParam subject{buffer.data(), buffer.size(), &mock}; + ASSERT_EQ(&mock, subject.upstream_resource()); + void* memory = subject.allocate(size_bytes / 2); + ASSERT_NE(nullptr, memory); + void* upstream_memory = subject.allocate(size_bytes); + ASSERT_NE(nullptr, upstream_memory); +} diff --git a/cetlvast/suites/unittest/test_pf17_polymorphic_allocator.cpp b/cetlvast/suites/unittest/test_pf17_polymorphic_allocator.cpp new file mode 100644 index 00000000..f204e76c --- /dev/null +++ b/cetlvast/suites/unittest/test_pf17_polymorphic_allocator.cpp @@ -0,0 +1,258 @@ +/// @file +/// Unit tests for cetl::pf17::pmr::polymorphic_allocator defined in memory_resource.hpp +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/cetl.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" +#include "cetl/pf17/byte.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" + +#if (__cplusplus >= CETL_CPP_STANDARD_17) +# include +#endif + +/// Helper to encapsulate the required protocol for allocating, constructing, destroying, and deallocating objects using +/// a polymorphic_allocator and a unique_ptr. This could be considered a generic factory pattern. +template +struct ObjectConstructionProtocol +{ + using value_type = T; + using allocator_type = ByteAllocator; + using byte_type = typename std::allocator_traits::value_type; + + static_assert( +#if (__cplusplus >= CETL_CPP_STANDARD_17) + std::is_same::value or +#endif + std::is_same::value, + "This test expects the ByteAllocator to use a known byte type."); + + static_assert(cetlvast::is_power_of_two(sizeof(byte_type)), "We only handle byte types that are powers of two."); + + ObjectConstructionProtocol(allocator_type& allocator, std::size_t allocated_size_bytes) + : alloc_(allocator) + , allocated_size_bytes_(allocated_size_bytes) + { + } + + void operator()(value_type* p) + { + if (p) + { + p->~value_type(); + } + alloc_.get().deallocate(reinterpret_cast(p), allocated_size_bytes_); + } + + template + static std::unique_ptr> make_unique( + allocator_type& alloc, + TArgs&&... args) + { + std::size_t allocate_size_bytes = sizeof(value_type) / sizeof(byte_type); + value_type* p = reinterpret_cast(alloc.allocate(allocate_size_bytes)); + alloc.construct(p, args...); + return {p, ObjectConstructionProtocol{alloc, allocate_size_bytes}}; + } + +private: + std::reference_wrapper alloc_; + std::size_t allocated_size_bytes_; +}; + +// +----------------------------------------------------------------------+ +// | Test Fixture(s) +// +----------------------------------------------------------------------+ +template +struct LeadingAllocType +{ + using allocator_type = Alloc; + + LeadingAllocType() + : alloc_() + , data_(0) + { + } + + LeadingAllocType(std::allocator_arg_t, const Alloc& alloc) + : alloc_(alloc) + , data_(0) + { + } + + LeadingAllocType(std::allocator_arg_t, const Alloc& alloc, int data) + : alloc_(alloc) + , data_(data) + { + } + + operator int() const + { + return data_; + } + +private: + Alloc alloc_; + int data_; +}; + +template +struct TrailingAllocType +{ + using allocator_type = Alloc; + + TrailingAllocType() + : alloc_() + , data_(0) + { + } + + explicit TrailingAllocType(const Alloc& alloc) + : alloc_(alloc) + , data_(0) + { + } + + TrailingAllocType(int data, const Alloc& alloc) + : alloc_(alloc) + , data_(data) + { + } + + operator int() const + { + return data_; + } + +private: + Alloc alloc_; + int data_; +}; + +struct NoAllocType +{ + NoAllocType() + : data_(0) + { + } + + explicit NoAllocType(int data) + : data_(data) + { + } + + operator int() const + { + return data_; + } + +private: + int data_; +}; + +// +----------------------------------------------------------------------+ +// | Test Suite :: TestPolymorphicAllocatorProtocols +// +----------------------------------------------------------------------+ + +/// +/// Test suite to verify that cetl::pf17::pmr::polymorphic_allocator adheres +/// to all conventions/protocols required of it by the C++ specification. +/// +template +class TestPolymorphicAllocatorProtocols : public ::testing::Test +{ +protected: +#if (__cplusplus >= CETL_CPP_STANDARD_17) + template + static typename std::enable_if_t::value, U> + make_default_allocator() + { + return U(std::pmr::new_delete_resource()); + } +#endif + + template + static typename std::enable_if_t::value, U> + make_default_allocator() + { + return U(cetl::pf17::pmr::new_delete_resource()); + } +}; + +// clang-format off +using TestPolymorphicAllocatorTypes = ::testing::Types< + cetl::pf17::pmr::polymorphic_allocator +#if (__cplusplus >= CETL_CPP_STANDARD_17) + , std::pmr::polymorphic_allocator +#endif +>; +// clang-format on + +TYPED_TEST_SUITE(TestPolymorphicAllocatorProtocols, TestPolymorphicAllocatorTypes, ); + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestPolymorphicAllocatorProtocols, TestDefaultConstruction) +{ + TypeParam subject = TestFixture::template make_default_allocator(); + typename TypeParam::value_type* p = subject.allocate(1); + ASSERT_FALSE(nullptr == p); + subject.deallocate(p, 1); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestPolymorphicAllocatorProtocols, TestUsesAllocatorConstructionNoAllocator) +{ + const int test_value = 0xAA; + TypeParam subject = TestFixture::template make_default_allocator(); + auto test_instance = ObjectConstructionProtocol::make_unique(subject, test_value); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get(), alignof(std::max_align_t))); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get())); + ASSERT_EQ(test_value, *test_instance); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestPolymorphicAllocatorProtocols, TestUsesAllocatorConstructionLeading) +{ + const int test_value = 0xAA; + TypeParam subject = TestFixture::template make_default_allocator(); + auto test_instance = + ObjectConstructionProtocol, TypeParam>::make_unique(subject, test_value); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get(), alignof(std::max_align_t))); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get())); + ASSERT_EQ(test_value, *test_instance); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestPolymorphicAllocatorProtocols, TestUsesAllocatorConstructionTrailing) +{ + const int test_value = 0xAA; + TypeParam subject = TestFixture::template make_default_allocator(); + auto test_instance = + ObjectConstructionProtocol, TypeParam>::make_unique(subject, test_value); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get(), alignof(std::max_align_t))); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get())); + ASSERT_EQ(test_value, *test_instance); +} + +// +----------------------------------------------------------------------+ + +TYPED_TEST(TestPolymorphicAllocatorProtocols, TestPairConstructionNoAllocator) +{ + const int test_value = 0xAA; + TypeParam subject = TestFixture::template make_default_allocator(); + using PairType = std::pair; + auto test_instance = ObjectConstructionProtocol::make_unique(subject, test_value, test_value); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get(), alignof(std::max_align_t))); + ASSERT_TRUE(cetlvast::is_aligned(test_instance.get())); + ASSERT_EQ(test_value, test_instance->first); + ASSERT_EQ(test_value, test_instance->second); +} diff --git a/cetlvast/suites/unittest/test_pf20.cpp b/cetlvast/suites/unittest/test_pf20.cpp index 1afa687c..568f924a 100644 --- a/cetlvast/suites/unittest/test_pf20.cpp +++ b/cetlvast/suites/unittest/test_pf20.cpp @@ -6,8 +6,9 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT /// +// cSpell: words Spantastic -#include "cetlvast/helpers.hpp" +#include "cetlvast/helpers_gtest.hpp" #include "cetl/pf20/cetlpf.hpp" namespace @@ -16,9 +17,15 @@ namespace TEST(PF20TestSuite, TestSpanIsSpantastic) { std::uint32_t data[5] = {0, 1, 2, 3, 4}; - std::span always_std(&data[1], &data[4]); + cetl::span always_std(&data[1], &data[4]); ASSERT_EQ(1, always_std[0]); ASSERT_EQ(3, always_std.back()); } +TEST(PF20TestSuite, TestNullMemoryResource) +{ + cetl::pmr::memory_resource* nmr = cetl::pmr::null_memory_resource(); + ASSERT_NE(nullptr, nmr); +} + } // namespace diff --git a/cetlvast/suites/unittest/test_span.cpp b/cetlvast/suites/unittest/test_pf20_span.cpp similarity index 99% rename from cetlvast/suites/unittest/test_span.cpp rename to cetlvast/suites/unittest/test_pf20_span.cpp index bae3e99c..943cc348 100644 --- a/cetlvast/suites/unittest/test_span.cpp +++ b/cetlvast/suites/unittest/test_pf20_span.cpp @@ -8,7 +8,7 @@ /// #include "cetl/cetl.hpp" -#include "cetlvast/helpers.hpp" +#include "cetlvast/helpers_gtest.hpp" #include #include #include diff --git a/cetlvast/suites/unittest/test_span_asserts.cpp b/cetlvast/suites/unittest/test_pf20_span_asserts.cpp similarity index 99% rename from cetlvast/suites/unittest/test_span_asserts.cpp rename to cetlvast/suites/unittest/test_pf20_span_asserts.cpp index 669d1a4c..36d5a600 100644 --- a/cetlvast/suites/unittest/test_span_asserts.cpp +++ b/cetlvast/suites/unittest/test_pf20_span_asserts.cpp @@ -8,7 +8,7 @@ /// #include "cetl/cetl.hpp" -#include "cetlvast/helpers.hpp" +#include "cetlvast/helpers_gtest.hpp" #include "cetl/pf20/span.hpp" namespace diff --git a/cetlvast/suites/unittest/test_variable_length_array_bool.cpp b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp new file mode 100644 index 00000000..d2ac67f8 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp @@ -0,0 +1,234 @@ +/// @file +/// Unit tests for bool specialization of cetl::VariableLengthArray +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/variable_length_array.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" + +#include "cetlvast/helpers_gtest.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" +#include + +// +---------------------------------------------------------------------------+ +// | TEST PROTOCOL +// +---------------------------------------------------------------------------+ + +template + class ContainerType, + typename AllocatorValueType> +struct TypeParamDef +{ + TypeParamDef() = delete; + + using allocator_factory = cetlvast::AllocatorTypeParamDef; + using container_type = ContainerType; + + template + static constexpr container_type make_bool_container(Args&&... args) + { + return container_type(std::forward(args)..., allocator_factory::make_allocator()); + } + + static constexpr void reset() + { + allocator_factory::reset(); + } +}; + +// +---------------------------------------------------------------------------+ +// | TEST SUITE +// +---------------------------------------------------------------------------+ + +template +class VLABoolTests : public ::testing::Test +{ +protected: + void TearDown() override + { + T::reset(); + } +}; + +// clang-format off +namespace cetlvast +{ +using MyTypes = ::testing::Types< + TypeParamDef + , TypeParamDef +>; +} // namespace cetlvast +// clang-format on + +TYPED_TEST_SUITE(VLABoolTests, cetlvast::MyTypes, ); + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: Copy Construction +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLABoolTests, SetGetOne) +{ + auto subject = TypeParam::make_bool_container(std::initializer_list{0U, 1U}); + EXPECT_EQ(subject[0], false); + EXPECT_EQ(subject[1], true); +} + +TYPED_TEST(VLABoolTests, PushBackNine) +{ + auto subject = TypeParam::make_bool_container(); + for (unsigned char i = 0; i < 9; ++i) + { + subject.push_back(i % 2); + } + EXPECT_EQ(subject.size(), 9); + EXPECT_GE(subject.capacity(), 16); + EXPECT_LE(subject.capacity(), 128) << "Not sure if this is an error but it's worth checking out."; + for (unsigned char i = 0; i < 9; ++i) + { + EXPECT_EQ(subject[i], i % 2); + } +} + +TYPED_TEST(VLABoolTests, TestBoolReference) +{ + auto array = TypeParam::make_bool_container(); + ASSERT_EQ(0, array.size()); + array.push_back(true); + ASSERT_EQ(1, array.size()); + ASSERT_TRUE(array[0]); + array.push_back(false); + ASSERT_EQ(2, array.size()); + ASSERT_FALSE(array[1]); + array.push_back(true); + ASSERT_EQ(3, array.size()); + ASSERT_TRUE(array[2]); + ASSERT_FALSE(array[1]); + ASSERT_TRUE(array[0]); + ASSERT_TRUE(!array[1]); + ASSERT_FALSE(!array[0]); + ASSERT_TRUE(array[0] == array[2]); + ASSERT_TRUE(array[0] != array[1]); + array[0] = array[1]; + ASSERT_FALSE(array[0]); + ASSERT_FALSE(array[1]); +} + +TYPED_TEST(VLABoolTests, TestBoolIterator) +{ + auto foo = TypeParam::make_bool_container( + std::initializer_list{false, true, false, false, true, true, false, true, true, false}); + ASSERT_EQ(+10, (foo.end() - foo.begin())); + ASSERT_EQ(-10, (foo.begin() - foo.end())); + auto a = foo.begin(); + auto b = foo.begin(); + // Comparison + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + ASSERT_TRUE(a <= b); + ASSERT_TRUE(a >= b); + ASSERT_FALSE(a < b); + ASSERT_FALSE(a > b); + ++a; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + ASSERT_FALSE(a <= b); + ASSERT_TRUE(a >= b); + ASSERT_FALSE(a < b); + ASSERT_TRUE(a > b); + ++b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + ASSERT_TRUE(a <= b); + ASSERT_TRUE(a >= b); + ASSERT_FALSE(a < b); + ASSERT_FALSE(a > b); + // Test the iterator traits + ASSERT_TRUE((std::is_same::iterator_category, + std::random_access_iterator_tag>::value)); + ASSERT_TRUE((std::is_same::value_type, bool>::value)); + ASSERT_TRUE((std::is_same::difference_type, std::ptrdiff_t>::value)); + // Test the iterator operations + ASSERT_EQ(0, a - b); + ASSERT_EQ(0, b - a); + ASSERT_EQ(0, a - a); + ASSERT_EQ(0, b - b); + ASSERT_EQ(1, a - foo.begin()); + ASSERT_EQ(1, b - foo.begin()); + ASSERT_EQ(-1, foo.begin() - b); + ASSERT_EQ(-1, foo.begin() - a); + ASSERT_EQ(1, a - foo.begin()); + ASSERT_EQ(1, b - foo.begin()); + // Augmented assignment + a += 1; + ASSERT_EQ(1, a - b); + ASSERT_EQ(-1, b - a); + b -= 1; + ASSERT_EQ(2, a - b); + ASSERT_EQ(2, a - foo.begin()); + ASSERT_EQ(0, b - foo.begin()); + // Inc/dec + ASSERT_EQ(2, (a++) - b); + ASSERT_EQ(3, a - b); + ASSERT_EQ(3, (a--) - b); + ASSERT_EQ(2, a - b); + ASSERT_EQ(3, (++a) - b); + ASSERT_EQ(3, a - b); + ASSERT_EQ(2, (--a) - b); + ASSERT_EQ(2, a - b); + // Add/sub + ASSERT_EQ(4, (a + 2) - b); + ASSERT_EQ(0, (a - 2) - b); + // Value access + ASSERT_EQ(2, a - foo.begin()); + ASSERT_EQ(0, b - foo.begin()); + ASSERT_EQ(false, *a); + ASSERT_EQ(false, *b); + ASSERT_EQ(true, a[-1]); + ASSERT_EQ(true, b[5]); + *a = true; + b[5] = false; + ASSERT_EQ(true, *a); + ASSERT_EQ(false, b[5]); + // Flip bit. + ASSERT_EQ(false, a[7]); + ASSERT_EQ(true, foo[7]); + a[7].flip(); + foo[7].flip(); + ASSERT_EQ(true, a[7]); + ASSERT_EQ(false, foo[7]); + // Check the final state. + ASSERT_EQ(10, foo.size()); + ASSERT_LE(10, foo.capacity()); +#if __cpp_exceptions + ASSERT_EQ(false, foo.at(0)); + ASSERT_EQ(true, foo.at(1)); + ASSERT_EQ(true, foo.at(2)); + ASSERT_EQ(false, foo.at(3)); + ASSERT_EQ(true, foo.at(4)); + ASSERT_EQ(false, foo.at(5)); + ASSERT_EQ(false, foo.at(6)); + ASSERT_EQ(false, foo.at(7)); + ASSERT_EQ(true, foo.at(8)); + ASSERT_EQ(true, foo.at(9)); +#else + ASSERT_EQ(false, foo[0]); + ASSERT_EQ(true, foo[1]); + ASSERT_EQ(true, foo[2]); + ASSERT_EQ(false, foo[3]); + ASSERT_EQ(true, foo[4]); + ASSERT_EQ(false, foo[5]); + ASSERT_EQ(false, foo[6]); + ASSERT_EQ(false, foo[7]); + ASSERT_EQ(true, foo[8]); + ASSERT_EQ(true, foo[9]); +#endif + // Constant iterators. + ASSERT_EQ(false, *foo.cbegin()); + ASSERT_EQ(true, *(foo.cend() - 1)); + ASSERT_EQ(true, foo.cbegin()[2]); +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_compat.cpp b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp new file mode 100644 index 00000000..75aef7b6 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp @@ -0,0 +1,1039 @@ +/// @file +/// Unit tests that confirm cetl::VariableLengthArray behaviour versus std::vector. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +// cSpell: words wself + +#include "cetl/variable_length_array.hpp" + +#include "cetl/pf17/sys/memory_resource.hpp" +#include "cetl/pmr/array_memory_resource.hpp" +#include "cetl/pf17/byte.hpp" + +#include "cetlvast/helpers_gtest.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" + +#include +#include +#include +#include +#include +#include +#include +#if (__cplusplus >= CETL_CPP_STANDARD_17) +# include +#endif + +// +-------------------------------------------------------------------------------------------------------------------+ +// | standard allocator with max_size defined. +// +-------------------------------------------------------------------------------------------------------------------+ + +template +struct MaxAllocator : public std::allocator +{ + template + struct rebind + { + typedef MaxAllocator other; + }; + + typename std::allocator::size_type max_size() const noexcept + { + return max_size_v; + } +}; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | memory_resource FACTORIES +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Provides null_memory_resource +struct NullResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind::type>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return 0; + } + + static constexpr typename std::add_pointer::type resource( + NullResourceFactory& resource_factory) + { + (void) resource_factory; + return cetl::pf17::pmr::null_memory_resource(); + } + + static constexpr typename std::add_pointer::type resource( + NullResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + return resource(resource_factory); + } + + static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + + template + struct Bind, Allocator>::value>::type> + { + static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) + { + (void) resource_factory; + (void) upstream; + return Allocator{}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +// Creates a standard allocator that implements max_size() and returns the given MaxSizeValue. +template +struct MaxSizeResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind::type>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return MaxSizeValue; + } + + static constexpr typename std::add_pointer::type resource( + MaxSizeResourceFactory& resource_factory) + { + (void) resource_factory; + return cetl::pf17::pmr::null_memory_resource(); + } + + static constexpr typename std::add_pointer::type resource( + MaxSizeResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + return resource(resource_factory); + } + + static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + + template + struct Bind, Allocator>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return MaxSizeValue; + } + + static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) resource_factory; + (void) upstream; + return Allocator{}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Creates a polymorphic allocator that uses the new_delete_resource. +struct CetlNewDeleteResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return std::numeric_limits::max() / sizeof(typename Allocator::value_type); + } + + static constexpr typename std::add_pointer::type resource( + CetlNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + (void) resource_factory; + return cetl::pf17::pmr::new_delete_resource(); + } + + static constexpr Allocator make_allocator(CetlNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Creates a polymorphic allocator that uses an UnsynchronizedArrayMemoryResourceDelegate-based memory resource. +template +class CetlUnsynchronizedArrayMemoryResourceFactory +{ +private: + template + class ResourceImpl : public cetl::pf17::pmr::memory_resource + { + public: + explicit ResourceImpl(cetl::pf17::pmr::memory_resource* upstream) + : memory_{} + , delegate_{memory_.data(), + memory_.size(), + upstream, + cetl::pf17::pmr::deviant::memory_resource_traits::max_size( + *upstream)} + { + } + + private: + void* do_allocate(std::size_t bytes, std::size_t alignment) override + { + return delegate_.allocate(bytes, alignment); + } + + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { + delegate_.deallocate(p, bytes, alignment); + } + + bool do_is_equal(const cetl::pf17::pmr::memory_resource& other) const noexcept override + { + return (this == &other); + } + + std::size_t do_max_size() const noexcept override + { + return delegate_.max_size(); + } + + void* do_reallocate(void* p, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment) override + { + return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); + } + + std::array memory_; + cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate delegate_; + }; + +public: + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return ArraySizeBytes / sizeof(typename Allocator::value_type); + } + + static constexpr typename std::add_pointer::type resource( + CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + auto resource = std::make_unique>( + UpstreamResourceFactoryType::template Bind::resource(upstream)); + resource_factory.resources_.push_back(std::move(resource)); + return resource_factory.resources_.back().get(); + } + static constexpr Allocator make_allocator(CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + +private: + std::vector>> resources_; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +#if (__cplusplus >= CETL_CPP_STANDARD_17) + +/// Creates a polymorphic allocator that uses std::pmr::new_delete_resource. +struct StdNewDeleteResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return std::numeric_limits::max() / sizeof(typename Allocator::value_type); + } + + static constexpr std::add_pointer::type resource( + StdNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + (void) resource_factory; + return std::pmr::new_delete_resource(); + } + + static constexpr Allocator make_allocator(StdNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; +}; +#endif + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TEST VALUE TYPES +// +-------------------------------------------------------------------------------------------------------------------+ + +/// A type that is not trivially constructable for use in test subject containers. +class NotTriviallyConstructable +{ +public: + NotTriviallyConstructable() + : data_{} + { + } + + NotTriviallyConstructable(std::size_t value) + : data_{std::to_string(value)} + { + } + + virtual ~NotTriviallyConstructable() = default; + + NotTriviallyConstructable(const NotTriviallyConstructable& rhs) + : data_{rhs.data_} + { + } + + NotTriviallyConstructable(NotTriviallyConstructable&& rhs) + : data_{std::move(rhs.data_)} + { + } + + NotTriviallyConstructable& operator=(const NotTriviallyConstructable& rhs) + { + data_ = rhs.data_; + return *this; + } + + NotTriviallyConstructable& operator=(NotTriviallyConstructable&& rhs) + { + data_ = std::move(rhs.data_); + return *this; + } + + virtual const char* what() const noexcept + { + return data_.c_str(); + } + + operator std::size_t() const noexcept + { + if (data_.empty()) + { + return 0; + } + else + { + return std::stoul(data_); + } + } + + NotTriviallyConstructable& operator=(std::size_t value) + { + data_ = std::to_string(value); + return *this; + } + +private: + std::string data_; +}; + +static_assert(!std::is_trivially_constructible::value, + "NotTriviallyConstructable must not be trivially constructable"); + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TYPED TEST PROTOCOL +// +-------------------------------------------------------------------------------------------------------------------+ + +struct VectorTag +{}; +struct VectorSkipReallocTag +{}; +struct VLATag +{}; + +/// The primary test protocol. Each test case will have a single VLA (the subject) with the given value_type and +/// allocator_type. The allocator_type will be constructed with the given memory_resource_factory_type and that +/// memory resource will be given the providing memory_resource_upstream_factory_type. +template +struct TestAllocatorType +{ + using container_type = ContainerType; + using allocator_type = Allocator; + using value_type = typename Allocator::value_type; + using memory_resource_factory_type = MemoryResourceFactoryType; + using memory_resource_upstream_factory_type = UpstreamMemoryResourceFactoryType; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TEST SUITE +// +-------------------------------------------------------------------------------------------------------------------+ + +/// +/// Test suite for running multiple allocators against the variable length array type. +/// +template +class VLATestsGeneric : public ::testing::Test +{ +public: + // Some tests will run std::min(maximumMaxSize, get_expected_max_size()) to avoid + // trying to run std::numeric_limits::max() loops or allocate that much memory. + constexpr static std::size_t maximumMaxSize = 1024; + using MemoryResourceFactoryType = typename T::memory_resource_factory_type; + using MemoryResourceFactoryTypePtr = typename std::add_pointer::type; + using MemoryResourceUpstreamFactoryType = typename T::memory_resource_upstream_factory_type; + using MemoryResourceUpstreamFactoryTypePtr = typename std::add_pointer::type; + using Allocator = typename T::allocator_type; + using Value = typename T::value_type; + using SubjectType = typename std::conditional::value, + cetl::VariableLengthArray, + std::vector>::type; + + static void SetUpTestSuite() + { +#ifdef CETLVAST_RTTI_ENABLED + ::testing::Test::RecordProperty("TestAllocatorType", typeid(T).name()); +#else + ::testing::Test::RecordProperty("TestAllocatorType", "(RTTI disabled)"); +#endif + } + void SetUp() override + { + memoryResourceUpstreamFactory_ = std::make_unique(); + memoryResourceFactory_ = std::make_unique(); + cetlvast::InstrumentedAllocatorStatistics::reset(); + } + + // Tears down the test fixture. + void TearDown() override + { + memoryResourceFactory_.reset(); + memoryResourceUpstreamFactory_.reset(); + } + + // Get the configured maximum number of objects for the allocator. + std::size_t get_expected_max_size(std::size_t clamp = std::numeric_limits::max() / sizeof(Value)) const noexcept + { + return std::min(clamp, + MemoryResourceFactoryType::template Bind:: + expected_max_size()); + } + + Allocator make_allocator() + { + return MemoryResourceFactoryType::template Bind:: + make_allocator(*memoryResourceFactory_, *memoryResourceUpstreamFactory_); + } + +private: + std::unique_ptr memoryResourceFactory_; + std::unique_ptr memoryResourceUpstreamFactory_; +}; + +template +const std::size_t VLATestsGeneric::maximumMaxSize; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TYPED TEST, TYPES +// | See comments on TestAllocatorType for the "protocol" in use here. +// +-------------------------------------------------------------------------------------------------------------------+ +// clang-format off + +using MyTypes = ::testing::Types< +/* container type tag | allocator type | primary memory resource factory */ +/* 0 */ TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> +/* 1 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> +/* 2 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> +/* 3 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> +/* 4 */ , TestAllocatorType, MaxSizeResourceFactory<24>> +/* 5 */ , TestAllocatorType, CetlNewDeleteResourceFactory> +/* 6 */ , TestAllocatorType, CetlNewDeleteResourceFactory> +#if (__cplusplus >= CETL_CPP_STANDARD_17) +/* 7 */ , TestAllocatorType, StdNewDeleteResourceFactory> +/* 8 */ , TestAllocatorType, StdNewDeleteResourceFactory> +#endif +>; +// clang-format on + +TYPED_TEST_SUITE(VLATestsGeneric, MyTypes, ); + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TESTS +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestReserve) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + ASSERT_EQ(0U, subject.capacity()); + ASSERT_EQ(0U, subject.size()); + ASSERT_EQ(this->get_expected_max_size(), subject.max_size()); + subject.reserve(1); + ASSERT_LE(1U, subject.capacity()); + ASSERT_EQ(0U, subject.size()); + ASSERT_EQ(this->get_expected_max_size(), subject.max_size()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestPush) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + ASSERT_EQ(0U, subject.size()); + + typename decltype(subject)::value_type x = 0; + + const std::size_t clamped_max = std::min(this->get_expected_max_size(), this->maximumMaxSize); + subject.reserve(clamped_max); + for (std::size_t i = 0; i < clamped_max; ++i) + { + subject.push_back(x); + + ASSERT_EQ(i + 1, subject.size()); + ASSERT_LE(subject.size(), subject.capacity()); + + ASSERT_EQ(x, subject[i]); + x = x + 1; + } +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestPop) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); + ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; + subject.reserve(clamped_max); + const std::size_t reserved = subject.capacity(); + ASSERT_LE(clamped_max, subject.capacity()); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(1, subject[0]); + ASSERT_EQ(1U, subject.size()); + subject.pop_back(); + ASSERT_EQ(0U, subject.size()); + ASSERT_EQ(reserved, subject.capacity()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestShrink) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); + ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; + + subject.reserve(clamped_max); + const auto reserved = subject.capacity(); + ASSERT_LE(clamped_max, reserved); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(1, subject[0]); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(reserved, subject.capacity()); + subject.shrink_to_fit(); + // shrink_to_fit implementations are not required to exactly match the size of the container, but they can't grow + // the size. + ASSERT_LE(subject.capacity(), clamped_max); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestCopyAssignment) +{ + if (std::is_same::value) + { + GTEST_SKIP() << "Skipping test that requires CETL reallocation support."; + } + typename TestFixture::SubjectType subject0{TestFixture::make_allocator()}; + typename TestFixture::SubjectType subject1{TestFixture::make_allocator()}; + + subject0.push_back(1); + subject0.push_back(2); + subject0.push_back(3); + + subject1 = subject0; + + ASSERT_EQ(subject0.size(), subject1.size()); + ASSERT_GE(subject0.capacity(), subject1.capacity()); + ASSERT_EQ(subject0.max_size(), subject1.max_size()); + ASSERT_EQ(subject0[0], subject1[0]); + ASSERT_EQ(subject0[1], subject1[1]); + ASSERT_EQ(subject0[2], subject1[2]); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestOverMaxSize) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + const std::size_t MaxSize = subject.max_size(); + if (MaxSize > TestFixture::maximumMaxSize) + { + GTEST_SKIP() << "The allocator under test has a max_size that is too large for this test."; + return; + } + if (MaxSize == 0U) + { + GTEST_SKIP() << "The allocator under test does not have a maximum size."; + return; + } + + subject.reserve(MaxSize); + + for (std::size_t i = 1; i <= MaxSize; ++i) + { + subject.push_back(static_cast(i)); + ASSERT_EQ(i, subject.size()); + ASSERT_EQ(static_cast(i), subject[i - 1]); + } + + ASSERT_EQ(MaxSize, subject.capacity()); +#if __cpp_exceptions + ASSERT_THROW(subject.reserve(MaxSize + 1), std::length_error); + ASSERT_EQ(MaxSize, subject.capacity()); +#endif + + ASSERT_EQ(MaxSize, subject.size()); + +#if __cpp_exceptions + ASSERT_THROW(subject.push_back(0), std::length_error); +#endif +} + +// +----------------------------------------------------------------------+ +/** + * Test suite to ensure non-trivial objects are properly handled. This one is both for bool and non-bool spec. + */ +template +class VLATestsNonTrivialCommon : public ::testing::Test +{}; +using VLATestsNonTrivialCommonTypes = ::testing::Types; +TYPED_TEST_SUITE(VLATestsNonTrivialCommon, VLATestsNonTrivialCommonTypes, ); + +TYPED_TEST(VLATestsNonTrivialCommon, TestMoveToVector) +{ + cetl::VariableLengthArray> + subject{cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()), 10U}; + subject.reserve(subject.max_size()); + ASSERT_EQ(subject.capacity(), subject.max_size()); + for (std::size_t i = 0; i < subject.max_size(); ++i) + { + subject.push_back(static_cast(i % 2)); + ASSERT_EQ(i + 1, subject.size()); + } + std::vector a(subject.cbegin(), subject.cend()); + for (std::size_t i = 0; i < subject.max_size(); ++i) + { + ASSERT_EQ(static_cast(i % 2), a[i]); + } +} + +TYPED_TEST(VLATestsNonTrivialCommon, TestPushBackGrowsCapacity) +{ + static constexpr std::size_t PushBackItems = 9; + cetl::VariableLengthArray> subject{ + cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource())}; + + ASSERT_EQ(0U, subject.size()); + ASSERT_EQ(0U, subject.capacity()); + for (std::size_t i = 0; i < PushBackItems; ++i) + { + ASSERT_EQ(i, subject.size()); + ASSERT_LE(i, subject.capacity()); + subject.push_back(static_cast(i)); + ASSERT_EQ(i + 1, subject.size()); + ASSERT_LE(i + 1, subject.capacity()); + } + ASSERT_EQ(PushBackItems, subject.size()); + ASSERT_LE(PushBackItems, subject.capacity()); +} + +TYPED_TEST(VLATestsNonTrivialCommon, TestForEachConstIterators) +{ + static constexpr std::size_t MaxSize = 9; + cetl::VariableLengthArray> + subject{cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()), MaxSize}; + auto& const_subject = subject; + ASSERT_EQ(0U, const_subject.size()); + ASSERT_EQ(0U, const_subject.capacity()); + for (const auto& item : const_subject) // Requires begin() const, end() const. + { + (void) item; + FAIL(); + } + ASSERT_EQ(0U, const_subject.size()); + ASSERT_EQ(0U, const_subject.capacity()); + for (std::size_t i = 0; i < MaxSize; ++i) + { + ASSERT_EQ(i, const_subject.size()); + ASSERT_LE(i, const_subject.capacity()); + subject.push_back(static_cast(i % 2)); + ASSERT_EQ(i + 1, const_subject.size()); + ASSERT_LE(i + 1, const_subject.capacity()); + } + ASSERT_EQ(MaxSize, const_subject.size()); + ASSERT_LE(MaxSize, const_subject.capacity()); + std::size_t i = 0; + for (const auto& item : const_subject) // Requires begin() const, end() const. + { + ASSERT_EQ(static_cast(i % 2), item); + ++i; + } + ASSERT_EQ(const_subject.size(), i); +} + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + +TYPED_TEST(VLATestsNonTrivialCommon, SelfAssignment) +{ + auto allocator = cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()); + cetl::VariableLengthArray> subject{allocator}; + subject.push_back(0); + subject.push_back(1); + ASSERT_EQ(2U, subject.size()); + subject = subject; + ASSERT_EQ(2U, subject.size()); + ASSERT_EQ(0, subject[0]); + ASSERT_EQ(1, subject[1]); +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +TEST(VLATestsNonTrivialSpecific, TestDeallocSizeNonBool) +{ + cetlvast::InstrumentedAllocatorStatistics& stats = cetlvast::InstrumentedAllocatorStatistics::get(); + cetlvast::InstrumentedNewDeleteAllocator allocator; + cetl::VariableLengthArray subject{allocator}; + + subject.reserve(10U); + ASSERT_EQ(10U, subject.capacity()); + ASSERT_EQ(1U, stats.allocations); + ASSERT_EQ(10U * sizeof(int), stats.last_allocation_size_bytes); + ASSERT_EQ(0U, stats.last_deallocation_size_bytes); + subject.pop_back(); + subject.shrink_to_fit(); + ASSERT_EQ(10U * sizeof(int), stats.last_deallocation_size_bytes); +} + +TEST(VLATestsNonTrivialSpecific, TestPush) +{ + cetlvast::InstrumentedAllocatorStatistics& stats = cetlvast::InstrumentedAllocatorStatistics::get(); + cetlvast::InstrumentedNewDeleteAllocator allocator; + cetl::VariableLengthArray subject{allocator}; + ASSERT_EQ(nullptr, subject.data()); + ASSERT_EQ(0U, subject.size()); + std::size_t x = 0; + for (std::size_t i = 0; i < 1024U; ++i) + { + subject.push_back(x); + ASSERT_EQ(i + 1, subject.size()); + ASSERT_LE(subject.size(), subject.capacity()); + const std::size_t* const pushed = &subject[i]; + ASSERT_EQ(*pushed, x); + ++x; + } + // The container must not allocate for each push. + ASSERT_GT(1024U, stats.allocations); + subject.clear(); + ASSERT_EQ(0U, subject.size()); + ASSERT_GE(subject.capacity(), 1024U); + subject.shrink_to_fit(); + ASSERT_EQ(0U, subject.capacity()); + ASSERT_EQ(0U, stats.outstanding_allocated_memory); +} + +/** + * Used to test that destructors were called. + */ +class Doomed +{ +public: + Doomed(int* out_signal_dtor) + : out_signal_dtor_(out_signal_dtor) + , moved_(false) + { + } + Doomed(Doomed&& from) noexcept + : out_signal_dtor_(from.out_signal_dtor_) + , moved_(false) + { + from.moved_ = true; + } + Doomed(const Doomed&) = delete; + Doomed& operator=(const Doomed&) = delete; + Doomed& operator=(Doomed&&) = delete; + + ~Doomed() + { + if (!moved_) + { + (*out_signal_dtor_) += 1; + } + } + +private: + int* out_signal_dtor_; + bool moved_; +}; + +TEST(VLATestsNonTrivialSpecific, TestDestroy) +{ + int dtor_called = 0; + + auto subject = + std::make_shared>>(std::allocator{}); + + subject->reserve(10); + ASSERT_EQ(10U, subject->capacity()); + subject->push_back(Doomed(&dtor_called)); + ASSERT_EQ(1U, subject->size()); + subject->push_back(Doomed(&dtor_called)); + ASSERT_EQ(2U, subject->size()); + ASSERT_EQ(0, dtor_called); + subject.reset(); + ASSERT_EQ(2, dtor_called); +} + +TEST(VLATestsNonTrivialSpecific, TestNonFundamental) +{ + int dtor_called = 0; + + cetl::VariableLengthArray> subject(std::allocator{}); + + subject.reserve(10U); + ASSERT_EQ(10U, subject.capacity()); + subject.push_back(Doomed(&dtor_called)); + ASSERT_EQ(1U, subject.size()); + subject.pop_back(); + ASSERT_EQ(1, dtor_called); +} + +TEST(VLATestsNonTrivialSpecific, TestNotMovable) +{ + class NotMovable + { + public: + NotMovable() {} + NotMovable(NotMovable&&) = delete; + NotMovable(const NotMovable& rhs) noexcept + { + (void) rhs; + } + }; + cetl::VariableLengthArray> subject(std::allocator{}); + + subject.reserve(10U); + ASSERT_EQ(10U, subject.capacity()); + NotMovable source; + subject.push_back(source); + ASSERT_EQ(1U, subject.size()); +} + +TEST(VLATestsNonTrivialSpecific, TestMovable) +{ + class Movable + { + public: + Movable(int data) + : data_(data) + { + } + Movable(const Movable&) = delete; + Movable(Movable&& move_from) noexcept + : data_(move_from.data_) + { + move_from.data_ = 0; + } + int get_data() const + { + return data_; + } + + private: + int data_; + }; + cetl::VariableLengthArray> subject(std::allocator{}); + subject.reserve(10U); + ASSERT_EQ(10U, subject.capacity()); + subject.push_back(Movable(1)); + ASSERT_EQ(1U, subject.size()); + Movable* pushed = &subject[0]; + ASSERT_NE(nullptr, pushed); + ASSERT_EQ(1, pushed->get_data()); +} + +TEST(VLATestsNonTrivialSpecific, TestInitializerArray) +{ + cetl::VariableLengthArray> subject{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + std::allocator{}}; + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } +} + +TEST(VLATestsNonTrivialSpecific, TestCopyConstructor) +{ + cetl::VariableLengthArray> fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + std::allocator{}}; + + cetl::VariableLengthArray> subject(fixture); + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } +} + +TEST(VLATestsNonTrivialSpecific, TestMoveConstructor) +{ + cetl::VariableLengthArray> fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + std::allocator{}}; + + cetl::VariableLengthArray> subject(std::move(fixture)); + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } + ASSERT_EQ(0U, fixture.size()); + ASSERT_EQ(0U, fixture.capacity()); +} + +TEST(VLATestsNonTrivialSpecific, TestCompare) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> one{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, allocator}; + cetl::VariableLengthArray> two{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, allocator}; + cetl::VariableLengthArray> three{{9, 8, 7, 6, 5, 4, 3, 2, 1}, allocator}; + ASSERT_EQ(one, one); + ASSERT_EQ(one, two); + ASSERT_NE(one, three); +} + +TEST(VLATestsNonTrivialSpecific, TestFPCompare) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> one{{1.00, 2.00}, allocator}; + cetl::VariableLengthArray> two{{1.00, 2.00}, allocator}; + const double epsilon_for_two_comparison = std::nextafter(4.00, INFINITY) - 4.00; + cetl::VariableLengthArray> + three{{1.00, std::nextafter(2.00 + epsilon_for_two_comparison, INFINITY)}, allocator}; + ASSERT_EQ(one, one); + ASSERT_EQ(one, two); + ASSERT_NE(one, three); +} + +TEST(VLATestsNonTrivialSpecific, TestCompareBool) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> one{{true, false, true}, allocator}; + cetl::VariableLengthArray> two{{true, false, true}, allocator}; + cetl::VariableLengthArray> three{{true, true, false}, allocator}; + ASSERT_EQ(one, one); + ASSERT_EQ(one, two); + ASSERT_NE(one, three); +} + +TEST(VLATestsNonTrivialSpecific, TestCopyAssignment) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> lhs{{1.00}, allocator}; + cetl::VariableLengthArray> rhs{{2.00, 3.00}, allocator}; + ASSERT_EQ(1U, lhs.size()); + ASSERT_EQ(2U, rhs.size()); + ASSERT_NE(lhs, rhs); + lhs = rhs; + ASSERT_EQ(2U, lhs.size()); + ASSERT_EQ(2U, rhs.size()); + ASSERT_EQ(lhs, rhs); +} + +TEST(VLATestsNonTrivialSpecific, TestMoveAssignment) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> lhs{{std::string("one"), std::string("two")}, + allocator}; + cetl::VariableLengthArray> rhs{{std::string("three"), + std::string("four"), + std::string("five")}, + allocator}; + ASSERT_EQ(2U, lhs.size()); + ASSERT_EQ(3U, rhs.size()); + ASSERT_NE(lhs, rhs); + lhs = std::move(rhs); + ASSERT_EQ(3U, lhs.size()); + ASSERT_EQ(0U, rhs.size()); + ASSERT_EQ(0U, rhs.capacity()); + ASSERT_NE(lhs, rhs); + ASSERT_EQ(std::string("three"), lhs[0]); +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_compiles.cpp b/cetlvast/suites/unittest/test_variable_length_array_compiles.cpp new file mode 100644 index 00000000..ef45fa97 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_compiles.cpp @@ -0,0 +1,83 @@ +/// @file +/// Unit tests for cetl::VariableLengthArray type that only test compilation. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +// cSpell: words pocma + +#include "cetlvast/helpers_gtest.hpp" +#include "cetl/variable_length_array.hpp" +#include "cetl/pf17/cetlpf.hpp" + +#include + +template +class TestVariableLengthArrayCompiles : public testing::Test +{}; + +// --------------------------------------------------------------------------------------------------------------------- + +using VLATypes = ::testing::Types>, + cetl::VariableLengthArray>>; + +TYPED_TEST_SUITE(TestVariableLengthArrayCompiles, VLATypes, ); + +// --------------------------------------------------------------------------------------------------------------------- + +TYPED_TEST(TestVariableLengthArrayCompiles, PolyMorphicAllocatorCompiles) +{ + static_assert(std::is_nothrow_constructible::value, + "VariableLengthArray's default allocator must be no-throw default constructible"); + + static_assert(std::is_nothrow_destructible::value, + "VariableLengthArray's default allocator must be no-throw destructible.'."); +} + +TYPED_TEST(TestVariableLengthArrayCompiles, MoveConstructorIsNoThrow) +{ + static_assert(noexcept(TypeParam(std::move(std::declval()))), "Must be no-throw move constructable."); +} + +// used by MoveAssignmentNoexcept test. +template +struct FakeAllocator +{ + using is_always_equal = isaType; + using propagate_on_container_move_assignment = pocmaType; + using value_type = T; + + template + struct rebind + { + typedef FakeAllocator other; + }; +}; + +TYPED_TEST(TestVariableLengthArrayCompiles, MoveAssignmentNoexcept) +{ + using IsAlways_and_DoesProp = FakeAllocator; + using IsAlways_but_DoesNotProp = FakeAllocator; + using NotAlways_but_DoesProp = FakeAllocator; + using NotAlways_and_DoesNotProp = FakeAllocator; + + using VLAType_0 = cetl::VariableLengthArray < typename TypeParam::value_type, IsAlways_and_DoesProp> ; + static_assert(noexcept(std::declval() = std::move(std::declval())), + "Violates noexcept specification for move constructor."); + + using VLAType_1 = cetl::VariableLengthArray < typename TypeParam::value_type, IsAlways_but_DoesNotProp> ; + static_assert(noexcept(std::declval() = std::move(std::declval())), + "Violates noexcept specification for move constructor."); + + using VLAType_2 = cetl::VariableLengthArray < typename TypeParam::value_type, NotAlways_but_DoesProp> ; + static_assert(noexcept(std::declval() = std::move(std::declval())), + "Violates noexcept specification for move constructor."); + + using VLAType_3 = cetl::VariableLengthArray < typename TypeParam::value_type, NotAlways_and_DoesNotProp> ; + static_assert(!noexcept(std::declval() = std::move(std::declval())), + "Violates noexcept specification for move constructor."); +} + +// --------------------------------------------------------------------------------------------------------------------- diff --git a/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp b/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp new file mode 100644 index 00000000..c2fc3235 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp @@ -0,0 +1,288 @@ +/// @file +/// Unit tests for the copy and move operations of cetl::VariableLengthArray +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/variable_length_array.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" +#include "cetl/pmr/array_memory_resource.hpp" +#include "cetl/pf17/byte.hpp" + +#include "cetlvast/helpers_gtest.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" +#include +#include +#include +#include + +// +---------------------------------------------------------------------------+ +// | TEST VALUE TYPES +// +---------------------------------------------------------------------------+ + +/// The most int-like struct you've ever seen. I mean, why even bother? It's +/// just an int. +struct BoxedInt +{ + BoxedInt() noexcept = default; + BoxedInt(const BoxedInt&) noexcept = default; + BoxedInt(BoxedInt&&) noexcept = default; + BoxedInt& operator=(const BoxedInt&) noexcept = default; + BoxedInt& operator=(BoxedInt&&) noexcept = default; + ~BoxedInt() noexcept = default; + + BoxedInt(int value) noexcept + : value_{value} + { + } + + BoxedInt& operator=(int rhs) noexcept + { + value_ = rhs; + return *this; + } + + operator int() const noexcept + { + return value_; + } + + bool operator==(int rhs) const noexcept + { + return value_ == rhs; + } + + bool operator!=(int rhs) const noexcept + { + return value_ != rhs; + } + +private: + int value_; +}; + +/// Acts like an int but is not trivially copyable, movable, constructable, nor destructible. +/// It also may throw from any of its operations. +struct NonTrivialBoxedInt +{ + NonTrivialBoxedInt() + : value_{std::make_unique(0)} + { + } + NonTrivialBoxedInt(int value) + : value_{std::make_unique(value)} + { + } + NonTrivialBoxedInt(const NonTrivialBoxedInt& rhs) + : value_{std::make_unique(*rhs.value_)} + { + } + NonTrivialBoxedInt(NonTrivialBoxedInt&& rhs) + : value_{std::move(rhs.value_)} + { + } + NonTrivialBoxedInt& operator=(int rhs) + { + value_ = std::make_unique(rhs); + return *this; + } + NonTrivialBoxedInt& operator=(const NonTrivialBoxedInt& rhs) + { + return operator=(*rhs.value_); + } + NonTrivialBoxedInt& operator=(NonTrivialBoxedInt&& rhs) + { + value_ = std::move(rhs.value_); + return *this; + } + ~NonTrivialBoxedInt() = default; + + operator int() const + { + return *value_; + } + + bool operator==(int rhs) const + { + return *value_ == rhs; + } + + bool operator!=(int rhs) const + { + return !operator==(rhs); + } + +private: + std::unique_ptr value_; +}; + +// +---------------------------------------------------------------------------+ +// | TEST PROTOCOL +// +---------------------------------------------------------------------------+ + +/// Protocol for the typed test suite +template < + typename SubjectValueType, + typename SubjectAllocatorFactoryType, + typename SourceValueType, + typename SourceAllocatorFactoryType, + typename SubjectAllocatorValueType = + typename std::conditional::value, unsigned char, SubjectValueType>::type, + typename SourceAllocatorValueType = + typename std::conditional::value, unsigned char, SourceValueType>::type> +struct TypeParamDef +{ + TypeParamDef() = delete; + + using subject = cetlvast::AllocatorTypeParamDef; + using source = cetlvast::AllocatorTypeParamDef; + + using subject_vla_type = cetl::VariableLengthArray; + using source_vla_type = cetl::VariableLengthArray; + + static constexpr typename subject::allocator_type make_subject_allocator() + { + return subject::allocator_factory::template make_allocator(); + } + + static constexpr typename source::allocator_type make_source_allocator() + { + return source::allocator_factory::template make_allocator(); + } + + static constexpr void reset() + { + subject::allocator_factory::template reset(); + source::allocator_factory::template reset(); + } +}; + +// +---------------------------------------------------------------------------+ +// | TEST SUITE +// +---------------------------------------------------------------------------+ + +template +class VLACopyMoveTests : public ::testing::Test +{ +protected: + void TearDown() override + { + T::reset(); + } +}; + +// clang-format off +namespace cetlvast +{ +using MyTypes = ::testing::Types< +/* source value type | allocator factory | subject val. type | subject allocator factory */ +/* 0 */ TypeParamDef +/* 1 */, TypeParamDef +/* 2 */, TypeParamDef +/* 3 */, TypeParamDef +/* 4 */, TypeParamDef, int, PolymorphicAllocatorNewDeleteFactory> +/* 5 */, TypeParamDef, bool, PolymorphicAllocatorNewDeleteFactory> +/* 6 */, TypeParamDef, BoxedInt, PolymorphicAllocatorNewDeleteFactory> +/* 7 */, TypeParamDef, NonTrivialBoxedInt, PolymorphicAllocatorNewDeleteFactory> +/* 8 */, TypeParamDef +/* 9 */, TypeParamDef +/* 10 */, TypeParamDef +/* 11 */, TypeParamDef + +>; +} // namespace cetlvast +// clang-format on + +TYPED_TEST_SUITE(VLACopyMoveTests, cetlvast::MyTypes, ); + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: Copy Construction +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, CopyConstruct) +{ + typename TypeParam::source_vla_type source{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 9); + + typename TypeParam::subject_vla_type subject{source}; + EXPECT_EQ(source.size(), 9); + EXPECT_EQ(subject.size(), source.size()); + EXPECT_EQ(subject, source); +} + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: Copy Assignment +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, CopyAssign) +{ + typename TypeParam::subject_vla_type subject{TypeParam::make_subject_allocator()}; + typename TypeParam::source_vla_type source{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 9); + EXPECT_EQ(subject.size(), 0); + EXPECT_NE(subject, source); + subject = source; + EXPECT_EQ(subject.size(), 9); + EXPECT_EQ(subject, source); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, CopyAssignReplaceWithLess) +{ + typename TypeParam::subject_vla_type subject{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_subject_allocator()}; + typename TypeParam::source_vla_type source{{0, 1, 0, 1}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 4); + EXPECT_EQ(subject.size(), 9); + EXPECT_NE(subject, source); + subject = source; + EXPECT_EQ(subject.size(), 4); + EXPECT_EQ(subject, source); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, CopyAssignReplaceWithMore) +{ + typename TypeParam::subject_vla_type subject{{0, 1, 0, 1}, TypeParam::make_subject_allocator()}; + typename TypeParam::source_vla_type source{{0, 1, 0, 1, 0, 1, 0, 1}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 8); + EXPECT_EQ(subject.size(), 4); + EXPECT_NE(subject, source); + subject = source; + EXPECT_EQ(subject.size(), 8); + EXPECT_EQ(subject, source); +} + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: Move Construction +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, MoveConstruct) +{ + typename TypeParam::source_vla_type source{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 9); + + typename TypeParam::subject_vla_type subject{std::move(source)}; + EXPECT_EQ(source.size(), 0); + EXPECT_EQ(subject.size(), 9); +} + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: Move Assignment +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, MoveAssign) +{ + typename TypeParam::subject_vla_type subject{TypeParam::make_subject_allocator()}; + typename TypeParam::source_vla_type source{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; + EXPECT_EQ(source.size(), 9); + EXPECT_EQ(subject.size(), 0); + EXPECT_NE(subject, source); + subject = std::move(source); + EXPECT_EQ(source.size(), 0); + EXPECT_EQ(subject.size(), 9); +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_detailed_allocation.cpp b/cetlvast/suites/unittest/test_variable_length_array_detailed_allocation.cpp new file mode 100644 index 00000000..7adac9c6 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_detailed_allocation.cpp @@ -0,0 +1,693 @@ +/// @file +/// Detailed introspection of allocation patterns within the +/// the VariableLengthArray type. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/variable_length_array.hpp" + +#include "cetlvast/helpers_gtest.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" +#include "cetlvast/datasets.h" +#include +#include +#include +#include +#include +#include + +// +---------------------------------------------------------------------------+ +// | TEST FIXTURES +// +---------------------------------------------------------------------------+ + +struct InstrumentedType +{ + static std::size_t instance_counter; + static std::size_t total_instances_constructed; + static std::size_t total_instances_default_constructed; + static std::size_t total_instances_copy_constructed; + static std::size_t total_instances_move_constructed; + static std::size_t total_instances_implicit_int_constructed; + + InstrumentedType() noexcept + : copy_assignments{0} + , move_assignments{0} + , value_{-1} + { + ++instance_counter; + ++total_instances_constructed; + ++total_instances_default_constructed; + } + + InstrumentedType(int implicit_value) noexcept + : copy_assignments{0} + , move_assignments{0} + , value_{implicit_value} + { + ++instance_counter; + ++total_instances_constructed; + ++total_instances_implicit_int_constructed; + } + + InstrumentedType(const InstrumentedType& rhs) noexcept + : copy_assignments{0} + , move_assignments{0} + , value_{rhs.value_} + { + ++instance_counter; + ++total_instances_constructed; + ++total_instances_copy_constructed; + } + + InstrumentedType(InstrumentedType&& rhs) noexcept + : copy_assignments{0} + , move_assignments{0} + , value_{rhs.value_} + { + rhs.value_ = -1; + ++instance_counter; + ++total_instances_constructed; + ++total_instances_move_constructed; + } + + ~InstrumentedType() + { + EXPECT_GT(instance_counter, 0) << "Attempted to destroy more instances than were created." << std::endl; + --instance_counter; + } + + InstrumentedType& operator=(InstrumentedType&& rhs) noexcept + { + value_ = rhs.value_; + rhs.value_ = -1; + ++move_assignments; + return *this; + } + + InstrumentedType& operator=(const InstrumentedType& rhs) noexcept + { + value_ = rhs.value_; + ++copy_assignments; + return *this; + } + + bool operator==(const InstrumentedType& rhs) const noexcept + { + return (value_ == rhs.value_); + } + + bool operator!=(const InstrumentedType& rhs) const noexcept + { + return !this->operator==(rhs); + } + + operator int() const noexcept + { + return value_; + } + + std::size_t copy_assignments; + std::size_t move_assignments; + +private: + int value_; +}; + +std::size_t InstrumentedType::instance_counter = 0; +std::size_t InstrumentedType::total_instances_constructed = 0; +std::size_t InstrumentedType::total_instances_default_constructed = 0; +std::size_t InstrumentedType::total_instances_copy_constructed = 0; +std::size_t InstrumentedType::total_instances_move_constructed = 0; +std::size_t InstrumentedType::total_instances_implicit_int_constructed = 0; + +// +---------------------------------------------------------------------------+ +// | TEST SUITE +// +---------------------------------------------------------------------------+ + +template +class VLADetailedAllocationTests : public ::testing::Test +{ +protected: + // +-----------------------------------------------------------------------+ + // | Test + // +-----------------------------------------------------------------------+ + void SetUp() override + { + InstrumentedType::instance_counter = 0; + InstrumentedType::total_instances_constructed = 0; + InstrumentedType::total_instances_copy_constructed = 0; + InstrumentedType::total_instances_move_constructed = 0; + InstrumentedType::total_instances_implicit_int_constructed = 0; + InstrumentedType::total_instances_default_constructed = 0; + cetlvast::InstrumentedAllocatorStatistics::get().reset(); + } + + void TearDown() override + { + ASSERT_EQ(0, InstrumentedType::instance_counter); + ASSERT_EQ(0, outstanding_memory()); + } + + // +-----------------------------------------------------------------------+ + // | Test Helpers + // +-----------------------------------------------------------------------+ + using ItemT = typename T::value_type; + constexpr static std::size_t ItemSize = sizeof(ItemT); + + std::size_t outstanding_memory() const + { + return cetlvast::InstrumentedAllocatorStatistics::get().outstanding_allocated_memory; + } + + template + void account_for_all_memory(const ContainerT0& c0, const ContainerT1& c1) const + { + const std::size_t expected_outstanding_memory = (c0.capacity() + c1.capacity()) * ItemSize; + EXPECT_EQ(expected_outstanding_memory, this->outstanding_memory()); + } +}; + +// Helper for determining if the given test instantiation includes assigning +// between allocators that the test subjects will consider equal. +template +struct AreAllocatorsEqual : public std::integral_constant +{}; + +// +---------------------------------------------------------------------------+ + +// clang-format off +namespace cetlvast +{ +using MyTypes = ::testing::Types< + /* container type | item type | allocator type | is_always_equal | is equal | move prop. | copy prop. | */ + /*--------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +/* 0*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::true_type, std::true_type > >, +/* 1*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::true_type, std::true_type > >, +/* 2*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::false_type, std::true_type > >, +/* 3*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::false_type, std::true_type > >, +/* 4*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::true_type, std::true_type > >, +/* 5*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::true_type, std::true_type > >, +/* 6*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::false_type, std::true_type > >, +/* 7*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::false_type, std::true_type > >, +/* 8*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::true_type, std::false_type > >, +/* 9*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::true_type, std::false_type > >, +/*10*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::false_type, std::false_type > >, +/*11*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::true_type, std::false_type, std::false_type > >, +/*12*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::true_type, std::false_type > >, +/*13*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::true_type, std::false_type > >, +/*14*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::false_type, std::false_type > >, +/*15*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::true_type, std::false_type, std::false_type > >, + +/*16*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::true_type, std::true_type > >, +/*17*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::true_type, std::true_type > >, +/*18*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::false_type, std::true_type > >, +/*19*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::false_type, std::true_type > >, +/*20*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::true_type, std::true_type > >, +/*21*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::true_type, std::true_type > >, +/*22*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::false_type, std::true_type > >, +/*23*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::false_type, std::true_type > >, +/*24*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::true_type, std::false_type > >, +/*25*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::true_type, std::false_type > >, +/*26*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::false_type, std::false_type > >, +/*27*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::true_type, std::false_type, std::false_type, std::false_type > >, +/*28*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::true_type, std::false_type > >, +/*29*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::true_type, std::false_type > >, +/*30*/ cetl::VariableLengthArray< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::false_type, std::false_type > >, +/*31*/ std::vector< InstrumentedType, InstrumentedNewDeleteAllocator< InstrumentedType, std::false_type, std::false_type, std::false_type, std::false_type > > +>; +} +// clang-format on + +TYPED_TEST_SUITE(VLADetailedAllocationTests, cetlvast::MyTypes, ); + +// +---------------------------------------------------------------------------+ +// | TEST CASES +// +---------------------------------------------------------------------------+ + +// This is a meta-test. It ensures that the test fixtures are working as expected. +TYPED_TEST(VLADetailedAllocationTests, AllocatorDefaultState) +{ + TypeParam test_subject{typename TypeParam::allocator_type()}; + EXPECT_EQ(0, TestFixture::ItemT::total_instances_constructed); + ASSERT_EQ(0, this->outstanding_memory()); + + test_subject.emplace_back(1); + EXPECT_EQ(1, TestFixture::ItemT::total_instances_constructed); + ASSERT_EQ(TestFixture::ItemSize * test_subject.capacity(), this->outstanding_memory()); + + ASSERT_EQ(1, test_subject.size()); + ASSERT_EQ(0, test_subject[0].move_assignments); + + ASSERT_EQ(TypeParam::allocator_type::propagate_on_container_copy_assignment::value, + std::allocator_traits::propagate_on_container_copy_assignment::value); + ASSERT_EQ(TypeParam::allocator_type::propagate_on_container_move_assignment::value, + std::allocator_traits::propagate_on_container_move_assignment::value); + ASSERT_EQ(TypeParam::allocator_type::is_always_equal::value, + std::allocator_traits::is_always_equal::value); + ASSERT_FALSE(test_subject.get_allocator().was_from_soccc); + + test_subject.pop_back(); + test_subject.shrink_to_fit(); + + ASSERT_EQ(0, this->outstanding_memory()); + + TypeParam sequence_one_to_four_a{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + TypeParam sequence_one_to_four_b{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + TypeParam sequence_six_to_nine_a{{6, 7, 8, 9}, typename TypeParam::allocator_type{}}; + + ASSERT_EQ(sequence_one_to_four_a, sequence_one_to_four_b); + ASSERT_NE(sequence_one_to_four_a, sequence_six_to_nine_a); + + std::cout << "Sizeof InstrumentedType: " << sizeof(InstrumentedType) << std::endl; +} + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: COPY ASSIGN +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyAssignSameSize) +{ + TypeParam test_subject{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7, 8, 9}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(4, test_subject.size()); + EXPECT_EQ(4, test_source.size()); + + test_subject = test_source; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(4, test_source.size()); + this->account_for_all_memory(test_subject, test_source); + + // For creating the initial values from integers + EXPECT_EQ(8, TestFixture::ItemT::total_instances_implicit_int_constructed); + if (AreAllocatorsEqual::value || + !TypeParam::allocator_type::propagate_on_container_copy_assignment::value) + { + // if the allocators are equal or the incoming allocator does not propagate + // we expect assignments to occur only for the elements in the container + EXPECT_EQ(8, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(16, TestFixture::ItemT::total_instances_constructed); + } + else + { + // because the allocators are not equal the container has to completely + // discard and reallocate its memory. This means that the container + // will end up invoking more object constructors. + EXPECT_EQ(12, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(20, TestFixture::ItemT::total_instances_constructed); + } + // all other movement should be via copy assignment since the size of the + // two containers is identical. + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyAssignLargeToSmall) +{ + TypeParam test_subject{{1, 2, 3, 4, 5}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7, 8, 9, 10, 11, 12}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(5, test_subject.size()); + EXPECT_EQ(7, test_source.size()); + + test_subject = test_source; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(7, test_source.size()); + this->account_for_all_memory(test_subject, test_source); + + // For creating the initial values from integers + EXPECT_EQ(12, TestFixture::ItemT::total_instances_implicit_int_constructed); + // Because we are copying from a larger container to a smaller container + // there is no different in the number of objects constructed for based on + // allocator equality or copy propagation. + EXPECT_EQ(19, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_move_constructed); + EXPECT_EQ(31, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyAssignSmallToLarge) +{ + TypeParam test_subject{{1, 2, 3, 4, 5}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(5, test_subject.size()); + EXPECT_EQ(2, test_source.size()); + + test_subject = test_source; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(2, test_subject.size()); + this->account_for_all_memory(test_subject, test_source); + + // For creating the initial values from integers + EXPECT_EQ(7, TestFixture::ItemT::total_instances_implicit_int_constructed); + if (AreAllocatorsEqual::value || + !TypeParam::allocator_type::propagate_on_container_copy_assignment::value) + { + // if the allocators are equal or the incoming allocator does not propagate + // we expect assignments to occur only for the elements in the container + EXPECT_EQ(7, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(14, TestFixture::ItemT::total_instances_constructed); + } + else + { + // because the allocators are not equal the container has to completely + // discard and reallocate its memory. This means that the container + // will end up invoking more object constructors. + EXPECT_EQ(7 + 2, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(16, TestFixture::ItemT::total_instances_constructed); + } + // all other movement should be via copy assignment since the size of the + // two containers is identical. + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyAssignVeryLargeToEmpty) +{ + TypeParam test_subject{typename TypeParam::allocator_type{}}; + TypeParam test_source{cetlvast::large_array_of_integers, + &cetlvast::large_array_of_integers[cetlvast::large_array_of_integers_size], + typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(0, test_subject.size()); + EXPECT_EQ(cetlvast::large_array_of_integers_size, test_source.size()); + + test_subject = test_source; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(cetlvast::large_array_of_integers_size, test_subject.size()); + this->account_for_all_memory(test_subject, test_source); + // For creating the initial values from integers + EXPECT_EQ(cetlvast::large_array_of_integers_size, TestFixture::ItemT::total_instances_implicit_int_constructed); + // Because we are copying from a larger container to a smaller container + // there is no different in the number of objects constructed for based on + // allocator equality or copy propagation. + EXPECT_EQ(cetlvast::large_array_of_integers_size, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_move_constructed); + EXPECT_EQ(cetlvast::large_array_of_integers_size * 2, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyAssignFromEmpty) +{ + TypeParam test_subject{{0, 1, 2}, typename TypeParam::allocator_type{}}; + TypeParam test_source{typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(3, test_subject.size()); + EXPECT_EQ(0, test_source.size()); + + test_subject = test_source; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(0, test_subject.size()); + this->account_for_all_memory(test_subject, test_source); + + // For creating the initial values from integers + EXPECT_EQ(3, TestFixture::ItemT::total_instances_implicit_int_constructed); + // Because we are copying from an empty container into a full one. We don't + // expect any additional object construction to occur. + EXPECT_EQ(3, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_move_constructed); + EXPECT_EQ(6, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: COPY CONSTRUCT +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, CopyConstruct) +{ + TypeParam test_source{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + EXPECT_EQ(4, test_source.size()); + + TypeParam test_subject{test_source}; + + EXPECT_EQ(test_subject, test_source); + EXPECT_EQ(4, test_subject.size()); + EXPECT_TRUE(test_subject.get_allocator().was_from_soccc); + + // For creating the initial values from integers and copying the source + // array. + EXPECT_EQ(4, TestFixture::ItemT::total_instances_implicit_int_constructed); + this->account_for_all_memory(test_subject, test_source); + EXPECT_EQ(8, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(12, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: MOVE ASSIGN +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveAssignSameSize) +{ + TypeParam test_subject{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7, 8, 9}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + // copy the source array because we don't inspect the state of a moved + // object. + TypeParam copy_of_source{test_source}; + EXPECT_EQ(4, test_subject.size()); + EXPECT_EQ(4, test_source.size()); + + test_subject = std::move(test_source); + + if (!TypeParam::allocator_type::propagate_on_container_move_assignment::value && + !AreAllocatorsEqual::value) + { + // we didn't actually move the allocator. + test_source.clear(); + test_source.shrink_to_fit(); + } + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(4, test_subject.size()); + this->account_for_all_memory(test_subject, copy_of_source); + + // For creating the initial values from integers and copying the source + // array. + EXPECT_EQ(8, TestFixture::ItemT::total_instances_implicit_int_constructed); + EXPECT_EQ(12, TestFixture::ItemT::total_instances_copy_constructed); + if (AreAllocatorsEqual::value || + TypeParam::allocator_type::propagate_on_container_move_assignment::value) + { + // if the allocators are equal or the incoming allocator does not propagate + // we expect assignments to occur only for the elements in the container + EXPECT_EQ(20, TestFixture::ItemT::total_instances_constructed); + } + else + { + // because the allocators are not equal the container can't steal + // from the rhs, however, it has enough capacity to simply move + // everything one item at a time without additional allocations. + // because this is assignment that means no more object constructors + // are invoked (yes, I kept the if statements just to organize these + // comments. It's a unittest. I can do that in a unittest). + EXPECT_EQ(20, TestFixture::ItemT::total_instances_constructed); + } + // all other movement should be via move assignment since the size of the + // two containers is identical. + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveAssignLargeToSmall) +{ + TypeParam test_subject{{1, 2, 3, 4, 5}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7, 8, 9, 10, 11, 12}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + TypeParam copy_of_source{test_source}; + EXPECT_EQ(5, test_subject.size()); + EXPECT_EQ(7, test_source.size()); + + test_subject = std::move(test_source); + + if (!TypeParam::allocator_type::propagate_on_container_move_assignment::value && + !AreAllocatorsEqual::value) + { + // we didn't actually move the allocator. + test_source.clear(); + test_source.shrink_to_fit(); + } + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(7, test_subject.size()); + this->account_for_all_memory(test_subject, copy_of_source); + + // For creating the initial values from integers and for copying the source + // container. + EXPECT_EQ(12, TestFixture::ItemT::total_instances_implicit_int_constructed); + EXPECT_EQ(12 + 7, TestFixture::ItemT::total_instances_copy_constructed); + + if (!AreAllocatorsEqual::value && + !TypeParam::allocator_type::propagate_on_container_move_assignment::value) + { + // Memory can't be moved. Each item will have to move instead. + EXPECT_EQ(7, TestFixture::ItemT::total_instances_move_constructed); + EXPECT_EQ(24 + 14, TestFixture::ItemT::total_instances_constructed); + } + else + { + // No more object construction is needed for this branch. + EXPECT_EQ(24 + 7, TestFixture::ItemT::total_instances_constructed); + } + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveAssignSmallToLarge) +{ + TypeParam test_subject{{1, 2, 3, 4, 5}, typename TypeParam::allocator_type{}}; + TypeParam test_source{{6, 7}, typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + TypeParam copy_of_source{test_source}; + EXPECT_EQ(5, test_subject.size()); + EXPECT_EQ(2, test_source.size()); + + test_subject = std::move(test_source); + + if (!TypeParam::allocator_type::propagate_on_container_move_assignment::value && + !AreAllocatorsEqual::value) + { + // we didn't actually move the allocator. + test_source.clear(); + test_source.shrink_to_fit(); + } + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(2, test_subject.size()); + this->account_for_all_memory(test_subject, copy_of_source); + + // For creating the initial values from integers and for copying the source + // container. + EXPECT_EQ(7, TestFixture::ItemT::total_instances_implicit_int_constructed); + EXPECT_EQ(7 + 2, TestFixture::ItemT::total_instances_copy_constructed); + // For any allocator possibility everything is via assignment so no + // further object construction is expected. + EXPECT_EQ(14 + 2, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveAssignVeryLargeToEmpty) +{ + TypeParam test_subject{typename TypeParam::allocator_type{}}; + TypeParam test_source{cetlvast::large_array_of_integers, + &cetlvast::large_array_of_integers[cetlvast::large_array_of_integers_size], + typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + TypeParam copy_of_source{test_source}; + EXPECT_EQ(0, test_subject.size()); + EXPECT_EQ(cetlvast::large_array_of_integers_size, test_source.size()); + + test_subject = std::move(test_source); + + if (!TypeParam::allocator_type::propagate_on_container_move_assignment::value && + !AreAllocatorsEqual::value) + { + // we didn't actually move the allocator. + test_source.clear(); + test_source.shrink_to_fit(); + } + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(cetlvast::large_array_of_integers_size, test_subject.size()); + this->account_for_all_memory(test_subject, copy_of_source); + + // For creating the initial values from integers and for copying the source + // container. + EXPECT_EQ(cetlvast::large_array_of_integers_size, TestFixture::ItemT::total_instances_implicit_int_constructed); + EXPECT_EQ(cetlvast::large_array_of_integers_size, TestFixture::ItemT::total_instances_copy_constructed); + + if (!AreAllocatorsEqual::value && + !TypeParam::allocator_type::propagate_on_container_move_assignment::value) + { + // Memory can't be moved. Each item will have to move instead. + EXPECT_EQ(cetlvast::large_array_of_integers_size, TestFixture::ItemT::total_instances_move_constructed); + EXPECT_EQ(cetlvast::large_array_of_integers_size * 3, TestFixture::ItemT::total_instances_constructed); + } + else + { + // No more object construction is needed for this branch. + EXPECT_EQ(cetlvast::large_array_of_integers_size * 2, TestFixture::ItemT::total_instances_constructed); + } + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveAssignFromEmpty) +{ + TypeParam test_subject{{0, 1, 2}, typename TypeParam::allocator_type{}}; + TypeParam test_source{typename TypeParam::allocator_type{}}; + this->account_for_all_memory(test_subject, test_source); + TypeParam copy_of_source{test_source}; + EXPECT_EQ(3, test_subject.size()); + EXPECT_EQ(0, test_source.size()); + + test_subject = std::move(test_source); + + if (!TypeParam::allocator_type::propagate_on_container_move_assignment::value && + !AreAllocatorsEqual::value) + { + // we didn't actually move the allocator. + test_source.clear(); + test_source.shrink_to_fit(); + } + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(0, test_subject.size()); + this->account_for_all_memory(test_subject, copy_of_source); + + // For creating the initial values from integers + EXPECT_EQ(3, TestFixture::ItemT::total_instances_implicit_int_constructed); + EXPECT_EQ(3, TestFixture::ItemT::total_instances_copy_constructed); + // For any allocator possibility everything is via assignment so no + // further object construction is expected. + EXPECT_EQ(6, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ +// | TEST CASES :: MOVE CONSTRUCT +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLADetailedAllocationTests, MoveConstruct) +{ + TypeParam test_source{{1, 2, 3, 4}, typename TypeParam::allocator_type{}}; + EXPECT_EQ(4, test_source.size()); + TypeParam copy_of_source{test_source}; + + TypeParam test_subject{std::move(test_source)}; + + EXPECT_EQ(test_subject, copy_of_source); + EXPECT_EQ(4, test_subject.size()); + + // For creating the initial values from integers and copying the source + // array. + EXPECT_EQ(4, TestFixture::ItemT::total_instances_implicit_int_constructed); + this->account_for_all_memory(test_subject, copy_of_source); + EXPECT_TRUE(test_source.empty()); + EXPECT_EQ(8, TestFixture::ItemT::total_instances_copy_constructed); + EXPECT_EQ(12, TestFixture::ItemT::total_instances_constructed); + EXPECT_EQ(0, TestFixture::ItemT::total_instances_default_constructed); +} + +// +---------------------------------------------------------------------------+ diff --git a/cetlvast/verify.py b/cetlvast/verify.py deleted file mode 100755 index 0b1795bd..00000000 --- a/cetlvast/verify.py +++ /dev/null @@ -1,1020 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) OpenCyphal Development Team -# Copyright Amazon.com Inc. or its affiliates. -# SPDX-License-Identifier: MIT -# -""" - Command-line helper for running verification builds. - - emrainey says this script should be replaced with conan profiles. I'll look - into this but for now "it's better than a bash script." - -""" - -import argparse -import functools -import logging -import os -import pathlib -import re -import shutil -import subprocess -import sys -import textwrap -import typing - -import xml.etree.ElementTree as ET - -# +---------------------------------------------------------------------------+ - - -def _make_parser() -> argparse.ArgumentParser: - - epilog = textwrap.dedent( - """ - - **Example Usage**:: - - ./verify.py docs - - """ - ) - - parser = argparse.ArgumentParser( - description="CMake command-line helper for running cetlvast suites.", - epilog=epilog, - formatter_class=argparse.RawTextHelpFormatter, - ) - - parser.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help=textwrap.dedent( - """ - Used to form -DCMAKE_MESSAGE_LOG_LEVEL and other options passed into - cmake as well as the verbosity of this script. - - # | cmake log-level | verify.py | cmake options - ---------------------------------------------------- - (none) : NOTICE : warning : - 1 : STATUS : warning : --warn-uninitialized - 2 : VERBOSE : info : --warn-uninitialized - 3 : DEBUG : debug : --warn-uninitialized - 4 : TRACE : debug : --warn-uninitialized - 5 : TRACE : debug : --trace --warn-uninitialized - 6+ : TRACE : debug : --trace-expand --warn-uninitialized - - """[1:]) - ) - - kit_args = parser.add_argument_group( - title="CETLVaSt suites", - description=textwrap.dedent( - """ - Select the test suite to run and the toolchain to run it against. - """[1:]) - ) - - kit_args.add_argument( - "suite", - choices=["none", "clean", "unittest", "compile", "lint", "docs", "ontarget"], - default="none", - help=textwrap.dedent( - """ - Used to form -DCETLVAST_TEST_SUITE value - - The five cetlvast suites are: - ----------------------------------------------------------------------- - - unit tests : googletest/googlemock tests compiled and executed - on the local system. these tests verify the core - interface contracts of CETL types are not violated - including the behavioural contracts, where - defined. These also provide some level of - verification of compatibility with different tool - chains. - compile tests : A specalized suite of unit tests that run in the - compiler. These verify that certain invariants are - properly protected by static assertions. - lint/analysis : Runs various static analysis tools against the - cetl types. - documentation : While not typically considered a test, this target - builds the doxygen documentation for cetlvast - which verifies that the types are all properly - documented. Furthermore, this builds and executes - any example code included in the docs to ensure - the code is correct. - on-target : This is future work. The suite will build one or - more firmware images for verifying the performance - of CETL on a select one or two embedded - processors. - - Psedo-suites: - none : No test suite. - - """[1:]) - ) - - kit_args.add_argument( - "-tc", - "--toolchain", - choices=["gcc", "clang"], - default="clang", - help=textwrap.dedent( - """ - - Used to form -DCMAKE_TOOLCHAIN_FILE value - - This selects the toolchain description cetlvast will tell Cmake to use. - - """[1:]) - ) - - variant_args = parser.add_argument_group( - title="build variants", - description=textwrap.dedent( - """ - Arguments the modify build parameters. - """[1:]) - ) - - variant_args.add_argument( - "-bf", - "--build-flavor", - choices=["Debug", "Release"], - default="Debug", - help=textwrap.dedent( - """ - Sets -DCMAKE_BUILD_TYPE value - - Debug : builds will be lightly optimized or not optimized. Debug - symbols will be included. - Release : builds will be reasonably optimized. - - """[1:]) - ) - - variant_args.add_argument( - "--coverage", - choices=["sonarqube", "html"], - help=textwrap.dedent( - """ - Enables instrumentation of code and selects coverage report format. - - -DCETLVAST_ENABLE_COVERAGE:BOOL=ON - - -DCETLVAST_COVERAGE_REPORT_FORMAT:STRING= - ----------------------------------------------------------------------- - - html : Generages a human-readable HTML report. - sonarqube : Generates an XML file compatible with sonarqube. - - """[1:]) - ) - - variant_args.add_argument( - "-cda", - "--asserts", - action="store_true", - help=textwrap.dedent( - """ - Sets -DCETL_ENABLE_DEBUG_ASSERT:BOOL=ON - - Enables CETL debug asserts. Also forces the build flavor to be Debug. - - """[1:]) - ) - - variant_args.add_argument( - "-std", - "--cpp-standard", - choices=["base", "intermediate", "target"], - default="target", - help=textwrap.dedent( - """ - - Sets -DCETLVAST_CPP_STANDARD value - ----------------------------------------------------------------------- - - base (C++14) : Use the C++ 14 standard which is the base - standard for CETL 1.0. This allows testing of - CETL as a polyfill library for C++17 and 20. - intermediate (C++17) : Use the C++ 17 standard. This enables testing of - CETL as a polyfill library for C++20 and enables - A/B testing of CETL against any C++17 types - it supports. - target (C++20) : Use the C++20 standard, the target support level - for CETL 1.0. This enables A/B testing of CETL - to ensure forwards compatibility. - - """[1:]) - ) - - action_args = parser.add_argument_group( - title="action modifiers", - description=textwrap.dedent( - """ - Arguments that change the actions taken by this script. - """[1:]) - ) - - action_args.add_argument( - "-f", - "--force", - action="store_true", - help=textwrap.dedent( - """ - Force recreation of verification directory if it already exists. - - ** WARNING ** This will delete the cmake build directory! - - """[1:]) - ) - - action_args.add_argument( - "--version", - action="store_true", - help=textwrap.dedent( - """ - Emits the current version. Use "none" suite to simply emit the version - and exit: - - export CETL_VERSION=$(./verify.py --version none) - - """[1:]) - ) - - action_args.add_argument( - "-ls", - "--list", - choices=["builddir", "extdir", "cppstd", "covri", "covrd", "tests"], - help=textwrap.dedent( - """ - - Lists various internal values to allow integration with CI or other - build scripts without hardcoding these values. - - -ls/--list values - ----------------------------------------------------------------------- - - builddir Emits a relative path to a build directory. Use with "none" - suite to emit the root build path and exit. For example: - - pushd $(./verify.py -ls builddir none) - ninja -t commands - popd - - Use with a suite to emite a path to the suite's build - output. For example: - - open "$(./verify.py -ls buildir docs)/html/index.html" - - extdir Emits a relative path to the external test dependencies - directory. Use with "none" suite to simply emit this path - and exit. For example: - - - name: setup environment - run: | - echo "CETLVAST_EXT_PATH=$(./cetlvast/verify.py -cd ./cetlvast -ls extdir none)" >> $GITHUB_ENV - - cppstd Emits an integer value for the cpp standard defined by - default or by a --cpp-standard argument provided to this - script. For example: - - echo "-std=c++$(./verify.py --cpp-standard base -ls cppstd docs)" - - covri If --coverage is set this will return a path to a report index file. - - open $(./verify.py --coverage html -ls covri unittest) - - covrd If --coverage is set this will return a path to a file or folder that - is/contains the coverage report. - - tar -vzcf report.gz $(./verify.py --coverage html -ls covrd unittest) - - tests If --generate-test-report is set this will return a path to the output file. - - ./verify.py -gtr tests.xml -ls tests unittest | xargs cat - - All ls actions happen before the build directory action so -rm will be - ignored. - - """[1:]) - ) - - action_args.add_argument( - "-gtr", - "--generate-test-report", - type=pathlib.Path, - metavar="{output_file}", - help=textwrap.dedent( - """ - Writes a test execution report in the sonarqube generic test execution - format after test execution. - - ./verify.py --generate-test-report path/to/write/sonarqube.xml unittest - - """[1:]) - ) - - action_args.add_argument( - "--builddir-only", - action="store_true", - help=textwrap.dedent( - """ - Handle -rm and -f arguments but do not configure, build, or run tests. - Use with none to perform a nuclear-clean operation: - - alias cv_superclean="./verify.py -rm -f --builddir-only none" - - """[1:]) - ) - - action_args.add_argument( - "-c", - "--configure-only", - action="store_true", - help=textwrap.dedent( - """ - Configure but do not build. - """[1:]) - ) - - action_args.add_argument( - "-b", - "--build-only", - action="store_true", - help=textwrap.dedent( - """ - Try to build without configuring. Do not try to run tests. - """[1:]) - ) - - action_args.add_argument( - "-t", - "--test-only", - action="store_true", - help=textwrap.dedent( - """ - Only try to run tests. Don't configure or build. - """[1:]) - ) - - action_args.add_argument( - "-co", - "--clean-only", - action="store_true", - help=textwrap.dedent( - """ - If specified, this will only run the cmake generated clean target for - the provided suite and configuration then exit. - - ./verify.py --clean-only unittest - - """[1:]) - ) - - action_args.add_argument( - "--dry-run", - action="store_true", - help=textwrap.dedent( - """ - Don't actually do anything. Just log what this script would have done. - Combine with --verbose to ensure you actually see the script's log - output. - """[1:]) - ) - - action_args.add_argument( - "-rm", - "--remove-first", - action="store_true", - help=textwrap.dedent( - """ - If specified, any existing build directory will be deleted first. Use - -f to skip the user prompt. - - Note: This only applies to the configure step. If you do a build-only - this argument has no effect. - """[1:]) - ) - - other_args = parser.add_argument_group( - title="other options", - description=textwrap.dedent( - """ - Additional stuff you probably can ignore. - """[1:]) - ) - - other_args.add_argument( - "--build-dir-name", - default="verifypy", - help=textwrap.dedent( - """ - This script always uses [build_{build-dir-name}] as the name of the - top-level directory it creates and under which it does its work. This - option lets you change the {build-dir-name} part of that file name. - - """[1:]) - ) - - other_args.add_argument( - "-cd", - "--cetlvast-dir", - default=pathlib.Path.cwd(), - type=pathlib.Path, - help=textwrap.dedent( - """ - By default this script uses the current-working directory as the root - for CETLVaSt. Use this option to specify a different root directory when - running the script. - - """[1:]) - ) - - other_args.add_argument( - "--force-ninja", - action="store_true", - help=textwrap.dedent( - """ - - -DCMAKE_GENERATOR=Ninja - - Form an argument requireing cmake use the Ninja build system instead of - the default for the current system which can be make. - - """[1:]) - ) - - - return parser - - -# +---------------------------------------------------------------------------+ - - -def _junit_to_sonarqube_generic_execution_format(junit_report: pathlib.Path, test_executions: ET.Element) -> None: - """Append junit testsuite data to sonarqube testExecutions data. - - Input Format: http://google.github.io/googletest/advanced.html#generating-an-xml-report - Output Format: https://docs.sonarqube.org/8.9/analyzing-source-code/generic-test-data/#generic-execution - - TODO: move this out into a standalone Python file (or, hell, even a Pypi package) and hook it up properly - to cmake as a target. - - """ - sq_files: typing.Dict[str, ET.Element] = dict() - junit_xml = ET.parse(junit_report) - - testsuite_or_testsuites = junit_xml.getroot() - - if testsuite_or_testsuites.tag == "testsuite": - testsuites: typing.Iterable[ET.Element] = [testsuite_or_testsuites] - else: - testsuites = testsuite_or_testsuites.findall("testsuite") - - for testsuite in testsuites: - logging.info("junit2sonarqube: parsing junit testsuite: {} name=\"{}\" tests=\"{}\"" - .format(testsuite.tag, - testsuite.get("name"), - testsuite.get("tests"))) - - for testcase in testsuite: - - testcase_file = testcase.get("file") - if testcase_file is not None: - quirksmode = "gtest" - else: - quirksmode = "ctest" - testcase_file = testcase.get("classname") - if testcase_file is None: - logging.warn("junit2sonarqube: Unknown tag {} (skipping)".format(testcase.tag)) - continue - - testcase_name: str = testcase.get("name", "") - if (type_param := testcase.get("type_param", None)) != None: - # mypy is not able to parse assignment expressions, aparently. - testcase_name = testcase_name + " " + type_param # type: ignore - - logging.debug("junit2sonarqube: found testcase \"{}\" (quirks={})".format(testcase_name, quirksmode)) - - try: - sq_file = sq_files[testcase_file] - except KeyError: - sq_file = ET.Element("file", attrib={"path": testcase_file}) - test_executions.append(sq_file) - sq_files[testcase_file] = sq_file - - sq_testcase_attrib: typing.Dict[str, str] = dict() - sq_testcase_attrib["name"] = testcase_name - test_duration = float(testcase.get("time", 0.0)) - sq_testcase_attrib["duration"] = str(test_duration) - - sq_test_case = ET.Element("testCase", attrib=sq_testcase_attrib) - sq_file.append(sq_test_case) - - skipped: typing.Optional[ET.Element] = testcase.find("skipped") - if skipped is not None: - sq_skipped = ET.Element("skipped", attrib={"message": skipped.get("message", "").rstrip()}) - sq_skipped.text = skipped.text.rstrip() # type: ignore - sq_test_case.append(sq_skipped) - failures = testcase.findall("failure") - if failures is not None and len(failures) > 0: - sq_failure = ET.Element("failure", attrib={"message": "failed"}) - sq_test_case.append(sq_failure) - sq_failure_text = [] - for failure in failures: - for failure_line in failure.get("message", "").split("\n"): - sq_failure_text.append(failure_line) - sq_failure.set("message", sq_failure_text[0]) - if len(sq_failure_text) > 1: - sq_failure.text = "\n".join(sq_failure_text[1:]) - - -# +---------------------------------------------------------------------------+ - - -def _is_pseudo_suite(suite_name: str) -> bool: - return (suite_name in ["none"]) - - -# +---------------------------------------------------------------------------+ - - -def _suite_dir(args: argparse.Namespace, cmake_dir: pathlib.Path) -> pathlib.Path: - if _is_pseudo_suite(args.suite): - raise RuntimeError("Cannot create path to a psedo-suite.") - return cmake_dir / "cetlvast" / "suites" / args.suite - - -# +---------------------------------------------------------------------------+ - - -def _cpp_standard_arg_to_number(args: argparse.Namespace, ) -> int: - if args.cpp_standard == "base": - return 14 - elif args.cpp_standard == "intermediate": - return 17 - elif args.cpp_standard == "target": - return 20 - else: - raise RuntimeError("internal error: illegal cpp-standard choice got through? ({})".format(args.cpp_standard)) - - -# +---------------------------------------------------------------------------+ - - -def _to_cmake_logging_level(verbose: int) -> str: - if verbose == 1: - cmake_logging_level = "STATUS" - elif verbose == 2: - cmake_logging_level = "VERBOSE" - elif verbose == 3: - cmake_logging_level = "DEBUG" - elif verbose > 3: - cmake_logging_level = "TRACE" - else: - cmake_logging_level = "NOTICE" - - return cmake_logging_level - - -# +---------------------------------------------------------------------------+ - - -def _cmake_run( - cmake_args: typing.List[str], - cmake_dir: pathlib.Path, - verbose: int, - dry_run: bool, - env: typing.Optional[typing.Dict] = None, -) -> int: - """ - Simple wrapper around cmake execution logic - """ - logging.info( - textwrap.dedent( - """ - ***************************************************************** - About to run command: {} - in directory : {} - ***************************************************************** - """ - ).format(" ".join(cmake_args), str(cmake_dir)) - ) - - copy_of_env: typing.Dict = {} - copy_of_env.update(os.environ) - if env is not None: - copy_of_env.update(env) - - if verbose > 1: - logging.debug(" *****************************************************************") - logging.debug(" Using Environment:") - for key, value in copy_of_env.items(): - overridden = key in env if env is not None else False - logging.debug(" {} = {}{}".format(key, value, (" (override)" if overridden else ""))) - logging.debug(" *****************************************************************\n") - - if not dry_run: - return subprocess.run(cmake_args, cwd=cmake_dir, env=copy_of_env).returncode - else: - return 0 - - -# +---------------------------------------------------------------------------+ - - -def _remove_build_dir_action(args: argparse.Namespace, cmake_dir: pathlib.Path) -> int: - """ - Handle all the logic, user input, logging, and file-system operations needed to - remove the cmake build directory ahead of invoking cmake. - """ - if args.remove_first and cmake_dir.exists(): - okay_to_remove = False - if not args.force: - response = input("Are you sure you want to delete {}? [y/N]:".format(cmake_dir)) - if (len(response) == 1 and response.lower() == "y") or (len(response) == 3 and response.lower() == "yes"): - okay_to_remove = True - else: - okay_to_remove = True - - if okay_to_remove: - if not args.dry_run: - logging.info("Removing directory {}".format(cmake_dir)) - shutil.rmtree(cmake_dir) - else: - logging.info("Is dry-run. Would have removed directory {}".format(cmake_dir)) - else: - raise RuntimeError( - """ - Build directory {} already exists, -rm or --remove-first was specified, - and permission was not granted to delete it. We cannot continue. Either - allow re-use of this build directory or allow deletion. (use -f flag to - skip user prompts).""".lstrip().format( - cmake_dir - ) - ) - return 0 - else: - return 1 - - -# +---------------------------------------------------------------------------+ - - -def _create_build_dir_action(args: argparse.Namespace, cmake_dir: pathlib.Path) -> None: - """ - Handle all the logic, user input, logging, and file-system operations needed to - create the cmake build directory ahead of invoking cmake. - """ - if not cmake_dir.exists(): - if not args.dry_run: - logging.info("Creating build directory at {}".format(cmake_dir)) - cmake_dir.mkdir() - else: - logging.info("Dry run: Would have created build directory at {}".format(cmake_dir)) - else: - logging.info("Using existing build directory at {}".format(cmake_dir)) - - -# +---------------------------------------------------------------------------+ - - -def _cmake_configure(args: argparse.Namespace, cmake_args: typing.List[str], cmake_dir: pathlib.Path, skip: bool = False) -> int: - """ - Format and execute cmake configure command. This also include the cmake build directory (re)creation - logic. - """ - - if args.build_only or args.test_only: - return 0 - - cmake_configure_args = cmake_args.copy() - - cmake_configure_args.append("-DCMAKE_MESSAGE_LOG_LEVEL:STRING={}".format(_to_cmake_logging_level(args.verbose))) - - if args.verbose >= 1: - cmake_configure_args.append("--warn-uninitialized") - if args.verbose == 5: - cmake_configure_args.append("--trace") - elif args.verbose >= 6: - cmake_configure_args.append("--trace-expand") - - flag_set_dir = pathlib.Path("cmake") / pathlib.Path("compiler_flag_sets") - flagset_file = (flag_set_dir / pathlib.Path("native")).with_suffix(_cmake_configure.cmake_suffix) - - cmake_configure_args.append("-DCETLVAST_FLAG_SET={}".format(str(flagset_file))) - - if not _is_pseudo_suite(args.suite): - test_suite_dir = pathlib.Path("cmake") / pathlib.Path("suites") - test_suite_dir = (test_suite_dir / pathlib.Path(args.suite)).with_suffix(_cmake_configure.cmake_suffix) - cmake_configure_args.append("-DCETLVAST_TEST_SUITE={}".format(str(test_suite_dir))) - if args.toolchain != "none": - toolchain = pathlib.Path("cmake") / pathlib.Path("toolchains") - if args.toolchain == "clang": - toolchain_file = toolchain / pathlib.Path("clang-native").with_suffix(_cmake_configure.cmake_suffix) - else: - toolchain_file = toolchain / pathlib.Path("gcc-native").with_suffix(_cmake_configure.cmake_suffix) - - cmake_configure_args.append("-DCMAKE_TOOLCHAIN_FILE={}".format(str(toolchain_file))) - - if args.coverage is not None: - cmake_configure_args.append("-DCETLVAST_ENABLE_COVERAGE:BOOL=ON") - cmake_configure_args.append("-DCETLVAST_COVERAGE_REPORT_FORMAT:STRING={}".format(args.coverage)) - else: - cmake_configure_args.append("-DCETLVAST_ENABLE_COVERAGE:BOOL=OFF") - - if args.asserts: - if args.build_flavor != "Debug": - logging.warning("-cda/--asserts forces the build to be Debug. Ignoring -bf/--build-flavor {}".format(args.build_flavor)) - cmake_configure_args.append("-DCMAKE_BUILD_TYPE=Debug") - cmake_configure_args.append("-DCETL_ENABLE_DEBUG_ASSERT:BOOL=ON") - else: - cmake_configure_args.append("-DCMAKE_BUILD_TYPE={}".format(args.build_flavor)) - cmake_configure_args.append("-DCETL_ENABLE_DEBUG_ASSERT:BOOL=OFF") - - cmake_configure_args.append("-DCETLVAST_CPP_STANDARD={}".format(_cpp_standard_arg_to_number(args))) - - if args.force_ninja: - cmake_configure_args.append("-DCMAKE_GENERATOR=Ninja") - - cmake_configure_args.append("..") - - if skip: - return 0 - else: - return _cmake_run(cmake_configure_args, cmake_dir, args.verbose, args.dry_run) - -_cmake_configure.cmake_suffix = ".cmake" - -# +---------------------------------------------------------------------------+ - - -def _cmake_build(args: argparse.Namespace, cmake_args: typing.List[str], cmake_dir: pathlib.Path) -> int: - """ - Format and execute cmake build command. This method assumes that the cmake_dir is already properly - configured. - """ - if not args.configure_only and not args.test_only: - cmake_build_args = cmake_args.copy() - - cmake_build_args += ["--build", ".", "--target"] - - if _is_pseudo_suite(args.suite): - logging.debug("no concrete test suite specified. Nothing to do.") - return 0 - elif args.suite == "lint": - logging.debug("lint target doesn't currently have a build step") - return 0 - elif args.suite == "ontarget": - logging.warning("ontarget tests not implemented yet!") - return -1 - elif args.clean_only: - cmake_build_args.append("clean") - else: - cmake_build_args.append("build_all") - - return _cmake_run(cmake_build_args, cmake_dir, args.verbose, args.dry_run) - - return 0 - - -# +---------------------------------------------------------------------------+ - - -def _cmake_test(args: argparse.Namespace, cmake_args: typing.List[str], cmake_dir: pathlib.Path) -> int: - """ - Format and execute cmake test command. This method assumes that the cmake_dir is already properly - configured. - """ - if not args.configure_only and not args.build_only: - - cmake_test_args = cmake_args.copy() - - cmake_test_args += ["--build", ".", "--target"] - - if args.suite == "ontarget": - logging.warning("ontarget tests not implemented yet!") - return -1 - elif _is_pseudo_suite(args.suite): - logging.debug("No test suite specified. Nothing to do.") - return 0 - else: - cmake_test_args.append("suite_all") - - return _cmake_run(cmake_test_args, cmake_dir, args.verbose, args.dry_run) - - return 0 - - -# +---------------------------------------------------------------------------+ - - -def _cmake_ctest(args: argparse.Namespace, _: typing.List[str], cmake_dir: pathlib.Path) -> int: - """ - run ctest - """ - if not args.configure_only and not args.build_only: - - if args.suite == "compile": - # we use ctest to run the compile tests so we take a different - # branch here. - report_path = pathlib.Path.cwd().joinpath(_suite_dir(args, cmake_dir) / "ctest.xml") - ctest_run = ["ctest", "-DCTEST_FULL_OUTPUT", "--output-junit", str(report_path)] - if not args.dry_run: - logging.debug("about to run {}".format(str(ctest_run))) - return subprocess.run(ctest_run, cwd=cmake_dir).returncode - else: - logging.info("Is dry-run. Would have run ctest: {}".format(str(ctest_run))) - return 0 - else: - logging.debug("No ctest action defined for {} test suite.".format(args.suite)) - - return 0 - - -# +---------------------------------------------------------------------------+ - - -def _create_build_dir_name(args: argparse.Namespace) -> str: - return "build_{}".format(args.build_dir_name) - - -# +---------------------------------------------------------------------------+ - - -@functools.cache -def _get_version_string(gitdir: pathlib.Path) -> typing.Tuple[int, int, int, str]: - git_output = subprocess.run(["git", "describe", "--abbrev=0", "--tags"], cwd=gitdir, capture_output=True, text=True).stdout - if (match_obj := re.match(r"^v(\d+)\.(\d+)\.(\d+)[-_]?(\w*)", git_output)) is not None: - _version_string = (int(match_obj.group(1)), - int(match_obj.group(2)), - int(match_obj.group(3)), - qualifier if (qualifier:=match_obj.group(4)) else "") - else: - _version_string = (0,0,0,"") - - return _version_string - - -# +---------------------------------------------------------------------------+ - - -def _handle_special_actions(args: argparse.Namespace, cmake_dir: pathlib.Path, gitdir: pathlib.Path) -> int: - - if args.version: - sys.stdout.write("{}.{}.{}{}".format(*_get_version_string(gitdir))) - - elif args.list is not None: - if args.list == "builddir": - if _is_pseudo_suite(args.suite): - sys.stdout.write(str(cmake_dir)) - else: - sys.stdout.write(str(_suite_dir(args, cmake_dir))) - - elif args.list == "extdir": - sys.stdout.write(str(cmake_dir.parent / pathlib.Path(cmake_dir.stem + "_ext"))) - elif args.list == "cppstd": - sys.stdout.write("{}".format(_cpp_standard_arg_to_number(args))) - elif args.list == "tests": - if args.generate_test_report is None: - logging.error("Cannot list the test report unless --generate-test-report is also specified.") - return -1 - sys.stdout.write(str(_suite_dir(args, cmake_dir) / args.generate_test_report)) - elif (is_covri := (args.list == "covri")) or args.list == "covrd": - if args.coverage is None: - raise RuntimeError("cannot list coverage output unless --coverage is specified.") - elif not _is_pseudo_suite(args.suite): - if args.coverage == "html": - html_dir = _suite_dir(args, cmake_dir) / "gcovr_html" - sys.stdout.write( str(html_dir / "coverage.html") if is_covri else str(html_dir) ) - elif args.coverage == "sonarqube": - suitedir = _suite_dir(args, cmake_dir) - sys.stdout.write( str(suitedir / "coverage.xml") ) - - else: - raise RuntimeError("invalid ls value {} got through argparse?".format(args.list)) - return 0 - - -# +---------------------------------------------------------------------------+ - - -def _handle_generate_test_report(args: argparse.Namespace, cmake_dir: pathlib.Path, test_result: int) -> int: - if (output_file := args.generate_test_report) is None: - return 0 - - output_path = pathlib.Path.cwd().joinpath(_suite_dir(args, cmake_dir) / output_file) - test_executions = ET.Element("testExecutions", attrib={"version": "1"}) - sq_report = ET.ElementTree(test_executions) - - for gtest_report in _suite_dir(args, cmake_dir).glob("*-gtest.xml"): - logging.debug("Found gtest report {}. Will combine into sonarqube report.".format(gtest_report)) - _junit_to_sonarqube_generic_execution_format(gtest_report, sq_report.getroot()) - - for ctest_report in _suite_dir(args, cmake_dir).glob("*ctest.xml"): - logging.debug("Found ctest report {}. Will combine into sonarqube report.".format(ctest_report)) - _junit_to_sonarqube_generic_execution_format(ctest_report, sq_report.getroot()) - - if args.dry_run: - logging.debug("Would have written a test report for {} files to {}".format(len(test_executions.findall("file")), output_path)) - else: - logging.debug("About to write a test report for {} files to {}".format(len(test_executions.findall("file")), output_path)) - ET.indent(sq_report) - output_path.parent.mkdir(parents=True, exist_ok=True) - sq_report.write(output_path, encoding="UTF-8") - return test_result - - -# +---------------------------------------------------------------------------+ - - -def main() -> int: - """ - Main method to execute when this package/script is invoked as a command. - """ - args = _make_parser().parse_args() - - verification_dir = args.cetlvast_dir - cmake_dir = verification_dir / pathlib.Path(_create_build_dir_name(args)) - cmake_args = ["cmake"] - - logging_level = logging.WARN - - if args.verbose == 2: - logging_level = logging.INFO - elif args.verbose >= 3: - logging_level = logging.DEBUG - - logging.basicConfig(format="%(levelname)s: %(message)s", level=logging_level) - - logging.info( - textwrap.dedent( - """ - - ***************************************************************** - Commandline Arguments to {}: - - {} - - For verify version {} - ***************************************************************** - - """ - ).format(os.path.basename(__file__), str(args), _get_version_string(verification_dir)) - ) - - special_action_result = _handle_special_actions(args, cmake_dir, verification_dir) - - if special_action_result != 0: - return special_action_result - elif args.list is not None: - return 0 - - remove_build_dir_result = _remove_build_dir_action(args, cmake_dir) - if (0 == remove_build_dir_result) and args.clean_only: - logging.debug("Since we deleted the build directory there's no point in running the clean target.") - return 0 - elif remove_build_dir_result < 0 and remove_build_dir_result > 1: # 1 means there wasn't a request to remove the build directory. - return remove_build_dir_result - - if args.builddir_only or _is_pseudo_suite(args.suite): - return 0 - - _create_build_dir_action(args, cmake_dir) - - configure_result = _cmake_configure(args, cmake_args, cmake_dir, args.builddir_only | args.clean_only) - - if configure_result != 0: - return configure_result - elif args.configure_only: - return 0 - - build_result = _cmake_build(args, cmake_args, cmake_dir) - - if build_result != 0: - return build_result - elif args.build_only or args.clean_only: - return 0 - - if not args.configure_only and not args.build_only and not args.clean_only: - test_result = _cmake_test(args, cmake_args, cmake_dir) - - if test_result == 0: - test_result = _cmake_ctest(args, cmake_args, cmake_dir) - - return _handle_generate_test_report(args, cmake_dir, test_result) - - raise RuntimeError("Internal logic error: only_do_x flags resulted in no action.") - - -# +---------------------------------------------------------------------------+ - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/include/cetl/cetl.hpp b/include/cetl/cetl.hpp index f7cc40a3..61b0c8b9 100644 --- a/include/cetl/cetl.hpp +++ b/include/cetl/cetl.hpp @@ -7,19 +7,15 @@ /// SPDX-License-Identifier: MIT /// /// @note -/// Keep this very spare. CETL's desire is to adapt to future C++ standards -/// and too many CETL-specific definitions makes it difficult for users to switch off of CETL in the -/// future. +/// Keep this very spare. CETL's desire is to adapt to future C++ standards and too many CETL-specific definitions makes +/// it difficult for users to switch off of CETL in the future. /// -/// If `CETL_H_ERASE` is defined then all CETL types will exclude the `cetl/cetl.hpp` header which -/// removes all common dependencies, other than C++ standard headers, from CETL. The types will not build due to -/// missing macros but the user can re-define these based on subsequent compiler errors. This allows elision of -/// cetl.hpp without modifying CETL source code. The CETL types are not guaranteed to work with cetl.hpp removed; you -/// have been warned. -/// -/// @warning -/// polyfill headers cannot be used if CETL_H_ERASE is defined. Presumably, if you really want to minimize your -/// dependencies, you would not be using the polyfill headers. +/// @def CETL_H_ERASE +/// If `CETL_H_ERASE` is defined then all CETL types will exclude all cetl headers which removes all common +/// dependencies, other than C++ standard headers, from CETL. The types will not build due to +/// missing macros and/or type aliases but the user can re-define these based on subsequent compiler errors. This allows +/// elision of cetl.hpp and dependencies on CETL polyfill types without modifying CETL source code. +/// Note that CETL polyfill headers cannot be used if CETL_H_ERASE is defined. /// #ifndef CETL_H_INCLUDED @@ -27,6 +23,10 @@ #ifdef CETL_H_ERASE # error "CETL_H_ERASE was defined. This header should never be included when the build is trying to erase it!" +#elif defined(CETL_DOXYGEN) +// Define then undefine to expose CETL_H_ERASE to doxygen. +# define CETL_H_ERASE +# undef CETL_H_ERASE #endif /// @defgroup CETL_VERSION The semantic version number of the CETL library. @@ -75,6 +75,11 @@ # define CETL_DEBUG_ASSERT(c, m) ((void) m) #endif // CETL_ENABLE_DEBUG_ASSERT +// Make the standard exceptions available only if exceptions are enabled. +#if __cpp_exceptions +# include +#endif + /// @defgroup CETL_CPP_STANDARD Guaranteed CETL c++ standard numbers /// These macros are an AUTOSAR-14 Rule A16-0-1 violation but can be used to conditionally include headers which /// is compliant with A16-0-1. The values were obtained by observation of compiler output using @@ -130,4 +135,7 @@ static_assert(__cplusplus == CETL_CPP_STANDARD_14 || __cplusplus == CETL_CPP_STA __cplusplus >= CETL_CPP_STANDARD_20, "Unknown __cplusplus value found?"); +/// @namespace cetl This namespace contains types specific to CETL and nested namespaces that contain types adhering +/// to target C++ specifications. +/// @namespace cetl::pmr CETL extensions to the standard Polymorphic Memory Resource (PMR) namespace, `std::pmr`. #endif // CETL_H_INCLUDED diff --git a/include/cetl/pf17/byte.hpp b/include/cetl/pf17/byte.hpp new file mode 100644 index 00000000..a6fad2df --- /dev/null +++ b/include/cetl/pf17/byte.hpp @@ -0,0 +1,34 @@ +/// @file +/// Defines a type compatible with C++17 std::byte. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PF17_BYTE_H_INCLUDED +#define CETL_PF17_BYTE_H_INCLUDED + +#include + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +namespace cetl +{ +namespace pf17 +{ + +/// A non-character type that implements the concept of a byte defined by the C++17 specification. +enum class byte : unsigned char +{ +}; + +/// TODO [support.types.byteops] non-member operations + +} // namespace pf17 +} // namespace cetl + +#endif // CETL_PF17_BYTE_H_INCLUDED diff --git a/include/cetl/pf17/cetlpf.hpp b/include/cetl/pf17/cetlpf.hpp new file mode 100644 index 00000000..e03161d9 --- /dev/null +++ b/include/cetl/pf17/cetlpf.hpp @@ -0,0 +1,184 @@ +/// @file +/// CETL polyfill header for C++17 types +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +/// @warning +/// This header violates AUTOSAR-14 M7-3-6, A17-0-1, and A16-0-1 (and possibly other rules). Don't use CETL polyfill +/// headers in AUTOSAR code. +/// +/// @warning +/// polyfill headers cannot be used if CETL_H_ERASE is defined. +/// +/// CETL polyfill headers will provide the CETL type as an aliased standard type in the std namespace when compiling +/// using older C++ libraries and will automatically provide types from the standard library when compiling +/// using newer C++ libraries. This allows for causal use of CETL as a true polyfill library but does violate certain +/// coding standards. As such, for more critical software we recommend not using these headers but including the +/// types you use from `cetl::pf17` directly. +/// +/// For example, (TODO: polyfill 17 examples.) +/// + +#ifndef CETL_PF17_H_INCLUDED +#define CETL_PF17_H_INCLUDED + +#include "cetl/cetl.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" + +/// @namespace cetl::pf17 This namespace contains C++17 polyfill (pf) types. +/// The types within this namespace adhere to the C++17 specification and should be drop-in replaceable +/// with said standard types where they are available. CETL polyfill types may implement a sub-set of +/// the required functionality but they will not implement non-compliant functionality. +/// +/// @namespace cetl::pf17::pmr CETL polyfill types for the standard Polymorphic Memory Resource (PMR) +/// namespace. +/// @namespace cetl::pf17::pmr::deviant Types or methods that deviate from the C++17 specification. +/// +#if (__cplusplus >= CETL_CPP_STANDARD_17 && !defined(CETL_DOXYGEN)) +# include +# include + +namespace cetl +{ + +using byte = std::byte; + +namespace pmr +{ + +using memory_resource = std::pmr::memory_resource; + +inline memory_resource* null_memory_resource() noexcept +{ + return std::pmr::null_memory_resource(); +} + +template +using polymorphic_allocator = std::pmr::polymorphic_allocator; + +inline memory_resource* new_delete_resource() noexcept +{ + return std::pmr::new_delete_resource(); +} + +} // namespace pmr +} // namespace cetl + +#else +# include "cetl/pf17/byte.hpp" + +namespace cetl +{ + +using byte = cetl::pf17::byte; + +namespace pmr +{ +using memory_resource = cetl::pf17::pmr::memory_resource; + +inline memory_resource* null_memory_resource() noexcept +{ + return cetl::pf17::pmr::null_memory_resource(); +} + +template +using polymorphic_allocator = cetl::pf17::pmr::polymorphic_allocator; + +inline memory_resource* new_delete_resource() noexcept +{ + return cetl::pf17::pmr::new_delete_resource(); +} + +} // namespace pmr +} // namespace cetl +#endif + +#ifdef CETL_PMR_ARRAY_MEMORY_RESOURCE_H_INCLUDED + +namespace cetl +{ +namespace pmr +{ + +/// Automatic implementation of cetl::pmr::memory_resource that uses +/// cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate as the internal implementation. +/// @tparam UpstreamResourceType The type of any upstream memory resource provided. +template +class UnsynchronizedArrayMemoryResource final : public cetl::pmr::memory_resource +{ +public: + /// See cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate for details. + /// @param buffer The buffer to provide to cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate. + /// @param buffer_size_bytes The size of the buffer to provide to + /// cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate + /// @param upstream The upstream memory resource to provide to cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate. + // This cannot be null. Use cetl::pmr::null_memory_resource() if you don't want upstream memory. + UnsynchronizedArrayMemoryResource(void* buffer, + std::size_t buffer_size_bytes, + UpstreamResourceType* upstream, + std::size_t upstream_max_size_bytes) noexcept + : delegate_{buffer, buffer_size_bytes, upstream, upstream_max_size_bytes} + { + } + + ~UnsynchronizedArrayMemoryResource() override = default; + UnsynchronizedArrayMemoryResource(const UnsynchronizedArrayMemoryResource&) = delete; + UnsynchronizedArrayMemoryResource& operator=(const UnsynchronizedArrayMemoryResource&) = delete; + UnsynchronizedArrayMemoryResource(UnsynchronizedArrayMemoryResource&&) = delete; + UnsynchronizedArrayMemoryResource& operator=(UnsynchronizedArrayMemoryResource&&) = delete; + +private: + void* do_allocate(std::size_t bytes, std::size_t alignment) override + { + return delegate_.allocate(bytes, alignment); + } + + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { + delegate_.deallocate(p, bytes, alignment); + } + + bool do_is_equal(const cetl::pmr::memory_resource& other) const noexcept override + { + return (this == &other); + } + +# if (__cplusplus < CETL_CPP_STANDARD_17 || defined(CETL_DOXYGEN)) + std::size_t do_max_size() const noexcept override + { + return delegate_.max_size(); + } + + void* do_reallocate(void* p, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) override + { + return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); + } +# else +public: + // These aren't visible through the std::memory_resource interface but they + // are still useable if you have access to the full type. + std::size_t max_size() const noexcept + { + return delegate_.max_size(); + } + + void* reallocate(void* p, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) + { + return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); + } + +private: +# endif + + cetl::pmr::UnsynchronizedArrayMemoryResourceDelegate delegate_; +}; + +} // namespace pmr +} // namespace cetl + +#endif // CETL_PMR_ARRAY_MEMORY_RESOURCE_H_INCLUDED + +#endif // CETL_PF17_H_INCLUDED diff --git a/include/cetl/pf17/memory_resource.hpp b/include/cetl/pf17/memory_resource.hpp new file mode 100644 index 00000000..3eefe303 --- /dev/null +++ b/include/cetl/pf17/memory_resource.hpp @@ -0,0 +1,769 @@ +/// @file +/// Defines the C++17 std::pmr::memory_resource abstract type and several other types within the std::pmr namespace. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PF17_PMR_MEMORY_RESOURCE_H_INCLUDED +#define CETL_PF17_PMR_MEMORY_RESOURCE_H_INCLUDED + +#include // for std::max +#include +#include +#include +#include +#include +#include // for std::pair + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +namespace cetl +{ +namespace pf17 +{ +namespace pmr +{ + +// +--[mem.res]--------------------------------------------------------------+ + +/// Interface to a class that manages memory resources. +/// This implementation Adheres to memory_resource as defined by the C++17 specification. It does not +/// incorporate changes made to this type in C++20. +class memory_resource +{ +public: + /// Implicitly defined default constructor. + memory_resource() = default; + + /// Implicitly defined copy constructor. + /// @param rhs The object to copy from. + memory_resource(const memory_resource& rhs) = default; + + /// Implicitly defined destructor. + virtual ~memory_resource() = default; + + /// Implicitly defined assignment operator. + /// @param rhs The object to assign from. + /// @return A reference to the assigned-to object (i.e. `*this`). + memory_resource& operator=(const memory_resource& rhs) = default; + + /// Allocate memory. + /// Allocates at least `size_bytes` of memory aligned to `alignment` by calling the template method `do_allocate`. + /// + /// @par Example + /// Because C++14 does not provide portable, low-level memory allocators that support arbitrary alignment the + /// ability for CETL to support over-alignment is limited. A possible implementation is proposed here to + /// demonstrate: + /// @snippet{trimleft} example_03_memory_resource.cpp do_allocate + /// + /// @param size_bytes The number of bytes to allocate. Implementations may allocate additional bytes but shall + /// not allocate fewer. Callers should always assume that a successful call has allocated + /// exactly the requested number of bytes. + /// @param alignment The alignment of the allocated memory. CETL will treat an inability to properly align + /// memory as an allocation failure. + /// @return The allocated memory or std::nullptr if exceptions are disabled and an allocation failure occurs. + /// @throws std::bad_alloc Throws if any allocation errors occur. Implementations shall not leak memory even if + /// the allocation fails. + void* allocate(std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + return do_allocate(size_bytes, alignment); + } + + /// Deallocate memory previous allocated by this class. + /// Deallocates memory by calling the template method `do_deallocate`. While the specification sets a + /// precondition that p was returned by a call to cetl::pf17::pmr::memory_resource::allocate with the same + /// size_bytes and alignment it does not require any errors to be raised if any of the inputs are invalid. + /// + /// @par Example + /// See cetl::pf17::pmr::memory_resource::allocate for the first part of this example and a discussion of the + /// limitations of CETL's implementations. + /// @snippet{trimleft} example_03_memory_resource.cpp do_deallocate + /// + /// @param p A pointer returned by cetl::pf17::pmr::memory_resource::allocate. + /// @param size_bytes The size passed into cetl::pf17::pmr::memory_resource::allocate. + /// @param alignment The alignment passed into cetl::pf17::pmr::memory_resource::allocate. + /// @throws nothing (but is not `noexcept`). + void deallocate(void* p, std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + return do_deallocate(p, size_bytes, alignment); + } + + /// Compares two memory_resource implementations by calling template method `do_is_equal`. + /// @param rhs The memory_resource to compare. + /// @return `true` if memory allocated from this object can be deallocated by `rhs` and vice-versa. + bool is_equal(const memory_resource& rhs) const noexcept + { + return do_is_equal(rhs); + } + + /// A necessary evil, this method deviates from std::pmr::memory_resource. This means CETL's memory_allocator + /// is not ABI compatible with std::pmr::memory_resource and std::pmr::memory_resource is not, necessarily, + /// backwards compatible with CETL. Because CETL does not require RTTI, it is not an option to define a specialized + /// interface that fixed size resources can implement since there is no way to determine the type of the resource + /// or to safely cast to it. + /// @return The maximum size that can be allocated by this object. + std::size_t max_size() const noexcept + { + return do_max_size(); + } + + /// Reallocate memory. + /// Follows a similar behaviour contract described by std::reallocate with the implementation defined behaviour for + /// zero-length reallocations is to always return nullptr. + /// + /// @par Note + /// The behaviour of this method when called with a different alignment then used in the original allocation is + /// implementation defined. + /// + /// @param ptr The pointer to reallocate. If the reallocation is successful this pointer is invalid + /// and must not be used even if it's address is the same as the returned pointer. + /// This pointer remains valid for any failure. + /// @param old_size_bytes The number of bytes returned by the original allocation. + /// @param new_size_bytes The number of bytes to change ptr to hold. + /// @param alignment The alignment of the allocated memory. CETL will treat an inability to properly align + /// or to support re-alignment of memory as an allocation failure. + /// @return The allocated memory or std::nullptr if exceptions are disabled and an allocation failure occurs. + /// @throws std::bad_alloc Throws if any allocation errors occur. Implementations shall not leak memory even if + /// the allocation fails. + void* reallocate(void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment = alignof(std::max_align_t)) + { + return do_reallocate(ptr, old_size_bytes, new_size_bytes, alignment); + } + +private: + /// Template method to implement in terms of cetl::pf17::pmr::memory_resource::allocate. + /// @param size_bytes See cetl::pf17::pmr::memory_resource::allocate. + /// @param alignment See cetl::pf17::pmr::memory_resource::allocate. + /// @return See cetl::pf17::pmr::memory_resource::allocate. + /// @throws See cetl::pf17::pmr::memory_resource::allocate. + virtual void* do_allocate(std::size_t size_bytes, std::size_t alignment) = 0; + + /// Template method to implement in terms of cetl::pf17::pmr::memory_resource::deallocate. + /// @param p See cetl::pf17::pmr::memory_resource::deallocate. + /// @param size_bytes See cetl::pf17::pmr::memory_resource::deallocate. + /// @param alignment See cetl::pf17::pmr::memory_resource::deallocate. + /// @throws See cetl::pf17::pmr::memory_resource::deallocate. + virtual void do_deallocate(void* p, std::size_t size_bytes, std::size_t alignment) = 0; + + /// Template method to implement in terms of cetl::pf17::pmr::memory_resource::is_equal. + /// @param rhs See cetl::pf17::pmr::memory_resource::is_equal. + /// @return See cetl::pf17::pmr::memory_resource::is_equal + virtual bool do_is_equal(const memory_resource& rhs) const noexcept = 0; + + /// Default implementation of cetl::pf17::pmr::memory_resource::max_size that simply returns + /// `std::numeric_limits::max()`. + /// Implementations can override this method and do not need to call the base class. + /// @return See cetl::pf17::pmr::memory_resource::max_size. + virtual std::size_t do_max_size() const noexcept + { + return std::numeric_limits::max(); + } + + /// Default implementation of cetl::pf17::pmr::memory_resource::reallocate always returns nullptr. + /// @return See cetl::pf17::pmr::memory_resource::reallocate. + virtual void* do_reallocate(void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment) + { + (void) ptr; + (void) old_size_bytes; + (void) new_size_bytes; + (void) alignment; + return nullptr; + } +}; + +inline bool operator==(const memory_resource& lhs, const memory_resource& rhs) noexcept +{ + return &lhs == &rhs || lhs.is_equal(rhs); +} + +inline bool operator!=(const memory_resource& lhs, const memory_resource& rhs) noexcept +{ + return !(lhs == rhs); +} + +namespace deviant +{ + +/// Used to access extensions to std::pmr::memory_resource that are only found in CETL. Proper defaults +/// are provided when using std::pmr::memory_resource. +/// @tparam T The type of the memory_resource to access. +template +struct memory_resource_traits +{ + ~memory_resource_traits() = delete; + memory_resource_traits(const memory_resource_traits&) = delete; + memory_resource_traits& operator=(const memory_resource_traits&) = delete; + + /// If using a CETL memory_resource, returns the result of calling the memory_resource's max_size method otherwise + /// returns std::numeric_limits::max(). + /// @param mr A reference to the memory_resource to query. + /// @return The maximum number of bytes that can be allocated by this memory_resource. + static constexpr std::size_t max_size(const T& mr) noexcept + { + return memory_resource_traits::max_size_impl(mr); + } + + /// `std::true_type` if T implements `reallocate` otherwise `std::false_type`. + using is_reallocate_defined = std:: + conditional_t::value, std::true_type, std::false_type>; + + /// If supported, this will delegate to the memory_resource's reallocate method otherwise it will return nullptr. + /// This follows a similar behaviour contract described by std::reallocate with the implementation defined behaviour + /// for zero-length reallocations being to always return nullptr. + /// + /// @par Note + /// The behaviour of this method when called with a different alignment then used in the original allocation is + /// implementation defined. + /// + /// @param mr The memory_resource to use for allocation. + /// @param ptr The pointer to reallocate. If the reallocation is successful this pointer is invalid + /// and must not be used even if it's address is the same as the returned pointer. + /// This pointer remains valid for any failure. + /// @param old_size_bytes The number of bytes returned by the original allocation. + /// @param new_size_bytes The number of bytes to change ptr to hold. + /// @param alignment The alignment of the allocated memory. CETL will treat an inability to properly align + /// or to support re-alignment of memory as an allocation failure. + /// @return The allocated memory or std::nullptr if exceptions are disabled and an allocation failure occurs. + /// @throws std::bad_alloc Throws if any allocation errors occur. Implementations shall not leak memory even if + /// the allocation fails. + static constexpr void* reallocate(T& mr, + void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment = alignof(std::max_align_t)) + { + return reallocate_impl(mr, ptr, old_size_bytes, new_size_bytes, alignment); + } + +private: + // +--[max_size_impl]-----------------------------------------------------+ + template + static constexpr + typename std::enable_if::value, std::size_t>::type + max_size_impl(const U& mr) noexcept + { + return mr.max_size(); + } + + template + static constexpr + typename std::enable_if::value, std::size_t>::type + max_size_impl(const U& mr) noexcept + { + (void) mr; + return std::numeric_limits::max(); + } + + // +--[reallocate_impl]---------------------------------------------------+ + template + static constexpr typename std::enable_if::value>::type* + reallocate_impl(U& mr, + void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment) noexcept + { + return mr.reallocate(ptr, old_size_bytes, new_size_bytes, alignment); + } + + template + static constexpr void* reallocate_impl(...) noexcept + { + return nullptr; + } +}; + +} // namespace deviant + +// +--[mem.res.global]-------------------------------------------------------+ + +/// Adheres to the null_memory_resource specification. +/// Note that the C++ specification [dcl.inline] allows non-static inline functions in headers to define function-scoped +/// static variables since 1, the inline specifier has no effect on the linkage of the function and 2, the ODR requires +/// that inline functions with external linkage have the same memory address in all translation units. Furthermore +/// the specification states "A static local variable in an inline function with external or module linkage always +/// refers to the same object." +/// +/// @return Pointer to a static memory_resource object (i.e. a singleton). +inline memory_resource* null_memory_resource() noexcept +{ + class cetl_null_memory_resource_impl : public memory_resource + { + protected: + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + (void) size_bytes; + (void) alignment; +#if __cpp_exceptions + throw std::bad_alloc(); +#endif + // This is not defined by the specification but should be reasonably + // expected given the behaviour of the new operator when exceptions + // are disabled. + // Section 6.7.5.5.2 of ISO/IEC 14882 states: + // + // > An allocation function that has a non-throwing exception specification (14.5) indicates + // > failure by returning a null pointer value. + // + // We interpret this this to mean that, when all exceptions are disabled, every method implicitly + // has a non-throwing exception specification. + return nullptr; + } + + void do_deallocate(void* p, std::size_t size_bytes, std::size_t alignment) override + { + (void) p; + (void) size_bytes; + (void) alignment; + } + + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + return (&rhs == this); + } + + std::size_t do_max_size() const noexcept override + { + return 0; + } + }; + + alignas(cetl_null_memory_resource_impl) static char singleton_storage[sizeof(cetl_null_memory_resource_impl)]; + static cetl_null_memory_resource_impl* singleton = new (singleton_storage) cetl_null_memory_resource_impl; + return singleton; +} + +// +--[mem.poly.allocator.class]---------------------------------------------+ +/// Adheres to the std::pmr::polymorphic_allocator specification. +/// @tparam T The type of object this allocator constructs. +template +class polymorphic_allocator +{ + // +-----------------------------------------------------------------------+ + // | TMP Helpers | + // +-----------------------------------------------------------------------+ + /// @brief See https://en.cppreference.com/w/cpp/memory/uses_allocator#Uses-allocator_construction for this + /// protocol. + + template + struct is_leading_allocator_constructible + { + static constexpr bool value = + std::uses_allocator>::value && + std::is_constructible&, Args...>::value; + }; + template + struct is_trailing_allocator_constructible + { + static constexpr bool value = std::uses_allocator>::value && + std::is_constructible&>::value; + }; + + template + struct is_not_allocator_constructable + { + static constexpr bool value = !std::uses_allocator>::value; + }; + + template + struct EnableIfNotPair + { + using return_type = void; + }; + + template + struct EnableIfNotPair, U>::value>> + {}; + + template + typename std::enable_if_t::value, std::tuple> + make_pair_member_args(std::tuple& pmArgs) + { + return {std::move(pmArgs)}; + } + + template + typename std::enable_if_t::value, + std::tuple> + make_pair_member_args(std::tuple& pmArgs) + { + // https://cplusplus.github.io/LWG/issue2969 + return std::tuple_cat(std::make_tuple(std::allocator_arg, *this), std::move(pmArgs)); + } + + template + typename std::enable_if_t::value, + std::tuple> + make_pair_member_args(std::tuple& pmArgs) + { + // https://cplusplus.github.io/LWG/issue2969 + return std::tuple_cat(std::move(pmArgs), std::make_tuple(*this)); + } + + template + typename std::enable_if_t::value> construct_not_pair_impl( + U* p, + UArgsT&&... uArgs) + { + new (p) U(std::forward(uArgs)...); + } + + template + typename std::enable_if_t::value> construct_not_pair_impl( + U* p, + UArgsT&&... uArgs) + { + // https://cplusplus.github.io/LWG/issue2969 + new (p) U(std::allocator_arg, *this, std::forward(uArgs)...); + } + + template + typename std::enable_if_t::value> construct_not_pair_impl( + U* p, + UArgsT&&... uArgs) + { + // https://cplusplus.github.io/LWG/issue2969 + new (p) U(std::forward(uArgs)..., *this); + } + + // +-----------------------------------------------------------------------+ +public: + using value_type = T; + + /// @brief CETL diverges from the c++ standard here. + /// By default, the C++ standard provides a new_delete_resource. Because C++14 did not provide standardized support + /// for arbitrary alignment of memory allocations it would inflate CETL significantly to support this functionality. + /// Instead, CETL declines to provide default resources and requires explicit resources are provided + /// in types that would otherwise use a silent default. + /// + polymorphic_allocator() noexcept = delete; + + polymorphic_allocator(memory_resource* r) noexcept + : memory_resource_(r) + { + CETL_DEBUG_ASSERT(nullptr != r, + "Passing a null memory_resource to polymorphic_allocator is undefined per the C++ " + "specification."); + } + + polymorphic_allocator(const polymorphic_allocator&) = default; + polymorphic_allocator& operator=(const polymorphic_allocator&) = delete; + + template + polymorphic_allocator(const polymorphic_allocator& rhs) noexcept + : memory_resource_(rhs.resource()) + { + } + + T* allocate(std::size_t object_count) + { + if (std::numeric_limits::max() / sizeof(T) < object_count) + { +#if __cpp_exceptions + // Per the specification, if memory needed to create object_count objects exceeds + // the ability to address this memory using std::size_t then throw bad_array_new_length; + throw std::bad_array_new_length(); +#else + return nullptr; +#endif + } + return static_cast(memory_resource_->allocate(object_count * sizeof(T), alignof(T))); + } + + template + typename std::enable_if::is_reallocate_defined::value, U>::type* + reallocate(U* ptr, std::size_t old_object_count, std::size_t new_object_count) + { + return static_cast( + deviant::memory_resource_traits::reallocate(*memory_resource_, + ptr, + sizeof(U) * old_object_count, + sizeof(U) * new_object_count, + alignof(U))); + } + + void deallocate(T* p, size_t object_count) noexcept + { + memory_resource_->deallocate(p, sizeof(T) * object_count, alignof(T)); + } + + memory_resource* resource() const noexcept + { + return memory_resource_; + } + + /// Constructs an object U in the storage p with the given arguments following the uses-allocator protocol + /// for constructors. + /// + /// See std::uses_allocator_construction_args for a C++20 helper and [allocator.uses.construction] in the C++ + /// specification for a discussion of this protocol. + /// + /// This signature participates in overload resolution only if U is not an std::pair and if U does not + /// use allocator construction. + /// + /// This method only throws if the constructor of U throws. + /// + /// @tparam U The type to construct. + /// @tparam ...Args The type of arguments to forward to the constructor of U. + /// @param p The memory to construct an instance of U within. + /// @param ...args The argument to forward to the constructor of U. + /// @return void The type is constructed in-place if U is not an std::pair. + template + typename EnableIfNotPair::return_type construct(U* p, Args&&... args) + { + construct_not_pair_impl(p, args...); + } + + template + void construct(std::pair* p, + std::piecewise_construct_t, + std::tuple x, + std::tuple y) + { + new (p) std::pair{std::piecewise_construct, + make_pair_member_args(x), + make_pair_member_args(y)}; + } + + template + void construct(std::pair* p) + { + construct(p, std::piecewise_construct, std::tuple<>(), std::tuple<>()); + } + + template + void construct(std::pair* p, FirstTypeArgType&& x, SecondTypeArgType&& y) + { + construct(p, + std::piecewise_construct, + std::forward_as_tuple(std::forward(x)), + std::forward_as_tuple(std::forward(y))); + } + + template + void construct(std::pair* p, + const std::pair& copyFrom) + { + construct(p, + std::piecewise_construct, + std::forward_as_tuple(copyFrom.first), + std::forward_as_tuple(copyFrom.second)); + } + + template + void construct(std::pair* p, std::pair&& moveFrom) + { + construct(p, + std::piecewise_construct, + std::forward_as_tuple(std::forward(moveFrom.first)), + std::forward_as_tuple(std::forward(moveFrom.second))); + } + + // https://cplusplus.github.io/LWG/issue3036 (deprecates this function) + template + void destroy(U* p) + { + if (nullptr != p) + { + p->~U(); + } + } + + polymorphic_allocator select_on_container_copy_construction() const + { + return polymorphic_allocator(resource()); + } + + /// The maximum number of elements this allocator can create. Where the allocator is backed by system memory + /// or if the memory_resource provided to this instance does not report a max_size, this is often + /// reported as `std::numeric_limits::max() / sizeof(T)`. + /// @return The maximum number of elements this allocator can create. + std::size_t max_size() const noexcept + { + return deviant::memory_resource_traits::max_size(*memory_resource_) / sizeof(T); + } + +private: + memory_resource* memory_resource_; +}; + +template +bool operator==(const polymorphic_allocator& lhs, const polymorphic_allocator& rhs) noexcept +{ + return *lhs.resource() == *rhs.resource(); +} + +template +bool operator!=(const polymorphic_allocator& lhs, const polymorphic_allocator& rhs) noexcept +{ + return !(lhs == rhs); +} + +// +--[mem.res.monotonic.buffer]---------------------------------------------+ + +namespace deviant +{ + +/// Adheres to the C++17 std::pmr::monotonic_buffer_resource specification except that it does not provide +/// default upstream constructors and therefore has no dependencies to types defined in +/// cetl/pf17/sys/memory_resource.hpp. +class basic_monotonic_buffer_resource : public memory_resource +{ +public: + basic_monotonic_buffer_resource(void* buffer, + size_t buffer_size, + memory_resource* upstream = null_memory_resource()) + : first_buffer_control_{buffer, buffer_size, alignof(basic_monotonic_buffer_resource), buffer_size, nullptr} + , upstream_{upstream} + , current_buffer_{&first_buffer_control_} + { + CETL_DEBUG_ASSERT(nullptr != upstream_, + "upstream cannot be null. Use cetl::pf17::pmr::null_memory_resource() instead."); + } + + basic_monotonic_buffer_resource(std::size_t initial_size, memory_resource* upstream) + : basic_monotonic_buffer_resource(nullptr, initial_size, upstream) + { + } + + explicit basic_monotonic_buffer_resource(memory_resource* upstream) + : basic_monotonic_buffer_resource(0, upstream) + { + } + + ~basic_monotonic_buffer_resource() override + { + release(); + } + + basic_monotonic_buffer_resource(const basic_monotonic_buffer_resource&) = delete; + basic_monotonic_buffer_resource& operator=(const basic_monotonic_buffer_resource&) = delete; + + // +--[public methods]---------------------------------------------------+ + void release() noexcept + { + BufferControl* head = current_buffer_; + while (head != &first_buffer_control_) + { + BufferControl* previous = head->previous; + upstream_->deallocate(head, sizeof(BufferControl) + head->buffer_size, head->buffer_align); + head = previous; + } + current_buffer_ = &first_buffer_control_; + first_buffer_control_.remaining_buffer_size = first_buffer_control_.buffer_size; + } + + memory_resource* upstream_resource() const + { + return upstream_; + } + +protected: + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + void* result = do_allocate_from_current_buffer(size_bytes, alignment); + + const std::size_t max_size_v = max_size(); + + while (nullptr == result) + { + // Grow the buffer starting with, at minimum 4 bytes, the current buffer size, or the requested size + // plus alignment, whichever is larger. + const std::size_t grow_from_size = + std::max({static_cast(4), current_buffer_->buffer_size, size_bytes + alignment}); + + // Simple geometric progression of buffer size growth. + const std::size_t next_buffer_size = std::min(max_size_v, grow_from_size + (grow_from_size / 2U)); + + void* raw = upstream_->allocate(sizeof(BufferControl) + next_buffer_size, alignment); + if (nullptr == raw) + { + // out-of-memory with no exceptions. + break; + } + void* buffer = &static_cast(raw)[1]; + current_buffer_ = + new (raw) BufferControl{buffer, next_buffer_size, alignment, next_buffer_size, current_buffer_}; + + result = do_allocate_from_current_buffer(size_bytes, alignment); + } + return result; + } + + void do_deallocate(void* p, std::size_t size_bytes, std::size_t alignment) override + { + (void) p; + (void) size_bytes; + (void) alignment; + } + + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + return (&rhs == this); + } + + std::size_t do_max_size() const noexcept override + { + const std::size_t local_max_size = first_buffer_control_.buffer_size; + + const std::size_t upstream_max = + cetl::pf17::pmr::deviant::memory_resource_traits::max_size(*upstream_); + if (upstream_max >= (std::numeric_limits::max() - local_max_size)) + { + return upstream_max; + } + else + { + return local_max_size + upstream_max; + } + } + +private: + void* do_allocate_from_current_buffer(std::size_t size_bytes, std::size_t alignment) + { + void* result = nullptr; + if (current_buffer_->buffer && current_buffer_->remaining_buffer_size >= size_bytes) + { + void* buffer = current_buffer_->buffer; + std::size_t buffer_size = current_buffer_->remaining_buffer_size; + result = std::align(alignment, size_bytes, buffer, buffer_size); + if (result) + { + current_buffer_->remaining_buffer_size = buffer_size - size_bytes; + } + } + return result; + } + + struct BufferControl + { + void* buffer; + std::size_t buffer_size; + std::size_t buffer_align; + std::size_t remaining_buffer_size; + BufferControl* previous; + }; + + BufferControl first_buffer_control_; + memory_resource* upstream_; + BufferControl* current_buffer_; +}; +} // namespace deviant +} // namespace pmr +} // namespace pf17 +} // namespace cetl + +#endif // CETL_PF17_PMR_MEMORY_RESOURCE_H_INCLUDED diff --git a/include/cetl/pf17/sys/memory_resource.hpp b/include/cetl/pf17/sys/memory_resource.hpp new file mode 100644 index 00000000..37d20cb8 --- /dev/null +++ b/include/cetl/pf17/sys/memory_resource.hpp @@ -0,0 +1,197 @@ +/// @file +/// Extends the cetl::pf17::pmr namespace with system memory resources. This is the only header in CETL that +/// relies on malloc/free which is why it is separate from cetl/pf17/memory_resource.hpp. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PF17_PMR_SYS_MEMORY_RESOURCE_H_INCLUDED +#define CETL_PF17_PMR_SYS_MEMORY_RESOURCE_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +// +--[mem.res.global]-------------------------------------------------------+ +#ifndef CETL_H_ERASE +# include "cetl/pf17/memory_resource.hpp" +#endif + +namespace cetl +{ +namespace pf17 +{ +namespace pmr +{ +namespace deviant +{ + +class MaxAlignNewDeleteResource : public memory_resource +{ +protected: + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + if (alignment > alignof(std::max_align_t)) + { +#if __cpp_exceptions + throw std::bad_alloc(); +#endif + return nullptr; + } + return ::operator new(size_bytes); + } + + void do_deallocate(void* p, std::size_t size_bytes, std::size_t alignment) override + { + (void) size_bytes; + (void) alignment; + ::operator delete(p); + } + + void* do_reallocate(void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment) override + { + (void) old_size_bytes; + (void) alignment; + return std::realloc(ptr, new_size_bytes); + } + + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + return (&rhs == this); + } +}; + +} // namespace deviant + +/// @namespace cetl::pf17::pmr::_detail Users should not utilize anything inside of this namespace directly. These +/// are quasi-private implementation details that are necessarily visible for header-only implementations. +namespace _detail +{ + +inline deviant::MaxAlignNewDeleteResource* get_max_align_new_delete_resource_singleton() noexcept +{ + alignas( + deviant::MaxAlignNewDeleteResource) static char singleton_storage[sizeof(deviant::MaxAlignNewDeleteResource)]; + static deviant::MaxAlignNewDeleteResource* singleton = new (singleton_storage) deviant::MaxAlignNewDeleteResource(); + return singleton; +} + +inline std::atomic& get_new_delete_resource_singleton() noexcept +{ + static std::atomic singleton{get_max_align_new_delete_resource_singleton()}; + return singleton; +} + +} // namespace _detail + +// +--[mem.res.global]-------------------------------------------------------+ + +inline memory_resource* new_delete_resource() noexcept +{ + return _detail::get_new_delete_resource_singleton().load(); +} + +// +-------------------------------------------------------------------------+ +namespace deviant +{ + +/// Replace the memory_resource returned from cetl::pf17::pmr::new_delete_resource(). +/// Because C++14 does not provide a portable way to obtain over-aligned memory from the system, which means +/// the default "new_delete" implementation used by CETL cannot allocate over-aligned memory, this deviation from +/// the C++17 specification is provided to allow replacement of this implementation by the user. +/// +/// @note +/// This implementation uses std::atomic::exchange to avoid data races with cetl::pf17::pmr::new_delete_resource(). +/// +/// @param r If nullptr then the implementation returned from cetl::pf17::pmr::new_delete_resource() is set to +/// a static-duration instance of cetl::pf17::pmr::MaxAlignNewDeleteResource otherwise +/// cetl::pf17::pmr::new_delete_resource() is set to r. +/// @return The previous value returned by cetl::pf17::pmr::new_delete_resource(). +inline memory_resource* set_new_delete_resource(memory_resource* r) noexcept +{ + if (nullptr == r) + { + return _detail::get_new_delete_resource_singleton().exchange( + _detail::get_max_align_new_delete_resource_singleton()); + } + else + { + return _detail::get_new_delete_resource_singleton().exchange(r); + } +} + +} // namespace deviant + +namespace _detail +{ +inline std::atomic& get_default_resource_singleton() noexcept +{ + alignas(std::atomic) static char singleton_storage[sizeof(std::atomic)]; + static std::atomic* singleton = + new (singleton_storage) std::atomic{new_delete_resource()}; + return *singleton; +} + +} // namespace _detail + +// +--[mem.res.global]-------------------------------------------------------+ + +inline memory_resource* set_default_resource(memory_resource* r) noexcept +{ + if (nullptr == r) + { + return _detail::get_default_resource_singleton().exchange(new_delete_resource()); + } + else + { + return _detail::get_default_resource_singleton().exchange(r); + } +} + +inline memory_resource* get_default_resource() noexcept +{ + return _detail::get_default_resource_singleton().load(); +} + +// +--[mem.res.monotonic.buffer]---------------------------------------------+ + +class monotonic_buffer_resource : public deviant::basic_monotonic_buffer_resource +{ +public: + using deviant::basic_monotonic_buffer_resource::basic_monotonic_buffer_resource; + + monotonic_buffer_resource(std::size_t initial_size, memory_resource* upstream) + : deviant::basic_monotonic_buffer_resource(nullptr, initial_size, upstream) + { + } + + monotonic_buffer_resource() + : deviant::basic_monotonic_buffer_resource(get_default_resource()) + { + } + + explicit monotonic_buffer_resource(size_t initial_size) + : deviant::basic_monotonic_buffer_resource(initial_size, get_default_resource()) + { + } + + monotonic_buffer_resource(void* buffer, size_t buffer_size) + : deviant::basic_monotonic_buffer_resource(buffer, buffer_size, get_default_resource()) + { + } +}; + +} // namespace pmr +} // namespace pf17 +} // namespace cetl + +#endif // CETL_PF17_PMR_SYS_MEMORY_RESOURCE_H_INCLUDED diff --git a/include/cetl/pf20/cetlpf.hpp b/include/cetl/pf20/cetlpf.hpp index 84ec3c1a..72f8049a 100644 --- a/include/cetl/pf20/cetlpf.hpp +++ b/include/cetl/pf20/cetlpf.hpp @@ -10,6 +10,9 @@ /// This header violates AUTOSAR-14 M7-3-6, A17-0-1, and A16-0-1 (and possibly other rules). Don't use CETL polyfill /// headers in AUTOSAR code. /// +/// @warning +/// polyfill headers cannot be used if CETL_H_ERASE is defined. +/// /// CETL polyfill headers will provide the CETL type as an aliased standard type in the std namespace when compiling /// using older C++ libraries and will automatically provide types from the standard library when compiling /// using newer C++ libraries. This allows for causal use of CETL as a true polyfill library but does violate certain @@ -36,25 +39,34 @@ #ifndef CETL_PF20_H_INCLUDED #define CETL_PF20_H_INCLUDED +/// @namespace cetl::pf20 This namespace contains C++20 polyfill (pf) types. +/// The types within this namespace adhere to the C++20 specification and should be drop-in replaceable +/// with said standard types where they are available. CETL polyfill types may implement a sub-set of +/// the required functionality but they will not implement non-compliant functionality. #include "cetl/cetl.hpp" +#include "cetl/pf17/cetlpf.hpp" -#if (__cplusplus >= CETL_CPP_STANDARD_20) +#if (__cplusplus >= CETL_CPP_STANDARD_20 && !defined(CETL_DOXYGEN)) # include +namespace cetl +{ +constexpr std::size_t dynamic_extent = std::dynamic_extent; + +template +using span = std::span; +} // namespace cetl #else # include "cetl/pf20/span.hpp" -namespace std +namespace cetl { -// Yes, this is something you normally don't do, adding to the std namespace, however; this is a -// polyfill library which is the exception to this rule. If you don't want this automatic behavior -// then simply include the cetl polyfill types directly and don't use this header. constexpr std::size_t dynamic_extent = cetl::pf20::dynamic_extent; template using span = cetl::pf20::span; -} // namespace std +} // namespace cetl #endif #endif // CETL_PF20_H_INCLUDED diff --git a/include/cetl/pf20/span.hpp b/include/cetl/pf20/span.hpp index 47142f78..5e47c464 100644 --- a/include/cetl/pf20/span.hpp +++ b/include/cetl/pf20/span.hpp @@ -86,7 +86,7 @@ class span /// Default constructor /// @tparam DeducedExtent The Extent deduced by the compiler. - /// @tparam type SFNAE enablement of this constructor only where the extent > 0. + /// @tparam type SFINAE enablement of this constructor only where the extent > 0. /// @see std::span::span() template ::type = true> constexpr span() noexcept @@ -112,7 +112,7 @@ class span /// Creates a span starting at an element to the element before the given end. /// That is, `span == [first, end)`. It is undefined to provide iterators where `end - first != Extent` - /// @tparam EndType End iterator type is deduced to support SFNAE pattern. + /// @tparam EndType End iterator type is deduced to support SFINAE pattern. /// @tparam type Participates in overload resolution only if the EndType cannot be converted to size_type. /// @param first The first element in the span. /// @param end The element after the last element in the span. @@ -136,7 +136,7 @@ class span } /// Creates a span over an entire `std::array` starting at the array's first element. - /// @tparam ArrayElementType Deduced array element type to support SFNAE enablement. + /// @tparam ArrayElementType Deduced array element type to support SFINAE enablement. /// @tparam type Enables this override only if the array's element type is the same as the span's or if the /// conversion is a simple [qualification /// conversion](https://en.cppreference.com/w/cpp/language/implicit_conversion#Qualification_conversions). @@ -150,7 +150,7 @@ class span } /// Creates a span over an entire `const std::array` starting at the array's first element. - /// @tparam ArrayElementType Deduced array element type to support SFNAE enablement. + /// @tparam ArrayElementType Deduced array element type to support SFINAE enablement. /// @tparam type Enables this override only if the array's element type is the same as the span's or if the /// conversion is a simple [qualification /// conversion](https://en.cppreference.com/w/cpp/language/implicit_conversion#Qualification_conversions). @@ -279,7 +279,7 @@ class span /// +-----------------------+ /// /// @endcode - /// @return reverse iterator from the begining of the span. + /// @return reverse iterator from the beginning of the span. /// @see std::span::rend constexpr reverse_iterator rend() const noexcept { @@ -315,7 +315,7 @@ class span /// /// Provides access to the internal data the span is a view into. - /// @return A pointer to the begining of the span sequence. + /// @return A pointer to the beginning of the span sequence. /// @see std::span::data constexpr pointer data() const noexcept { @@ -359,7 +359,7 @@ class span } ///@} // +----------------------------------------------------------------------+ - ///@{ @name Subviews + ///@{ @name sub-views // +----------------------------------------------------------------------+ /// Create a new span from the start of the current span for `Count` elements. /// @tparam Count Number of elements for the sub-span. @@ -368,7 +368,7 @@ class span template constexpr span first() const { - static_assert(Count <= Extent, "subviews beyond the size of the span's view are not allowed."); + static_assert(Count <= Extent, "sub-views beyond the size of the span's view are not allowed."); return span(data_, Count); } @@ -380,7 +380,7 @@ class span constexpr span first(size_type count) const { CETL_DEBUG_ASSERT(count <= Extent, - "CDE_span_008: Dynamic subviews beyond the size of the span's view are undefined."); + "CDE_span_008: Dynamic sub-views beyond the size of the span's view are undefined."); return span(data_, count); } @@ -391,7 +391,7 @@ class span template constexpr span last() const { - static_assert(Count <= extent, "subviews beyond the size of the span's view are not allowed."); + static_assert(Count <= extent, "sub-views beyond the size of the span's view are not allowed."); return span{&data_[extent - Count], Count}; } @@ -403,7 +403,7 @@ class span constexpr span last(size_type count) const { CETL_DEBUG_ASSERT(count <= extent, - "CDE_span_009: Dynamic subviews beyond the size of the span's view are undefined."); + "CDE_span_009: Dynamic sub-views beyond the size of the span's view are undefined."); return span(&data_[extent - count], count); } @@ -516,7 +516,7 @@ class span /// Creates a span starting at an element to the element before the given end. /// That is, `span == [first, end)`. The size of the span becomes end - first. - /// @tparam EndType End iterator type is deduced to support SFNAE pattern. + /// @tparam EndType End iterator type is deduced to support SFINAE pattern. /// @tparam type Participates in overload resolution only if the EndType cannot be converted to size_type. /// @param first The first element in the span. /// @param end The element after the last element in the span. @@ -546,7 +546,7 @@ class span /// Creates a span over an entire `std::array` starting at the array's first element and with a size set to /// `ArrayLen`. - /// @tparam ArrayElementType Deduced array element type to support SFNAE enablement. + /// @tparam ArrayElementType Deduced array element type to support SFINAE enablement. /// @tparam ArrayLen The length of the array. span::size() is set to this value. /// @tparam type Enables this override only if the array's element type is the same as the span's or if the /// conversion is a simple [qualification @@ -564,7 +564,7 @@ class span /// Creates a span over an entire `const std::array` starting at the array's first element and with a size set to /// `ArrayLen`. - /// @tparam ArrayElementType Deduced array element type to support SFNAE enablement. + /// @tparam ArrayElementType Deduced array element type to support SFINAE enablement. /// @tparam ArrayLen The length of the array. span::size() is set to this value. /// @tparam type Enables this override only if the array's element type is the same as the span's or if the /// conversion is a simple [qualification @@ -715,7 +715,7 @@ class span } ///@} // +----------------------------------------------------------------------+ - ///@{ @name Subviews + ///@{ @name sub-views // +----------------------------------------------------------------------+ /// Create a new span from the start of the current span for `Count` elements. /// @tparam Count Number of elements for the sub-span. The behavior of this method and the returned span is @@ -725,7 +725,7 @@ class span template constexpr span first() const { - CETL_DEBUG_ASSERT(Count <= size_, "CDE_span_017: Subviews beyond the size of the span's view are undefined."); + CETL_DEBUG_ASSERT(Count <= size_, "CDE_span_017: sub-views beyond the size of the span's view are undefined."); return span(data_, Count); } @@ -734,7 +734,7 @@ class span constexpr span first(size_type count) const { CETL_DEBUG_ASSERT(count <= size_, - "CDE_span_018: Dynamic subviews beyond the size of the span's view are undefined."); + "CDE_span_018: Dynamic sub-views beyond the size of the span's view are undefined."); return span(data_, count); } @@ -746,7 +746,7 @@ class span template constexpr span last() const { - CETL_DEBUG_ASSERT(Count <= size_, "CDE_span_019: Subviews beyond the size of the span's view are undefined."); + CETL_DEBUG_ASSERT(Count <= size_, "CDE_span_019: sub-views beyond the size of the span's view are undefined."); return span(&data_[size_ - Count], Count); } @@ -755,7 +755,7 @@ class span constexpr span last(size_type count) const { CETL_DEBUG_ASSERT(count <= size_, - "CDE_span_020: Dynamic subviews beyond the size of the span's view are undefined."); + "CDE_span_020: Dynamic sub-views beyond the size of the span's view are undefined."); return span(&data_[size_ - count], count); } diff --git a/include/cetl/pmr/array_memory_resource.hpp b/include/cetl/pmr/array_memory_resource.hpp new file mode 100644 index 00000000..4346ce80 --- /dev/null +++ b/include/cetl/pmr/array_memory_resource.hpp @@ -0,0 +1,196 @@ +/// @file +/// Defines memory_resource types specific to CETL but adhering to the C++17 std::pmr::memory_resource interface +/// contract that are backed by simple contiguous blocks of memory. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PMR_ARRAY_MEMORY_RESOURCE_H_INCLUDED +#define CETL_PMR_ARRAY_MEMORY_RESOURCE_H_INCLUDED + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +#include +#include +#include +#include // for std::align + +namespace cetl +{ +namespace pmr +{ + +/// Memory resource that supports a single allocation request within a single, contiguous block of memory. +/// Without any memory barriers or other synchronization primitives this is one of the simplest possible implementations +/// of a std::pmr::memory_resource with only one feature of supporting an, optional, upstream allocator. +/// +/// This is a delegate class that isn't useful on its own but is used to implement other memory resource types. There +/// are two ways to use this class: +/// +/// @par PolyFill +/// If you are using cetl/pf17/cetlpf.hpp then include this header just before that one and use +/// cetl::pmr::UnsynchronizedArrayMemoryResource as defined in that header. When compiling for C++17 this type will +/// implement std::pmr::memory_resource and when compiling for C++14 this type will implement +/// cetl::pf17::pmr::memory_resource. +/// +/// @par Delegate Class +/// Manually build an implementation of memory_resource that uses this class as a delegate. For example: +/// +/// @snippet{trimleft} example_05_array_memory_resource_alignment.cpp example_delegate +/// +/// Also note that this class supports over-alignment. For example: +/// +/// @snippet{trimleft} example_05_array_memory_resource_alignment.cpp example_0 +/// +/// In that example the buffer is too small to support the requested alignment. In that case you can do this: +/// +/// @snippet{trimleft} example_05_array_memory_resource_alignment.cpp example_1 +/// +/// @tparam MemoryResourceType The type of the upstream memory resource to use. +template +class UnsynchronizedArrayMemoryResourceDelegate final +{ +private: + /// Saturating add of two max size values clamped to the maximum value for the pointer difference type + /// for the current architecture. + static constexpr std::size_t calculate_max_size_bytes(std::size_t max_size_left, std::size_t max_size_right) + { + static_assert(std::numeric_limits::max() >= 0, + "We don't know what it means to have a negative maximum pointer diff? Serious, what gives?"); + + constexpr const std::size_t max_diff_as_size = + static_cast(std::numeric_limits::max()); + const std::size_t left_clamped = std::min(max_size_left, max_diff_as_size); + const std::size_t right_clamped = std::min(max_size_right, max_diff_as_size); + if (right_clamped > (max_diff_as_size - left_clamped)) + { + return max_diff_as_size; + } + else + { + return left_clamped + right_clamped; + } + } + +public: + /// Designated constructor that initializes the object with a fixed buffer and an optional upstream memory resource. + /// @param buffer The buffer that is used to satisfy allocation requests. + /// @param buffer_size_bytes The size, in bytes, of the buffer. + /// @param upstream An optional upstream memory resource to use if the buffer is already in use. + /// @param upstream_max_size_bytes The maximum size of the upstream buffer. + UnsynchronizedArrayMemoryResourceDelegate(void* buffer, + std::size_t buffer_size_bytes, + UpstreamMemoryResourceType* upstream, + std::size_t upstream_max_size_bytes) noexcept + : upstream_{upstream} + , buffer_{buffer} + , buffer_size_bytes_{buffer_size_bytes} + , max_size_bytes_{calculate_max_size_bytes(buffer_size_bytes, upstream_max_size_bytes)} + , in_use_{nullptr} + { + CETL_DEBUG_ASSERT(nullptr != upstream, + "Upstream memory resource cannot be null. Use std::pmr::null_memory_resource or " + "cetl::pmr::null_memory_resource if you don't want an upstream memory resource."); + } + + ~UnsynchronizedArrayMemoryResourceDelegate() = default; + UnsynchronizedArrayMemoryResourceDelegate(const UnsynchronizedArrayMemoryResourceDelegate&) = delete; + UnsynchronizedArrayMemoryResourceDelegate& operator=(const UnsynchronizedArrayMemoryResourceDelegate&) = delete; + UnsynchronizedArrayMemoryResourceDelegate(UnsynchronizedArrayMemoryResourceDelegate&&) = delete; + UnsynchronizedArrayMemoryResourceDelegate& operator=(UnsynchronizedArrayMemoryResourceDelegate&&) = delete; + + // +--[public methods]---------------------------------------------------+ + constexpr UpstreamMemoryResourceType* upstream_resource() const + { + return upstream_; + } + + constexpr void* allocate(std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + void* result = nullptr; + if (!in_use_) + { + result = allocate_internal_buffer(size_bytes, alignment); + } + if (nullptr != result) + { + in_use_ = result; + } + else if (upstream_) + { + result = upstream_->allocate(size_bytes, alignment); + } + +#if __cpp_exceptions + if (nullptr == result) + { + throw std::bad_alloc(); + } +#endif + return result; + } + + constexpr void* reallocate(void* p, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t new_align) + { + (void) old_size_bytes; + CETL_DEBUG_ASSERT(nullptr == p || in_use_ == p || nullptr != upstream_, + "Unknown pointer passed into reallocate."); + if (p == in_use_) + { + return allocate_internal_buffer(new_size_bytes, new_align); + } + else + { + return nullptr; + } + return nullptr; + } + + constexpr void deallocate(void* p, std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + CETL_DEBUG_ASSERT(nullptr == p || in_use_ == p || nullptr != upstream_, + "Unknown pointer passed into deallocate."); + if (p == in_use_) + { + in_use_ = nullptr; + } + else if (nullptr != upstream_) + { + upstream_->deallocate(p, size_bytes, alignment); + } + } + + std::size_t max_size() const noexcept + { + return max_size_bytes_; + } + +private: + constexpr void* allocate_internal_buffer(std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + void* result = nullptr; + if (nullptr != buffer_ && size_bytes <= buffer_size_bytes_) + { + void* storage_ptr = buffer_; + std::size_t storage_size = buffer_size_bytes_; + result = std::align(alignment, size_bytes, storage_ptr, storage_size); + } + return result; + } + + UpstreamMemoryResourceType* upstream_; + void* buffer_; + const std::size_t buffer_size_bytes_; + const std::size_t max_size_bytes_; + void* in_use_; +}; + +} // namespace pmr +} // namespace cetl + +#endif // CETL_PMR_ARRAY_MEMORY_RESOURCE_H_INCLUDED diff --git a/include/cetl/pmr/memory.hpp b/include/cetl/pmr/memory.hpp new file mode 100644 index 00000000..9c6d1ca0 --- /dev/null +++ b/include/cetl/pmr/memory.hpp @@ -0,0 +1,245 @@ +/// @file +/// Extensions and utilities for types found in the standard `memory` header to better integrate with pmr types. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PMR_MEMORY_H_INCLUDED +#define CETL_PMR_MEMORY_H_INCLUDED + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +#include +#include + +namespace cetl +{ +namespace pmr +{ + +/// RAII helper for memory_resource::allocate() and memory_resource::deallocate(). This type is designed to work with +/// std::unique_ptr. +/// +/// Example usage: +/// +/// @snippet{trimleft} example_06_memory_resource_deleter.cpp example_usage +/// +class MemoryResourceDeleter final +{ +private: + struct MemoryResourceData + { + memory_resource* mem_resource; + std::size_t mem_size; + std::size_t mem_align; + }; + +public: + /// Designated constructor. + /// @param mem_resource The memory resource to use for deallocation. + /// @param mem_size The size of the memory to deallocate. + /// @param mem_align The alignment of the memory to deallocate. + MemoryResourceDeleter(memory_resource* mem_resource, + std::size_t mem_size, + std::size_t mem_align = alignof(std::max_align_t)) noexcept + : data_{mem_resource, mem_size, mem_align} + { + } + + /// To support unique_ptr move semantics. + /// @param rhs The deleter to move from. + MemoryResourceDeleter(MemoryResourceDeleter&& rhs) noexcept + : data_{nullptr, 0, 0} + { + swap(std::move(rhs)); + } + + /// To support unique_ptr move semantics. + /// @param rhs The deleter to move from. + /// @return Reference to this. + MemoryResourceDeleter& operator=(MemoryResourceDeleter&& rhs) noexcept + { + swap(std::move(rhs)); + return *this; + } + + ~MemoryResourceDeleter() = default; + + MemoryResourceDeleter() noexcept = delete; + MemoryResourceDeleter(const MemoryResourceDeleter&) = delete; + MemoryResourceDeleter& operator=(const MemoryResourceDeleter&) = delete; + + /// Functor called by smart-pointer to deallocate memory. + /// @param p The memory to deallocate. + void operator()(void* p) noexcept + { + CETL_DEBUG_ASSERT(nullptr == p || nullptr != data_.mem_resource, "mem_resource was null in deleter?"); + if (nullptr != data_.mem_resource) + { + data_.mem_resource->deallocate(p, data_.mem_size, data_.mem_align); + } + } + + /// Size of the memory this deleter will or did deallocate. + /// @return Size in bytes. + std::size_t size() const noexcept + { + return data_.mem_size; + } + + /// Alignment of the memory this deleter will or did deallocate. + /// @return Alignment. + std::size_t alignment() const noexcept + { + return data_.mem_align; + } + + /// The memory resource this deleter will or did use to deallocate memory. + /// @return The memory resource or nullptr if this deleter was moved from and is now invalid. + memory_resource* resource() const noexcept + { + return data_.mem_resource; + } + +private: + void swap(MemoryResourceDeleter&& rhs) noexcept + { + CETL_DEBUG_ASSERT(nullptr != rhs.data_.mem_resource, "Moving from resource deleter with null memory resource."); + data_.mem_resource = nullptr; + std::swap(data_, rhs.data_); + } + + MemoryResourceData data_; +}; + +/// RAII helper for cetl::pf17::pmr::polymorphic_allocator and std::pmr::polymorphic_allocator. This type is designed +/// to work with std::unique_ptr but is still cumbersome to use. Use with cetl::pmr::Factory for the best and safest +/// experience. Remember, be safe, use the cetl::pmr::Factory. +/// +/// @note +/// See cetl::pmr::Factory for an example of how to use this type. +/// +/// @tparam PolymorphicDeallocator The type of the polymorphic allocator to use for deallocation. +/// +template +class PolymorphicDeleter final +{ +public: + /// The allocator type this deleter uses to, uh... delete memory? + using allocator = PolymorphicDeallocator; + + /// The type this deleter, you know...deletes? (It is in the name of the class and all) + using value_type = typename PolymorphicDeallocator::value_type; + + /// While this object is simply a functor and could be used with other smart pointers the + /// design is optimized for and tested with this std::unique_ptr type. + using unique_ptr = std::unique_ptr>; + + PolymorphicDeleter() noexcept = delete; + + /// Designated constructor that copies a given allocator and records the object count to use when deleting + /// the smart pointer resources. + /// @param alloc The allocator to use when deleting the objects in the smart pointer. + /// @param object_count The number of objects in the smart pointer that will be deleted. + PolymorphicDeleter(const PolymorphicDeallocator& alloc, std::size_t object_count) noexcept( + std::is_nothrow_copy_constructible::value) + : alloc_{alloc} + , obj_count_{object_count} + { + } + + ~PolymorphicDeleter() = default; + + PolymorphicDeleter(PolymorphicDeleter&& rhs) noexcept( + std::is_nothrow_move_constructible::value) + : alloc_{rhs.alloc_} + , obj_count_{rhs.obj_count_} + { + } + + PolymorphicDeleter& operator=(PolymorphicDeleter&& rhs) noexcept( + std::is_nothrow_move_assignable::value) + { + alloc_ = rhs.alloc_; + obj_count_ = rhs.obj_count_; + return *this; + } + + PolymorphicDeleter(const PolymorphicDeleter& rhs) noexcept( + std::is_nothrow_copy_constructible::value) + : alloc_{rhs.alloc_} + , obj_count_{rhs.obj_count_} + { + } + PolymorphicDeleter& operator=(const PolymorphicDeleter& rhs) noexcept( + std::is_nothrow_copy_assignable::value) + { + alloc_ = rhs.alloc_; + obj_count_ = rhs.obj_count_; + return *this; + } + + /// Functor called by smart-pointer to deallocate and deconstruct objects. + /// @param p The object to deconstruct and deallocate. + void operator()(value_type* p) noexcept + { + if (nullptr != p) + { + // while pmr allocators define a destroy method, it is deprecated in C++20 + // since it adds little value over simply calling the destructor directly. + p->~value_type(); + } + alloc_.deallocate(p, obj_count_); + } + +private: + PolymorphicDeallocator alloc_; + std::size_t obj_count_; +}; + +/// Factory helper for creating objects with polymorphic allocators using proper RAII semantics. +/// Uses the cetl::pmr::PolymorphicDeleter type to ensure proper deallocation. +/// +/// Example usage: +/// +/// @snippet{trimleft} example_06_polymorphic_alloc_deleter.cpp example_usage_0 +/// @snippet{trimleft} example_06_polymorphic_alloc_deleter.cpp example_usage_1 +/// +class Factory final +{ +public: + ~Factory() = delete; + Factory() = delete; + + template + using allocator_t = typename std::allocator_traits::template rebind_alloc< + typename RebindAllocatorType::value_type>; + + template + using unique_ptr_t = std::unique_ptr::value_type, + cetl::pmr::PolymorphicDeleter>>; + + // TODO: make_unique_array + template + static auto make_unique(PolymorphicAllocatorTypeU& alloc, Args&&... args) -> + typename Factory::unique_ptr_t + { + typename Factory::unique_ptr_t + p{alloc.allocate(1), typename Factory::unique_ptr_t::deleter_type{alloc, 1}}; + if (nullptr != p) + { + alloc.construct(p.get(), std::forward(args)...); + } + return p; + } +}; + +} // namespace pmr +} // namespace cetl + +#endif // CETL_PMR_MEMORY_H_INCLUDED diff --git a/include/cetl/pmr/o1heap_memory_resource.hpp b/include/cetl/pmr/o1heap_memory_resource.hpp new file mode 100644 index 00000000..f6c9ffc6 --- /dev/null +++ b/include/cetl/pmr/o1heap_memory_resource.hpp @@ -0,0 +1,103 @@ +/// @file +/// Defines cetl::pmr::O1HeapMemoryResource which is a std::pmr::memory_resource implemented in terms of Pavel +/// Kirienko's [o1heap](https://github.com/pavel-kirienko/o1heap). If including this file you will need to either first +/// include cetl/pf17/cetlpf.hpp or provide the memory_resource definition you want this class to use. +/// You'll also need to provide an include path to o1heap.h and compile in o1heap.c when using this type. +/// +/// TODO: examples +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#ifndef CETL_PMR_O1HEAP_MEMORY_RESOURCE_H_INCLUDED +#define CETL_PMR_O1HEAP_MEMORY_RESOURCE_H_INCLUDED + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +#include // for aligned_storage + +#include "o1heap.h" + +namespace cetl +{ +namespace pmr +{ + +template +struct O1HeapAlignedStorage +{ + static constexpr std::size_t size_bytes = StorageSizeBytes; + static constexpr std::size_t alignment = O1HEAP_ALIGNMENT; + + static_assert(O1HEAP_ALIGNMENT >= alignof(std::max_align_t), "O1HEAP_ALIGNMENT is too small for this platform."); + + typename std::aligned_storage::type storage[size_bytes]; +}; + +class UnsynchronizedO1HeapMemoryResourceDelegate +{ +public: + UnsynchronizedO1HeapMemoryResourceDelegate(void* buffer, std::size_t buffer_size_bytes) + : o1heap_{o1heapInit(buffer, buffer_size_bytes)} + , max_size_bytes_{buffer_size_bytes} + { + // TODO: https://github.com/pavel-kirienko/o1heap/issues/17 + CETL_DEBUG_ASSERT(nullptr != o1heap_, "o1heapInit failed."); + } + + template + explicit UnsynchronizedO1HeapMemoryResourceDelegate(AlignedStorageType& aligned_storage) + : UnsynchronizedO1HeapMemoryResourceDelegate(aligned_storage.storage, AlignedStorageType::size_bytes) + { + } + + ~UnsynchronizedO1HeapMemoryResourceDelegate() = default; + UnsynchronizedO1HeapMemoryResourceDelegate(const UnsynchronizedO1HeapMemoryResourceDelegate&) = delete; + UnsynchronizedO1HeapMemoryResourceDelegate& operator=(const UnsynchronizedO1HeapMemoryResourceDelegate&) = delete; + UnsynchronizedO1HeapMemoryResourceDelegate(UnsynchronizedO1HeapMemoryResourceDelegate&&) = delete; + UnsynchronizedO1HeapMemoryResourceDelegate& operator=(UnsynchronizedO1HeapMemoryResourceDelegate&&) = delete; + + void* allocate(std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + (void) alignment; + // TODO: https://github.com/pavel-kirienko/o1heap/issues/13 + void* result = o1heapAllocate(o1heap_, size_bytes); + +#if __cpp_exceptions + if (nullptr == result) + { + throw std::bad_alloc(); + } +#endif + return result; + } + + void deallocate(void* p, std::size_t size_bytes, std::size_t alignment = alignof(std::max_align_t)) + { + // TODO https://github.com/pavel-kirienko/o1heap/issues/13 + (void) alignment; + (void) size_bytes; + o1heapFree(o1heap_, p); + } + + std::size_t max_size() const noexcept + { + // TODO: https://github.com/pavel-kirienko/o1heap/issues/18 + return max_size_bytes_; + } + +private: + O1HeapInstance* o1heap_; + // TODO: remove when https://github.com/pavel-kirienko/o1heap/issues/18 is fixed. + std::size_t max_size_bytes_; +}; + +} // namespace pmr +} // namespace cetl + +#endif // CETL_PMR_O1HEAP_MEMORY_RESOURCE_H_INCLUDED diff --git a/include/cetl/variable_length_array.hpp b/include/cetl/variable_length_array.hpp new file mode 100644 index 00000000..70e41360 --- /dev/null +++ b/include/cetl/variable_length_array.hpp @@ -0,0 +1,2049 @@ +/// @file +/// Includes cetl::VariableLengthArray type and non-member functions. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +/// cSpell:ignore cend cbegin rnext rend lnext lbegin pocca pocma + +#ifndef CETL_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED +#define CETL_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#ifndef CETL_H_ERASE +# include "cetl/cetl.hpp" +#endif + +static_assert( + __cplusplus >= 201402L, + "Unsupported language: ISO C14, C++14, or a newer version of either is required to use the built-in VLA type"); + +namespace cetl +{ + +// Common template implementation for VariableLengthArray and its specializations. This is not intended to be used +// directly. +template +class VariableLengthArrayBase +{ +public: + /// + /// STL-like declaration of the container's storage type. + /// + using value_type = BaseValueType; + + /// + /// Per the std::uses_allocator protocol. + /// + using allocator_type = typename std::allocator_traits::template rebind_alloc; + + /// + /// STL-like declaration of the container's difference type. + /// + using difference_type = std::ptrdiff_t; + + /// + /// STL-like declaration of the container's size type. + /// + using size_type = std::size_t; + + constexpr VariableLengthArrayBase( + const allocator_type& alloc, + value_type* data, + size_type initial_capacity, + size_type size, + size_type max_size_max) noexcept(std::is_nothrow_copy_constructible::value) + : alloc_(alloc) + , data_(data) + , capacity_(initial_capacity) + , size_(size) + , max_size_max_(max_size_max) + { + } + + constexpr VariableLengthArrayBase(const VariableLengthArrayBase& rhs) noexcept( + std::is_nothrow_copy_constructible::value) + : alloc_(std::allocator_traits::select_on_container_copy_construction(rhs.alloc_)) + , data_(nullptr) + , capacity_(0) + , size_(0) + , max_size_max_(rhs.max_size_max_) + { + } + + constexpr VariableLengthArrayBase(VariableLengthArrayBase&& rhs) noexcept + : alloc_(std::move(rhs.alloc_)) + , data_(std::move(rhs.data_)) + , capacity_(rhs.capacity_) + , size_(rhs.size_) + , max_size_max_(rhs.max_size_max_) + { + static_assert(std::is_nothrow_move_constructible::value, + "Allocator must be nothrow move constructible."); + rhs.size_ = 0; + rhs.capacity_ = 0; + rhs.data_ = nullptr; + } + +protected: + // +----------------------------------------------------------------------+ + // | TYPE HELPERS + // +----------------------------------------------------------------------+ + // adapted from https://en.cppreference.com/w/cpp/experimental/is_detected + // implements a C++14-compatible detection idiom. + + template using _void_t = void; + + template class Op, class... Args> + struct detector + { + using value_t = std::false_type; + }; + + template