mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -82,7 +82,7 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
|
||||
furi_message_queue_put(app->input_queue, input_event, 0);
|
||||
}
|
||||
|
||||
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
static void input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
FuriMessageQueue* queue = object;
|
||||
EventLoopBlinkTestApp* app = context;
|
||||
|
||||
@@ -107,8 +107,6 @@ static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void blink_timer_callback(void* context) {
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
let tests = require("tests");
|
||||
let flipper = require("flipper");
|
||||
|
||||
tests.assert_eq(1337, 1337);
|
||||
tests.assert_eq("hello", "hello");
|
||||
|
||||
tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1));
|
||||
tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0));
|
||||
tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0));
|
||||
tests.assert_eq(true, doesSdkSupport(["baseline"]));
|
||||
tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
|
||||
|
||||
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
|
||||
tests.assert_eq(0, flipper.jsSdkVersion[0]);
|
||||
tests.assert_eq(1, flipper.jsSdkVersion[1]);
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
#include "../test.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
#define TAG "TestFuriEventLoop"
|
||||
|
||||
#define EVENT_LOOP_EVENT_COUNT (256u)
|
||||
|
||||
typedef struct {
|
||||
FuriMessageQueue* mq;
|
||||
|
||||
FuriEventLoop* producer_event_loop;
|
||||
uint32_t producer_counter;
|
||||
|
||||
FuriEventLoop* consumer_event_loop;
|
||||
uint32_t consumer_counter;
|
||||
} TestFuriData;
|
||||
|
||||
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == object, "Invalid queue");
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->producer_event_loop);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->producer_counter++;
|
||||
furi_check(
|
||||
furi_message_queue_put(data->mq, &data->producer_counter, 0) == FuriStatusOk,
|
||||
"furi_message_queue_put failed");
|
||||
furi_delay_us(furi_hal_random_get() % 1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t test_furi_event_loop_producer(void* p) {
|
||||
furi_check(p);
|
||||
|
||||
TestFuriData* data = p;
|
||||
|
||||
FURI_LOG_I(TAG, "producer start 1st run");
|
||||
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_mq_callback,
|
||||
data);
|
||||
|
||||
furi_event_loop_run(data->producer_event_loop);
|
||||
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer start 2nd run");
|
||||
|
||||
data->producer_counter = 0;
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_mq_callback,
|
||||
data);
|
||||
|
||||
furi_event_loop_run(data->producer_event_loop);
|
||||
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer end");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 1000);
|
||||
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->consumer_event_loop);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t test_furi_event_loop_consumer(void* p) {
|
||||
furi_check(p);
|
||||
|
||||
TestFuriData* data = p;
|
||||
|
||||
FURI_LOG_I(TAG, "consumer start 1st run");
|
||||
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_mq_callback,
|
||||
data);
|
||||
|
||||
furi_event_loop_run(data->consumer_event_loop);
|
||||
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer start 2nd run");
|
||||
|
||||
data->consumer_counter = 0;
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_mq_callback,
|
||||
data);
|
||||
|
||||
furi_event_loop_run(data->consumer_event_loop);
|
||||
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer end");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_furi_event_loop(void) {
|
||||
TestFuriData data = {};
|
||||
|
||||
data.mq = furi_message_queue_alloc(16, sizeof(uint32_t));
|
||||
|
||||
FuriThread* producer_thread = furi_thread_alloc();
|
||||
furi_thread_set_name(producer_thread, "producer_thread");
|
||||
furi_thread_set_stack_size(producer_thread, 1 * 1024);
|
||||
furi_thread_set_callback(producer_thread, test_furi_event_loop_producer);
|
||||
furi_thread_set_context(producer_thread, &data);
|
||||
furi_thread_start(producer_thread);
|
||||
|
||||
FuriThread* consumer_thread = furi_thread_alloc();
|
||||
furi_thread_set_name(consumer_thread, "consumer_thread");
|
||||
furi_thread_set_stack_size(consumer_thread, 1 * 1024);
|
||||
furi_thread_set_callback(consumer_thread, test_furi_event_loop_consumer);
|
||||
furi_thread_set_context(consumer_thread, &data);
|
||||
furi_thread_start(consumer_thread);
|
||||
|
||||
// Wait for thread to complete their tasks
|
||||
furi_thread_join(producer_thread);
|
||||
furi_thread_join(consumer_thread);
|
||||
|
||||
// The test itself
|
||||
mu_assert_int_eq(data.producer_counter, data.consumer_counter);
|
||||
mu_assert_int_eq(data.producer_counter, EVENT_LOOP_EVENT_COUNT);
|
||||
|
||||
// Release memory
|
||||
furi_thread_free(consumer_thread);
|
||||
furi_thread_free(producer_thread);
|
||||
furi_message_queue_free(data.mq);
|
||||
}
|
||||
490
applications/debug/unit_tests/tests/furi/furi_event_loop_test.c
Normal file
490
applications/debug/unit_tests/tests/furi/furi_event_loop_test.c
Normal file
@@ -0,0 +1,490 @@
|
||||
#include "../test.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
#define TAG "TestFuriEventLoop"
|
||||
|
||||
#define MESSAGE_COUNT (256UL)
|
||||
#define EVENT_FLAG_COUNT (23UL)
|
||||
#define PRIMITIVE_COUNT (4UL)
|
||||
#define RUN_COUNT (2UL)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
uint32_t message_queue_count;
|
||||
uint32_t stream_buffer_count;
|
||||
uint32_t event_flag_count;
|
||||
uint32_t semaphore_count;
|
||||
uint32_t primitives_tested;
|
||||
} TestFuriEventLoopThread;
|
||||
|
||||
typedef struct {
|
||||
FuriMessageQueue* message_queue;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
FuriEventFlag* event_flag;
|
||||
FuriSemaphore* semaphore;
|
||||
|
||||
TestFuriEventLoopThread producer;
|
||||
TestFuriEventLoopThread consumer;
|
||||
} TestFuriEventLoopData;
|
||||
|
||||
static void test_furi_event_loop_pending_callback(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopThread* test_thread = context;
|
||||
furi_check(test_thread->primitives_tested < PRIMITIVE_COUNT);
|
||||
|
||||
test_thread->primitives_tested++;
|
||||
FURI_LOG_I(TAG, "primitives tested: %lu", test_thread->primitives_tested);
|
||||
|
||||
if(test_thread->primitives_tested == PRIMITIVE_COUNT) {
|
||||
furi_event_loop_stop(test_thread->event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_thread_init(TestFuriEventLoopThread* test_thread) {
|
||||
memset(test_thread, 0, sizeof(TestFuriEventLoopThread));
|
||||
test_thread->event_loop = furi_event_loop_alloc();
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_thread_run_and_cleanup(TestFuriEventLoopThread* test_thread) {
|
||||
furi_event_loop_run(test_thread->event_loop);
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
furi_event_loop_free(test_thread->event_loop);
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_producer_message_queue_callback(
|
||||
FuriEventLoopObject* object,
|
||||
void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->message_queue == object);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"producer MessageQueue: %lu %lu",
|
||||
data->producer.message_queue_count,
|
||||
data->consumer.message_queue_count);
|
||||
|
||||
if(data->producer.message_queue_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer.event_loop,
|
||||
data->message_queue,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_message_queue_callback,
|
||||
data);
|
||||
|
||||
} else if(data->producer.message_queue_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
|
||||
furi_event_loop_pend_callback(
|
||||
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
|
||||
return;
|
||||
}
|
||||
|
||||
data->producer.message_queue_count++;
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(data->message_queue, &data->producer.message_queue_count, 0) ==
|
||||
FuriStatusOk);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_producer_stream_buffer_callback(
|
||||
FuriEventLoopObject* object,
|
||||
void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->stream_buffer == object);
|
||||
|
||||
TestFuriEventLoopThread* producer = &data->producer;
|
||||
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"producer StreamBuffer: %lu %lu",
|
||||
producer->stream_buffer_count,
|
||||
consumer->stream_buffer_count);
|
||||
|
||||
if(producer->stream_buffer_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
producer->event_loop,
|
||||
data->stream_buffer,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_stream_buffer_callback,
|
||||
data);
|
||||
|
||||
} else if(producer->stream_buffer_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
|
||||
furi_event_loop_pend_callback(
|
||||
producer->event_loop, test_furi_event_loop_pending_callback, producer);
|
||||
return;
|
||||
}
|
||||
|
||||
producer->stream_buffer_count++;
|
||||
|
||||
furi_check(
|
||||
furi_stream_buffer_send(
|
||||
data->stream_buffer, &producer->stream_buffer_count, sizeof(uint32_t), 0) ==
|
||||
sizeof(uint32_t));
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
}
|
||||
|
||||
static void
|
||||
test_furi_event_loop_producer_event_flag_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->event_flag == object);
|
||||
|
||||
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
|
||||
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
|
||||
|
||||
FURI_LOG_I(TAG, "producer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
|
||||
|
||||
furi_check(furi_event_flag_set(data->event_flag, producer_flags) & producer_flags);
|
||||
|
||||
if(data->producer.event_flag_count == EVENT_FLAG_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
|
||||
furi_event_loop_subscribe_event_flag(
|
||||
data->producer.event_loop,
|
||||
data->event_flag,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_event_flag_callback,
|
||||
data);
|
||||
|
||||
} else if(data->producer.event_flag_count == EVENT_FLAG_COUNT) {
|
||||
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
|
||||
furi_event_loop_pend_callback(
|
||||
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
|
||||
return;
|
||||
}
|
||||
|
||||
data->producer.event_flag_count++;
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
}
|
||||
|
||||
static void
|
||||
test_furi_event_loop_producer_semaphore_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->semaphore == object);
|
||||
|
||||
TestFuriEventLoopThread* producer = &data->producer;
|
||||
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "producer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
|
||||
furi_check(furi_semaphore_release(data->semaphore) == FuriStatusOk);
|
||||
|
||||
if(producer->semaphore_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
|
||||
furi_event_loop_subscribe_semaphore(
|
||||
producer->event_loop,
|
||||
data->semaphore,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_semaphore_callback,
|
||||
data);
|
||||
|
||||
} else if(producer->semaphore_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
|
||||
furi_event_loop_pend_callback(
|
||||
producer->event_loop, test_furi_event_loop_pending_callback, producer);
|
||||
return;
|
||||
}
|
||||
|
||||
data->producer.semaphore_count++;
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
}
|
||||
|
||||
static int32_t test_furi_event_loop_producer(void* p) {
|
||||
furi_check(p);
|
||||
|
||||
TestFuriEventLoopData* data = p;
|
||||
TestFuriEventLoopThread* producer = &data->producer;
|
||||
|
||||
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
|
||||
FURI_LOG_I(TAG, "producer start run %lu", i);
|
||||
|
||||
test_furi_event_loop_thread_init(producer);
|
||||
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
producer->event_loop,
|
||||
data->message_queue,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_message_queue_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
producer->event_loop,
|
||||
data->stream_buffer,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_stream_buffer_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_event_flag(
|
||||
producer->event_loop,
|
||||
data->event_flag,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_event_flag_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_semaphore(
|
||||
producer->event_loop,
|
||||
data->semaphore,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_semaphore_callback,
|
||||
data);
|
||||
|
||||
test_furi_event_loop_thread_run_and_cleanup(producer);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "producer end");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_consumer_message_queue_callback(
|
||||
FuriEventLoopObject* object,
|
||||
void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->message_queue == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_get(data->message_queue, &data->consumer.message_queue_count, 0) ==
|
||||
FuriStatusOk);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"consumer MessageQueue: %lu %lu",
|
||||
data->producer.message_queue_count,
|
||||
data->consumer.message_queue_count);
|
||||
|
||||
if(data->consumer.message_queue_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer.event_loop,
|
||||
data->message_queue,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_message_queue_callback,
|
||||
data);
|
||||
|
||||
} else if(data->consumer.message_queue_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
|
||||
furi_event_loop_pend_callback(
|
||||
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_furi_event_loop_consumer_stream_buffer_callback(
|
||||
FuriEventLoopObject* object,
|
||||
void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->stream_buffer == object);
|
||||
|
||||
TestFuriEventLoopThread* producer = &data->producer;
|
||||
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(
|
||||
data->stream_buffer, &consumer->stream_buffer_count, sizeof(uint32_t), 0) ==
|
||||
sizeof(uint32_t));
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"consumer StreamBuffer: %lu %lu",
|
||||
producer->stream_buffer_count,
|
||||
consumer->stream_buffer_count);
|
||||
|
||||
if(consumer->stream_buffer_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(consumer->event_loop, data->stream_buffer);
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
consumer->event_loop,
|
||||
data->stream_buffer,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_stream_buffer_callback,
|
||||
data);
|
||||
|
||||
} else if(consumer->stream_buffer_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(data->consumer.event_loop, data->stream_buffer);
|
||||
furi_event_loop_pend_callback(
|
||||
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_furi_event_loop_consumer_event_flag_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->event_flag == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
|
||||
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
|
||||
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
|
||||
|
||||
furi_check(
|
||||
furi_event_flag_wait(data->event_flag, consumer_flags, FuriFlagWaitAny, 0) &
|
||||
consumer_flags);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
|
||||
|
||||
if(data->consumer.event_flag_count == EVENT_FLAG_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
|
||||
furi_event_loop_subscribe_event_flag(
|
||||
data->consumer.event_loop,
|
||||
data->event_flag,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_event_flag_callback,
|
||||
data);
|
||||
|
||||
} else if(data->consumer.event_flag_count == EVENT_FLAG_COUNT) {
|
||||
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
|
||||
furi_event_loop_pend_callback(
|
||||
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
|
||||
return;
|
||||
}
|
||||
|
||||
data->consumer.event_flag_count++;
|
||||
}
|
||||
|
||||
static void
|
||||
test_furi_event_loop_consumer_semaphore_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriEventLoopData* data = context;
|
||||
furi_check(data->semaphore == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 100);
|
||||
|
||||
TestFuriEventLoopThread* producer = &data->producer;
|
||||
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||
|
||||
furi_check(furi_semaphore_acquire(data->semaphore, 0) == FuriStatusOk);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "consumer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
|
||||
|
||||
if(consumer->semaphore_count == MESSAGE_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
|
||||
furi_event_loop_subscribe_semaphore(
|
||||
consumer->event_loop,
|
||||
data->semaphore,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_semaphore_callback,
|
||||
data);
|
||||
|
||||
} else if(consumer->semaphore_count == MESSAGE_COUNT) {
|
||||
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
|
||||
furi_event_loop_pend_callback(
|
||||
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
|
||||
return;
|
||||
}
|
||||
|
||||
data->consumer.semaphore_count++;
|
||||
}
|
||||
|
||||
static int32_t test_furi_event_loop_consumer(void* p) {
|
||||
furi_check(p);
|
||||
|
||||
TestFuriEventLoopData* data = p;
|
||||
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||
|
||||
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
|
||||
FURI_LOG_I(TAG, "consumer start run %lu", i);
|
||||
|
||||
test_furi_event_loop_thread_init(consumer);
|
||||
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
consumer->event_loop,
|
||||
data->message_queue,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_message_queue_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
consumer->event_loop,
|
||||
data->stream_buffer,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_stream_buffer_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_event_flag(
|
||||
consumer->event_loop,
|
||||
data->event_flag,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_event_flag_callback,
|
||||
data);
|
||||
furi_event_loop_subscribe_semaphore(
|
||||
consumer->event_loop,
|
||||
data->semaphore,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_semaphore_callback,
|
||||
data);
|
||||
|
||||
test_furi_event_loop_thread_run_and_cleanup(consumer);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "consumer end");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_furi_event_loop(void) {
|
||||
TestFuriEventLoopData data = {};
|
||||
|
||||
data.message_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
|
||||
data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t));
|
||||
data.event_flag = furi_event_flag_alloc();
|
||||
data.semaphore = furi_semaphore_alloc(8, 0);
|
||||
|
||||
FuriThread* producer_thread =
|
||||
furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data);
|
||||
furi_thread_start(producer_thread);
|
||||
|
||||
FuriThread* consumer_thread =
|
||||
furi_thread_alloc_ex("consumer_thread", 1 * 1024, test_furi_event_loop_consumer, &data);
|
||||
furi_thread_start(consumer_thread);
|
||||
|
||||
// Wait for thread to complete their tasks
|
||||
furi_thread_join(producer_thread);
|
||||
furi_thread_join(consumer_thread);
|
||||
|
||||
TestFuriEventLoopThread* producer = &data.producer;
|
||||
TestFuriEventLoopThread* consumer = &data.consumer;
|
||||
|
||||
// The test itself
|
||||
mu_assert_int_eq(producer->message_queue_count, consumer->message_queue_count);
|
||||
mu_assert_int_eq(producer->message_queue_count, MESSAGE_COUNT);
|
||||
mu_assert_int_eq(producer->stream_buffer_count, consumer->stream_buffer_count);
|
||||
mu_assert_int_eq(producer->stream_buffer_count, MESSAGE_COUNT);
|
||||
mu_assert_int_eq(producer->event_flag_count, consumer->event_flag_count);
|
||||
mu_assert_int_eq(producer->event_flag_count, EVENT_FLAG_COUNT);
|
||||
mu_assert_int_eq(producer->semaphore_count, consumer->semaphore_count);
|
||||
mu_assert_int_eq(producer->semaphore_count, MESSAGE_COUNT);
|
||||
|
||||
// Release memory
|
||||
furi_thread_free(consumer_thread);
|
||||
furi_thread_free(producer_thread);
|
||||
|
||||
furi_message_queue_free(data.message_queue);
|
||||
furi_stream_buffer_free(data.stream_buffer);
|
||||
furi_event_flag_free(data.event_flag);
|
||||
furi_semaphore_free(data.semaphore);
|
||||
}
|
||||
103
applications/debug/unit_tests/tests/furi/furi_primitives_test.c
Normal file
103
applications/debug/unit_tests/tests/furi/furi_primitives_test.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <furi.h>
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#define MESSAGE_QUEUE_CAPACITY (16U)
|
||||
#define MESSAGE_QUEUE_ELEMENT_SIZE (sizeof(uint32_t))
|
||||
|
||||
#define STREAM_BUFFER_SIZE (32U)
|
||||
#define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U)
|
||||
|
||||
typedef struct {
|
||||
FuriMessageQueue* message_queue;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} TestFuriPrimitivesData;
|
||||
|
||||
static void test_furi_message_queue(TestFuriPrimitivesData* data) {
|
||||
FuriMessageQueue* message_queue = data->message_queue;
|
||||
|
||||
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_capacity(message_queue));
|
||||
mu_assert_int_eq(
|
||||
MESSAGE_QUEUE_ELEMENT_SIZE, furi_message_queue_get_message_size(message_queue));
|
||||
|
||||
for(uint32_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_space(message_queue));
|
||||
mu_assert_int_eq(i, furi_message_queue_get_count(message_queue));
|
||||
|
||||
if(furi_message_queue_put(message_queue, &i, 0) != FuriStatusOk) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mu_assert_int_eq(0, furi_message_queue_get_space(message_queue));
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_count(message_queue));
|
||||
|
||||
for(uint32_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(i, furi_message_queue_get_space(message_queue));
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_count(message_queue));
|
||||
|
||||
uint32_t value;
|
||||
if(furi_message_queue_get(message_queue, &value, 0) != FuriStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
|
||||
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
|
||||
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
|
||||
}
|
||||
|
||||
static void test_furi_stream_buffer(TestFuriPrimitivesData* data) {
|
||||
FuriStreamBuffer* stream_buffer = data->stream_buffer;
|
||||
|
||||
mu_assert(furi_stream_buffer_is_empty(stream_buffer), "Must be empty");
|
||||
mu_assert(!furi_stream_buffer_is_full(stream_buffer), "Must be not full");
|
||||
mu_assert_int_eq(0, furi_stream_buffer_bytes_available(stream_buffer));
|
||||
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_spaces_available(stream_buffer));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(i, furi_stream_buffer_bytes_available(stream_buffer));
|
||||
mu_assert_int_eq(
|
||||
STREAM_BUFFER_SIZE - i, furi_stream_buffer_spaces_available(stream_buffer));
|
||||
|
||||
if(furi_stream_buffer_send(stream_buffer, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mu_assert(!furi_stream_buffer_is_empty(stream_buffer), "Must be not empty");
|
||||
mu_assert(furi_stream_buffer_is_full(stream_buffer), "Must be full");
|
||||
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_bytes_available(stream_buffer));
|
||||
mu_assert_int_eq(0, furi_stream_buffer_spaces_available(stream_buffer));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(
|
||||
STREAM_BUFFER_SIZE - i, furi_stream_buffer_bytes_available(stream_buffer));
|
||||
mu_assert_int_eq(i, furi_stream_buffer_spaces_available(stream_buffer));
|
||||
|
||||
uint8_t value;
|
||||
if(furi_stream_buffer_receive(stream_buffer, &value, sizeof(uint8_t), 0) !=
|
||||
sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a stub that needs expanding
|
||||
void test_furi_primitives(void) {
|
||||
TestFuriPrimitivesData data = {
|
||||
.message_queue =
|
||||
furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE),
|
||||
.stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL),
|
||||
};
|
||||
|
||||
test_furi_message_queue(&data);
|
||||
test_furi_stream_buffer(&data);
|
||||
|
||||
furi_message_queue_free(data.message_queue);
|
||||
furi_stream_buffer_free(data.stream_buffer);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ void test_furi_pubsub(void);
|
||||
void test_furi_memmgr(void);
|
||||
void test_furi_event_loop(void);
|
||||
void test_errno_saving(void);
|
||||
void test_furi_primitives(void);
|
||||
|
||||
static int foo = 0;
|
||||
|
||||
@@ -47,6 +48,10 @@ MU_TEST(mu_test_errno_saving) {
|
||||
test_errno_saving();
|
||||
}
|
||||
|
||||
MU_TEST(mu_test_furi_primitives) {
|
||||
test_furi_primitives();
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_suite) {
|
||||
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
|
||||
MU_RUN_TEST(test_check);
|
||||
@@ -57,6 +62,7 @@ MU_TEST_SUITE(test_suite) {
|
||||
MU_RUN_TEST(mu_test_furi_memmgr);
|
||||
MU_RUN_TEST(mu_test_furi_event_loop);
|
||||
MU_RUN_TEST(mu_test_errno_saving);
|
||||
MU_RUN_TEST(mu_test_furi_primitives);
|
||||
}
|
||||
|
||||
int run_minunit_test_furi(void) {
|
||||
|
||||
Reference in New Issue
Block a user