From eda3a85936a64fbfb5489e7a08fe4ed5c33c247b Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 17 Dec 2024 11:06:50 -0700 Subject: [PATCH] tests: timer: Create timer test Create a test to exercise the zephyr::timer timers. This uses both SimpleTimer and CallbackTimer intensely for 5 seconds and prints out the results. Signed-off-by: David Brown --- tests/timer/CMakeLists.txt | 8 ++ tests/timer/Cargo.toml | 18 +++++ tests/timer/prj.conf | 8 ++ tests/timer/src/lib.rs | 145 +++++++++++++++++++++++++++++++++++++ tests/timer/testcase.yaml | 17 +++++ 5 files changed, 196 insertions(+) create mode 100644 tests/timer/CMakeLists.txt create mode 100644 tests/timer/Cargo.toml create mode 100644 tests/timer/prj.conf create mode 100644 tests/timer/src/lib.rs create mode 100644 tests/timer/testcase.yaml diff --git a/tests/timer/CMakeLists.txt b/tests/timer/CMakeLists.txt new file mode 100644 index 0000000..0f240c9 --- /dev/null +++ b/tests/timer/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(timer_rust) + +rust_cargo_application() diff --git a/tests/timer/Cargo.toml b/tests/timer/Cargo.toml new file mode 100644 index 0000000..52edcd2 --- /dev/null +++ b/tests/timer/Cargo.toml @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +# This must be rustapp for now. +name = "rustapp" +version = "3.7.0" +edition = "2021" +description = "Tests of timeers" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rand = { version = "0.8", default-features = false } +rand_pcg = { version = "0.3.1", default-features = false } +zephyr = "3.7.0" diff --git a/tests/timer/prj.conf b/tests/timer/prj.conf new file mode 100644 index 0000000..295d919 --- /dev/null +++ b/tests/timer/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_MAIN_STACK_SIZE=2048 + +# Timers need alloc +CONFIG_RUST_ALLOC=y diff --git a/tests/timer/src/lib.rs b/tests/timer/src/lib.rs new file mode 100644 index 0000000..8676286 --- /dev/null +++ b/tests/timer/src/lib.rs @@ -0,0 +1,145 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +extern crate alloc; + +use core::{pin::Pin, sync::atomic::Ordering}; + +use alloc::{boxed::Box, vec::Vec}; +use rand::Rng; +use rand_pcg::Pcg32; +use zephyr::{ + printkln, sync::{atomic::AtomicUsize, Arc}, time::{Duration, NoWait, Tick}, timer::{Callback, CallbackTimer, SimpleTimer, StoppedTimer} +}; + +// Test the timers interface. There are a couple of things this tries to test: +// 1. Do timers dynamically allocated and dropped work. +// 2. Can simple timers count properly. +// 3. Can we wait on a Simple timer. +// 4. Do callbacks work with messages and semaphores. + +#[no_mangle] +extern "C" fn rust_main() { + printkln!("Tick frequency: {}", zephyr::time::SYS_FREQUENCY); + timer_test(); + printkln!("All tests passed"); +} + +fn timer_test() { + let mut rng = Pcg32::new(1, 1); + + // Track a global "stop" time when the entire test should be shut down. + // let mut total_test = StoppedTimer::new().start_simple(Duration::secs_at_least(5), NoWait); + let mut total_test = StoppedTimer::new().start_simple(Duration::secs_at_least(5), NoWait); + + // This simple timer lets us pause periodically to allow other timers to build up. + let mut period = StoppedTimer::new().start_simple( + Duration::millis_at_least(100), + Duration::millis_at_least(100), + ); + + let mut simples: Vec<_> = (0..10).map(|_| TestSimple::new(&mut rng)).collect(); + let atomics: Vec<_> = (0..10).map(|_| TestAtomic::new(&mut rng)).collect(); + + let mut count = 0; + loop { + // Wait for the period timer. + let num = period.read_count_wait(); + + if num > 1 { + // Getting this is actually a good indicator that we've overwhelmed ourselves with + // timers, and are stress testing things. + printkln!("Note: Missed period ticks"); + } + + count += 1; + + if count % 10 == 0 { + printkln!("Ticks {}", count); + } + + if total_test.read_count() > 0 { + break; + } + + simples.iter_mut().for_each(|m| m.update()); + } + + // Collect all of the times they fired. + let simple_count: usize = simples.iter().map(|s| s.count).sum(); + printkln!("Simple fired {} times", simple_count); + let atomic_count: usize = atomics.iter().map(|s| s.count()).sum(); + printkln!("Atomics fired {} times", atomic_count); + + printkln!("Period ticks: {}", count); + + // Now that everything is done and cleaned up, allow a little time to pass to make sure there + // are no stray timers. We can re-use the total test timer. + let mut total_test = total_test.stop().start_simple(Duration::millis_at_least(1), NoWait); + total_test.read_count_wait(); +} + +/// Test a SimpleTimer. +/// +/// This allocates a simple timer, and starts it with a small somewhat random period. It will track +/// the total number of times that it fires when checked. +struct TestSimple { + timer: SimpleTimer, + _delay: Tick, + count: usize, +} + +impl TestSimple { + fn new(rng: &mut impl Rng) -> TestSimple { + let delay = rng.gen_range(2..16); + TestSimple { + timer: StoppedTimer::new() + .start_simple(Duration::from_ticks(delay), Duration::from_ticks(delay)), + _delay: delay, + count: 0, + } + } + + /// Update from the total count from the timer itself. + fn update(&mut self) { + self.count += self.timer.read_count() as usize; + } +} + +/// Test a callback using an atomic counter. +/// +/// This allocates a Callback timer, and uses the callback to increment an atomic value. +struct TestAtomic { + _timer: Pin>>>, + counter: Arc, +} + +impl TestAtomic { + fn new(rng: &mut impl Rng) -> TestAtomic { + let delay = rng.gen_range(2..16); + let counter = Arc::new(AtomicUsize::new(0)); + TestAtomic { + _timer: StoppedTimer::new().start_callback( + Callback { + call: Self::expiry, + data: counter.clone(), + }, + Duration::from_ticks(delay), + Duration::from_ticks(delay), + ), + counter: counter.clone(), + } + } + + // Read the atomic count. + fn count(&self) -> usize { + self.counter.load(Ordering::Acquire) + } + + /// Expire the function + fn expiry(data: &Arc) { + data.fetch_add(1, Ordering::Relaxed); + } +} diff --git a/tests/timer/testcase.yaml b/tests/timer/testcase.yaml new file mode 100644 index 0000000..eb08eec --- /dev/null +++ b/tests/timer/testcase.yaml @@ -0,0 +1,17 @@ +common: + filter: CONFIG_RUST_SUPPORTED + platform_allow: + - qemu_cortex_m0 + - qemu_cortex_m3 + - qemu_riscv32 + - qemu_riscv32/qemu_virt_riscv32/smp + - qemu_riscv64 + - qemu_riscv64/qemu_virt_riscv64/smp + - nrf52840dk/nrf52840 +tests: + test.rust.timer: + harness: console + harness_config: + type: one_line + regex: + - "All tests passed"