From 4d7a5a175f81eaa144c321feec6b8204045d0be3 Mon Sep 17 00:00:00 2001 From: John R Patek Sr <31934875+johnpatek@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:57:42 -0600 Subject: [PATCH] Maxtest Integration (#10) * added Maxtest to unit test * fixed CMakeLists.txt * added PRIVATE specifier to CMakeLists.txt --- LICENSE | 2 +- tests/CMakeLists.txt | 71 +++++----- tests/Threadpool.cpp | 83 ----------- tests/Threadpool.hpp | 124 ---------------- tests/unit.cpp | 328 +++++++++++++++++-------------------------- 5 files changed, 167 insertions(+), 441 deletions(-) delete mode 100644 tests/Threadpool.cpp delete mode 100644 tests/Threadpool.hpp diff --git a/LICENSE b/LICENSE index 7298042..73f03ab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2024 John R Patek Sr +Copyright (c) 2024 Maxtek Consulting Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 492762a..e491ab2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,39 +1,40 @@ -# add_executable( -# unit -# unit.cpp -# ${SIGFN_SOURCES}) +include(FetchContent) +FetchContent_Declare( + Maxtest + GIT_REPOSITORY https://github.com/maxtek6/maxtest.git + GIT_TAG master +) +FetchContent_MakeAvailable(Maxtest) -# target_include_directories(unit PRIVATE ${SIGFN_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/../src) +add_executable( + unit + unit.cpp + ${SIGFN_SOURCES}) -# if(SIGFN_COVER) -# if(WIN32) -# message("skipping code coverage for windows") -# else() -# target_compile_options(unit PRIVATE -fprofile-arcs -ftest-coverage -g -O0) -# target_link_libraries(unit PRIVATE gcov "--coverage") -# add_custom_target( -# cover -# DEPENDS unit) -# add_custom_command( -# TARGET cover -# COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR}/.. -e ${CMAKE_CURRENT_SOURCE_DIR}) -# endif() -# endif() +target_include_directories(unit PRIVATE ${SIGFN_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/../src) -# add_test(NAME sigfn_handle COMMAND unit sigfn_handle ) -# add_test(NAME sigfn_ignore COMMAND unit sigfn_ignore) -# add_test(NAME sigfn_reset COMMAND unit sigfn_reset) -# add_test(NAME sigfn_error COMMAND unit sigfn_error) -# add_test(NAME sigfn::handle COMMAND unit sigfn::handle) -# add_test(NAME sigfn::ignore COMMAND unit sigfn::ignore) -# add_test(NAME sigfn::reset COMMAND unit sigfn::reset) +if(SIGFN_COVER) + if(WIN32) + message("skipping code coverage for windows") + target_link_libraries(unit PRIVATE Maxtest::Maxtest) + else() + target_compile_options(unit PRIVATE -fprofile-arcs -ftest-coverage -g -O0) + target_link_libraries(unit PRIVATE Maxtest::Maxtest gcov "--coverage") + add_custom_target( + cover + DEPENDS unit) + add_custom_command( + TARGET cover + COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR}/.. -e ${CMAKE_CURRENT_SOURCE_DIR}) + endif() +else() + target_link_libraries(unit PRIVATE Maxtest::Maxtest) +endif() -add_library(threadpool_a STATIC threadpool.cpp) -target_include_directories(threadpool_a PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(threadpool SHARED threadpool.cpp) -target_include_directories(threadpool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(threadpool INTERFACE) - -target_include_directories(threadpool INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file +maxtest_add_test(unit sigfn_handle "") +maxtest_add_test(unit sigfn_ignore "") +maxtest_add_test(unit sigfn_reset "") +maxtest_add_test(unit sigfn_error "") +maxtest_add_test(unit sigfn::handle "") +maxtest_add_test(unit sigfn::ignore "") +maxtest_add_test(unit sigfn::reset "") diff --git a/tests/Threadpool.cpp b/tests/Threadpool.cpp deleted file mode 100644 index c3fbe2e..0000000 --- a/tests/Threadpool.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include "threadpool.hpp" - -maxtek::threadpool::threadpool(size_t threads) -{ - if (threads == 0) - { - throw std::runtime_error("failed to construct threadpool with zero threads"); - } - - _active = true; - - _workers.reserve(threads); - - while (_workers.size() < _workers.capacity()) - { - _workers.push_back(std::thread([&]() - { - std::function task; - while (pop_task(task)) - { - task(); - } })); - } -} - -maxtek::threadpool::~threadpool() -{ - if (_active) - { - shutdown(); - } -} - -bool maxtek::threadpool::active() const -{ - return _active; -} - -void maxtek::threadpool::shutdown() -{ - if (!_active) - { - throw std::runtime_error("failed to shut down inactive threadpool"); - } - _active = false; - _condition.notify_all(); - for (std::thread &worker : _workers) - { - worker.join(); - } -} - -void maxtek::threadpool::push_task(std::function &&task) -{ - std::unique_lock lock(_mutex); - if (!_active) - { - throw std::runtime_error("failed to submit to inactive threadpool"); - } - _tasks.push(std::move(task)); - lock.unlock(); - _condition.notify_one(); -} - -bool maxtek::threadpool::pop_task(std::function &task) -{ - std::unique_lock lock(_mutex); - bool result(false); - _condition.wait( - lock, - [&]() - { - return (!_active || !_tasks.empty()); - }); - if (_active) - { - task = _tasks.front(); - _tasks.pop(); - result = true; - } - return result; -} \ No newline at end of file diff --git a/tests/Threadpool.hpp b/tests/Threadpool.hpp deleted file mode 100644 index a6fafd2..0000000 --- a/tests/Threadpool.hpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -** Copyright 2024 Maxtek Consulting -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. -*/ - -#ifndef THREADPOOL_HPP -#define THREADPOOL_HPP - -/** - * @file threadpool.hpp - * @brief Maxtek threadpool - * @author Max Guerrero and John R. Patek Sr. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#define DLL_EXPORT __declspec(dllexport) -#else -#define DLL_EXPORT -#endif - -namespace maxtek -{ - /** - * @brief threadpool - * - * @class allows tasks to be submitted and executed asynchronously across multiple threads. - */ - class DLL_EXPORT threadpool - { - public: - - /** - * @brief constructs a new threadpool - * - * @param threads number of threads to use for constructing the threadpool - * @exception std::runtime_error if threads is set to zero - */ - threadpool(size_t threads = std::thread::hardware_concurrency()); - - /** - * @brief destroys threadpool after calling shutdown if necessary - */ - ~threadpool(); - - /** - * @brief submits a function with its arguments to the threadpool - * @tparam F function signature - * @tparam Args function argument types - * @param function function signature - * @param args arguments to pass to the function - * @returns a future holding the asynchronous function result - * @exception std::runtime_error if the thread pool has been shut down - */ - template - std::future> submit(F &&function, Args &&...args) - { - std::shared_ptr()>> packaged_task; - std::function work; - std::future> result; - - packaged_task = std::make_shared()>>(std::bind(std::forward(function), std::forward(args)...)); - result = packaged_task->get_future(); - - work = [packaged_task]() - { - (*packaged_task)(); - }; - - push_task(std::move(work)); - - return result; - } - - /** - * @brief check if the threadpool is active - * @returns true if the threadpool is active, false if it has been shut down - */ - bool active() const; - - /** - * @brief shut down threadpool by joining threads and rejecting submissions - * @exception std::runtime_error if the thread pool has already been shut down - */ - void shutdown(); - - private: - void push_task(std::function &&task); - bool pop_task(std::function &task); - - size_t num_threads; - bool _active; - std::vector _workers; - std::queue> _tasks; - std::mutex _mutex; - std::condition_variable _condition; - }; -} -#endif \ No newline at end of file diff --git a/tests/unit.cpp b/tests/unit.cpp index 0f0b228..c243f43 100644 --- a/tests/unit.cpp +++ b/tests/unit.cpp @@ -20,12 +20,9 @@ * SOFTWARE. */ +#include #include "internal.hpp" -#include -#include -#include - #define PASS 0 #define FAIL 1 @@ -40,219 +37,154 @@ #endif #endif -#define TEST_ASSERT(COND) test_assert((COND), __FILE__, __FUNCTION_NAME__, __LINE__, #COND) - -static void test_assert( - bool condition, - const std::string &file, - const std::string &function, - int line, - const std::string &description); - -namespace c -{ - static void sigfn_handle(); - static void sigfn_ignore(); - static void sigfn_reset(); - static void sigfn_error(); -} - -namespace cpp -{ - static void sigfn_handle(); - static void sigfn_ignore(); - static void sigfn_reset(); -} - static void echo_signum(int signum, void *userdata); + // GCOV_EXCL_START -int main(int argc, const char **argv) +MAXTEST_MAIN { - const std::unordered_map> unit_tests = { - {"sigfn_handle", c::sigfn_handle}, - {"sigfn_ignore", c::sigfn_ignore}, - {"sigfn_reset", c::sigfn_reset}, - {"sigfn_error", c::sigfn_error}, - {"sigfn::handle", cpp::sigfn_handle}, - {"sigfn::ignore", cpp::sigfn_ignore}, - {"sigfn::reset", cpp::sigfn_reset}, - }; - int result; - result = PASS; - if (argc > 1) + MAXTEST_TEST_CASE(sigfn_handle) { - try - { - const std::function &unit_test = unit_tests.at(argv[1]); - unit_test(); - } - catch (const std::exception &exception) - { - std::cerr << exception.what() << std::endl; - result = FAIL; - } - } - return result; -} + int result; + int signum(SIGINT); + int flag(INVALID_SIGNUM); + result = ::sigfn_handle(signum, echo_signum, &flag); + MAXTEST_ASSERT(result == PASS); + raise(signum); + MAXTEST_ASSERT(flag == signum); + }; -void test_assert( - bool condition, - const std::string &file, - const std::string &function, - int line, - const std::string &description) -{ - std::stringstream error_stream; - if (!condition) + MAXTEST_TEST_CASE(sigfn_ignore) { - error_stream << file << ":" << function << ":" << line << ":" - << " failed to assert \"" << description << "\""; - throw std::runtime_error(error_stream.str()); - } -} - -void c::sigfn_handle() -{ - int result; - int signum(SIGINT); - int flag(INVALID_SIGNUM); - result = ::sigfn_handle(signum, echo_signum, &flag); - TEST_ASSERT(result == PASS); - raise(signum); - TEST_ASSERT(flag == signum); -} - -void c::sigfn_ignore() -{ - TEST_ASSERT(::sigfn_ignore(INVALID_SIGNUM) == -1); - TEST_ASSERT(::sigfn_ignore(SIGINT) == 0); -} - -void c::sigfn_reset() -{ - TEST_ASSERT(::sigfn_reset(INVALID_SIGNUM) == -1); - TEST_ASSERT(::sigfn_reset(SIGINT) == 0); -} - -void c::sigfn_error() -{ - int flag; - std::string error; - ::sigfn_handle(INVALID_SIGNUM, echo_signum, &flag); - error = ::sigfn_error(); - TEST_ASSERT(error == sigfn::internal::invalid_syscall); - ::sigfn_handle(SIGINT, INVALID_HANDLER, &flag); - error = ::sigfn_error(); - TEST_ASSERT(error == sigfn::internal::invalid_handler); - ::sigfn_handle(SIGINT, echo_signum, &flag); - TEST_ASSERT(::sigfn_error() == nullptr); -} + MAXTEST_ASSERT(::sigfn_ignore(INVALID_SIGNUM) == -1); + MAXTEST_ASSERT(::sigfn_ignore(SIGINT) == 0); + }; -void cpp::sigfn_handle() -{ - int flag(0); - const sigfn::handler_function invalid_copy; - const sigfn::handler_function valid_copy = [&](int signum) + MAXTEST_TEST_CASE(sigfn_reset) { - flag = 1; + MAXTEST_ASSERT(::sigfn_reset(INVALID_SIGNUM) == -1); + MAXTEST_ASSERT(::sigfn_reset(SIGINT) == 0); }; - sigfn::handler_function invalid_move; - sigfn::handler_function valid_move = [&](int signum) + + MAXTEST_TEST_CASE(sigfn_error) { - flag = 2; + int flag; + std::string error; + ::sigfn_handle(INVALID_SIGNUM, echo_signum, &flag); + error = ::sigfn_error(); + MAXTEST_ASSERT(error == sigfn::internal::invalid_syscall); + ::sigfn_handle(SIGINT, INVALID_HANDLER, &flag); + error = ::sigfn_error(); + MAXTEST_ASSERT(error == sigfn::internal::invalid_handler); + ::sigfn_handle(SIGINT, echo_signum, &flag); + MAXTEST_ASSERT(::sigfn_error() == nullptr); }; - - const std::function try_catch_copy( - []( - int signum, - const sigfn::handler_function &handler, - const std::string& expected_error) + + MAXTEST_TEST_CASE(sigfn::handle) + { + int flag(0); + const sigfn::handler_function invalid_copy; + const sigfn::handler_function valid_copy = [&](int signum) { - std::string actual_error; - try - { - sigfn::handle(signum, handler); - } - catch (const std::exception &e) - { - actual_error = e.what(); - } - TEST_ASSERT(expected_error == actual_error); - }); - const std::function try_catch_move( - []( - int signum, - sigfn::handler_function &&handler, - const std::string& expected_error) + flag = 1; + }; + sigfn::handler_function invalid_move; + sigfn::handler_function valid_move = [&](int signum) { - std::string actual_error; - try + flag = 2; + }; + + const std::function try_catch_copy( + []( + int signum, + const sigfn::handler_function &handler, + const std::string &expected_error) { - sigfn::handle(signum, std::move(handler)); - } - catch (const std::exception &e) + std::string actual_error; + try + { + sigfn::handle(signum, handler); + } + catch (const std::exception &e) + { + actual_error = e.what(); + } + MAXTEST_ASSERT(expected_error == actual_error); + }); + const std::function try_catch_move( + []( + int signum, + sigfn::handler_function &&handler, + const std::string &expected_error) { - actual_error = e.what(); - } - TEST_ASSERT(expected_error == actual_error); - }); - try_catch_copy(INVALID_SIGNUM, valid_copy, sigfn::internal::invalid_syscall); - try_catch_copy(SIGINT, invalid_copy, sigfn::internal::invalid_handler); - try_catch_copy(SIGINT, valid_copy, ""); - raise(SIGINT); - TEST_ASSERT(flag == 1); - try_catch_move(SIGINT, std::move(invalid_move), sigfn::internal::invalid_handler); - try_catch_move(SIGINT, std::move(valid_move), ""); - raise(SIGINT); - TEST_ASSERT(flag == 2); -} - -void cpp::sigfn_ignore() -{ - const std::function try_catch_assert( - []( - int signum, - bool expect_error) - { - bool has_error; + std::string actual_error; + try + { + sigfn::handle(signum, std::move(handler)); + } + catch (const std::exception &e) + { + actual_error = e.what(); + } + MAXTEST_ASSERT(expected_error == actual_error); + }); + try_catch_copy(INVALID_SIGNUM, valid_copy, sigfn::internal::invalid_syscall); + try_catch_copy(SIGINT, invalid_copy, sigfn::internal::invalid_handler); + try_catch_copy(SIGINT, valid_copy, ""); + raise(SIGINT); + MAXTEST_ASSERT(flag == 1); + try_catch_move(SIGINT, std::move(invalid_move), sigfn::internal::invalid_handler); + try_catch_move(SIGINT, std::move(valid_move), ""); + raise(SIGINT); + MAXTEST_ASSERT(flag == 2); + }; - has_error = false; - try - { - sigfn::ignore(signum); - } - catch (const std::exception &e) + MAXTEST_TEST_CASE(sigfn::ignore) + { + const std::function try_catch_assert( + []( + int signum, + bool expect_error) { - has_error = (e.what() != nullptr); - } - TEST_ASSERT(expect_error == has_error); - }); - try_catch_assert(INVALID_SIGNUM, true); - try_catch_assert(SIGINT, false); -} - -void cpp::sigfn_reset() -{ - const std::function try_catch_assert( - []( - int signum, - bool expect_error) - { - bool has_error; + bool has_error; + + has_error = false; + try + { + sigfn::ignore(signum); + } + catch (const std::exception &e) + { + has_error = (e.what() != nullptr); + } + MAXTEST_ASSERT(expect_error == has_error); + }); + try_catch_assert(INVALID_SIGNUM, true); + try_catch_assert(SIGINT, false); + }; - has_error = false; - try - { - sigfn::reset(signum); - } - catch (const std::exception &e) + MAXTEST_TEST_CASE(sigfn::reset) + { + const std::function try_catch_assert( + []( + int signum, + bool expect_error) { - has_error = (e.what() != nullptr); - } - TEST_ASSERT(expect_error == has_error); - }); - try_catch_assert(INVALID_SIGNUM, true); - try_catch_assert(SIGINT, false); + bool has_error; + + has_error = false; + try + { + sigfn::reset(signum); + } + catch (const std::exception &e) + { + has_error = (e.what() != nullptr); + } + MAXTEST_ASSERT(expect_error == has_error); + }); + try_catch_assert(INVALID_SIGNUM, true); + try_catch_assert(SIGINT, false); + }; } void echo_signum(int signum, void *userdata)