Kernel improvements (#485)
* **New Features** * Added public accessors for querying module/device start and ready state. * **Refactor** * Internal state moved to opaque internal objects; module/device/driver initializers now explicitly initialize internal pointers. * Lifecycle handling updated to construct/destruct internal state and use accessors. * **Tests** * Tests updated to use public accessors and explicit construct/destruct lifecycle calls. * **Chores** * Test build/include paths and small metadata updated.
This commit is contained in:
committed by
GitHub
parent
1757af859c
commit
79e43b093a
@@ -0,0 +1,37 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/Dispatcher.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("dispatcher should not call callback if consume isn't called") {
|
||||
int counter = 0;
|
||||
Dispatcher dispatcher;
|
||||
dispatcher.dispatch([&counter]() { counter++; });
|
||||
kernel::delayTicks(10);
|
||||
|
||||
CHECK_EQ(counter, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("dispatcher should be able to dealloc when message is not consumed") {
|
||||
auto* dispatcher = new Dispatcher();
|
||||
auto context = std::make_shared<uint32_t>();
|
||||
dispatcher->dispatch([]() { /* NO-OP */ });
|
||||
delete dispatcher;
|
||||
}
|
||||
|
||||
TEST_CASE("dispatcher should call callback when consume is called") {
|
||||
int counter = 0;
|
||||
Dispatcher dispatcher;
|
||||
|
||||
CHECK_EQ(dispatcher.dispatch([&counter] { counter++; }), true);
|
||||
CHECK_EQ(dispatcher.consume(100), 1);
|
||||
|
||||
CHECK_EQ(counter, 1);
|
||||
}
|
||||
|
||||
TEST_CASE("message should be passed on correctly") {
|
||||
Dispatcher dispatcher;
|
||||
|
||||
dispatcher.dispatch([]() { /* NO-OP */ });
|
||||
dispatcher.consume(100);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/DispatcherThread.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("DispatcherThread state test") {
|
||||
DispatcherThread thread("test");
|
||||
CHECK_EQ(thread.isStarted(), false);
|
||||
|
||||
thread.start();
|
||||
CHECK_EQ(thread.isStarted(), true);
|
||||
|
||||
thread.stop();
|
||||
CHECK_EQ(thread.isStarted(), false);
|
||||
}
|
||||
|
||||
TEST_CASE("DispatcherThread should consume jobs") {
|
||||
DispatcherThread thread("test");
|
||||
thread.start();
|
||||
int counter = 0;
|
||||
|
||||
thread.dispatch([&counter]() { counter++; });
|
||||
|
||||
tt::kernel::delayTicks(10);
|
||||
|
||||
CHECK_EQ(counter, 1);
|
||||
thread.stop();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/Semaphore.h>
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("withLock() locks correctly on Semaphore") {
|
||||
auto semaphore = std::make_shared<Semaphore>(2U);
|
||||
semaphore->withLock([semaphore](){
|
||||
CHECK_EQ(semaphore->getAvailable(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("withLock() unlocks correctly on Semaphore") {
|
||||
auto semaphore = std::make_shared<Semaphore>(2U);
|
||||
semaphore->withLock([=](){
|
||||
// NO-OP
|
||||
});
|
||||
|
||||
CHECK_EQ(semaphore->getAvailable(), 2);
|
||||
}
|
||||
|
||||
TEST_CASE("withLock() locks correctly on Mutex") {
|
||||
auto mutex = std::make_shared<Mutex>();
|
||||
mutex->withLock([mutex](){
|
||||
CHECK_EQ(mutex->lock(1), false);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("withLock() unlocks correctly on Mutex") {
|
||||
auto mutex = std::make_shared<Mutex>();
|
||||
mutex->withLock([=](){
|
||||
// NO-OP
|
||||
});
|
||||
|
||||
CHECK_EQ(mutex->lock(1), true);
|
||||
mutex->unlock();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT
|
||||
#include "doctest.h"
|
||||
#include <cassert>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
typedef struct {
|
||||
int argc;
|
||||
char** argv;
|
||||
int result;
|
||||
} TestTaskData;
|
||||
|
||||
void test_task(void* parameter) {
|
||||
auto* data = (TestTaskData*)parameter;
|
||||
|
||||
doctest::Context context;
|
||||
|
||||
context.applyCommandLine(data->argc, data->argv);
|
||||
|
||||
// overrides
|
||||
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
|
||||
|
||||
data->result = context.run();
|
||||
|
||||
vTaskEndScheduler();
|
||||
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
TestTaskData data = {
|
||||
.argc = argc,
|
||||
.argv = argv,
|
||||
.result = 0
|
||||
};
|
||||
|
||||
BaseType_t task_result = xTaskCreate(
|
||||
test_task,
|
||||
"test_task",
|
||||
8192,
|
||||
&data,
|
||||
1,
|
||||
nullptr
|
||||
);
|
||||
assert(task_result == pdPASS);
|
||||
|
||||
vTaskStartScheduler();
|
||||
|
||||
return data.result;
|
||||
}
|
||||
|
||||
// NOTE: This is normally provided by the platform kernel module, but that's not loaded for TactilityCore
|
||||
extern "C" {
|
||||
// Required for FreeRTOS
|
||||
void vAssertCalled(unsigned long line, const char* const file) {
|
||||
__assert_fail("assert failed", file, line, "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/MessageQueue.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("message queue initial count should be 0") {
|
||||
MessageQueue queue(10, 1);
|
||||
|
||||
uint32_t count = queue.getCount();
|
||||
CHECK_EQ(count, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("message queue count should increase when message is added") {
|
||||
MessageQueue queue(10, sizeof(uint32_t));
|
||||
|
||||
uint32_t message = 123;
|
||||
queue.put(&message, 100);
|
||||
uint32_t count = queue.getCount();
|
||||
CHECK_EQ(count, 1);
|
||||
}
|
||||
|
||||
TEST_CASE("message queue count should be 0 when message is added and queue is reset") {
|
||||
MessageQueue queue(10, sizeof(uint32_t));
|
||||
|
||||
uint32_t message = 123;
|
||||
|
||||
queue.put(&message, 100);
|
||||
queue.reset();
|
||||
uint32_t count = queue.getCount();
|
||||
CHECK_EQ(count, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("message queue consumption should work") {
|
||||
MessageQueue queue(10, sizeof(uint32_t));
|
||||
|
||||
uint32_t out_message = 123;
|
||||
queue.put(&out_message, 100);
|
||||
|
||||
uint32_t in_message = 0;
|
||||
queue.get(&in_message, 100);
|
||||
CHECK_EQ(in_message, 123);
|
||||
}
|
||||
|
||||
TEST_CASE("message queue count should decrease when message is consumed") {
|
||||
MessageQueue queue(10, sizeof(uint32_t));
|
||||
|
||||
uint32_t out_message = 123;
|
||||
queue.put(&out_message, 100);
|
||||
|
||||
uint32_t in_message = 0;
|
||||
queue.get(&in_message, 100);
|
||||
uint32_t count = queue.getCount();
|
||||
CHECK_EQ(count, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("message queue should make copy of data") {
|
||||
// Given a number that we can later delete
|
||||
MessageQueue queue(1, sizeof(int32_t));
|
||||
const int32_t test_value = 123;
|
||||
auto* number = new int32_t();
|
||||
*number = test_value;
|
||||
|
||||
// When we put the number in the queue and then delete it
|
||||
queue.put(number, 100);
|
||||
delete number;
|
||||
|
||||
// We want to verify that the value was copied into the queue and retrieved properly
|
||||
int32_t queue_number = 0;
|
||||
CHECK_EQ(queue.get(&queue_number, 100), true);
|
||||
CHECK_EQ(queue_number, test_value);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Thread.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("a Mutex can block a thread") {
|
||||
auto mutex = Mutex();
|
||||
CHECK_EQ(mutex.lock(kernel::MAX_TICKS), true);
|
||||
|
||||
Thread thread = Thread(
|
||||
"thread",
|
||||
1024,
|
||||
[&mutex] {
|
||||
mutex.lock(kernel::MAX_TICKS);
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
thread.start();
|
||||
|
||||
kernel::delayMillis(5);
|
||||
CHECK_EQ(thread.getState(), Thread::State::Running);
|
||||
|
||||
mutex.unlock();
|
||||
|
||||
kernel::delayMillis(5);
|
||||
CHECK_EQ(thread.getState(), Thread::State::Stopped);
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST_CASE("a Mutex can be locked exactly once") {
|
||||
Mutex mutex;
|
||||
CHECK_EQ(mutex.lock(0), true);
|
||||
CHECK_EQ(mutex.lock(0), false);
|
||||
mutex.unlock();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/PubSub.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("PubSub publishing with no subscriptions should not crash") {
|
||||
PubSub<int> pubsub;
|
||||
pubsub.publish(1);
|
||||
}
|
||||
|
||||
TEST_CASE("PubSub subscription receives published data") {
|
||||
PubSub<int> pubsub;
|
||||
int value = 0;
|
||||
|
||||
auto subscription = pubsub.subscribe([&value](auto newValue) {
|
||||
value = newValue;
|
||||
});
|
||||
pubsub.publish(1);
|
||||
|
||||
pubsub.unsubscribe(subscription);
|
||||
|
||||
CHECK_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_CASE("PubSub unsubscribed subscription does not receive published data") {
|
||||
PubSub<int> pubsub;
|
||||
int value = 0;
|
||||
|
||||
auto subscription = pubsub.subscribe([&value](auto newValue) {
|
||||
value = newValue;
|
||||
});
|
||||
pubsub.unsubscribe(subscription);
|
||||
pubsub.publish(1);
|
||||
|
||||
CHECK_EQ(value, 0);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
#include <Tactility/RecursiveMutex.h>
|
||||
#include <Tactility/Thread.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("a RecursiveMutex can block a thread") {
|
||||
auto mutex = RecursiveMutex();
|
||||
CHECK_EQ(mutex.lock(kernel::MAX_TICKS), true);
|
||||
|
||||
Thread thread = Thread(
|
||||
"thread",
|
||||
1024,
|
||||
[&mutex] {
|
||||
mutex.lock(kernel::MAX_TICKS);
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
thread.start();
|
||||
|
||||
kernel::delayMillis(5);
|
||||
CHECK_EQ(thread.getState(), Thread::State::Running);
|
||||
|
||||
mutex.unlock();
|
||||
|
||||
kernel::delayMillis(5);
|
||||
CHECK_EQ(thread.getState(), Thread::State::Stopped);
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST_CASE("a RecursiveMutex can be locked more than once from the same context") {
|
||||
RecursiveMutex mutex;
|
||||
CHECK_EQ(mutex.lock(0), true);
|
||||
CHECK_EQ(mutex.lock(0), true);
|
||||
mutex.unlock();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/Semaphore.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
// We want a distinct test for 1 item, because it creates the Semaphore differently
|
||||
TEST_CASE("a Semaphore with max count of 1 can be acquired exactly once") {
|
||||
auto semaphore = Semaphore(1);
|
||||
CHECK_EQ(semaphore.acquire(0), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 0);
|
||||
CHECK_EQ(semaphore.acquire(0), false);
|
||||
CHECK_EQ(semaphore.release(), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE("a Semaphore with max count of 2 can be acquired exactly twice") {
|
||||
auto semaphore = Semaphore(2);
|
||||
CHECK_EQ(semaphore.acquire(0), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 1);
|
||||
CHECK_EQ(semaphore.acquire(0), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 0);
|
||||
CHECK_EQ(semaphore.acquire(0), false);
|
||||
CHECK_EQ(semaphore.release(), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 1);
|
||||
CHECK_EQ(semaphore.release(), true);
|
||||
CHECK_EQ(semaphore.getAvailable(), 2);
|
||||
}
|
||||
|
||||
TEST_CASE("the semaphore count should be correct initially") {
|
||||
auto semaphore_a = Semaphore(2);
|
||||
CHECK_EQ(semaphore_a.getAvailable(), 2);
|
||||
|
||||
auto semaphore_b = Semaphore(2, 0);
|
||||
CHECK_EQ(semaphore_b.getAvailable(), 0);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/Thread.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("when a thread is started then its callback should be called") {
|
||||
bool has_called = false;
|
||||
auto* thread = new Thread(
|
||||
"immediate return task",
|
||||
4096,
|
||||
[&has_called]() {
|
||||
has_called = true;
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
CHECK(!has_called);
|
||||
thread->start();
|
||||
thread->join();
|
||||
delete thread;
|
||||
CHECK(has_called);
|
||||
}
|
||||
|
||||
TEST_CASE("a thread can be started and stopped") {
|
||||
bool interrupted = false;
|
||||
auto* thread = new Thread(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
[&interrupted]() {
|
||||
while (!interrupted) {
|
||||
kernel::delayMillis(5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
CHECK(thread);
|
||||
thread->start();
|
||||
interrupted = true;
|
||||
thread->join();
|
||||
delete thread;
|
||||
}
|
||||
|
||||
TEST_CASE("thread id should only be set at when thread is started") {
|
||||
bool interrupted = false;
|
||||
auto* thread = new Thread(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
[&interrupted]() {
|
||||
while (!interrupted) {
|
||||
kernel::delayMillis(5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
CHECK_EQ(thread->getTaskHandle(), nullptr);
|
||||
thread->start();
|
||||
CHECK_NE(thread->getTaskHandle(), nullptr);
|
||||
interrupted = true;
|
||||
thread->join();
|
||||
CHECK_EQ(thread->getTaskHandle(), nullptr);
|
||||
delete thread;
|
||||
}
|
||||
|
||||
TEST_CASE("thread state should be correct") {
|
||||
bool interrupted = false;
|
||||
auto* thread = new Thread(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
[&interrupted]() {
|
||||
while (!interrupted) {
|
||||
kernel::delayMillis(5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
);
|
||||
CHECK_EQ(thread->getState(), Thread::State::Stopped);
|
||||
thread->start();
|
||||
Thread::State state = thread->getState();
|
||||
CHECK((state == Thread::State::Starting || state == Thread::State::Running));
|
||||
interrupted = true;
|
||||
thread->join();
|
||||
CHECK_EQ(thread->getState(), Thread::State::Stopped);
|
||||
delete thread;
|
||||
}
|
||||
|
||||
TEST_CASE("thread id should only be set at when thread is started") {
|
||||
int code = 123;
|
||||
auto* thread = new Thread(
|
||||
"return code",
|
||||
4096,
|
||||
[&code]() { return code; }
|
||||
);
|
||||
thread->start();
|
||||
thread->join();
|
||||
CHECK_EQ(thread->getReturnCode(), code);
|
||||
delete thread;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "doctest.h"
|
||||
#include <Tactility/Timer.h>
|
||||
|
||||
using namespace tt;
|
||||
|
||||
TEST_CASE("TimerType::Periodic timers can be stopped and restarted") {
|
||||
int counter = 0;
|
||||
auto* timer = new Timer(Timer::Type::Periodic, 1, [&counter] { counter++; });
|
||||
CHECK_EQ(timer->start(), true);
|
||||
kernel::delayTicks(10);
|
||||
CHECK_EQ(timer->stop(), true);
|
||||
CHECK_EQ(timer->start(), true);
|
||||
kernel::delayTicks(10);
|
||||
CHECK_EQ(timer->stop(), true);
|
||||
delete timer;
|
||||
|
||||
CHECK_GE(counter, 2);
|
||||
}
|
||||
|
||||
TEST_CASE("TimerType::Periodic calls the callback periodically") {
|
||||
int ticks_to_run = 10;
|
||||
int counter = 0;
|
||||
auto* timer = new Timer(Timer::Type::Periodic, 1, [&counter] { counter++; });
|
||||
CHECK_EQ(timer->start(), true);
|
||||
kernel::delayTicks(ticks_to_run);
|
||||
CHECK_EQ(timer->stop(), true);
|
||||
delete timer;
|
||||
|
||||
CHECK_EQ(counter, ticks_to_run);
|
||||
}
|
||||
|
||||
TEST_CASE("restarting TimerType::Once timers calls the callback again") {
|
||||
int counter = 0;
|
||||
auto* timer = new Timer(Timer::Type::Once, 1, [&counter] { counter++; });
|
||||
CHECK_EQ(timer->start(), true);
|
||||
kernel::delayTicks(10);
|
||||
CHECK_EQ(timer->stop(), true);
|
||||
CHECK_EQ(timer->start(), true);
|
||||
kernel::delayTicks(10);
|
||||
CHECK_EQ(timer->stop(), true);
|
||||
delete timer;
|
||||
|
||||
CHECK_EQ(counter, 2);
|
||||
}
|
||||
Reference in New Issue
Block a user