Skip to content

Commit

Permalink
Testing for task thread
Browse files Browse the repository at this point in the history
  • Loading branch information
MeijisIrlnd committed Aug 12, 2024
1 parent fbe886a commit e47ef37
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 0 deletions.
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ install(
# DESTINATION "${MOSTLYHARMLESS_INSTALL_DEST}"
# COMPONENT MostlyHarmless
# )
if(TESTS)
set(CMAKE_DEBUG_POSTFIX "")
FetchContent_Declare(Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.0.1
GIT_SHALLOW ON)
FetchContent_MakeAvailable(Catch2)
add_subdirectory(tests)
add_executable(mostly-harmless-tests)
target_sources(mostly-harmless-tests PRIVATE ${MOSTLYHARMLESS_TEST_SOURCE})
target_link_libraries(mostly-harmless-tests PUBLIC MostlyHarmless Catch2::Catch2WithMain)

endif()

if(${PROJECT_IS_TOP_LEVEL})
add_subdirectory(docs)
Expand Down
15 changes: 15 additions & 0 deletions include/mostly_harmless/utils/mostlyharmless_TaskThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define MOSTLYHARMLESS_MOSTLYHARMLESS_TASKTHREAD_H
#include <functional>
#include <atomic>
#include <mutex>
namespace mostly_harmless::utils {
/**
* \brief Wrapper around a std::thread to perform a single action, then exit.
Expand All @@ -20,6 +21,16 @@ namespace mostly_harmless::utils {
*/
void perform();

/**
* Sleeps the thread until `wake` is called. <b>Only call this from the thread!</b>
*/
void sleep();

/**
* Wakes a thread previously suspended with `sleep()`.
*/
void wake();

/**
* Sets an internal atomic bool to specify that the thread should exit, accessible through `threadShouldExit`. Note that this doesn't actually kill the thread,
* it's up to you to do something with the bool.
Expand All @@ -41,8 +52,12 @@ namespace mostly_harmless::utils {
*/
std::function<void(void)> action{ nullptr };
private:
std::mutex m_mutex;
std::condition_variable m_conditionVariable;
std::atomic<bool> m_canWakeUp{ false };
std::atomic<bool> m_isThreadRunning{ false };
std::atomic<bool> m_threadShouldExit{ false };

};
}
#endif // MOSTLYHARMLESS_MOSTLYHARMLESS_TASKTHREAD_H
16 changes: 16 additions & 0 deletions source/utils/mostlyharmless_TaskThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,27 @@ namespace mostly_harmless::utils {
worker.detach();
}

void TaskThread::sleep() {
m_canWakeUp = false;
std::unique_lock<std::mutex> lock(m_mutex);
m_conditionVariable.wait(lock, [this]() -> bool{ return m_canWakeUp; });
}

void TaskThread::wake() {
std::lock_guard<std::mutex> guard{ m_mutex };
m_canWakeUp = true;
m_conditionVariable.notify_one();
}

void TaskThread::signalThreadShouldExit() {
m_threadShouldExit.store(true);
}

bool TaskThread::threadShouldExit() const noexcept {
return m_threadShouldExit;
}

bool TaskThread::isThreadRunning() const noexcept {
return m_isThreadRunning;
}
}
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
set(MOSTLYHARMLESS_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/mostlyharmless_TestCreatePluginImpl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mostlyharmless_TestDescriptor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/mostlyharmless_TaskThreadTests.cpp
PARENT_SCOPE)
9 changes: 9 additions & 0 deletions tests/mostlyharmless_TestCreatePluginImpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// Created by Syl on 12/08/2024.
//
#include <mostly_harmless/mostlyharmless_Plugin.h>
namespace mostly_harmless::entry {
const clap_plugin* clap_create_plugin(const clap_plugin_factory* /*f*/, const clap_host* /*h*/, const char* /*id*/) {
return nullptr;
}
}
34 changes: 34 additions & 0 deletions tests/mostlyharmless_TestDescriptor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

#include <mostly_harmless/mostlyharmless_Descriptor.h>
namespace mostly_harmless {
// clang-format off
static BusConfig s_audioBusConfig { BusConfig::InputOutput };
static BusConfig s_noteBusConfig { BusConfig::None };

static std::vector<const char*> s_features { "audio-effect", nullptr };
static clap_plugin_descriptor s_descriptor{
.clap_version = CLAP_VERSION,
.id = "slma.gain",
.name = "Gain",
.vendor = "",
.url = "",
.manual_url = "",
.support_url = "",
.version = "",
.description = "",
.features = s_features.data()
};

clap_plugin_descriptor& getDescriptor() {
return s_descriptor;
}

BusConfig getAudioBusConfig() noexcept {
return s_audioBusConfig;
}

BusConfig getNoteBusConfig() noexcept {
return s_noteBusConfig;
}
// clang-format on
} // namespace mostly_harmless
55 changes: 55 additions & 0 deletions tests/utils/mostlyharmless_TaskThreadTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Created by Syl on 12/08/2024.
//
#include <catch2/catch_test_macros.hpp>
#include <mostly_harmless/utils/mostlyharmless_TaskThread.h>
#include <mutex>
namespace mostly_harmless::testing {
TEST_CASE("Test TaskThread") {
std::mutex mutex;
mostly_harmless::utils::TaskThread taskThread;
SECTION("Wait for lock") {
auto x{ false };
auto task = [&mutex, &x]() -> void {
std::scoped_lock<std::mutex> sl{ mutex };
std::this_thread::sleep_for(std::chrono::milliseconds(10));
x = true;
};
taskThread.action = std::move(task);
taskThread.perform();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
REQUIRE(taskThread.isThreadRunning());
// Sleep for a ms so the task has a chance to acquire the mutex..
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::scoped_lock<std::mutex> sl{ mutex };
REQUIRE(x);
REQUIRE(!taskThread.isThreadRunning());
}

SECTION("Kill") {
auto task = [&taskThread]() -> void {
while(!taskThread.threadShouldExit());
};
taskThread.action = std::move(task);
taskThread.perform();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
REQUIRE(taskThread.isThreadRunning());
taskThread.signalThreadShouldExit();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
REQUIRE(!taskThread.isThreadRunning());
}

SECTION("Sleep/Wake") {
auto task = [&taskThread]() -> void {
taskThread.sleep();
};
taskThread.action = std::move(task);
taskThread.perform();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
REQUIRE(taskThread.isThreadRunning());
taskThread.wake();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
REQUIRE(!taskThread.isThreadRunning());
}
}
}

0 comments on commit e47ef37

Please sign in to comment.