From dd463e1f6d4c64e884ec231e9e3fc7e7c9d313f0 Mon Sep 17 00:00:00 2001 From: Thomas Boyer-Chammard <49786685+thomas-bc@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:48:30 -0700 Subject: [PATCH] Refactor OS::Mutex in CMake selection (#2790) --- .gitignore | 1 + Os/CMakeLists.txt | 9 +- Os/Models/CMakeLists.txt | 1 + Os/Models/Models.hpp | 11 +++ Os/Models/Mutex.fpp | 14 +++ Os/Mutex.cpp | 45 +++++++++ Os/Mutex.hpp | 75 ++++++++++++--- Os/Posix/CMakeLists.txt | 28 +++++- Os/Posix/DefaultMutex.cpp | 17 ++++ Os/Posix/Mutex.cpp | 78 +++++++-------- Os/Posix/Mutex.hpp | 47 +++++++++ Os/Posix/error.cpp | 18 ++++ Os/Posix/error.hpp | 6 ++ Os/Posix/test/ut/PosixMutexTests.cpp | 66 +++++++++++++ Os/Stub/CMakeLists.txt | 33 ++++++- Os/Stub/DefaultMutex.cpp | 18 ++++ Os/Stub/Mutex.cpp | 24 +++++ Os/Stub/Mutex.hpp | 44 +++++++++ Os/Stub/test/DefaultMutex.cpp | 18 ++++ Os/Stub/test/Mutex.cpp | 41 ++++++++ Os/Stub/test/Mutex.hpp | 68 ++++++++++++++ Os/Stub/test/ut/StubMutexTests.cpp | 90 ++++++++++++++++++ Os/Task.cpp | 2 +- Os/test/ut/mutex/CommonTests.cpp | 83 ++++++++++++++++ Os/test/ut/mutex/CommonTests.hpp | 35 +++++++ Os/test/ut/mutex/MutexRules.cpp | 99 +++++++++++++++++++ Os/test/ut/mutex/MutexRules.hpp | 136 +++++++++++++++++++++++++++ Os/test/ut/mutex/RulesHeaders.hpp | 53 +++++++++++ cmake/API.cmake | 1 + cmake/platform/Darwin.cmake | 1 + cmake/platform/Linux.cmake | 1 + config/FpConfig.h | 4 +- 32 files changed, 1105 insertions(+), 62 deletions(-) create mode 100644 Os/Models/Mutex.fpp create mode 100644 Os/Mutex.cpp create mode 100644 Os/Posix/DefaultMutex.cpp create mode 100644 Os/Posix/Mutex.hpp create mode 100644 Os/Posix/test/ut/PosixMutexTests.cpp create mode 100644 Os/Stub/DefaultMutex.cpp create mode 100644 Os/Stub/Mutex.cpp create mode 100644 Os/Stub/Mutex.hpp create mode 100644 Os/Stub/test/DefaultMutex.cpp create mode 100644 Os/Stub/test/Mutex.cpp create mode 100644 Os/Stub/test/Mutex.hpp create mode 100644 Os/Stub/test/ut/StubMutexTests.cpp create mode 100644 Os/test/ut/mutex/CommonTests.cpp create mode 100644 Os/test/ut/mutex/CommonTests.hpp create mode 100644 Os/test/ut/mutex/MutexRules.cpp create mode 100644 Os/test/ut/mutex/MutexRules.hpp create mode 100644 Os/test/ut/mutex/RulesHeaders.hpp diff --git a/.gitignore b/.gitignore index 5f56ccddac..fdfa3cc063 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ Tester.* !**/test/ut/GTestBase.* !**/test/ut/TesterBase.* !**/test/ut/Tester.* +seed-history *.sconsign.dblite *.class diff --git a/Os/CMakeLists.txt b/Os/CMakeLists.txt index 1411c8ecd2..6119e93668 100644 --- a/Os/CMakeLists.txt +++ b/Os/CMakeLists.txt @@ -37,6 +37,7 @@ set(SOURCE_FILES # Refactored common files "${CMAKE_CURRENT_LIST_DIR}/File.cpp" "${CMAKE_CURRENT_LIST_DIR}/Task.cpp" + "${CMAKE_CURRENT_LIST_DIR}/Mutex.cpp" ) # Check for default logger if (NOT FPRIME_DISABLE_DEFAULT_LOGGER) @@ -52,11 +53,9 @@ if (FPRIME_USE_POSIX) "${CMAKE_CURRENT_LIST_DIR}/Pthreads/BufferQueueCommon.cpp" "${CMAKE_CURRENT_LIST_DIR}/Pthreads/PriorityBufferQueue.cpp" "${CMAKE_CURRENT_LIST_DIR}/Pthreads/MaxHeap/MaxHeap.cpp" -# "${CMAKE_CURRENT_LIST_DIR}/Posix/Task.cpp" "${CMAKE_CURRENT_LIST_DIR}/Linux/InterruptLock.cpp" "${CMAKE_CURRENT_LIST_DIR}/Linux/WatchdogTimer.cpp" "${CMAKE_CURRENT_LIST_DIR}/Posix/IntervalTimer.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Posix/Mutex.cpp" "${CMAKE_CURRENT_LIST_DIR}/Linux/FileSystem.cpp" "${CMAKE_CURRENT_LIST_DIR}/Linux/Directory.cpp" ) @@ -96,7 +95,11 @@ if (FPRIME_USE_BAREMETAL_SCHEDULER) list(APPEND SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/Baremetal/SystemResources.cpp") endif() register_fprime_module() + require_fprime_implementation(Os/File) +# require_fprime_implementation(Os/Task) # should be added in +require_fprime_implementation(Os/Mutex) + ### UTS ### Note: 3 separate UTs registered here. set(UT_SOURCE_FILES @@ -110,7 +113,7 @@ set(UT_SOURCE_FILES ) register_fprime_ut() if (BUILD_TESTING) - foreach (TEST IN ITEMS StubFileTest PosixFileTest) + foreach (TEST IN ITEMS StubFileTest PosixFileTest StubMutexTest PosixMutexTest) # add? : StubTaskTest PosixTaskTest if (TARGET "${TEST}") add_dependencies("Os_ut_exe" "${TEST}") endif() diff --git a/Os/Models/CMakeLists.txt b/Os/Models/CMakeLists.txt index d97d72449d..a3b46cfaad 100644 --- a/Os/Models/CMakeLists.txt +++ b/Os/Models/CMakeLists.txt @@ -9,5 +9,6 @@ set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/File.fpp" "${CMAKE_CURRENT_LIST_DIR}/Task.fpp" + "${CMAKE_CURRENT_LIST_DIR}/Mutex.fpp" ) register_fprime_module() diff --git a/Os/Models/Models.hpp b/Os/Models/Models.hpp index bdfe18c7b8..680336c7f6 100644 --- a/Os/Models/Models.hpp +++ b/Os/Models/Models.hpp @@ -6,8 +6,10 @@ #include "Os/Models/FileStatusEnumAc.hpp" #include "Os/Models/FileModeEnumAc.hpp" #include "Os/Models/TaskStatusEnumAc.hpp" +#include "Os/Models/MutexStatusEnumAc.hpp" #include "Os/File.hpp" #include "Os/Task.hpp" +#include "Os/Mutex.hpp" #ifndef OS_MODELS_MODELS_HPP #define OS_MODELS_MODELS_HPP @@ -79,4 +81,13 @@ static_assert(static_cast(Os::Task::Status::ERROR_PERMISSION) static_assert(static_cast(Os::Task::Status::INVALID_STATE) == Os::TaskStatus::T::INVALID_STATE, "Task status and FPP shadow enum do not match"); +static_assert(static_cast(Os::Mutex::Status::OP_OK) == Os::MutexStatus::T::OP_OK, + "Mutex status and FPP shadow enum do not match"); +static_assert(static_cast(Os::Mutex::Status::ERROR_BUSY) == Os::MutexStatus::T::ERROR_BUSY, + "Mutex status and FPP shadow enum do not match"); +static_assert(static_cast(Os::Mutex::Status::ERROR_DEADLOCK) == Os::MutexStatus::T::ERROR_DEADLOCK, + "Mutex status and FPP shadow enum do not match"); +static_assert(static_cast(Os::Mutex::Status::ERROR_OTHER) == Os::MutexStatus::T::ERROR_OTHER, + "Mutex status and FPP shadow enum do not match"); + #endif // OS_MODELS_MODELS_HPP diff --git a/Os/Models/Mutex.fpp b/Os/Models/Mutex.fpp new file mode 100644 index 0000000000..62b681f7b9 --- /dev/null +++ b/Os/Models/Mutex.fpp @@ -0,0 +1,14 @@ +# ====================================================================== +# \title Os/Models/Mutex.fpp +# \brief FPP type definitions for Os/Mutex.hpp concepts +# ====================================================================== + +module Os { + @ FPP shadow-enum representing Os::Mutex::Status + enum MutexStatus { + OP_OK, @< Operation was successful + ERROR_BUSY, @< Mutex is busy + ERROR_DEADLOCK, @< Deadlock condition detected + ERROR_OTHER @< All other errors + } +} diff --git a/Os/Mutex.cpp b/Os/Mutex.cpp new file mode 100644 index 0000000000..de3e58a7ec --- /dev/null +++ b/Os/Mutex.cpp @@ -0,0 +1,45 @@ +// ====================================================================== +// \title Os/Mutex.cpp +// \brief common function implementation for Os::Mutex +// ====================================================================== +#include +#include + +namespace Os { + +Mutex::Mutex() : m_handle_storage(), m_delegate(*MutexInterface::getDelegate(m_handle_storage)) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +Mutex::~Mutex() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + m_delegate.~MutexInterface(); +} + +MutexHandle* Mutex::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +Mutex::Status Mutex::take() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.take(); +} + +Mutex::Status Mutex::release() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.release(); +} + +void Mutex::lock() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + Mutex::Status status = this->take(); + FW_ASSERT(status == Mutex::Status::OP_OK, status); +} + +void Mutex::unLock() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + Mutex::Status status = this->release(); + FW_ASSERT(status == Mutex::Status::OP_OK, status); +} +} // namespace Os diff --git a/Os/Mutex.hpp b/Os/Mutex.hpp index 9fc6507ff9..e46fae04f8 100644 --- a/Os/Mutex.hpp +++ b/Os/Mutex.hpp @@ -1,24 +1,73 @@ -#ifndef _Mutex_hpp_ -#define _Mutex_hpp_ +// ====================================================================== +// \title Os/Mutex.hpp +// \brief common definitions for Os::Mutex +// ====================================================================== +#ifndef Os_Mutex_hpp +#define Os_Mutex_hpp #include +#include namespace Os { - class Mutex { - public: +struct MutexHandle {}; - Mutex(); //!< Constructor. Mutex is unlocked when created - virtual ~Mutex(); //!< Destructor +class MutexInterface { + // add enum with + public: + enum Status { + OP_OK, //!< Operation was successful + ERROR_BUSY, //!< Mutex is busy + ERROR_DEADLOCK, //!< Deadlock condition detected + ERROR_OTHER //!< All other errors + }; - void lock(); //!< lock the mutex - void unLock(); //!< unlock the mutex - void unlock() { this->unLock(); } //!< alias for unLock to meet BasicLockable requirements + //! \brief default constructor + MutexInterface() = default; - private: + //! \brief default virtual destructor + virtual ~MutexInterface() = default; - POINTER_CAST m_handle; //!< Stored handle to mutex - }; -} + //! \brief copy constructor is forbidden + MutexInterface(const MutexInterface& other) = delete; + + //! \brief assignment operator is forbidden + MutexInterface& operator=(const MutexInterface& other) = delete; + + //! \brief return the underlying mutex handle (implementation specific) + //! \return internal mutex handle representation + virtual MutexHandle* getHandle() = 0; + + //! \brief provide a pointer to a Mutex delegate object + static MutexInterface* getDelegate(HandleStorage& aligned_new_memory); // TODO + + virtual Status take() = 0; //!< lock the mutex return status + virtual Status release() = 0; //!< unlock the mutex return status +}; + +class Mutex final : public MutexInterface { + public: + Mutex(); //!< Constructor. Mutex is unlocked when created + ~Mutex() final; //!< Destructor + + //! \brief return the underlying mutex handle (implementation specific) + //! \return internal mutex handle representation + MutexHandle* getHandle() override; + + Status take() override; //!< lock the mutex and get return status + Status release() override; //!< unlock the mutex and get return status + void lock(); //!< lock the mutex and assert success + void unLock(); //!< unlock the mutex and assert success + void unlock() { this->unLock(); } //!< alias for unLock to meet BasicLockable requirements + + private: + // This section is used to store the implementation-defined mutex handle. To Os::Mutex and fprime, this type is + // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in + // the byte-array here and set `handle` to that address for storage. + // + alignas(FW_HANDLE_ALIGNMENT) HandleStorage m_handle_storage; //!< Mutex handle storage + MutexInterface& m_delegate; //!< Delegate for the real implementation +}; +} // namespace Os #endif diff --git a/Os/Posix/CMakeLists.txt b/Os/Posix/CMakeLists.txt index 85cf7116ec..c9f3b32317 100644 --- a/Os/Posix/CMakeLists.txt +++ b/Os/Posix/CMakeLists.txt @@ -50,7 +50,7 @@ set(HEADER_FILES "${CMAKE_CURRENT_LIST_DIR}/Task.hpp" ) -set(MOD_DEPS Os_Posix_Shared Fw/Time Os) +set(MOD_DEPS Os_Posix_Shared Fw/Time Os) # Os needs to be removed here? register_fprime_module(Os_Task_Posix) register_fprime_implementation(Os/Task Os_Task_Posix "${CMAKE_CURRENT_LIST_DIR}/DefaultTask.cpp") @@ -65,4 +65,28 @@ set(UT_MOD_DEPS STest ) choose_fprime_implementation(Os/Task Os_Task_Posix) -register_fprime_ut(PosixTaskTest) \ No newline at end of file +register_fprime_ut(PosixTaskTest) + +#### Os/Mutex/Posix Section #### +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Mutex.cpp" +) +set(HEADER_FILES + "${CMAKE_CURRENT_LIST_DIR}/Mutex.hpp" +) +set(MOD_DEPS Os_Posix_Shared) +register_fprime_module(Os_Mutex_Posix) +register_fprime_implementation(Os/Mutex Os_Mutex_Posix "${CMAKE_CURRENT_LIST_DIR}/DefaultMutex.cpp") + +#### Os/Mutex/Posix Unit Tests #### +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/../test/ut/mutex/CommonTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/../test/ut/mutex/MutexRules.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/PosixMutexTests.cpp" +) +set(UT_MOD_DEPS + Os + STest +) +choose_fprime_implementation(Os/Mutex Os_Mutex_Posix) +register_fprime_ut(PosixMutexTest) diff --git a/Os/Posix/DefaultMutex.cpp b/Os/Posix/DefaultMutex.cpp new file mode 100644 index 0000000000..0659d9ada5 --- /dev/null +++ b/Os/Posix/DefaultMutex.cpp @@ -0,0 +1,17 @@ +// ====================================================================== +// \title Os/Posix/DefaultMutex.cpp +// \brief sets default Os::Mutex Posix implementation via linker +// ====================================================================== +#include "Os/Posix/Mutex.hpp" +#include "Os/Delegate.hpp" +namespace Os { + +//! \brief get a delegate for MutexInterface that intercepts calls for Posix +//! \param aligned_new_memory: aligned memory to fill +//! \return: pointer to delegate +MutexInterface *MutexInterface::getDelegate(HandleStorage& aligned_new_memory) { + return Os::Delegate::makeDelegate( + aligned_new_memory + ); +} +} diff --git a/Os/Posix/Mutex.cpp b/Os/Posix/Mutex.cpp index f0c2e5d5f4..369d80f042 100644 --- a/Os/Posix/Mutex.cpp +++ b/Os/Posix/Mutex.cpp @@ -1,51 +1,51 @@ -#include -#include +// ====================================================================== +// \title Os/Posix/Mutex.cpp +// \brief Posix implementation for Os::Mutex +// ====================================================================== +#include +#include #include -#include namespace Os { +namespace Posix { +namespace Mutex { - Mutex::Mutex() { - pthread_mutex_t* handle = new(std::nothrow) pthread_mutex_t; - FW_ASSERT(handle != nullptr); +PosixMutex::PosixMutex() : Os::MutexInterface(), m_handle() { + // set attributes + pthread_mutexattr_t attribute; + PlatformIntType status = pthread_mutexattr_init(&attribute); + FW_ASSERT(status == 0, status); - // set attributes - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); + // set to normal mutex type + status = pthread_mutexattr_settype(&attribute, PTHREAD_MUTEX_NORMAL); + FW_ASSERT(status == 0, status); - NATIVE_INT_TYPE stat; - // set to error checking -// stat = pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ERRORCHECK); -// FW_ASSERT(stat == 0,stat); + // set to check for priority inheritance + status = pthread_mutexattr_setprotocol(&attribute, PTHREAD_PRIO_INHERIT); + FW_ASSERT(status == 0, status); - // set to normal mutex type - stat = pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL); - FW_ASSERT(stat == 0,stat); - - // set to check for priority inheritance - stat = pthread_mutexattr_setprotocol(&attr,PTHREAD_PRIO_INHERIT); - FW_ASSERT(stat == 0,stat); - - stat = pthread_mutex_init(handle,&attr); - FW_ASSERT(stat == 0,stat); - - this->m_handle = reinterpret_cast(handle); - } + status = pthread_mutex_init(&this->m_handle.m_mutex_descriptor, &attribute); + FW_ASSERT(status == 0, status); +} - Mutex::~Mutex() { - NATIVE_INT_TYPE stat = pthread_mutex_destroy(reinterpret_cast(this->m_handle)); - FW_ASSERT(stat == 0,stat); - delete reinterpret_cast(this->m_handle); - } +PosixMutex::~PosixMutex() { + PlatformIntType status = pthread_mutex_destroy(&this->m_handle.m_mutex_descriptor); + FW_ASSERT(status == 0, status); +} - void Mutex::lock() { - NATIVE_INT_TYPE stat = pthread_mutex_lock(reinterpret_cast(this->m_handle)); - FW_ASSERT(stat == 0,stat); - } +PosixMutex::Status PosixMutex::take() { + PlatformIntType status = pthread_mutex_lock(&this->m_handle.m_mutex_descriptor); + return Os::Posix::posix_status_to_mutex_status(status); +} - void Mutex::unLock() { - NATIVE_INT_TYPE stat = pthread_mutex_unlock(reinterpret_cast(this->m_handle)); - FW_ASSERT(stat == 0,stat); - } +PosixMutex::Status PosixMutex::release() { + PlatformIntType status = pthread_mutex_unlock(&this->m_handle.m_mutex_descriptor); + return Os::Posix::posix_status_to_mutex_status(status); +} +MutexHandle* PosixMutex::getHandle() { + return &this->m_handle; } +} // namespace Mutex +} // namespace Posix +} // namespace Os diff --git a/Os/Posix/Mutex.hpp b/Os/Posix/Mutex.hpp new file mode 100644 index 0000000000..d2f1420360 --- /dev/null +++ b/Os/Posix/Mutex.hpp @@ -0,0 +1,47 @@ +// ====================================================================== +// \title Os/Posix/Mutex.hpp +// \brief Posix definitions for Os::Mutex +// ====================================================================== +#ifndef OS_POSIX_MUTEX_HPP +#define OS_POSIX_MUTEX_HPP +#include +#include + +namespace Os { +namespace Posix { +namespace Mutex { + +struct PosixMutexHandle : public MutexHandle { + pthread_mutex_t m_mutex_descriptor = PTHREAD_MUTEX_INITIALIZER; +}; + +//! \brief Posix implementation of Os::Mutex +//! +//! Posix implementation of `MutexInterface` for use as a delegate class handling error-only file operations. +//! +class PosixMutex : public MutexInterface { + public: + //! \brief constructor + //! + PosixMutex(); + + //! \brief destructor + //! + ~PosixMutex() override; + + //! \brief return the underlying mutex handle (implementation specific) + //! \return internal mutex handle representation + MutexHandle* getHandle() override; + + Status take() override; //!< lock the mutex and get return status + Status release() override; //!< unlock the mutex and get return status + + private: + //! Handle for PosixMutex + PosixMutexHandle m_handle; +}; + +} // namespace Mutex +} // namespace Posix +} // namespace Os +#endif // OS_POSIX_MUTEX_HPP diff --git a/Os/Posix/error.cpp b/Os/Posix/error.cpp index 741916c7b2..0672cc55ed 100644 --- a/Os/Posix/error.cpp +++ b/Os/Posix/error.cpp @@ -70,5 +70,23 @@ Task::Status posix_status_to_task_status(PlatformIntType posix_status) { return status; } +Mutex::Status posix_status_to_mutex_status(PlatformIntType posix_status){ + Mutex::Status status = Mutex::Status::ERROR_OTHER; + switch (posix_status) { + case 0: + status = Mutex::Status::OP_OK; + break; + case EBUSY: + status = Mutex::Status::ERROR_BUSY; + break; + case EDEADLK: + status = Mutex::Status::ERROR_DEADLOCK; + break; + default: + status = Mutex::Status::ERROR_OTHER; + break; + } + return status; +} } } diff --git a/Os/Posix/error.hpp b/Os/Posix/error.hpp index b405d8e603..55e5c44895 100644 --- a/Os/Posix/error.hpp +++ b/Os/Posix/error.hpp @@ -22,6 +22,12 @@ Os::File::Status errno_to_file_status(PlatformIntType errno_input); //! Os::Task::Status posix_status_to_task_status(PlatformIntType posix_status); +//! Convert a Posix return status (int) for mutex operations to the Os::Mutex::Status representation. +//! \param posix_status: return status +//! \return: Os::Mutex::Status representation of the error +//! +Os::Mutex::Status posix_status_to_mutex_status(PlatformIntType posix_status); + } } #endif diff --git a/Os/Posix/test/ut/PosixMutexTests.cpp b/Os/Posix/test/ut/PosixMutexTests.cpp new file mode 100644 index 0000000000..20505f33c1 --- /dev/null +++ b/Os/Posix/test/ut/PosixMutexTests.cpp @@ -0,0 +1,66 @@ +// ====================================================================== +// \title Os/Posix/test/ut/PosixMutexTests.cpp +// \brief tests for posix implementation for Os::Mutex +// ====================================================================== +#include "Os/test/ut/mutex/RulesHeaders.hpp" +#include "Os/test/ut/mutex/CommonTests.hpp" +#include "Os/Posix/Task.hpp" +#include +#include "STest/Scenario/Scenario.hpp" +#include "STest/Pick/Pick.hpp" +#include "Fw/Types/String.hpp" + +// A routine that modifies the internal state of the MutexTester to test that the mutex +// protects the shared variable successfully when ran in parallel with the main task +static void testTaskRoutine(void* pointer) { + Os::Test::Mutex::Tester* tester = reinterpret_cast(pointer); + + for (FwIndexType i = 0; i < 100000; i++) { + tester->m_mutex.lock(); + tester->m_state = Os::Test::Mutex::Tester::MutexState::LOCKED; + + U32 randomValue = STest::Pick::any(); + tester->m_value = randomValue; + ASSERT_EQ(tester->m_value, randomValue); + + tester->m_state = Os::Test::Mutex::Tester::MutexState::UNLOCKED; + tester->m_mutex.unLock(); + } + +} + +// ---------------------------------------------------------------------- +// Posix Test Cases +// ---------------------------------------------------------------------- + +// Attempt to delete a locked mutex - expect an assertion +TEST_F(FunctionalityTester, PosixDeleteLockedMutex) { + Os::Test::Mutex::Tester::LockMutex lock_rule; + lock_rule.apply(*tester); + // tester is a unique_ptr, retrieve the raw pointer and attempt to delete the Mutex + ASSERT_DEATH_IF_SUPPORTED(delete tester.get(), Os::Test::Mutex::Tester::ASSERT_IN_MUTEX_CPP); +} + +// Test behavior of the mutex - two threads (main and test_task) using a mutex to protect a shared variable +TEST_F(FunctionalityTester, PosixMutexDataProtection) { + // start a task that will lock the mutex, change the value and assert, then unlock mutex + Os::Task test_task; + Os::Task::Arguments arguments(Fw::String("MutexTestLockTask"), testTaskRoutine, static_cast(tester.get())); + Os::Task::Status stat = test_task.start(arguments); + FW_ASSERT(Os::Task::OP_OK == stat, static_cast(stat)); + + Os::Test::Mutex::Tester::ProtectDataCheck protect_data_rule; + + for (FwIndexType i = 0; i < 100000; i++) { + protect_data_rule.apply(*tester); + } + + test_task.join(); +} + + +int main(int argc, char** argv) { + STest::Random::seed(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Os/Stub/CMakeLists.txt b/Os/Stub/CMakeLists.txt index 589de397eb..0d793eeb29 100644 --- a/Os/Stub/CMakeLists.txt +++ b/Os/Stub/CMakeLists.txt @@ -6,6 +6,8 @@ # #### add_custom_target("${FPRIME_CURRENT_MODULE}") + +######## File Stub ######## set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/File.cpp" "${CMAKE_CURRENT_LIST_DIR}/DefaultFile.cpp" @@ -13,6 +15,14 @@ set(SOURCE_FILES register_fprime_module(Os_File_Stub) register_fprime_implementation(Os/File Os_File_Stub) +######## Mutex Stub ######## +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Mutex.cpp" +) +register_fprime_module(Os_Mutex_Stub) +register_fprime_implementation(Os/Mutex Os_Mutex_Stub "${CMAKE_CURRENT_LIST_DIR}/DefaultMutex.cpp") + + # Remainder of file is specific to UTs if (NOT BUILD_TESTING) return() @@ -40,7 +50,6 @@ set(UT_MOD_DEPS choose_fprime_implementation(Os/File Os_File_Test_Stub) register_fprime_ut(StubFileTest) - #### Task Stub Testing #### set(SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/test/Task.cpp" @@ -61,4 +70,24 @@ set(UT_MOD_DEPS STest ) choose_fprime_implementation(Os/Task Os_Task_Test_Stub) -register_fprime_ut(StubTaskTest) \ No newline at end of file +register_fprime_ut(StubTaskTest) + +#### Mutex Stub Testing #### +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/test/Mutex.cpp" +) +register_fprime_module(Os_Mutex_Test_Stub) +register_fprime_implementation(Os/Mutex Os_Mutex_Test_Stub "${CMAKE_CURRENT_LIST_DIR}/test/DefaultMutex.cpp") + +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/test/ut/StubMutexTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/../test/ut/mutex/CommonTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/../test/ut/mutex/MutexRules.cpp" +) +set(UT_MOD_DEPS + Os + Os_Models + STest +) +choose_fprime_implementation(Os/Mutex Os_Mutex_Test_Stub) +register_fprime_ut(StubMutexTest) \ No newline at end of file diff --git a/Os/Stub/DefaultMutex.cpp b/Os/Stub/DefaultMutex.cpp new file mode 100644 index 0000000000..56381815f3 --- /dev/null +++ b/Os/Stub/DefaultMutex.cpp @@ -0,0 +1,18 @@ +// ====================================================================== +// \title Os/Stub/DefaultMutex.cpp +// \brief sets default Os::Mutex to no-op stub implementation via linker +// ====================================================================== +#include "Os/Stub/Mutex.hpp" +#include "Os/Delegate.hpp" +namespace Os { + +//! \brief get a delegate for MutexInterface that intercepts calls for stub file usage +//! \param aligned_new_memory: aligned memory to fill +//! \param to_copy: pointer to copy-constructor input +//! \return: pointer to delegate +MutexInterface *MutexInterface::getDelegate(HandleStorage& aligned_new_memory) { + return Os::Delegate::makeDelegate( + aligned_new_memory + ); +} +} diff --git a/Os/Stub/Mutex.cpp b/Os/Stub/Mutex.cpp new file mode 100644 index 0000000000..c430a19b0f --- /dev/null +++ b/Os/Stub/Mutex.cpp @@ -0,0 +1,24 @@ +// ====================================================================== +// \title Os/Stub/Mutex.cpp +// \brief stub implementation for Os::Mutex +// ====================================================================== +#include "Os/Stub/Mutex.hpp" + +namespace Os { +namespace Stub { +namespace Mutex { + + StubMutex::Status StubMutex::take() { + return Status::OP_OK; + } + + StubMutex::Status StubMutex::release() { + return Status::OP_OK; + } + + MutexHandle* StubMutex::getHandle() { + return &this->m_handle; + } +} // namespace Mutex +} // namespace Stub +} // namespace Os diff --git a/Os/Stub/Mutex.hpp b/Os/Stub/Mutex.hpp new file mode 100644 index 0000000000..0b7fa70862 --- /dev/null +++ b/Os/Stub/Mutex.hpp @@ -0,0 +1,44 @@ +// ====================================================================== +// \title Os/Stub/Mutex.hpp +// \brief stub definitions for Os::Mutex +// ====================================================================== +#include "Os/Mutex.hpp" + +#ifndef OS_STUB_MUTEX_HPP +#define OS_STUB_MUTEX_HPP +namespace Os { +namespace Stub { +namespace Mutex { + +struct StubMutexHandle : public MutexHandle {}; + +//! \brief stub implementation of Os::Mutex +//! +//! Stub implementation of `MutexInterface` for use as a delegate class handling error-only file operations. +//! +class StubMutex : public MutexInterface { + public: + //! \brief constructor + //! + StubMutex() = default; + + //! \brief destructor + //! + ~StubMutex() override = default; + + //! \brief return the underlying mutex handle (implementation specific) + //! \return internal mutex handle representation + MutexHandle* getHandle() override; + + Status take() override; //!< lock the mutex and get return status + Status release() override; //!< unlock the mutex and get return status + + private: + //! Handle for StubMutex + StubMutexHandle m_handle; +}; + +} // namespace Mutex +} // namespace Stub +} // namespace Os +#endif // OS_STUB_MUTEX_HPP diff --git a/Os/Stub/test/DefaultMutex.cpp b/Os/Stub/test/DefaultMutex.cpp new file mode 100644 index 0000000000..5201d67af0 --- /dev/null +++ b/Os/Stub/test/DefaultMutex.cpp @@ -0,0 +1,18 @@ +// ====================================================================== +// \title Os/Stub/test/DefaultMutex.cpp +// \brief sets default Os::Mutex to test stub implementation via linker +// ====================================================================== +#include "Os/Stub/test/Mutex.hpp" +#include "Os/Delegate.hpp" + +namespace Os { + +//! \brief get a delegate for Mutex that intercepts calls for for stub test Mutex usage +//! \param aligned_new_memory: aligned memory to fill +//! \return: pointer to delegate +MutexInterface *MutexInterface::getDelegate(HandleStorage& aligned_placement_new_memory) { + return Os::Delegate::makeDelegate( + aligned_placement_new_memory + ); +} +} diff --git a/Os/Stub/test/Mutex.cpp b/Os/Stub/test/Mutex.cpp new file mode 100644 index 0000000000..4c2f2472b6 --- /dev/null +++ b/Os/Stub/test/Mutex.cpp @@ -0,0 +1,41 @@ +// ====================================================================== +// \title Os/Stub/test/Mutex.cpp +// \brief implementation for TestMutex stubs for interface testing +// ====================================================================== + +#include "Os/Stub/test/Mutex.hpp" +namespace Os { +namespace Stub { +namespace Mutex { +namespace Test { + +StaticData StaticData::data; + +TestMutex::TestMutex() { + StaticData::data.lastCalled = StaticData::LastFn::CONSTRUCT_FN; +} + +TestMutex::~TestMutex() { + StaticData::data.lastCalled = StaticData::LastFn::DESTRUCT_FN; +} + +Os::MutexInterface::Status TestMutex::take() { + StaticData::data.lastCalled = StaticData::LastFn::TAKE_FN; + return StaticData::data.takeStatus; +} + +Os::MutexInterface::Status TestMutex::release() { + StaticData::data.lastCalled = StaticData::LastFn::RELEASE_FN; + return StaticData::data.releaseStatus; +} + +Os::MutexHandle *TestMutex::getHandle() { + StaticData::data.lastCalled = StaticData::LastFn::GET_HANDLE_FN; + return nullptr; +} + + +} +} +} +} diff --git a/Os/Stub/test/Mutex.hpp b/Os/Stub/test/Mutex.hpp new file mode 100644 index 0000000000..580579e2b6 --- /dev/null +++ b/Os/Stub/test/Mutex.hpp @@ -0,0 +1,68 @@ +// ====================================================================== +// \title Os/Stub/test/Mutex.cpp +// \brief definitions for TestMutex stubs for interface testing +// ====================================================================== +#include "Os/Mutex.hpp" + +#ifndef OS_STUB_MUTEX_TEST_HPP +#define OS_STUB_MUTEX_TEST_HPP +namespace Os { +namespace Stub { +namespace Mutex { +namespace Test { + +//! Data that supports the stubbed Mutex implementation. +//!/ +struct StaticData { + //! Enumeration of last function called + //! + enum LastFn { + NONE_FN, + CONSTRUCT_FN, + DESTRUCT_FN, + TAKE_FN, + RELEASE_FN, + GET_HANDLE_FN + }; + StaticData() = default; + ~StaticData() = default; + + //! Last function called + LastFn lastCalled = NONE_FN; + + Os::Mutex::Status takeStatus = Os::Mutex::Status::OP_OK; + Os::Mutex::Status releaseStatus = Os::Mutex::Status::OP_OK; + + // Singleton data + static StaticData data; +}; + +//! Test task handle +class TestMutexHandle : public MutexHandle {}; + +//! Implementation of task +class TestMutex : public MutexInterface { + public: + //! Constructor + TestMutex(); + + //! Destructor + ~TestMutex() override; + + //! \brief lock mutex and return status + Status take() override; + + //! \brief unlock mutex and return status + Status release() override; + + //! \brief return the underlying mutex handle (implementation specific) + //! \return internal task handle representation + MutexHandle* getHandle() override; + +}; + +} +} +} +} +#endif // End OS_STUB_MUTEX_TEST_HPP diff --git a/Os/Stub/test/ut/StubMutexTests.cpp b/Os/Stub/test/ut/StubMutexTests.cpp new file mode 100644 index 0000000000..1e2f498bcb --- /dev/null +++ b/Os/Stub/test/ut/StubMutexTests.cpp @@ -0,0 +1,90 @@ +// ====================================================================== +// \title Os/Stub/test/ut/StubMutexTests.cpp +// \brief stub implementation for Os::MutexInterface testing +// This ensures the delegation of function calls happens properly +// ====================================================================== +#include +#include "Os/test/ut/mutex/CommonTests.hpp" +#include "Os/test/ut/mutex/RulesHeaders.hpp" +#include "Os/Stub/test/Mutex.hpp" + +using namespace Os::Stub::Mutex::Test; + + +// Basic file tests +class Interface : public ::testing::Test { +public: + //! Setup function delegating to UT setUp function + void SetUp() override { + StaticData::data = StaticData(); + } + + //! Setup function delegating to UT tearDown function + void TearDown() override { + StaticData::data = StaticData(); + } +}; + + +// Ensure that Os::Mutex properly calls the implementation constructor +TEST_F(Interface, Construction) { + Os::Mutex mutex; + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::CONSTRUCT_FN); +} + +// Ensure that Os::Mutex properly calls the implementation destructor +TEST_F(Interface, Destruction) { + delete (new Os::Mutex); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::DESTRUCT_FN); +} + +// Ensure that Os::Mutex properly calls the implementation take() +TEST_F(Interface, Take) { + Os::Mutex mutex; + StaticData::data.takeStatus = Os::Mutex::Status::ERROR_OTHER; + ASSERT_EQ(mutex.take(), StaticData::data.takeStatus); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::TAKE_FN); +} + +// Ensure that Os::Mutex properly calls the implementation release() +TEST_F(Interface, Release) { + Os::Mutex mutex; + StaticData::data.releaseStatus = Os::Mutex::Status::ERROR_OTHER; + ASSERT_EQ(mutex.release(), StaticData::data.releaseStatus); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::RELEASE_FN); +} + +// Ensure that Os::Mutex properly calls the implementation lock() +TEST_F(Interface, Lock) { + Os::Mutex mutex; + mutex.lock(); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::TAKE_FN); + +} + +// Ensure that Os::Mutex properly calls the implementation unLock() +TEST_F(Interface, UnLock) { + Os::Mutex mutex; + mutex.unLock(); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::RELEASE_FN); +} + +// Ensure that Os::Mutex properly calls the implementation unlock() +TEST_F(Interface, UnlockAlias) { + Os::Mutex mutex; + mutex.unlock(); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::RELEASE_FN); +} + +// Ensure that Os::Mutex properly calls the implementation getHandle() +TEST_F(Interface, GetHandle) { + Os::Mutex mutex; + ASSERT_EQ(mutex.getHandle(), nullptr); + ASSERT_EQ(StaticData::data.lastCalled, StaticData::LastFn::GET_HANDLE_FN); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + STest::Random::seed(); + return RUN_ALL_TESTS(); +} diff --git a/Os/Task.cpp b/Os/Task.cpp index d7f3ae5587..caa36ed8ef 100644 --- a/Os/Task.cpp +++ b/Os/Task.cpp @@ -51,7 +51,7 @@ void Task::TaskRoutineWrapper::invoke() { TaskRegistry* Task::s_taskRegistry = nullptr; FwSizeType Task::s_numTasks = 0; -Mutex Task::s_taskMutex = Mutex(); +Mutex Task::s_taskMutex; bool TaskInterface::isCooperative() { return false; diff --git a/Os/test/ut/mutex/CommonTests.cpp b/Os/test/ut/mutex/CommonTests.cpp new file mode 100644 index 0000000000..a499d301cb --- /dev/null +++ b/Os/test/ut/mutex/CommonTests.cpp @@ -0,0 +1,83 @@ +// ====================================================================== +// \title Os/test/ut/mutex/CommonTests.cpp +// \brief common test implementations +// ====================================================================== +#include "Os/test/ut/mutex/CommonTests.hpp" + +// ---------------------------------------------------------------------- +// Test Fixture +// ---------------------------------------------------------------------- + +std::unique_ptr get_tester_implementation() { + return std::unique_ptr(new Os::Test::Mutex::Tester()); +} + +FunctionalityTester::FunctionalityTester() : tester(get_tester_implementation()) {} + +void FunctionalityTester::SetUp() { + // No setup required +} + +void FunctionalityTester::TearDown() { + // Ensure the mutex is unlocked for safe destruction + if (this->tester->m_state == Os::Test::Mutex::Tester::MutexState::LOCKED) { + this->tester->m_state = Os::Test::Mutex::Tester::MutexState::UNLOCKED; + this->tester->m_mutex.unLock(); + } +} + +// ---------------------------------------------------------------------- +// Test Cases +// ---------------------------------------------------------------------- + +// Lock then unlock mutex +TEST_F(FunctionalityTester, LockAndUnlockMutex) { + Os::Test::Mutex::Tester::LockMutex lock_rule; + Os::Test::Mutex::Tester::UnlockMutex unlock_rule; + lock_rule.apply(*tester); + unlock_rule.apply(*tester); +} + +// Take then release mutex +TEST_F(FunctionalityTester, TakeAndReleaseMutex) { + Os::Test::Mutex::Tester::TakeMutex take_rule; + Os::Test::Mutex::Tester::ReleaseMutex release_rule; + take_rule.apply(*tester); + release_rule.apply(*tester); +} + +// Randomized sequence of conditioned take/release/lock/unlock +TEST_F(FunctionalityTester, RandomizedInterfaceTesting) { + // Enumerate all rules and construct an instance of each + Os::Test::Mutex::Tester::TakeMutex take_rule; + Os::Test::Mutex::Tester::ReleaseMutex release_rule; + Os::Test::Mutex::Tester::LockMutex lock_rule; + Os::Test::Mutex::Tester::UnlockMutex unlock_rule; + + // Place these rules into a list of rules + STest::Rule* rules[] = { + &take_rule, + &release_rule, + &lock_rule, + &unlock_rule, + }; + + // Take the rules and place them into a random scenario + STest::RandomScenario random( + "Random Rules", + rules, + FW_NUM_ARRAY_ELEMENTS(rules) + ); + + // Create a bounded scenario wrapping the random scenario + STest::BoundedScenario bounded( + "Bounded Random Rules Scenario", + random, + 100 + ); + // Run! + const U32 numSteps = bounded.run(*tester); + printf("Ran %u steps.\n", numSteps); + // add one run of unlock for safe destruction +} + diff --git a/Os/test/ut/mutex/CommonTests.hpp b/Os/test/ut/mutex/CommonTests.hpp new file mode 100644 index 0000000000..4831a2efba --- /dev/null +++ b/Os/test/ut/mutex/CommonTests.hpp @@ -0,0 +1,35 @@ +// ====================================================================== +// \title Os/test/ut/mutex/CommonTests.hpp +// \brief GoogleTest fixture definitions used in common Mutex testing +// ====================================================================== +#include +#include +#include + +#ifndef OS_TEST_UT_COMMON_MUTEX_TESTS_HPP +#define OS_TEST_UT_COMMON_MUTEX_TESTS_HPP +namespace Os { +namespace Test { +namespace Mutex { + + +} // namespace Mutex +} // namespace Test +} // namespace Os + +class FunctionalityTester : public ::testing::Test { + public: + //! Constructor + FunctionalityTester(); + + //! SetUp test fixture + void SetUp() override; + + //! TearDown test fixture for safe destruction + void TearDown() override; + + //! Tester/state implementation + std::unique_ptr tester; +}; + +#endif // OS_TEST_UT_COMMON_MUTEX_TESTS_HPP diff --git a/Os/test/ut/mutex/MutexRules.cpp b/Os/test/ut/mutex/MutexRules.cpp new file mode 100644 index 0000000000..230b8ca8bf --- /dev/null +++ b/Os/test/ut/mutex/MutexRules.cpp @@ -0,0 +1,99 @@ +// ====================================================================== +// \title Os/test/ut/mutex/MutexRules.cpp +// \brief rule implementations for common testing of mutex +// ====================================================================== + +#include "RulesHeaders.hpp" +#include "MutexRules.hpp" +#include "STest/Pick/Pick.hpp" + +// ------------------------------------------------------------------------------------------------------ +// Rule: LockMutex -> Lock a mutex successfully +// ------------------------------------------------------------------------------------------------------ + +Os::Test::Mutex::Tester::LockMutex::LockMutex() : + STest::Rule("LockMutex") {} + +bool Os::Test::Mutex::Tester::LockMutex::precondition(const Os::Test::Mutex::Tester &state) { + return state.m_state == Os::Test::Mutex::Tester::MutexState::UNLOCKED; +} + +void Os::Test::Mutex::Tester::LockMutex::action(Os::Test::Mutex::Tester &state) { + state.m_state = Os::Test::Mutex::Tester::MutexState::LOCKED; + state.m_mutex.lock(); +} + +// ------------------------------------------------------------------------------------------------------ +// Rule: UnlockMutex -> Unlock a locked mutex successfully +// ------------------------------------------------------------------------------------------------------ + +Os::Test::Mutex::Tester::UnlockMutex::UnlockMutex() : + STest::Rule("UnlockMutex") {} + +bool Os::Test::Mutex::Tester::UnlockMutex::precondition(const Os::Test::Mutex::Tester &state) { + return state.m_state == Os::Test::Mutex::Tester::MutexState::LOCKED; +} + +void Os::Test::Mutex::Tester::UnlockMutex::action(Os::Test::Mutex::Tester &state) { + state.m_state = Os::Test::Mutex::Tester::MutexState::UNLOCKED; + state.m_mutex.unLock(); +} + +// ------------------------------------------------------------------------------------------------------ +// Rule: TakeMutex -> Lock a mutex successfully +// ------------------------------------------------------------------------------------------------------ + +Os::Test::Mutex::Tester::TakeMutex::TakeMutex() : + STest::Rule("TakeMutex") {} + +bool Os::Test::Mutex::Tester::TakeMutex::precondition(const Os::Test::Mutex::Tester &state) { + return state.m_state == Os::Test::Mutex::Tester::MutexState::UNLOCKED; +} + +void Os::Test::Mutex::Tester::TakeMutex::action(Os::Test::Mutex::Tester &state) { + state.m_state = Os::Test::Mutex::Tester::MutexState::LOCKED; + Os::Mutex::Status status = state.m_mutex.take(); + ASSERT_EQ(status, Os::Mutex::Status::OP_OK); +} + +// ------------------------------------------------------------------------------------------------------ +// Rule: ReleaseMutex -> Lock a mutex successfully +// ------------------------------------------------------------------------------------------------------ + +Os::Test::Mutex::Tester::ReleaseMutex::ReleaseMutex() : + STest::Rule("ReleaseMutex") {} + +bool Os::Test::Mutex::Tester::ReleaseMutex::precondition(const Os::Test::Mutex::Tester &state) { + return state.m_state == Os::Test::Mutex::Tester::MutexState::LOCKED; +} + +void Os::Test::Mutex::Tester::ReleaseMutex::action(Os::Test::Mutex::Tester &state) { + state.m_state = Os::Test::Mutex::Tester::MutexState::UNLOCKED; + Os::Mutex::Status status = state.m_mutex.release(); + ASSERT_EQ(status, Os::Mutex::Status::OP_OK); +} + +// ------------------------------------------------------------------------------------------------------ +// Rule: ProtectDataCheck: Lock a mutex, set data, assert data and unlock mutex +// By running this concurrently with another thread, we can test the mutex does protect data +// ------------------------------------------------------------------------------------------------------ +Os::Test::Mutex::Tester::ProtectDataCheck::ProtectDataCheck() : + STest::Rule("ProtectDataCheck") {} + +bool Os::Test::Mutex::Tester::ProtectDataCheck::precondition(const Os::Test::Mutex::Tester &state) { + return true; +} + +void Os::Test::Mutex::Tester::ProtectDataCheck::action(Os::Test::Mutex::Tester &state) { + state.m_mutex.lock(); + state.m_state = Os::Test::Mutex::Tester::MutexState::LOCKED; + + U32 randomValue = STest::Pick::any(); + state.m_value = randomValue; + ASSERT_EQ(state.m_value, randomValue); + + state.m_state = Os::Test::Mutex::Tester::MutexState::UNLOCKED; + state.m_mutex.unLock(); +} + + diff --git a/Os/test/ut/mutex/MutexRules.hpp b/Os/test/ut/mutex/MutexRules.hpp new file mode 100644 index 0000000000..88504300f7 --- /dev/null +++ b/Os/test/ut/mutex/MutexRules.hpp @@ -0,0 +1,136 @@ +// ====================================================================== +// \title Os/test/ut/mutex/MutexRules.hpp +// \brief rule definitions for common testing of mutex +// ====================================================================== +// Stripped when compiled, here for IDEs +#include "RulesHeaders.hpp" + +// ------------------------------------------------------------------------------------------------------ +// Rule: LockMutex: Lock a mutex successfully +// ------------------------------------------------------------------------------------------------------ +struct LockMutex : public STest::Rule { + // ---------------------------------------------------------------------- + // Construction + // ---------------------------------------------------------------------- + + //! Constructor + LockMutex(); + + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Precondition + bool precondition(const Os::Test::Mutex::Tester &state //!< The test state + ); + + //! Action + void action(Os::Test::Mutex::Tester &state //!< The test state + ); +}; + +// ------------------------------------------------------------------------------------------------------ +// Rule: UnlockMutex: Unlock a mutex that is locked successfully +// ------------------------------------------------------------------------------------------------------ +struct UnlockMutex : public STest::Rule { + + // ---------------------------------------------------------------------- + // Construction + // ---------------------------------------------------------------------- + + //! Constructor + UnlockMutex(); + + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Precondition + bool precondition( + const Os::Test::Mutex::Tester &state //!< The test state + ); + + //! Action + void action( + Os::Test::Mutex::Tester &state //!< The test state + ); + +}; + + +// ------------------------------------------------------------------------------------------------------ +// Rule: TakeMutex: Take a mutex successfully +// ------------------------------------------------------------------------------------------------------ +struct TakeMutex : public STest::Rule { + // ---------------------------------------------------------------------- + // Construction + // ---------------------------------------------------------------------- + + //! Constructor + TakeMutex(); + + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Precondition + bool precondition(const Os::Test::Mutex::Tester &state //!< The test state + ); + + //! Action + void action(Os::Test::Mutex::Tester &state //!< The test state + ); +}; + + +// ------------------------------------------------------------------------------------------------------ +// Rule: ReleaseMutex: Take a mutex successfully +// ------------------------------------------------------------------------------------------------------ +struct ReleaseMutex : public STest::Rule { + // ---------------------------------------------------------------------- + // Construction + // ---------------------------------------------------------------------- + + //! Constructor + ReleaseMutex(); + + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Precondition + bool precondition(const Os::Test::Mutex::Tester &state //!< The test state + ); + + //! Action + void action(Os::Test::Mutex::Tester &state //!< The test state + ); +}; + +// ------------------------------------------------------------------------------------------------------ +// Rule: ProtectDataCheck: Check that data is protected by a mutex (running another task in parallel) +// ------------------------------------------------------------------------------------------------------ +struct ProtectDataCheck : public STest::Rule { + + // ---------------------------------------------------------------------- + // Construction + // ---------------------------------------------------------------------- + + //! Constructor + ProtectDataCheck(); + + // ---------------------------------------------------------------------- + // Public member functions + // ---------------------------------------------------------------------- + + //! Precondition + bool precondition( + const Os::Test::Mutex::Tester &state //!< The test state + ); + + //! Action + void action( + Os::Test::Mutex::Tester &state //!< The test state + ); + +}; diff --git a/Os/test/ut/mutex/RulesHeaders.hpp b/Os/test/ut/mutex/RulesHeaders.hpp new file mode 100644 index 0000000000..1add908b06 --- /dev/null +++ b/Os/test/ut/mutex/RulesHeaders.hpp @@ -0,0 +1,53 @@ +// ====================================================================== +// \title Os/test/ut/mutex/RulesHeaders.hpp +// \brief rule definitions for common testing +// ====================================================================== + +#ifndef __RULES_HEADERS__ +#define __RULES_HEADERS__ +#include +#include "Os/Mutex.hpp" +#include "STest/Rule/Rule.hpp" +#include "STest/Scenario/BoundedScenario.hpp" +#include "STest/Scenario/RandomScenario.hpp" +#include "STest/Scenario/Scenario.hpp" + +namespace Os { +namespace Test { +namespace Mutex { + +struct Tester { + //! State representation of a Mutex. + //! + enum MutexState { + UNINITIALIZED, //!< Mutex is uninitialized + LOCKED, //!< Mutex is locked + UNLOCKED //!< Mutex is unlocked + }; + + //! Assert in Mutex.cpp for searching death text + static constexpr const char* ASSERT_IN_MUTEX_CPP = "Assert: \".*/Os/.*/Mutex\\.cpp:[0-9]+\""; + + // Constructors that ensures the mutex is always valid + Tester(): m_mutex(), m_state(UNLOCKED) {} + + // Destructor must be virtual + virtual ~Tester() = default; + + //! Mutex under test + Os::Mutex m_mutex; + + //! Shared value protected by the mutex for testing purposes + int m_value = 0; + + //! Mutex state, for testing purposes + MutexState m_state = UNINITIALIZED; + +// Do NOT alter, adds rules to Tester as inner classes +#include "MutexRules.hpp" +}; + +} // namespace Mutex +} // namespace Test +} // namespace Os +#endif // __RULES_HEADERS__ diff --git a/cmake/API.cmake b/cmake/API.cmake index d711cb4d05..72e0ec677d 100644 --- a/cmake/API.cmake +++ b/cmake/API.cmake @@ -627,6 +627,7 @@ endfunction() # # **IMPLEMENTATION:** implementation module name that is implemented by IMPLEMENTOR # **IMPLEMENTOR:** implementor of IMPLEMENTATION +# **ARGN:** (optional) list of source files required to build the implementor #### function(register_fprime_implementation IMPLEMENTATION IMPLEMENTOR) resolve_dependencies(IMPLEMENTATION "${IMPLEMENTATION}") diff --git a/cmake/platform/Darwin.cmake b/cmake/platform/Darwin.cmake index 2958c12f72..a51674248b 100644 --- a/cmake/platform/Darwin.cmake +++ b/cmake/platform/Darwin.cmake @@ -20,6 +20,7 @@ if (NOT DEFINED FPRIME_USE_BAREMETAL_SCHEDULER) endif() choose_fprime_implementation(Os/File Os/File/Posix) choose_fprime_implementation(Os/Task Os/Task/Posix) +choose_fprime_implementation(Os/Mutex Os/Mutex/Posix) # Add linux include path which is compatible with Darwin for PlatformTypes.hpp include_directories(SYSTEM "${CMAKE_CURRENT_LIST_DIR}/types") diff --git a/cmake/platform/Linux.cmake b/cmake/platform/Linux.cmake index 00c09facfe..3b19460ee4 100644 --- a/cmake/platform/Linux.cmake +++ b/cmake/platform/Linux.cmake @@ -11,6 +11,7 @@ if (NOT DEFINED FPRIME_USE_BAREMETAL_SCHEDULER) endif() choose_fprime_implementation(Os/File Os/File/Posix) choose_fprime_implementation(Os/Task Os/Task/Posix) +choose_fprime_implementation(Os/Mutex Os/Mutex/Posix) # Use common linux setup add_definitions(-DTGT_OS_TYPE_LINUX) diff --git a/config/FpConfig.h b/config/FpConfig.h index 137cc4ec97..3d59e7ad4c 100644 --- a/config/FpConfig.h +++ b/config/FpConfig.h @@ -371,11 +371,11 @@ typedef FwIndexType FwQueueSizeType; // OS configuration #ifndef FW_HANDLE_MAX_SIZE -#define FW_HANDLE_MAX_SIZE 24 //!< Maximum size of a handle for OS resources (files, queues, locks, etc.) +#define FW_HANDLE_MAX_SIZE 72 //!< Maximum size of a handle for OS resources (files, queues, locks, etc.) #endif #ifndef FW_HANDLE_ALIGNMENT -#define FW_HANDLE_ALIGNMENT 16 //!< Alignment of handle storage +#define FW_HANDLE_ALIGNMENT 8 //!< Alignment of handle storage #endif #ifndef FW_FILE_CHUNK_SIZE