diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a4071a3..e5c80d2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,6 +5,8 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] +#on: +# - push env: CARGO_TERM_COLOR: always @@ -13,9 +15,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose + - uses: actions/checkout@v3 + - name: Build --lib + run: cargo build --verbose test: runs-on: ubuntu-latest @@ -30,3 +32,39 @@ jobs: - uses: actions/checkout@v3 - name: Fmt run: cargo fmt --all -- --check + +# TODO: change to cargo build --example + xtensa-esp32-rust-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # TODO: to avoid setupping, ci should pass on prepared dockers on self-hosted runners + - name: Setup build environment + run: cargo install espup && espup install + - name: Build + run: cd ./examples/rust-examples/xtensa-esp32 && . $HOME/export-esp.sh && cargo build + - name: Fmt + run: cd ./examples/rust-examples/xtensa-esp32 && cargo fmt --all -- --check + + xtensa-esp32-static-library: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # TODO: to avoid setupping, ci should pass on prepared dockers on self-hosted runners + - name: Setup build environment + run: cargo install espup && espup install + - name: Build + run: cd ./c-library/xtensa-esp32 && . $HOME/export-esp.sh && cargo build + - name: Fmt + run: cd ./c-library/xtensa-esp32 && cargo fmt --all -- --check + + xtensa-esp32-c-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # TODO: to avoid setupping, ci should pass on prepared dockers on self-hosted runners + - name: Setup build environment + run: cargo install espup && espup install && sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 && mkdir -p ~/esp && cd ~/esp && git clone -b v5.2 --recursive https://github.com/espressif/esp-idf.git && cd ~/esp/esp-idf && ./install.sh esp32 + # TODO: to avoid building static library, this job should use artifacts from xtensa-esp32-static-library job + - name: Build + run: cd ./c-library/xtensa-esp32 && . $HOME/export-esp.sh && cargo build && cd ../../examples/c-examples/xtensa-esp32 && . $HOME/esp/esp-idf/export.sh && idf.py build \ No newline at end of file diff --git a/.gitignore b/.gitignore index ada8be9..8e809c5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,8 @@ Cargo.lock **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information -*.pdb \ No newline at end of file +*.pdb + +# Espessif generated files and directories +sdkconfig +build/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index bb26b5b..0ec132a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ name = "ma_rtos" version = "0.1.0" edition = "2021" -[dependencies] -lazy_static = "1.4" +[features] +default = [] +c-library = [] \ No newline at end of file diff --git a/README.md b/README.md index 3b82cf9..4232430 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# MA-RTOS +# Martos + +Martos is a simple RTOS for developing multi-agent real-time systems. +Software for Martos can be written on both Rust (recommended) and C languages. + +In current version it has only primitive task manager and timer counter. + +## Programming on Rust +For writing software on Rust you can use Martos as dependency: +``` +[dependencies] +ma_rtos = ... +``` + +Rust examples for different architecures you can see in examples/rust-examples. + +## Programming on C +For writing software on C you can link your project with Martos static library. +You can get Martos static library from release artifacts. +If you want to build Martos static library yourself, see c-library directory. +It contains static library targets for different architectures. + +C examples for different architecures you can see in examples/c-examples. diff --git a/c-library/xtensa-esp32/.cargo/config.toml b/c-library/xtensa-esp32/.cargo/config.toml new file mode 100644 index 0000000..20da577 --- /dev/null +++ b/c-library/xtensa-esp32/.cargo/config.toml @@ -0,0 +1,19 @@ +[target.xtensa-esp32-none-elf] +runner = "espflash flash --monitor" + + +[build] +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + + "-C", "link-arg=-nostartfiles", +] + +target = "xtensa-esp32-none-elf" + +[unstable] +build-std = ["core"] + +[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))'] +runner = "espflash flash --monitor" + diff --git a/c-library/xtensa-esp32/Cargo.toml b/c-library/xtensa-esp32/Cargo.toml new file mode 100644 index 0000000..e0c5131 --- /dev/null +++ b/c-library/xtensa-esp32/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "xtensa_esp32_static_lib" +version = "0.1.0" +edition = "2021" + +[lib] +name = "xtensa_esp32_static_lib" +crate-type = ["staticlib"] + +[profile.release] +debug = true + +[dependencies] +# TODO: path should be from git, then from crates.io +ma_rtos = { path = "../../", features = ["c-library"] } +esp32-hal = "0.18.0" +esp-backtrace = { version = "0.11.0", features = ["esp32", "panic-handler", "exception-handler", "println"] } +esp-println = { version = "0.9.0", features = ["esp32"] } + +[features] +default = ["esp32-hal/xtal-40mhz"] \ No newline at end of file diff --git a/c-library/xtensa-esp32/rust-toolchain.toml b/c-library/xtensa-esp32/rust-toolchain.toml new file mode 100644 index 0000000..a2f5ab5 --- /dev/null +++ b/c-library/xtensa-esp32/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/c-library/xtensa-esp32/src/lib.rs b/c-library/xtensa-esp32/src/lib.rs new file mode 100644 index 0000000..a7eb650 --- /dev/null +++ b/c-library/xtensa-esp32/src/lib.rs @@ -0,0 +1,36 @@ +// TODO: maybe all this should be in martos, not in c-library folder + +#![no_std] + +use ma_rtos::task_manager::TaskExecutor; +use ma_rtos::timer::{TickType, Timer}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +pub extern "C" fn setup_timer() { + Timer::setup_timer() +} + +#[no_mangle] +pub extern "C" fn loop_timer() { + Timer::loop_timer() +} + +#[no_mangle] +pub extern "C" fn stop_condition_timer() -> bool { + Timer::stop_condition_timer() +} + +#[no_mangle] +pub extern "C" fn get_tick_counter() -> TickType { + Timer::get_tick_counter() +} + +#[no_mangle] +pub extern "C" fn new_task_executor() -> TaskExecutor { + TaskExecutor::new() +} diff --git a/examples/c-examples/xtensa-esp32/CMakeLists.txt b/examples/c-examples/xtensa-esp32/CMakeLists.txt new file mode 100644 index 0000000..0a454d0 --- /dev/null +++ b/examples/c-examples/xtensa-esp32/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hello_world) diff --git a/examples/c-examples/xtensa-esp32/README.md b/examples/c-examples/xtensa-esp32/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/c-examples/xtensa-esp32/README.md @@ -0,0 +1 @@ + diff --git a/examples/c-examples/xtensa-esp32/main/CMakeLists.txt b/examples/c-examples/xtensa-esp32/main/CMakeLists.txt new file mode 100644 index 0000000..b4a1240 --- /dev/null +++ b/examples/c-examples/xtensa-esp32/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register(SRCS "hello_world_main.c" + INCLUDE_DIRS "") + +# Linking with martos static library. Set here path to it in your project. +# Here path is so for ci. +# TODO: should be more beautiful linking +target_link_libraries(${COMPONENT_LIB} ${CMAKE_CURRENT_LIST_DIR}/../../../../c-library/xtensa-esp32/target/xtensa-esp32-none-elf/debug/libxtensa_esp32_static_lib.a -Wl,--allow-multiple-definition) diff --git a/examples/c-examples/xtensa-esp32/main/hello_world_main.c b/examples/c-examples/xtensa-esp32/main/hello_world_main.c new file mode 100644 index 0000000..50b7f5b --- /dev/null +++ b/examples/c-examples/xtensa-esp32/main/hello_world_main.c @@ -0,0 +1,11 @@ +#include + +extern long get_tick_counter(); + +void app_main(void) +{ + long tick = get_tick_counter(); + printf("tick in c = %ld\n", tick); + + for (;;) {} +} diff --git a/examples/rust-examples/xtensa-esp32/.cargo/config.toml b/examples/rust-examples/xtensa-esp32/.cargo/config.toml new file mode 100644 index 0000000..20da577 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/.cargo/config.toml @@ -0,0 +1,19 @@ +[target.xtensa-esp32-none-elf] +runner = "espflash flash --monitor" + + +[build] +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + + "-C", "link-arg=-nostartfiles", +] + +target = "xtensa-esp32-none-elf" + +[unstable] +build-std = ["core"] + +[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))'] +runner = "espflash flash --monitor" + diff --git a/examples/rust-examples/xtensa-esp32/Cargo.toml b/examples/rust-examples/xtensa-esp32/Cargo.toml new file mode 100644 index 0000000..5e8dba1 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "example_xtensa_esp32" +version = "0.1.0" +edition = "2021" + +[profile.release] +debug = true + +[dependencies] +# TODO: path should be from git, then from crates.io +ma_rtos = { path = "../../../" } +esp32-hal = "0.18.0" +esp-backtrace = { version = "0.11.0", features = ["esp32", "panic-handler", "exception-handler", "println"] } +esp-println = { version = "0.9.0", features = ["esp32"] } + +[features] +default = ["esp32-hal/xtal-40mhz"] \ No newline at end of file diff --git a/examples/rust-examples/xtensa-esp32/rust-toolchain.toml b/examples/rust-examples/xtensa-esp32/rust-toolchain.toml new file mode 100644 index 0000000..a2f5ab5 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/examples/rust-examples/xtensa-esp32/src/main.rs b/examples/rust-examples/xtensa-esp32/src/main.rs new file mode 100644 index 0000000..716cdd3 --- /dev/null +++ b/examples/rust-examples/xtensa-esp32/src/main.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use esp32_hal::entry; +use esp_backtrace as _; +use esp_println::println; +use ma_rtos::task_manager; + +/// Counter to work with in loop. +static COUNTER: AtomicU32 = AtomicU32::new(1); + +/// Setup function for task to execute. +fn setup_fn() { + println!("Setup hello world!") +} + +/// Loop function for task to execute. +fn loop_fn() { + COUNTER.fetch_add(1, Ordering::Relaxed); + println!("Loop hello world!"); + println!("Counter = {}", unsafe { COUNTER.as_ptr().read() }); +} + +/// Stop condition function for task to execute. +fn stop_condition_fn() -> bool { + let value = unsafe { COUNTER.as_ptr().read() }; + if value % 50 == 0 { + return true; + } + return false; +} + +#[entry] +fn main() -> ! { + // Creating task executer. + let mut task_executor = task_manager::TaskExecutor::new(); + // Add task to execute. + task_executor.add_task(setup_fn, loop_fn, stop_condition_fn); + // Start task manager. + task_executor.start_task_manager(); +} diff --git a/src/connection.rs b/src/connection.rs deleted file mode 100644 index 8529bd7..0000000 --- a/src/connection.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::timer::TickType; - -/// Type for information from one timer -type TimerInformation = TickType; -/// Type for information from several timers -type TimersInformation = Vec; - -/// Sends timer information to neighbours (or adds it into package to send). -pub fn send_timer_information(_timer_information: TimerInformation) { - // TODO: Some code for sending information or adding it into package to send. It may be hardware dependent. -} - -/// Gets timer information from neighbour timers. -pub fn get_timers_information() -> TimersInformation { - // TODO: Some code, receiving information from other timers. It may be hardware dependent. - vec![10, 2, 3] -} diff --git a/src/lib.rs b/src/lib.rs index ccb8ae3..1f58ee2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,4 @@ -use crate::timer::{TickType, Timer}; -use lazy_static::lazy_static; -use std::thread; -use std::time::Duration; +#![no_std] -pub mod connection; +pub mod task_manager; pub mod timer; - -lazy_static! { - static ref TIMER: Timer = Timer::new(0, 5, 0.1); -} - -#[no_mangle] -pub extern "C" fn init_timer() { - TIMER.start(); - thread::sleep(Duration::from_millis(5)); -} - -#[no_mangle] -pub extern "C" fn stop_timer() { - thread::sleep(Duration::from_millis(5)); - TIMER.stop(); -} - -#[no_mangle] -pub extern "C" fn get_tick_counter() -> TickType { - TIMER.get_tick_counter() -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 30c14d0..0000000 --- a/src/main.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::timer::Timer; - -use core::sync::atomic::{AtomicU32, Ordering}; -use std::thread; -use std::time::Duration; - -mod connection; -mod task_manager; -mod timer; - -static COUNTER: AtomicU32 = AtomicU32::new(1); -static COUNTER2: AtomicU32 = AtomicU32::new(1); - -fn setup_fn() { - println!("Setup!") -} -fn loop_fn() { - COUNTER.fetch_add(1, Ordering::Relaxed); - println!("Counter {}", unsafe { COUNTER.as_ptr().read() }); - println!("Loop!") -} - -fn stop_condition_fn() -> bool { - let value = unsafe { COUNTER.as_ptr().read() }; - if value % 50 == 0 { - return true; - } - return false; -} - -fn setup_fn2() { - println!("Setup2!") -} -fn loop_fn2() { - COUNTER2.fetch_add(1, Ordering::Relaxed); - println!("Counter2 {}", unsafe { COUNTER2.as_ptr().read() }); - println!("Loop2!") -} - -fn stop_condition_fn2() -> bool { - let value = unsafe { COUNTER2.as_ptr().read() }; - if value % 55 == 0 { - return true; - } - return false; -} - -fn main() { - let timer = Timer::new(0, 5, 0.1); - - timer.start(); - println!("Ticks time: {}", timer.get_tick_counter()); - thread::sleep(Duration::from_millis(5)); - println!("Ticks time: {}", timer.get_tick_counter()); - thread::sleep(Duration::from_millis(3)); - timer.stop(); - - println!("Final ticks time: {}", timer.get_tick_counter()); - - let mut task_executor = task_manager::TaskExecutor::new(); - task_executor.add_task(setup_fn, loop_fn, stop_condition_fn); - task_executor.add_task(setup_fn2, loop_fn2, stop_condition_fn2); - task_executor.start_task_manager(); -} diff --git a/src/task_manager.rs b/src/task_manager.rs index 4df327e..5eb8524 100644 --- a/src/task_manager.rs +++ b/src/task_manager.rs @@ -1,3 +1,4 @@ +use core::array::from_fn; use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; @@ -5,19 +6,45 @@ use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; /// The number of tasks can fit into a type usize. pub type TaskNumberType = usize; +// TODO: rewrite with cfg! +#[cfg(not(feature = "c-library"))] +/// Type of setup function, that is called once at the beginning of task. +type TaskSetupFunctionType = fn() -> (); +#[cfg(feature = "c-library")] +/// Type of setup function, that is called once at the beginning of task. +type TaskSetupFunctionType = extern "C" fn() -> (); +#[cfg(not(feature = "c-library"))] +/// Type of loop function, that is called in loop. +type TaskLoopFunctionType = fn() -> (); +#[cfg(feature = "c-library")] +/// Type of loop function, that is called in loop. +type TaskLoopFunctionType = extern "C" fn() -> (); +#[cfg(not(feature = "c-library"))] +/// Type of condition function for stopping loop function execution. +type TaskStopConditionFunctionType = fn() -> bool; +#[cfg(feature = "c-library")] +/// Type of condition function for stopping loop function execution. +type TaskStopConditionFunctionType = extern "C" fn() -> bool; + +/// Max number of tasks. +// TODO: Should be dynamic array of tasks. +const MAX_NUMBER_OF_TASKS: TaskNumberType = 20; + +#[repr(C)] /// Task representation for task manager. struct Task { /// Setup function, that is called once at the beginning of task. - setup_fn: fn() -> (), + setup_fn: TaskSetupFunctionType, /// Loop function, that is called in loop. - loop_fn: fn() -> (), - /// Condition for stopping loop function execution. - stop_condition_fn: fn() -> bool, + loop_fn: TaskLoopFunctionType, + /// Condition function for stopping loop function execution. + stop_condition_fn: TaskStopConditionFunctionType, } -/// Future shell for task for execution +#[repr(C)] +/// Future shell for task for execution. struct FutureTask { - /// Task to execute. + /// Task to execute in task manager. task: Task, /// Marker for setup function completion. is_setup_completed: bool, @@ -27,6 +54,8 @@ impl Future for FutureTask { type Output = (); fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + let mut array: [usize; 8] = core::array::from_fn(|i| i); + array[0] = 5; if (self.task.stop_condition_fn)() { Poll::Ready(()) } else { @@ -60,27 +89,63 @@ fn task_waker() -> Waker { unsafe { Waker::from_raw(raw_waker) } } -/// Task executor representation. +#[repr(C)] +/// Task executor representation. Based on round-robin scheduling without priorities. pub struct TaskExecutor { - tasks: Vec, + /// Static array of tasks to execute. + tasks: [FutureTask; MAX_NUMBER_OF_TASKS], + /// Index of task, that should be executed. task_to_execute_index: TaskNumberType, + /// Number of tasks to execute. + tasks_number: TaskNumberType, } impl TaskExecutor { /// Creates new task executor. pub fn new() -> TaskExecutor { + #[cfg(not(feature = "c-library"))] + fn setup_fn() {} + #[cfg(not(feature = "c-library"))] + fn loop_fn() {} + #[cfg(not(feature = "c-library"))] + fn stop_condition_fn() -> bool { + return true; + } + #[cfg(feature = "c-library")] + extern "C" fn setup_fn() {} + #[cfg(feature = "c-library")] + extern "C" fn loop_fn() {} + #[cfg(feature = "c-library")] + extern "C" fn stop_condition_fn() -> bool { + return true; + } + + let tasks: [FutureTask; MAX_NUMBER_OF_TASKS] = from_fn(|_| { + let task = Task { + setup_fn: setup_fn, + loop_fn: loop_fn, + stop_condition_fn: stop_condition_fn, + }; + FutureTask { + task: task, + is_setup_completed: true, + } + }); + TaskExecutor { - tasks: vec![], + tasks: tasks, task_to_execute_index: 0, + tasks_number: 0, } } - /// Add task to task executor. You should pass setup and loop functions. + /// Add task to task executor. You should pass setup, loop and condition functions. + // TODO: Maybe it is better to pass tasks here. Functions are done for C compatibility. pub fn add_task( &mut self, - setup_fn: fn() -> (), - loop_fn: fn() -> (), - stop_condition_fn: fn() -> bool, + setup_fn: TaskSetupFunctionType, + loop_fn: TaskLoopFunctionType, + stop_condition_fn: TaskStopConditionFunctionType, ) { let task = Task { setup_fn, @@ -91,23 +156,28 @@ impl TaskExecutor { task, is_setup_completed: false, }; - self.tasks.push(future_task) + self.tasks[self.tasks_number] = future_task; + self.tasks_number += 1 } /// Starts task manager work. + // TODO: Support priorities. + // TODO: Delete tasks from task vector if they are pending pub fn start_task_manager(&mut self) -> ! { loop { - let waker = task_waker(); - let task = &mut self.tasks[self.task_to_execute_index]; - let mut task_future_pin = Pin::new(task); - let _ = task_future_pin - .as_mut() - .poll(&mut Context::from_waker(&waker)); - - if self.task_to_execute_index < self.tasks.len() - 1 { - self.task_to_execute_index += 1; - } else { - self.task_to_execute_index = 0; + if !self.tasks.is_empty() { + let waker = task_waker(); + let task = &mut self.tasks[self.task_to_execute_index]; + let mut task_future_pin = Pin::new(task); + let _ = task_future_pin + .as_mut() + .poll(&mut Context::from_waker(&waker)); + + if self.task_to_execute_index + 1 < self.tasks.len() { + self.task_to_execute_index += 1; + } else { + self.task_to_execute_index = 0; + } } } } diff --git a/src/timer.rs b/src/timer.rs index 53bddb3..509cf1a 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,82 +1,41 @@ -use std::sync::{Arc, Mutex, MutexGuard}; -use std::thread; -use std::time::Duration; - -use crate::connection; - /// Type for tick counting. It is signed for synchronization. It should be u128. pub type TickType = i64; /// The definition of the timers themselves. +/// TODO: Should contain synchronization period and synchronization scale. pub struct Timer { /// Number of ticks in timer. - tick_counter: Arc>, - /// Flag is timer running. - running: Arc>, - /// Synchronization period in ticks. - synchronization_period: TickType, - /// Scale coefficient for local vote protocol. - synchronization_scale: f64, + tick_counter: TickType, } +/// Operating system timer. +// TODO: Maybe it can be non static. It is static to make functions to pass to task manager. +// TODO: Default parameters should be read from config file. +static mut TIMER: Timer = Timer { tick_counter: 0 }; + impl Timer { - /// Creates new timer. - pub fn new( - tick_counter: TickType, - synchronization_period: TickType, - synchronization_scale: f64, - ) -> Timer { - Timer { - tick_counter: Arc::new(Mutex::new(tick_counter)), - running: Arc::new(Mutex::new(false)), - synchronization_period, - synchronization_scale, + /// Setup function. May be used for setting configuration parameters. + pub fn setup_timer() { + unsafe { + TIMER.tick_counter = 0; } } /// Starts timer ticking. - pub fn start(&self) { - let counter = self.tick_counter.clone(); - let running = self.running.clone(); - let synchronization_period = self.synchronization_period.clone(); - let synchronization_scale = self.synchronization_scale.clone(); - - *running.lock().unwrap() = true; - - // TODO: this ticking should be added as a privilege task in task manager. Now it is in a separate thread. - thread::spawn(move || { - while *running.lock().unwrap() { - thread::sleep(Duration::from_millis(1)); - let mut count = counter.lock().unwrap(); - // TODO: this ticking should work with hardware ticks or with system ticks, not '+1' - *count += 1; - if *count % synchronization_period == 0 { - Timer::synchronize(&mut count, synchronization_scale); - } - - connection::send_timer_information(*count); - } - }); + // TODO: What should happen after overflow? + pub fn loop_timer() { + unsafe { + TIMER.tick_counter += 1; + } } - /// Stops timer ticking. - pub fn stop(&self) { - *self.running.lock().unwrap() = false; + /// Stops timer ticking. By default, it does not stop. + pub fn stop_condition_timer() -> bool { + return false; } /// Returns tick counter. - pub fn get_tick_counter(&self) -> TickType { - *self.tick_counter.lock().unwrap() - } - - /// Synchronizes tick counter by information from other timers - fn synchronize(_count: &mut MutexGuard, synchronization_scale: f64) { - let timers_information = connection::get_timers_information(); - // Local vote protocol. - let old_count = **_count; - for info in timers_information { - **_count += - (synchronization_scale * (old_count - info).abs() as f64).round() as TickType; - } + pub fn get_tick_counter() -> TickType { + return unsafe { TIMER.tick_counter }; } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs deleted file mode 100644 index 4270386..0000000 --- a/tests/integration_tests.rs +++ /dev/null @@ -1,30 +0,0 @@ -#[cfg(test)] -mod integration_tests { - use ma_rtos::timer::Timer; - use std::thread; - use std::time::Duration; - - #[test] - /// Tests work of several timers. - fn test_several_timers() { - let timer1 = Timer::new(0, 10000, 0.1); - let timer2 = Timer::new(10, 10000, 0.1); - - timer1.start(); - thread::sleep(Duration::from_millis(2)); - let count1 = timer1.get_tick_counter(); - - timer2.start(); - thread::sleep(Duration::from_millis(3)); - let count2 = timer2.get_tick_counter(); - - timer2.stop(); - let count3 = timer1.get_tick_counter(); - timer1.stop(); - - // Exact values cannot be presented because of threads - assert!(count1 <= 2); - assert!(count2 <= 12); - assert!(count3 <= 5); - } -} diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs index c6657c9..a7cfc0d 100644 --- a/tests/unit_tests.rs +++ b/tests/unit_tests.rs @@ -1,63 +1,313 @@ #[cfg(test)] mod unit_tests { + use ma_rtos::task_manager; use ma_rtos::timer::Timer; - use std::thread; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::thread::{sleep, spawn}; use std::time::Duration; + // TODO: refactor unit tests. They should check less. Separate tests for setup, loop and stop functions. + // TODO: refactor unit tests. Task manager and timer tests should be in different files in one directory. + + #[test] + /// Tests if task manager without tasks works during 1 second without panic. + fn test_empty_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); + + assert_eq!(fun_thread.is_finished(), false); + } + + /// Counter for task for test_one_finite_task_task_manager. + static TEST_ONE_FINITE_TASK_TASK_MANAGER_COUNTER: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_one_finite_task_task_manager. + fn test_one_finite_task_task_manager_setup_fn() {} + /// Loop function for task for test_one_finite_task_task_manager. + fn test_one_finite_task_task_manager_loop_fn() { + TEST_ONE_FINITE_TASK_TASK_MANAGER_COUNTER.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_one_finite_task_task_manager. + fn test_one_finite_task_task_manager_stop_condition_fn() -> bool { + let value = unsafe { TEST_ONE_FINITE_TASK_TASK_MANAGER_COUNTER.as_ptr().read() }; + if value % 50 == 0 { + return true; + } + return false; + } + #[test] + /// Tests if task manager with one finite task works correctly during 1 second without panic. + fn test_one_finite_task_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_one_finite_task_task_manager_setup_fn, + test_one_finite_task_task_manager_loop_fn, + test_one_finite_task_task_manager_stop_condition_fn, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); + + assert_eq!(fun_thread.is_finished(), false); + assert_eq!( + unsafe { TEST_ONE_FINITE_TASK_TASK_MANAGER_COUNTER.as_ptr().read() }, + 50 + ); + } + + /// Counter for task for test_one_infinite_task_task_manager. + static TEST_ONE_INFINITE_TASK_TASK_MANAGER_COUNTER: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_one_infinite_task_task_manager. + fn test_one_infinite_task_task_manager_setup_fn() {} + /// Loop function for task for test_one_infinite_task_task_manager. + fn test_one_infinite_task_task_manager_loop_fn() { + TEST_ONE_INFINITE_TASK_TASK_MANAGER_COUNTER.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_one_infinite_task_task_manager. + fn test_one_infinite_task_task_manager_stop_condition_fn() -> bool { + return false; + } + #[test] + /// Tests if task manager with one infinite task works correctly during 1 second without panic. + fn test_one_infinite_task_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_one_infinite_task_task_manager_setup_fn, + test_one_infinite_task_task_manager_loop_fn, + test_one_infinite_task_task_manager_stop_condition_fn, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); + + assert_eq!(fun_thread.is_finished(), false); + } + + /// Counter for task for test_two_finite_tasks_task_manager. + static TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER1: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_setup_fn1() {} + /// Loop function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_loop_fn1() { + TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER1.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_stop_condition_fn1() -> bool { + let value = unsafe { TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER1.as_ptr().read() }; + if value % 50 == 0 { + return true; + } + return false; + } + /// Counter for task for test_two_finite_tasks_task_manager. + static TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER2: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_setup_fn2() {} + /// Loop function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_loop_fn2() { + TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER2.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_finite_tasks_task_manager. + fn test_two_finite_tasks_task_manager_stop_condition_fn2() -> bool { + let value = unsafe { TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER2.as_ptr().read() }; + if value % 25 == 0 { + return true; + } + return false; + } + #[test] + /// Tests if task manager with two finite tasks works correctly during 1 second without panic. + fn test_two_finite_tasks_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_two_finite_tasks_task_manager_setup_fn1, + test_two_finite_tasks_task_manager_loop_fn1, + test_two_finite_tasks_task_manager_stop_condition_fn1, + ); + task_executor.add_task( + test_two_finite_tasks_task_manager_setup_fn2, + test_two_finite_tasks_task_manager_loop_fn2, + test_two_finite_tasks_task_manager_stop_condition_fn2, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); + + assert_eq!(fun_thread.is_finished(), false); + assert_eq!( + unsafe { TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER1.as_ptr().read() }, + 50 + ); + assert_eq!( + unsafe { TEST_TWO_FINITE_TASK_TASK_MANAGER_COUNTER2.as_ptr().read() }, + 25 + ); + } + + /// Counter for task for test_two_different_tasks_task_manager. + static TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER1: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_setup_fn1() {} + /// Loop function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_loop_fn1() { + TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER1.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_stop_condition_fn1() -> bool { + let value = unsafe { + TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER1 + .as_ptr() + .read() + }; + if value % 50 == 0 { + return true; + } + return false; + } + /// Counter for task for test_two_different_tasks_task_manager. + static TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER2: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_setup_fn2() {} + /// Loop function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_loop_fn2() { + TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER2.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_different_tasks_task_manager. + fn test_two_different_tasks_task_manager_stop_condition_fn2() -> bool { + return false; + } #[test] - /// Tests new function of timer. - fn test_timer_new() { - let timer1 = Timer::new(0, 10000, 0.1); - let timer2 = Timer::new(42, 10000, 0.1); - let count1 = timer1.get_tick_counter(); - let count2 = timer2.get_tick_counter(); + /// Tests if task manager with two different (finite and infinite) tasks works correctly during 1 second without panic. + fn test_two_different_tasks_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_two_different_tasks_task_manager_setup_fn1, + test_two_different_tasks_task_manager_loop_fn1, + test_two_different_tasks_task_manager_stop_condition_fn1, + ); + task_executor.add_task( + test_two_different_tasks_task_manager_setup_fn2, + test_two_different_tasks_task_manager_loop_fn2, + test_two_different_tasks_task_manager_stop_condition_fn2, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); - assert_eq!(count1, 0); - assert_eq!(count2, 42); + assert_eq!(fun_thread.is_finished(), false); + assert_eq!( + unsafe { + TEST_TWO_DIFFERENT_TASK_TASK_MANAGER_COUNTER1 + .as_ptr() + .read() + }, + 50 + ); } + /// Counter for task for test_two_infinite_tasks_task_manager. + static TEST_TWO_INFINITE_TASK_TASK_MANAGER_COUNTER1: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_setup_fn1() {} + /// Loop function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_loop_fn1() { + TEST_TWO_INFINITE_TASK_TASK_MANAGER_COUNTER1.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_stop_condition_fn1() -> bool { + return false; + } + /// Counter for task for test_two_infinite_tasks_task_manager. + static TEST_TWO_INFINITE_TASK_TASK_MANAGER_COUNTER2: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_setup_fn2() {} + /// Loop function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_loop_fn2() { + TEST_TWO_INFINITE_TASK_TASK_MANAGER_COUNTER2.fetch_add(1, Ordering::Relaxed); + } + /// Stop function for task for test_two_infinite_tasks_task_manager. + fn test_two_infinite_tasks_task_manager_stop_condition_fn2() -> bool { + return false; + } #[test] - /// Tests start function of timer. - fn test_timer_start() { - let timer = Timer::new(0, 10000, 0.1); - timer.start(); - thread::sleep(Duration::from_millis(2)); - let count = timer.get_tick_counter(); + /// Tests if task manager with two infinite tasks works correctly during 1 second without panic. + fn test_two_infinite_tasks_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_two_infinite_tasks_task_manager_setup_fn1, + test_two_infinite_tasks_task_manager_loop_fn1, + test_two_infinite_tasks_task_manager_stop_condition_fn1, + ); + task_executor.add_task( + test_two_infinite_tasks_task_manager_setup_fn2, + test_two_infinite_tasks_task_manager_loop_fn2, + test_two_infinite_tasks_task_manager_stop_condition_fn2, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); - // Exact values cannot be presented because of threads - assert!(count <= 2); + assert_eq!(fun_thread.is_finished(), false); } + /// Counter for task for test_setup_task_manager. + static TEST_SETUP_TASK_MANAGER_COUNTER: AtomicU32 = AtomicU32::new(1); + /// Setup function for task for test_setup_task_manager. + fn test_setup_task_manager_setup_fn() { + TEST_SETUP_TASK_MANAGER_COUNTER.store(42, Ordering::Relaxed); + } + /// Loop function for task for test_setup_task_manager. + fn test_setup_task_manager_loop_fn() {} + /// Stop function for task for test_setup_task_manager. + fn test_setup_task_manager_stop_condition_fn() -> bool { + return false; + } #[test] - /// Tests stop function of timer. - fn test_timer_stop() { - let timer = Timer::new(0, 10000, 0.1); - timer.start(); - thread::sleep(Duration::from_millis(3)); - timer.stop(); - let count = timer.get_tick_counter(); + /// Tests if task manager works correctly with setup function during 1 second without panic. + fn test_setup_task_manager() { + let fun_thread = spawn(|| { + let mut task_executor = task_manager::TaskExecutor::new(); + task_executor.add_task( + test_setup_task_manager_setup_fn, + test_setup_task_manager_loop_fn, + test_setup_task_manager_stop_condition_fn, + ); + task_executor.start_task_manager() + }); + sleep(Duration::from_secs(1)); - // Exact values cannot be presented because of threads - assert!(count <= 3); + assert_eq!(fun_thread.is_finished(), false); + assert_eq!( + unsafe { TEST_SETUP_TASK_MANAGER_COUNTER.as_ptr().read() }, + 42 + ); } #[test] - /// Tests get_tick_counter function of timer. - fn test_timer_get_tick_counter() { - let timer = Timer::new(0, 10000, 0.1); - timer.start(); - let count0 = timer.get_tick_counter(); - thread::sleep(Duration::from_millis(3)); - let count1 = timer.get_tick_counter(); - thread::sleep(Duration::from_millis(4)); - let count2 = timer.get_tick_counter(); - timer.stop(); - let count3 = timer.get_tick_counter(); + /// Tests setup timer function and getting tick counter (bad unit test). + fn test_setup_timer() { + Timer::setup_timer(); + assert_eq!(Timer::get_tick_counter(), 0); + } - assert_eq!(count0, 0); - // Exact values cannot be presented because of threads - assert!(count1 <= 3); - assert!(count2 <= 7); - assert!(count3 <= 7); + #[test] + /// Tests loop timer function. + fn test_loop_timer() { + Timer::setup_timer(); + Timer::loop_timer(); + assert_eq!(Timer::get_tick_counter(), 1); + } + + #[test] + /// Tests stop condition timer function. + fn test_stop_condition_timer() { + assert_eq!(Timer::stop_condition_timer(), false); } }