cleaned up low level tests. Run on linux as well now

This commit is contained in:
2025-11-18 17:18:29 +01:00
parent c482185ffd
commit e1daa74c5a
9 changed files with 143 additions and 140 deletions
+2 -3
View File
@@ -17,10 +17,11 @@ set(ROMEO_WARNING_FLAGS
-Wpedantic
-Werror) # TODO so far, this only affects mission code, not bsp
option(ROMEO_LOW_LEVEL_TESTS "enable semihosting for emulation" OFF)
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
@@ -37,8 +38,6 @@ if (${CMAKE_CROSSCOMPILING})
if(${ARM_SEMIHOSTING})
add_compile_definitions(ARM_SEMIHOSTING)
endif()
else()
unset(ZYNQ_UART)
unset(ZYNQ_UART CACHE)
+10 -1
View File
@@ -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. */
+5 -3
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,14 +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
// void rust_eh_personality() { puts("eh_personality"); }
// -> 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(
+5 -5
View File
@@ -7,11 +7,11 @@
#TODO look into corrosion cmake plugin
if (${CMAKE_CROSSCOMPILING})
if(${ROMEO_LOW_LEVEL_TESTS})
set(FEATURES "--features=\"low_level_tests\"")
endif()
if(${ROMEO_LOW_LEVEL_TESTS})
set(FEATURES "--features=\"low_level_tests\"")
endif()
if (${CMAKE_CROSSCOMPILING})
add_custom_target(
mission_rust_internal
@@ -31,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}
)
+43 -60
View File
@@ -33,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,
@@ -110,8 +110,6 @@ pub use test_api::*;
#[cfg(test)]
mod test_api{
use core::ptr;
use super::*;
////////////////////////
@@ -119,127 +117,112 @@ mod test_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 fn freertos_create_task_static(
taskFunction: 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,
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");
ptr::null()
}
// pub fn stop_it();
pub fn freertos_task_suspend(handle: *const core::ffi::c_void){
pub unsafe fn freertos_task_suspend(_handle: *const core::ffi::c_void){
panic!("freeRTOS APIs are not implemented for tests");
}
pub fn freertos_task_delete(handle: *const core::ffi::c_void) -> !{
panic!("freeRTOS APIs are not implemented for tests");
loop{}
pub unsafe fn freertos_task_delete(_handle: *const core::ffi::c_void) -> !{
panic!("freeRTOS APIs are not implemented for tests")
}
pub fn freertos_task_storage_set(
handle: *const core::ffi::c_void,
data: *const core::ffi::c_void,
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 fn freertos_task_storage_get(handle: *const core::ffi::c_void) -> *const core::ffi::c_void{
panic!("freeRTOS APIs are not implemented for tests");
ptr::null()
}
pub fn freertos_task_delay(milliseconds: u32){
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 fn freertos_task_stack_watermark() -> u32{
pub unsafe fn freertos_task_delay(_milliseconds: u32){
panic!("freeRTOS APIs are not implemented for tests");
1
}
pub fn freertos_task_current() -> *const core::ffi::c_void{
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");
ptr::null()
}
// TODO this should be passed by compile time value
pub fn freertos_task_priority_max() -> u32{
pub unsafe fn freertos_task_priority_max() -> u32{
panic!("freeRTOS APIs are not implemented for tests");
u32::MAX
}
//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 fn freertos_queue_create_static(
depth: u32,
element_size: u32,
queue_data: *mut core::ffi::c_char,
queue_data_len: u32,
queue: *mut u8,
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");
ptr::null()
}
pub fn freertos_queue_receive(
queue: *const core::ffi::c_void,
message: *const core::ffi::c_void,
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");
1
}
pub fn freertos_queue_send(
queue: *const core::ffi::c_void,
message: *const core::ffi::c_void,
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");
1
}
pub fn freertos_mutex_create_static(
mutex_data: *const core::ffi::c_char,
mutex_data_len: u32,
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");
ptr::null()
}
pub fn freertos_mutex_take(mutex: *const core::ffi::c_void) -> u8{
pub unsafe fn freertos_mutex_take(_mutex: *const core::ffi::c_void) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
1
}
pub fn freertos_mutex_give(mutex: *const core::ffi::c_void) -> u8{
pub unsafe fn freertos_mutex_give(_mutex: *const core::ffi::c_void) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
1
}
//uint8_t freertos_simple_once(uint8_t *once_data)
pub fn freertos_simple_once(once_data: *const u8) -> u8{
pub unsafe fn freertos_simple_once(_once_data: *const u8) -> u8{
panic!("freeRTOS APIs are not implemented for tests");
1
}
//int freertos_get_sys_error()
pub fn freertos_get_sys_error() -> core::ffi::c_int{
pub unsafe fn freertos_get_sys_error() -> core::ffi::c_int{
panic!("freeRTOS APIs are not implemented for tests");
1
}
////////////////////////
/// 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 fn hw_device_open(path: *const core::ffi::c_char, path_len: core::ffi::c_size_t) -> core::ffi::c_int{
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");
1
}
}
////////////////////////
+4 -10
View File
@@ -206,8 +206,7 @@ impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
panic!("StaticThread(\"{}\")::spawn: priority {} is larger than maximum ({})", self.name, self.priority, unsafe{super::ffi::freertos_task_priority_max()});
}
// let stack = self.stack.take_no_init().unwrap();
let stack : &mut Box<[core::ffi::c_char;STACKSIZE]> = &mut Box::new([0;STACKSIZE]);
let stack = self.stack.take_no_init().unwrap();
let closure_space = core::mem::size_of::<F>();
// Will be checked later on, but this way we get a more precise output instead of a generic panic
@@ -247,8 +246,7 @@ impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
);
}
// let thread_data = self.thread_data.take_no_init().unwrap();
let thread_data: &mut Box<[core::ffi::c_char;ffi::Sizes::TASK_DATA_SIZE]> = &mut Box::new([0;ffi::Sizes::TASK_DATA_SIZE]);
let thread_data = self.thread_data.take_no_init().unwrap();
// SAFE: We only pass pointers derived from &'static references to the OS, so the pointers will
// be valid for the complete runtime. This includes values borrowed by the closure
@@ -267,11 +265,7 @@ impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
panic!("could not create thread");
}
// let handle = self.handle.take_no_init().unwrap();
let handle = &mut Box::new(ThreadHandle {
name: "todo",
freertos_handle: None,
});
let handle = self.handle.take_no_init().unwrap();
handle.freertos_handle = Some(freertos_handle);
// Store the Handle in task local storage, so we can use the rust types later on
@@ -284,7 +278,7 @@ impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
)
};
// this creates a copy of handle, as it impls Copy
**handle
*handle
}
}
+3 -3
View File
@@ -1,4 +1,4 @@
#![cfg_attr(target_env = "newlib", 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]
@@ -6,7 +6,7 @@ mod dh;
pub mod fsrc;
mod panic;
//#[cfg(feature = "low_level_tests")]
#[cfg(feature = "low_level_tests")]
mod low_level_tests;
use core::fmt::Write;
@@ -229,6 +229,6 @@ fn init_task() -> ! {
mod tests {
#[test]
fn works_at_all() {
fn work_at_all() {
}
}
+26 -13
View File
@@ -54,17 +54,25 @@ static TEST_RUNNER_THREAD_DATA: StaticReadOnceLock<
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!(mutex), function_with_name!(fail), function_with_name!(mutex)]);
run_tests(&mut [function_with_name!(work_at_all)]);
}
use std::sync::{Arc, Mutex};
#[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 stack = Arc::new(Mutex::new([0i8;102400]));
let thread_data = TEST_RUNNER_THREAD_DATA.take_no_init().unwrap();
let thread_data = Arc::new(Mutex::new([0i8;osal::ffi::Sizes::TASK_DATA_SIZE]));
let number_of_tests = tests.len();
@@ -79,20 +87,21 @@ fn run_tests(tests: &mut [(fn(), &'static str)]) -> ! {
let mut passed = 0;
let mut failed = 0;
let mut priority = 1;
for test in tests {
// Note for the pedantic: Static FreeRTOS can not delete tasks, only
// suspend them. So it is not formally correct to resuse the task data
// and stack. But as the suspended task will not be restarted, it does work
// in this (testing!) context.
let test_function = test.0 as *const core::ffi::c_void;
let stack = &mut stack.lock().unwrap();
let thread_data = &mut thread_data.lock().unwrap();
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,
2,
priority,
thread_data.as_mut_ptr(),
thread_data.len().try_into().unwrap(),
stack.as_mut_ptr(),
@@ -108,6 +117,11 @@ fn run_tests(tests: &mut [(fn(), &'static str)]) -> ! {
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!();
@@ -118,7 +132,6 @@ fn run_tests(tests: &mut [(fn(), &'static str)]) -> ! {
sifln!("test result: ❌ FAILED. {passed} passed; {failed} failed");
unsafe { done_error() }
}
}
#[no_mangle]
@@ -131,7 +144,7 @@ extern "C" fn run_one_test(parameter: *mut core::ffi::c_void) -> ! {
osal::thread::current().delete();
}
fn mutex() {}
fn work_at_all() {}
fn fail() {
assert_eq!(1, 2);
+45 -42
View File
@@ -1,49 +1,52 @@
#[cfg(target_env = "newlib")]
use core::panic::PanicInfo;
#[cfg(target_env = "newlib")]
use core::fmt::Write;
#[cfg(any(target_env = "newlib", feature = "low_level_tests"))]
pub use no_std::*;
#[cfg(target_env = "newlib")]
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);
}
#[cfg(any(target_env = "newlib", feature = "low_level_tests"))]
mod no_std {
use core::fmt::Write;
use core::panic::PanicInfo;
#[cfg(target_env = "newlib")]
#[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 {}
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);
}
#[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(
super::low_level_tests::CONTROLLER_TASK_HANDLE,
super::low_level_tests::PANIC,
);
#[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();
}
super::osal::thread::current().delete();
}
}