fork/ci #2

Open
mohr wants to merge 16 commits from fork/ci into main
17 changed files with 476 additions and 93 deletions
+17 -6
View File
@@ -1,5 +1,6 @@
name: ROMEO CI Builder
on: push
on:
pull_request
jobs:
Build Code:
@@ -10,21 +11,31 @@ jobs:
with:
token: ${{ secrets.ROMEO_NUMALFIX_TOKEN }}
submodules: 'recursive'
- name: Configure z7
- name: Build z7
run: |
mkdir build_z7
cd build_z7
cmake -DCMAKE_TOOLCHAIN_FILE=../bsp_z7/cmake/arm-none-eabi.toolchain ..
- name: Build z7
cmake -DCMAKE_TOOLCHAIN_FILE=../bsp_z7/cmake/arm-none-eabi.toolchain -DCMAKE_BUILD_TYPE=Release ..
make -j8
- name: Low level tests z7
run: |
cd build_z7
cmake -DARM_SEMIHOSTING=ON -DROMEO_LOW_LEVEL_TESTS=ON ..
make -j8
- name: Configure linux
qemu-system-arm -semihosting -nographic -monitor none -serial none -serial stdio -machine xilinx-zynq-a9 -m 500M -kernel romeo-low_level_tests
- name: Build linux
run: |
mkdir build_linux
cd build_linux
cmake ..
- name: Build linux
make -j8
- name: Low level tests linux
run: |
cd build_linux
cmake -DROMEO_LOW_LEVEL_TESTS=ON ..
make -j8
./romeo-low_level_tests
- name: Rust tests
run: |
cd mission_rust
cargo test
+2 -2
View File
@@ -3,7 +3,7 @@
url = https://github.com/FreeRTOS/FreeRTOS-Kernel
[submodule "contrib/lwip"]
path = contrib/lwip
url = ../lwip
url = ../../romeo/lwip
[submodule "fsbl-compiled"]
path = fsbl-compiled
url = ../fsbl-compiled
url = ../../romeo/fsbl-compiled
+7
View File
@@ -17,6 +17,13 @@ set(ROMEO_WARNING_FLAGS
-Wpedantic
-Werror) # TODO so far, this only affects mission code, not bsp
option(ROMEO_LOW_LEVEL_TESTS "build low level tests INSTEAD of normal binary" OFF)
if(${ROMEO_LOW_LEVEL_TESTS})
add_compile_definitions(LOW_LEVEL_TESTS)
set(OBSW_NAME romeo-low_level_tests)
endif()
# CMake options which are only available when crosscompiling
if (${CMAKE_CROSSCOMPILING})
set(ZYNQ_UART UART1 CACHE STRING "Which PS UART to use for stdout")
+11 -2
View File
@@ -46,7 +46,7 @@
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) PTHREAD_STACK_MIN ) /* The stack size being passed is equal to the minimum stack size needed by pthread_create(). */
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 200 * 1024 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 12 )
#define configUSE_TRACE_FACILITY 1
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
@@ -78,6 +78,14 @@
#define configRECORD_STACK_HIGH_ADDRESS 1
// see low_level_tests/mod.rs, some weird effect in FreeRTOS
#ifndef LOW_LEVEL_TESTS
#define configMAX_PRIORITIES ( 10 )
#else
#define configMAX_PRIORITIES ( 1024 )
#endif
/* Software timer related configuration options. The maximum possible task
* priority is configMAX_PRIORITIES - 1. The priority of the timer task is
* deliberately set higher to ensure it is correctly capped back to
@@ -87,7 +95,8 @@
#define configTIMER_QUEUE_LENGTH 20
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
#define configMAX_PRIORITIES ( 10 )
/* Run time stats gathering configuration options. */
unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */
+4 -1
View File
@@ -8,6 +8,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <hardware/interfaces.h>
const char *sim_ip = "localhost";
int ai_family = AF_UNSPEC;
@@ -21,13 +22,15 @@ void done_error() { exit(1); }
int test_socket();
// TODO link to GCC's personality or make the linux build not use it?
// -> linux build is std at the moment, only used for tests
#ifdef LOW_LEVEL_TESTS
void rust_eh_personality() { puts("eh_personality"); }
#endif
void print_usage(const char *name) {
fprintf(stderr, "Usage: %s [-s sim_ip] [-4|6]\n", name);
}
#include <hardware/interfaces.h>
int main(int argc, char **argv) {
hw_device_open(
+4 -1
View File
@@ -91,7 +91,10 @@
#define configUSE_QUEUE_SETS 1
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
// TODO disable for non-testing?
#define configUSE_TASK_NOTIFICATIONS 1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1
/* Include the query-heap CLI command to query the free heap space. */
#define configINCLUDE_QUERY_HEAP_COMMAND 0
+13 -1
View File
@@ -15,6 +15,8 @@
#include "xscutimer.h"
#include "xuartps_hw.h"
#include <unistd.h>
/*
* Configure the hardware as necessary to run this demo.
*/
@@ -172,11 +174,21 @@ void vApplicationIdleHook(void) {
management options. If there is a lot of heap memory free then the
configTOTAL_HEAP_SIZE value in FreeRTOSConfig.h can be reduced to free up
RAM. */
// xFreeHeapSpace = xPortGetFreeHeapSize();
// xFreeHeapSpace = xPortGetFreeHeapSize();
// xMinimumEverFreeHeapSpace = xPortGetMinimumEverFreeHeapSize();
// /* Remove compiler warning about xFreeHeapSpace being set but never used.
// */ (void)xFreeHeapSpace; (void)xMinimumEverFreeHeapSpace;
#ifdef LOW_LEVEL_TESTS
#define ERROR_MSG "\nPanic occured in the code executing the tests.\nWe'll consider this a failure:\n\ntest result: ❌ FAILED HORRIBLY\n"
// During low level tests, panics do not halt the execution but delete the current task.
// If this happens in the initial task, only the idle task remains. In this case, we
// simply end the execution.
write(2, ERROR_MSG, sizeof(ERROR_MSG));
done_error();
#endif
}
void vApplicationTickHook(void) {
+3 -3
View File
@@ -1,9 +1,8 @@
FROM debian:12.7-slim
# Install required packages
RUN apt-get update && apt-get upgrade --yes
RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
RUN dpkg-reconfigure debconf --frontend=noninteractive && apt-get update && apt-get upgrade --yes && \
apt-get install --yes --no-install-recommends \
git \
ssh \
make \
@@ -14,6 +13,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends
ca-certificates \
gcc-arm-none-eabi \
libnewlib-arm-none-eabi \
qemu-system-arm \
# remove for image size
&& rm -rf /var/lib/apt/lists/*
+25 -13
View File
@@ -10,7 +10,6 @@
// TODO namespace the names
// TODO panic if able, but not in runtime calls
// Wraps xTaskGetCurrentTaskHandle and returns NULL if no Task is running
//
// xTaskGetCurrentTaskHandle() will return a handle even if no task is
@@ -33,8 +32,8 @@ StackType_t init_task_stack[configMINIMAL_STACK_SIZE * 10];
void freertos_init_and_start_scheduling(TaskFunction_t init_task) {
// TaskHandle_t handle =
xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10,
NULL, configMAX_PRIORITIES - 1, init_task_stack, &init_task_data);
xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10, NULL,
configMAX_PRIORITIES - 1, init_task_stack, &init_task_data);
// vTaskSetThreadLocalStoragePointer(handle, 0, NULL);
vTaskStartScheduler();
@@ -194,7 +193,8 @@ uint8_t freertos_once(char *once_data_in, uint32_t once_data_len,
once_data->local_mutex =
xSemaphoreCreateRecursiveMutexStatic(&once_data->local_mutex_data);
// if (once_data->local_mutex == NULL) {
// // will not happen, xSemaphoreCreateRecursiveMutexStatic returns its parameter
// // will not happen, xSemaphoreCreateRecursiveMutexStatic returns its
// parameter
// // which we checked above to be != NULL
// }
once_data->once_state = INIT;
@@ -236,7 +236,8 @@ uint8_t freertos_simple_once(uint8_t *once_data) {
uint8_t result = 0;
// This function is basically a flag stored in once_data, protected by
// a critical section
// Critical section is ok, because simple arithmetic is bounded in execution time
// Critical section is ok, because simple arithmetic is bounded in execution
// time
taskENTER_CRITICAL();
if (*once_data == 0) {
*once_data = 1;
@@ -253,9 +254,10 @@ uint32_t freertos_task_priority_max(void) {
}
// Note char* to keep sizeof correct on any platform
void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter, uint32_t priority,
char *task_data, uint32_t task_data_len,
char *stack, uint32_t stack_size) {
void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter,
uint32_t priority, char *task_data,
uint32_t task_data_len, char *stack,
uint32_t stack_size) {
// TODO verify uint32_t vs configSTACK_DEPTH_TYPE
if (task_data_len < sizeof(StaticTask_t)) {
// printf("freertos_create_task_static: task data needs to be %zu long\n",
@@ -275,9 +277,10 @@ void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter,
// printf("freertos_create_task_static: Stack: %p %"PRIu32" %zu %zu\n", stack,
// stack_size, stack_size_words, sizeof(StackType_t));
return xTaskCreateStatic(taskFunction, "rust", stack_size_words, parameter, priority,
(StackType_t *)stack, (StaticTask_t *)task_data);
return xTaskCreateStatic(taskFunction, "rust", stack_size_words, parameter,
priority, (StackType_t *)stack,
(StaticTask_t *)task_data);
}
void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) {
@@ -293,6 +296,15 @@ void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) {
}
// TODO: might be the wrong place
int freertos_get_sys_error(){
return errno;
int freertos_get_sys_error() { return errno; }
// private, for testing only TODO #define
void freertos_task_notify(void *task, uint32_t value) {
xTaskNotify(task, value, eSetValueWithOverwrite);
}
uint32_t freertos_task_notify_wait() {
uint32_t value;
xTaskNotifyWait(0, 0, &value, portMAX_DELAY);
return value;
}
+12
View File
@@ -11,7 +11,11 @@
#include <git_version.h>
#include <string.h>
#ifndef LOW_LEVEL_TESTS
void rust_main(void);
#else
void rust_low_level_tests(void);
#endif
// called to stop execution on error
// to be implemented by bsp (do not return from it!)
@@ -19,10 +23,18 @@ void done_error();
void init_task(void *_) {
(void)_;
#ifndef LOW_LEVEL_TESTS
rust_main();
#else
rust_low_level_tests();
#endif
}
#ifndef LOW_LEVEL_TESTS
#define STARTUP_MESSAGE1 "\nROMEO embedded obsw\nRelease: "
#else
#define STARTUP_MESSAGE1 "\nROMEO embedded obsw-tests\nRelease: "
#endif
#define STARTUP_MESSAGE2 "\nBuild time: "
void mission(void) {
+6 -2
View File
@@ -7,11 +7,15 @@
#TODO look into corrosion cmake plugin
if(${ROMEO_LOW_LEVEL_TESTS})
set(FEATURES "--features=\"low_level_tests\"")
endif()
if (${CMAKE_CROSSCOMPILING})
add_custom_target(
mission_rust_internal
COMMAND cargo build -Zbuild-std=core --target=${CMAKE_SYSTEM_PROCESSOR} $<$<CONFIG:Release>:--release>
COMMAND cargo build -Zbuild-std=core --target=${CMAKE_SYSTEM_PROCESSOR} $<$<CONFIG:Release>:--release> ${FEATURES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
@@ -27,7 +31,7 @@ else()
add_custom_target(
mission_rust_internal
COMMAND cargo build $<$<CONFIG:Release>:--release>
COMMAND cargo build $<$<CONFIG:Release>:--release> ${FEATURES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
+5 -2
View File
@@ -4,10 +4,13 @@ version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
crate-type = ["staticlib", "lib"]
[profile.dev]
panic = 'abort'
[profile.release]
panic = 'abort'
panic = 'abort'
[features]
low_level_tests = []
+19 -19
View File
@@ -1,31 +1,31 @@
/// A trait to allow objects to be queried for some of their inner attributes at runtime.
///
///
pub trait Introspection {
/// Executes a function/closure on all members of the implementing struct.
///
///
/// The parameters for the closure a reference to the member as well as the name of the member.
///
///
/// To keep the trait object safe, we need to pass the closure as a reference. Otherwise,
/// the trait can not be used as a `dyn` reference, because the function becomes generic.
/// (Either using `impl FnMut` not `where F: FnMut` will be treated as generic making the
/// trait not object safe).
/// Additionally, the closure is typed as `FnMut`, to allow the closure to capture mutably.
///
///
///
///
/// # Examples
///
///
/// ```rust
/// use fsrc::introspection::Introspection;
///
/// use mission_rust::fsrc::introspection::Introspection;
///
/// struct Empty {}
///
///
/// impl Empty {
/// fn getRandomNumber(&self) -> u8 {
/// return 4 // chosen by fair dice roll.
/// // guaranteed to be random.
/// }
/// }
///
///
/// fn parsing_something(something: &dyn Introspection) {
/// let mut a = 0;
/// something.for_each_member(&mut |x, _| {
@@ -39,18 +39,18 @@ pub trait Introspection {
fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ());
/// Same as [for_each_member](Introspection::for_each_member), only with a return value for the closure.
///
///
/// # Examples
///
///
/// The return value allows using `x.downcast_ref()?` or similar constructs in the closure like in:
///
///
/// ```rust
/// use fsrc::introspection::Introspection;
///
/// use mission_rust::fsrc::introspection::Introspection;
///
/// struct Empty {}
///
///
/// fn do_something(_: &Empty) {}
///
///
/// fn parsing_something(something: &dyn Introspection) {
/// something.for_each_member_return(&mut |x, _| {
/// do_something(x.downcast_ref()?);
@@ -58,6 +58,6 @@ pub trait Introspection {
/// });
/// }
/// ```
///
///
fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>);
}
}
+125 -1
View File
@@ -23,6 +23,9 @@ impl Sizes {
pub const TASK_MINIMAL_STACK_SIZE: usize = 10240;
}
#[cfg(not(test))]
extern "C" {
////////////////////////
/// FreeRTOS API
@@ -30,7 +33,7 @@ extern "C" {
//void *create_task_static(TaskFunction_t taskFunction, void *parameter,
// char *task_data, uint32_t task_data_len, char *stack, uint32_t stack_size)
pub fn freertos_create_task_static(
taskFunction: TaskFunction,
task_function: TaskFunction,
parameter: *const core::ffi::c_void,
priority: u32,
task_data: *mut core::ffi::c_char,
@@ -101,6 +104,127 @@ extern "C" {
//int hw_device_open(const char * path, size_t path_len);
pub fn hw_device_open(path: *const core::ffi::c_char, path_len: core::ffi::c_size_t) -> core::ffi::c_int;
}
#[cfg(test)]
pub use test_api::*;
#[cfg(test)]
mod test_api{
use super::*;
////////////////////////
/// FreeRTOS API
/// shimmed in mission/freeRTOS_rust_helper.c
//void *create_task_static(TaskFunction_t taskFunction, void *parameter,
// char *task_data, uint32_t task_data_len, char *stack, uint32_t stack_size)
pub unsafe fn freertos_create_task_static(
_task_function: TaskFunction,
_parameter: *const core::ffi::c_void,
_priority: u32,
_task_data: *mut core::ffi::c_char,
_task_data_len: u32,
_stack: *mut core::ffi::c_char,
_stack_size: u32,
) -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
}
// pub fn stop_it();
pub unsafe fn freertos_task_suspend(_handle: *const core::ffi::c_void){
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_task_delete(_handle: *const core::ffi::c_void) -> !{
panic!("freeRTOS APIs are not implemented for tests")
}
pub unsafe fn freertos_task_storage_set(
_handle: *const core::ffi::c_void,
_data: *const core::ffi::c_void,
){
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_task_storage_get(_handle: *const core::ffi::c_void) -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_task_delay(_milliseconds: u32){
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_task_stack_watermark() -> u32{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_task_current() -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
}
// TODO this should be passed by compile time value
pub unsafe fn freertos_task_priority_max() -> u32{
panic!("freeRTOS APIs are not implemented for tests");
}
//void *freertos_queue_create_static(uint32_t depth, uint32_t element_size,
// char *queue_data, uint32_t queue_data_len,
// uint8_t *queue, uint32_t queue_len) {
pub unsafe fn freertos_queue_create_static(
_depth: u32,
_element_size: u32,
_queue_data: *mut core::ffi::c_char,
_queue_data_len: u32,
_queue: *mut u8,
) -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_queue_receive(
_queue: *const core::ffi::c_void,
_message: *const core::ffi::c_void,
) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_queue_send(
_queue: *const core::ffi::c_void,
_message: *const core::ffi::c_void,
) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_mutex_create_static(
_mutex_data: *const core::ffi::c_char,
_mutex_data_len: u32,
) -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_mutex_take(_mutex: *const core::ffi::c_void) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
}
pub unsafe fn freertos_mutex_give(_mutex: *const core::ffi::c_void) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
}
//uint8_t freertos_simple_once(uint8_t *once_data)
pub unsafe fn freertos_simple_once(_once_data: *const u8) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
}
//int freertos_get_sys_error()
pub unsafe fn freertos_get_sys_error() -> core::ffi::c_int{
panic!("freeRTOS APIs are not implemented for tests");
}
////////////////////////
/// Harware Abstraction API in common/include/interfaces.h
/// Used for access to peripherals to make switching between linux and embedded easier
//int hw_device_open(const char * path, size_t path_len);
pub unsafe fn hw_device_open(_path: *const core::ffi::c_char, _path_len: core::ffi::c_size_t) -> core::ffi::c_int{
panic!("freeRTOS APIs are not implemented for tests");
}
}
////////////////////////
/// libc API (read/write/etc)
#[cfg(target_env = "gnu")]
+23 -18
View File
@@ -1,12 +1,14 @@
#![no_std]
#![cfg_attr(any(target_env = "newlib", feature = "low_level_tests"), no_std)]
#![feature(never_type)]
#![feature(c_size_t)] // for ffi, tracking issue [88345]
//TODO os errors in API calls
mod dh;
pub mod fsrc;
mod panic;
#[cfg(feature = "low_level_tests")]
mod low_level_tests;
use core::fmt::Write;
use core::time::Duration;
@@ -19,10 +21,7 @@ use osal::{
use crate::{
dh::EchoHandler,
fsrc::{
dh::debug::DeviceHandlerDebugger,
osal::io::HardwareInterface,
},
fsrc::{dh::debug::DeviceHandlerDebugger, osal::io::HardwareInterface},
};
static THREAD_INIT: StaticReadOnceLock<
@@ -34,7 +33,7 @@ extern "C" fn rust_main() {
// we are already in a task (init task started by C), so we can use all APIs safely,
// but start a new init task anyway to be able to control that task's stack here
sifln!("Rust startup 🚀");
THREAD_INIT.take_no_init().unwrap().spawn(init_task);
THREAD_INIT.take_no_init().unwrap_or_else(|| {loop{}}).spawn(init_task);
// delete self, init task will take over
thread::current().delete();
}
@@ -198,7 +197,7 @@ fn init_task() -> ! {
Duration::from_millis(500),
0.5,
);
debugger.run().unwrap();
let test1 = TEST1.take().unwrap();
@@ -208,22 +207,28 @@ fn init_task() -> ! {
let clone = sender_handle.clone();
let sender = SENDER.take_no_init().unwrap();
let mutex_copy = test1.mutex.clone();
let a = Duration::from_millis(10);
// let mutex_copy = test1.mutex.clone();
// let a = Duration::from_millis(10);
THREAD_2.take_no_init().unwrap().spawn(move || {
test2.run(mutex_copy, a);
});
// THREAD_2.take_no_init().unwrap().spawn(move || {
// test2.run(mutex_copy, a);
// });
THREAD_1.take_no_init().unwrap().spawn(move || {
test1.run();
sender.run(clone);
});
// THREAD_1.take_no_init().unwrap().spawn(move || {
// test1.run();
// sender.run(clone);
// });
THREAD_3.take_no_init().unwrap().spawn(move || {
receiver.run();
});
sifln!("=====================Mission delete");
osal::thread::current().delete();
}
mod tests {
#[test]
fn work_at_all() {
}
}
+151
View File
@@ -0,0 +1,151 @@
//! Low level tests which need to be run with FreeRTOS present
//!
//! They can not be run as standard rust tests as they depend on the C code.
//! As such this module is to be linked
//! as part of the main library with the low level C code.
//!
//! Entry into the tests is provided by the extern "C" function
//! rust_low_level_tests() to be called from C. It is assumed to be running in an init Thread
//! and will spawn a new testing thread for each unit test.
use super::*;
const OK: u32 = 0;
pub const PANIC: u32 = 1;
macro_rules! function_with_name {
($x:expr) => {{
($x, type_name_of($x))
}};
}
fn type_name_of<T>(_: T) -> &'static str {
core::any::type_name::<T>()
}
extern "C" {
// forward declaration of done[_error]() which are implemented in bsp.
// this is not part of ffi as users are not expected to use it.
fn done() -> !;
fn done_error() -> !;
// Until further notice, these are private to testing
//uint32_t freertos_task_notify(void *task, uint32_t value)
fn freertos_task_notify(task: *const core::ffi::c_void, value: u32);
//uint32_t freertos_task_notify_wait()
fn freertos_task_notify_wait() -> u32;
}
pub static mut CONTROLLER_TASK_HANDLE: *const core::ffi::c_void = 0 as *const core::ffi::c_void;
/// Stack for test threads, is reused as tests are run one after another
///
/// assumes we run in qemu, so memory is no consideration
static TEST_RUNNER_STACK: StaticReadOnceLock<[core::ffi::c_char; 1024000]> = StaticReadOnceLock::new([0; 1024000]);
static TEST_RUNNER_THREAD_DATA: StaticReadOnceLock<
[core::ffi::c_char; osal::ffi::Sizes::TASK_DATA_SIZE],
> = StaticReadOnceLock::new([0; osal::ffi::Sizes::TASK_DATA_SIZE]);
/// Runs the low level tests
///
/// test are assumed to panic when failing. As we are running no-std with a custom panic,
/// tests are not continued after failing, as we have no reliable way of catching panics so far.
#[no_mangle]
extern "C" fn rust_low_level_tests() {
// Safe because I do not care right now. TODO
unsafe { CONTROLLER_TASK_HANDLE = osal::ffi::freertos_task_current() };
run_tests(&mut [function_with_name!(work_at_all)]);
}
#[cfg(not(target_os = "linux"))]
fn increment_on_linux(number: &mut u32) {
}
#[cfg(target_os = "linux")]
fn increment_on_linux(number: &mut u32) {
*number = *number + 1;
}
/// Runner for low level tests, will attempt to look a bit like std rust tests.
/// expects a function and its name, to print it
/// use the function_with_name! macro to generate the tuple
fn run_tests(tests: &mut [(fn(), &'static str)]) -> ! {
let stack = TEST_RUNNER_STACK.take_no_init().unwrap();
let thread_data = TEST_RUNNER_THREAD_DATA.take_no_init().unwrap();
let number_of_tests = tests.len();
sifln!();
if number_of_tests == 0 {
sifln!("no tests implemented 😢")
} else if number_of_tests == 1 {
sifln!("running 1 test")
} else {
sifln!("running {number_of_tests} tests");
}
let mut passed = 0;
let mut failed = 0;
let mut priority = 1;
for test in tests {
let test_function = test.0 as *const core::ffi::c_void;
for element in thread_data.iter_mut() {
*element = 0;
}
for element in stack.iter_mut() {
*element = 0;
}
unsafe {
osal::ffi::freertos_create_task_static(
run_one_test,
test_function,
priority,
thread_data.as_mut_ptr(),
thread_data.len().try_into().unwrap(),
stack.as_mut_ptr(),
stack.len().try_into().unwrap(),
)
};
let result = unsafe { freertos_task_notify_wait() };
sif!("test {} ... ", test.1);
if result == OK {
sifln!("✅ ok");
passed+=1;
} else {
sifln!("❌ FAILED");
failed+=1;
}
// some weird bug/effect in FreeRTOS on linux breaks here
// if a task priority is reused.
// So, we just increase the priority lineary. We mande sure in FreeRTOSConfig.h
// to have enough (1024) available.
increment_on_linux(&mut priority)
}
sifln!();
if failed == 0 {
sifln!("test result: ✅ ok. {passed} passed; {failed} failed");
unsafe { done() }
} else {
sifln!("test result: ❌ FAILED. {passed} passed; {failed} failed");
unsafe { done_error() }
}
}
#[no_mangle]
extern "C" fn run_one_test(parameter: *mut core::ffi::c_void) -> ! {
let function: fn() = unsafe { core::mem::transmute(parameter) };
function();
unsafe {
freertos_task_notify(CONTROLLER_TASK_HANDLE, OK);
}
osal::thread::current().delete();
}
fn work_at_all() {}
fn fail() {
assert_eq!(1, 2);
}
+49 -22
View File
@@ -1,26 +1,53 @@
use core::panic::PanicInfo;
use core::fmt::Write;
#[cfg(any(target_env = "newlib", feature = "low_level_tests"))]
pub use no_std::*;
extern "C" {
fn done_error(); // exit code != 0
}
#[cfg(any(target_env = "newlib", feature = "low_level_tests"))]
mod no_std {
use core::fmt::Write;
use core::panic::PanicInfo;
#[cfg(not(test))]
#[panic_handler]
fn panic(panic: &PanicInfo<'_>) -> ! {
// unsafe { this breaks in ISR
// osal::stop_it();
// }
_ = writeln!(crate::fsrc::sif::Stderr {}, "");
_ = writeln!(
crate::fsrc::sif::Stderr {},
"Thread '{}' {}",
crate::fsrc::osal::thread::current().name(),
panic
);
//TODO: stop RTOS, exit if hosted
unsafe { done_error() };
loop {}
extern "C" {
#[cfg(not(feature = "low_level_tests"))]
fn done_error() -> !; // exit code != 0
#[cfg(feature = "low_level_tests")]
//uint32_t freertos_task_notify(void *task, uint32_t value)
fn freertos_task_notify(task: *const core::ffi::c_void, value: u32);
}
#[panic_handler]
fn panic(panic: &PanicInfo<'_>) -> ! {
// unsafe { this breaks in ISR
// osal::stop_it();
// }
_ = writeln!(crate::fsrc::sif::Stderr {}, "");
_ = writeln!(
crate::fsrc::sif::Stderr {},
"Thread '{}' {}",
crate::fsrc::osal::thread::current().name(),
panic
);
#[cfg(not(feature = "low_level_tests"))]
{
unsafe { done_error() };
#[allow(unreachable_code)] // safeguard as this is only a weak contract with C
loop {}
}
#[cfg(feature = "low_level_tests")]
{
// When running low level tests, do not exit on panic
// but delete thread so the main thread can continue
// with the next test
unsafe {
freertos_task_notify(
crate::low_level_tests::CONTROLLER_TASK_HANDLE,
crate::low_level_tests::PANIC,
);
}
crate::fsrc::osal::thread::current().delete();
}
}
}
#[no_mangle]
@@ -41,4 +68,4 @@ extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi:
#[no_mangle]
extern "C" fn rust_alloc_failed() {
panic!("allocation failed!");
}
}