Implement unit testing (#30)
- Create `test` and `tactility-core-tests` subprojects - Created tests for `thread.c` - Fixed issue with thread cleanup (see what I did there? :P) - Removed functions from `thread.h` that did not exist anymore - Updated `ideas.md`
This commit is contained in:
parent
50e7fb92c8
commit
47377439dd
20
.github/workflows/tests.yml
vendored
Normal file
20
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Tests
|
||||
on: [push]
|
||||
jobs:
|
||||
Build-PC:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SKIP_SDL: true
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Configure Project
|
||||
uses: threeal/cmake-action@v1.3.0
|
||||
- name: Prepare Project
|
||||
run: cmake -S ./ -B build
|
||||
- name: Build Tests
|
||||
run: cmake --build build --target build-tests
|
||||
- name: Run Tests
|
||||
run: build/tests/tactility-core/tactility-core-tests --exit
|
||||
@ -45,10 +45,17 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
)
|
||||
|
||||
add_subdirectory(libs/mbedtls)
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
add_subdirectory(app-sim)
|
||||
endif()
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
# region SDL & LVGL
|
||||
|
||||
# TODO: This is a temporary skipping option for running unit tests
|
||||
# TODO: Remove when github action for SDL is working again
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
|
||||
add_subdirectory(libs/lvgl) # Added as idf component for ESP and as library for other targets
|
||||
@ -112,4 +119,5 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
endif(LV_USE_FREETYPE)
|
||||
|
||||
# endregion SDL & LVGL
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
# TODOs
|
||||
- Update `view_port` to use `ViewPort` as handle externally and `ViewPortData` internally
|
||||
- Replace FreeRTOS semaphore from `Loader` with internal `Mutex`
|
||||
- Create unit tests for `tactility-core` and `tactility` (PC-only for now)
|
||||
- Create more unit tests for `tactility-core` and `tactility` (PC-only for now)
|
||||
- Have a way to deinit LVGL drivers that are created from `HardwareConfig`
|
||||
- Thread is broken: `tt_thread_join()` always hangs because `tt_thread_cleanup_tcb_event()`
|
||||
is not automatically called. This is normally done by a hook in `FreeRTOSConfig.h`
|
||||
but that seems to not work with ESP32. I should investigate task cleanup hooks further.
|
||||
- Set DPI in sdkconfig for Waveshare display
|
||||
- Try to drive Yellow Board backlight with PWM to reduce backlight strength
|
||||
- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials.
|
||||
- Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot.
|
||||
- Try out Waveshare S3 120MHz mode for PSRAM (see "enabling 120M PSRAM is necessary" in [docs](https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-4.3#Other_Notes))
|
||||
@ -20,7 +16,6 @@ but that seems to not work with ESP32. I should investigate task cleanup hooks f
|
||||
- If present, use LED to show boot status
|
||||
|
||||
# App Improvement Ideas
|
||||
- Make a Settings app to show all the apps that have a "settings" app type (and hide those in desktop)
|
||||
- Sort desktop apps by name.
|
||||
- Light/dark mode selection in Display settings app.
|
||||
|
||||
|
||||
4
runtests.sh
Executable file
4
runtests.sh
Executable file
@ -0,0 +1,4 @@
|
||||
cmake -S ./ -B build-sim
|
||||
cmake --build build-sim --target build-tests -j 14
|
||||
build-sim/tests/tactility-core/tactility-core-tests --exit
|
||||
|
||||
@ -72,7 +72,7 @@ static void tt_thread_body(void* context) {
|
||||
tt_assert(context);
|
||||
Thread* thread = context;
|
||||
|
||||
// store thread instance to thread local storage
|
||||
// Store thread instance to thread local storage
|
||||
tt_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL);
|
||||
vTaskSetThreadLocalStoragePointer(NULL, 0, thread);
|
||||
|
||||
@ -80,19 +80,23 @@ static void tt_thread_body(void* context) {
|
||||
tt_thread_set_state(thread, ThreadStateRunning);
|
||||
|
||||
thread->ret = thread->callback(thread->context);
|
||||
TT_LOG_I(TAG, "thread returned: %s", thread->name ?: "[no name]");
|
||||
|
||||
tt_assert(thread->state == ThreadStateRunning);
|
||||
|
||||
if (thread->is_static) {
|
||||
TT_LOG_I(
|
||||
TAG,
|
||||
"%s service thread TCB memory will not be reclaimed",
|
||||
"%s static task memory will not be reclaimed",
|
||||
thread->name ? thread->name : "<unnamed service>"
|
||||
);
|
||||
}
|
||||
|
||||
tt_thread_set_state(thread, ThreadStateStopped);
|
||||
|
||||
vTaskSetThreadLocalStoragePointer(NULL, 0, NULL);
|
||||
thread->task_handle = NULL;
|
||||
|
||||
vTaskDelete(NULL);
|
||||
tt_thread_catch();
|
||||
}
|
||||
@ -114,7 +118,7 @@ Thread* tt_thread_alloc() {
|
||||
tt_thread_set_appid(thread, "unknown");
|
||||
}
|
||||
} else {
|
||||
// if scheduler is not started, we are starting driver thread
|
||||
// If scheduler is not started, we are starting driver thread
|
||||
tt_thread_set_appid(thread, "driver");
|
||||
}
|
||||
|
||||
@ -250,28 +254,18 @@ void tt_thread_start(Thread* thread) {
|
||||
#else
|
||||
TT_LOG_E(TAG, "static tasks are not supported by current FreeRTOS config/platform - creating regular one");
|
||||
BaseType_t ret = xTaskCreate(
|
||||
tt_thread_body, thread->name, stack, thread, priority, &thread->task_handle
|
||||
tt_thread_body, thread->name, stack, thread, priority, &(thread->task_handle)
|
||||
);
|
||||
tt_check(ret == pdPASS);
|
||||
#endif
|
||||
} else {
|
||||
BaseType_t ret = xTaskCreate(
|
||||
tt_thread_body, thread->name, stack, thread, priority, &thread->task_handle
|
||||
tt_thread_body, thread->name, stack, thread, priority, &(thread->task_handle)
|
||||
);
|
||||
tt_check(ret == pdPASS);
|
||||
}
|
||||
|
||||
tt_check(thread->task_handle);
|
||||
}
|
||||
|
||||
void tt_thread_cleanup_tcb_event(TaskHandle_t task) {
|
||||
Thread* thread = pvTaskGetThreadLocalStoragePointer(task, 0);
|
||||
if (thread) {
|
||||
// clear thread local storage
|
||||
vTaskSetThreadLocalStoragePointer(task, 0, NULL);
|
||||
tt_assert(thread->task_handle == task);
|
||||
thread->task_handle = NULL;
|
||||
}
|
||||
tt_check(thread->state == ThreadStateStopped || thread->task_handle);
|
||||
}
|
||||
|
||||
bool tt_thread_join(Thread* thread) {
|
||||
|
||||
@ -197,26 +197,6 @@ bool tt_thread_join(Thread* thread);
|
||||
*/
|
||||
ThreadId tt_thread_get_id(Thread* thread);
|
||||
|
||||
/** Enable heap tracing
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_enable_heap_trace(Thread* thread);
|
||||
|
||||
/** Disable heap tracing
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_disable_heap_trace(Thread* thread);
|
||||
|
||||
/** Get thread heap size
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t tt_thread_get_heap_size(Thread* thread);
|
||||
|
||||
/** Get thread return code
|
||||
*
|
||||
* @param thread Thread instance
|
||||
@ -276,33 +256,6 @@ const char* tt_thread_get_appid(ThreadId thread_id);
|
||||
*/
|
||||
uint32_t tt_thread_get_stack_space(ThreadId thread_id);
|
||||
|
||||
/** Get STDOUT callback for thead
|
||||
*
|
||||
* @return STDOUT callback
|
||||
*/
|
||||
ThreadStdoutWriteCallback tt_thread_get_stdout_callback();
|
||||
|
||||
/** Set STDOUT callback for thread
|
||||
*
|
||||
* @param callback callback or NULL to clear
|
||||
*/
|
||||
void tt_thread_set_stdout_callback(ThreadStdoutWriteCallback callback);
|
||||
|
||||
/** Write data to buffered STDOUT
|
||||
*
|
||||
* @param data input data
|
||||
* @param size input data size
|
||||
*
|
||||
* @return size_t written data size
|
||||
*/
|
||||
size_t tt_thread_stdout_write(const char* data, size_t size);
|
||||
|
||||
/** Flush data to STDOUT
|
||||
*
|
||||
* @return int32_t error code
|
||||
*/
|
||||
int32_t tt_thread_stdout_flush();
|
||||
|
||||
/** Suspend thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
|
||||
9
tests/CMakeLists.txt
Normal file
9
tests/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
project(tests)
|
||||
|
||||
set(DOCTESTINC ${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tactility-core)
|
||||
|
||||
add_custom_target(build-tests)
|
||||
add_dependencies(build-tests tactility-core-tests)
|
||||
7106
tests/include/doctest.h
Normal file
7106
tests/include/doctest.h
Normal file
File diff suppressed because it is too large
Load Diff
21
tests/tactility-core/CMakeLists.txt
Normal file
21
tests/tactility-core/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
project(tactility-core-tests)
|
||||
|
||||
enable_language(C CXX ASM)
|
||||
|
||||
set(CMAKE_CXX_COMPILER g++)
|
||||
|
||||
file(GLOB_RECURSE TEST_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp)
|
||||
add_executable(tactility-core-tests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
||||
|
||||
target_include_directories(tactility-core-tests PRIVATE
|
||||
${DOCTESTINC}
|
||||
)
|
||||
|
||||
add_test(NAME tactility-core-tests
|
||||
COMMAND tactility-core-tests
|
||||
)
|
||||
|
||||
target_link_libraries(tactility-core-tests
|
||||
PUBLIC tactility-core
|
||||
freertos-kernel
|
||||
)
|
||||
58
tests/tactility-core/main.cpp
Normal file
58
tests/tactility-core/main.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#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();
|
||||
|
||||
if (context.shouldExit()) { // important - query flags (and --exit) rely on the user doing this
|
||||
vTaskEndScheduler();
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
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,
|
||||
NULL
|
||||
);
|
||||
assert(task_result == pdPASS);
|
||||
|
||||
vTaskStartScheduler();
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// Required for FreeRTOS
|
||||
void vAssertCalled(unsigned long line, const char* const file) {
|
||||
__assert_fail("assert failed", file, line, "");
|
||||
}
|
||||
}
|
||||
102
tests/tactility-core/thread_test.cpp
Normal file
102
tests/tactility-core/thread_test.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "doctest.h"
|
||||
#include "thread.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
static int interruptable_thread(TT_UNUSED void* parameter) {
|
||||
bool* interrupted = (bool*)parameter;
|
||||
while (!*interrupted) {
|
||||
vTaskDelay(5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool immedate_return_thread_called = false;
|
||||
static int immediate_return_thread(TT_UNUSED void* parameter) {
|
||||
immedate_return_thread_called = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thread_with_return_code(TT_UNUSED void* parameter) {
|
||||
int* code = (int*)parameter;
|
||||
return *code;
|
||||
}
|
||||
|
||||
TEST_CASE("when a thread is started then its callback should be called") {
|
||||
Thread* thread = tt_thread_alloc_ex(
|
||||
"immediate return task",
|
||||
4096,
|
||||
&immediate_return_thread,
|
||||
NULL
|
||||
);
|
||||
CHECK(!immedate_return_thread_called);
|
||||
tt_thread_start(thread);
|
||||
tt_thread_join(thread);
|
||||
tt_thread_free(thread);
|
||||
CHECK(immedate_return_thread_called);
|
||||
}
|
||||
|
||||
TEST_CASE("a thread can be started and stopped") {
|
||||
bool interrupted = false;
|
||||
Thread* thread = tt_thread_alloc_ex(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
&interruptable_thread,
|
||||
&interrupted
|
||||
);
|
||||
CHECK(thread);
|
||||
tt_thread_start(thread);
|
||||
interrupted = true;
|
||||
tt_thread_join(thread);
|
||||
tt_thread_free(thread);
|
||||
}
|
||||
|
||||
TEST_CASE("thread id should only be set at when thread is started") {
|
||||
bool interrupted = false;
|
||||
Thread* thread = tt_thread_alloc_ex(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
&interruptable_thread,
|
||||
&interrupted
|
||||
);
|
||||
CHECK(tt_thread_get_id(thread) == NULL);
|
||||
tt_thread_start(thread);
|
||||
CHECK(tt_thread_get_id(thread) != NULL);
|
||||
interrupted = true;
|
||||
tt_thread_join(thread);
|
||||
CHECK(tt_thread_get_id(thread) == NULL);
|
||||
tt_thread_free(thread);
|
||||
}
|
||||
|
||||
TEST_CASE("thread state should be correct") {
|
||||
bool interrupted = false;
|
||||
Thread* thread = tt_thread_alloc_ex(
|
||||
"interruptable thread",
|
||||
4096,
|
||||
&interruptable_thread,
|
||||
&interrupted
|
||||
);
|
||||
CHECK_EQ(tt_thread_get_state(thread), ThreadStateStopped);
|
||||
tt_thread_start(thread);
|
||||
ThreadState state = tt_thread_get_state(thread);
|
||||
CHECK((state == ThreadStateStarting || state == ThreadStateRunning));
|
||||
interrupted = true;
|
||||
tt_thread_join(thread);
|
||||
CHECK_EQ(tt_thread_get_state(thread), ThreadStateStopped);
|
||||
tt_thread_free(thread);
|
||||
}
|
||||
|
||||
TEST_CASE("thread id should only be set at when thread is started") {
|
||||
int code = 123;
|
||||
Thread* thread = tt_thread_alloc_ex(
|
||||
"return code",
|
||||
4096,
|
||||
&thread_with_return_code,
|
||||
&code
|
||||
);
|
||||
tt_thread_start(thread);
|
||||
tt_thread_join(thread);
|
||||
CHECK_EQ(tt_thread_get_return_code(thread), code);
|
||||
tt_thread_free(thread);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user