#![cfg(feature = "posix")]
use core::ptr::null_mut;
use core::time::Duration;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use osal_rs::os::*;
use osal_rs::utils::{Error, OsalRsBool, Result, MAX_DELAY};
use osal_rs::{log_debug, log_info};
const TAG: &str = "ErrorPathTests";
#[test]
fn test_raw_mutex_after_delete() -> Result<()> {
log_info!(TAG, "Starting test_raw_mutex_after_delete");
let mut mutex = RawMutex::new()?;
assert!(!mutex.is_null());
mutex.delete();
assert!(mutex.is_null());
assert_eq!(mutex.lock(), OsalRsBool::False);
assert_eq!(mutex.lock_from_isr(), OsalRsBool::False);
assert_eq!(mutex.unlock(), OsalRsBool::False);
assert_eq!(mutex.unlock_from_isr(), OsalRsBool::False);
mutex.delete();
assert!(mutex.is_null());
log_info!(TAG, "test_raw_mutex_after_delete PASSED");
Ok(())
}
#[test]
fn test_raw_mutex_lock_from_isr_contended() -> Result<()> {
log_info!(TAG, "Starting test_raw_mutex_lock_from_isr_contended");
let mutex = Arc::new(RawMutex::new()?);
let holder_mutex = mutex.clone();
let acquired = Arc::new(AtomicBool::new(false));
let release = Arc::new(AtomicBool::new(false));
let holder_acquired = acquired.clone();
let holder_release = release.clone();
let mut holder = Thread::new("raw-mutex-holder", 8192, 5);
let spawned = holder.spawn_simple(move || {
holder_mutex.lock();
holder_acquired.store(true, Ordering::Release);
while !holder_release.load(Ordering::Acquire) {
System::delay(1);
}
holder_mutex.unlock();
Ok(Arc::new(()))
})?;
while !acquired.load(Ordering::Acquire) {
System::delay(1);
}
let contended = mutex.lock_from_isr();
log_debug!(TAG, "contended lock_from_isr -> {:?}", contended);
assert_eq!(contended, OsalRsBool::False);
release.store(true, Ordering::Release);
spawned.join(null_mut())?;
assert_eq!(mutex.lock_from_isr(), OsalRsBool::True);
assert_eq!(mutex.unlock_from_isr(), OsalRsBool::True);
log_info!(TAG, "test_raw_mutex_lock_from_isr_contended PASSED");
Ok(())
}
#[test]
fn test_mutex_lock_from_isr_contended() -> Result<()> {
log_info!(TAG, "Starting test_mutex_lock_from_isr_contended");
let mutex = Arc::new(Mutex::new(0u32));
let holder_mutex = mutex.clone();
let acquired = Arc::new(AtomicBool::new(false));
let release = Arc::new(AtomicBool::new(false));
let holder_acquired = acquired.clone();
let holder_release = release.clone();
let mut holder = Thread::new("mutex-holder", 8192, 5);
let spawned = holder.spawn_simple(move || {
let mut guard = holder_mutex.lock()?;
*guard = 5;
holder_acquired.store(true, Ordering::Release);
while !holder_release.load(Ordering::Acquire) {
System::delay(1);
}
Ok(Arc::new(()))
})?;
while !acquired.load(Ordering::Acquire) {
System::delay(1);
}
assert!(matches!(mutex.lock_from_isr(), Err(Error::MutexLockFailed)));
assert!(matches!(
mutex.lock_from_isr_explicit(),
Err(Error::MutexLockFailed)
));
release.store(true, Ordering::Release);
spawned.join(null_mut())?;
assert_eq!(*mutex.lock()?, 5);
log_info!(TAG, "test_mutex_lock_from_isr_contended PASSED");
Ok(())
}
#[test]
fn test_mutex_accessors_without_locking() -> Result<()> {
log_info!(TAG, "Starting test_mutex_accessors_without_locking");
let mut mutex = Mutex::new(vec![1u8, 2, 3]);
mutex.get_mut().push(4);
assert_eq!(mutex.lock()?.len(), 4);
let inner = mutex.into_inner()?;
assert_eq!(inner, vec![1, 2, 3, 4]);
let shared = Mutex::new_arc(0u32);
let clone = shared.clone();
*clone.lock()? += 1;
assert_eq!(*shared.lock()?, 1);
log_debug!(TAG, "Mutex debug: {:?} display: {}", shared, shared);
assert!(!format!("{:?}", shared).is_empty());
assert!(!format!("{}", shared).is_empty());
log_info!(TAG, "test_mutex_accessors_without_locking PASSED");
Ok(())
}
#[test]
fn test_semaphore_after_delete() -> Result<()> {
log_info!(TAG, "Starting test_semaphore_after_delete");
let mut sem = Semaphore::new(2, 1)?;
assert!(!sem.is_null());
sem.delete();
assert!(sem.is_null());
assert_eq!(sem.wait(Duration::from_millis(1)), OsalRsBool::False);
assert_eq!(sem.wait_from_isr(), OsalRsBool::False);
assert_eq!(sem.signal(), OsalRsBool::False);
assert_eq!(sem.signal_from_isr(), OsalRsBool::False);
sem.delete();
assert!(sem.is_null());
log_info!(TAG, "test_semaphore_after_delete PASSED");
Ok(())
}
#[test]
fn test_semaphore_blocking_wait_forever() -> Result<()> {
log_info!(TAG, "Starting test_semaphore_blocking_wait_forever");
let sem = Arc::new(Semaphore::new(1, 0)?);
let signaller_sem = sem.clone();
let mut signaller = Thread::new("sem-signaller", 8192, 5);
let spawned = signaller.spawn_simple(move || {
System::delay(40);
signaller_sem.signal();
Ok(Arc::new(()))
})?;
assert_eq!(MAX_DELAY.to_ticks(), types::TickType::MAX);
assert_eq!(sem.wait(MAX_DELAY), OsalRsBool::True);
spawned.join(null_mut())?;
log_info!(TAG, "test_semaphore_blocking_wait_forever PASSED");
Ok(())
}
#[test]
fn test_semaphore_deadline_crosses_second_boundary() -> Result<()> {
log_info!(TAG, "Starting test_semaphore_deadline_crosses_second_boundary");
let sem = Semaphore::new(4, 4)?;
wait_for_late_subsecond();
assert_eq!(sem.wait(Duration::from_millis(999)), OsalRsBool::True);
assert_eq!(sem.wait(Duration::from_millis(1)), OsalRsBool::True);
log_info!(TAG, "test_semaphore_deadline_crosses_second_boundary PASSED");
Ok(())
}
#[test]
fn test_event_group_after_delete() -> Result<()> {
log_info!(TAG, "Starting test_event_group_after_delete");
let mut events = EventGroup::new()?;
events.set(0b101);
assert!(!events.is_null());
events.delete();
assert!(events.is_null());
assert_eq!(events.get(), 0);
assert_eq!(events.get_from_isr(), 0);
assert_eq!(events.set(0b1), 0);
assert_eq!(events.clear(0b1), 0);
assert_eq!(events.wait(0b1, false, 1), 0);
assert_eq!(events.wait_with_to_tick(0b1, false, Duration::from_millis(1)), 0);
assert!(matches!(events.set_from_isr(0b1), Err(Error::NullPtr)));
assert!(matches!(events.clear_from_isr(0b1), Err(Error::NullPtr)));
events.delete();
assert!(events.is_null());
log_info!(TAG, "test_event_group_after_delete PASSED");
Ok(())
}
#[test]
fn test_event_group_blocking_wait_forever() -> Result<()> {
log_info!(TAG, "Starting test_event_group_blocking_wait_forever");
let events = Arc::new(EventGroup::new()?);
let setter_events = events.clone();
let mut setter = Thread::new("eg-setter", 8192, 5);
let spawned = setter.spawn_simple(move || {
System::delay(40);
setter_events.set(0b110);
Ok(Arc::new(()))
})?;
let bits = events.wait(0b010, true, types::TickType::MAX);
log_debug!(TAG, "unbounded wait returned {:#b}", bits);
assert_eq!(bits & 0b010, 0b010);
spawned.join(null_mut())?;
log_info!(TAG, "test_event_group_blocking_wait_forever PASSED");
Ok(())
}
#[test]
fn test_event_group_deadline_crosses_second_boundary() -> Result<()> {
log_info!(TAG, "Starting test_event_group_deadline_crosses_second_boundary");
let events = EventGroup::new()?;
events.set(0b1);
wait_for_late_subsecond();
assert_eq!(events.wait(0b1, true, 999) & 0b1, 0b1);
assert_eq!(events.wait(0b1, true, 1) & 0b1, 0b1);
log_info!(TAG, "test_event_group_deadline_crosses_second_boundary PASSED");
Ok(())
}
#[test]
fn test_queue_invalid_construction() -> Result<()> {
log_info!(TAG, "Starting test_queue_invalid_construction");
assert!(matches!(Queue::new(0, 4), Err(Error::InvalidQueueSize)));
assert!(matches!(Queue::new(4, 0), Err(Error::InvalidQueueSize)));
assert!(matches!(Queue::new(0, 0), Err(Error::InvalidQueueSize)));
assert!(Queue::new(1, 1).is_ok());
log_info!(TAG, "test_queue_invalid_construction PASSED");
Ok(())
}
#[test]
fn test_queue_after_delete() -> Result<()> {
log_info!(TAG, "Starting test_queue_after_delete");
let mut queue = Queue::new(2, 4)?;
queue.post(&[1u8, 2, 3, 4], 0)?;
assert!(!queue.is_null());
queue.delete();
assert!(queue.is_null());
let mut buffer = [0u8; 4];
assert!(matches!(queue.fetch(&mut buffer, 0), Err(Error::NullPtr)));
assert!(matches!(queue.fetch_from_isr(&mut buffer), Err(Error::NullPtr)));
assert!(matches!(queue.post(&[1u8, 2, 3, 4], 0), Err(Error::NullPtr)));
assert!(matches!(
queue.post_from_isr(&[1u8, 2, 3, 4]),
Err(Error::NullPtr)
));
assert!(matches!(
queue.fetch_with_to_tick(&mut buffer, Duration::from_millis(1)),
Err(Error::NullPtr)
));
assert!(matches!(
queue.post_with_to_tick(&[1u8, 2, 3, 4], Duration::from_millis(1)),
Err(Error::NullPtr)
));
queue.delete();
assert!(queue.is_null());
log_info!(TAG, "test_queue_after_delete PASSED");
Ok(())
}
#[test]
fn test_queue_undersized_buffers() -> Result<()> {
log_info!(TAG, "Starting test_queue_undersized_buffers");
let queue = Queue::new(2, 4)?;
let mut too_small = [0u8; 2];
assert!(matches!(
queue.fetch(&mut too_small, 0),
Err(Error::InvalidQueueSize)
));
assert!(matches!(
queue.fetch_from_isr(&mut too_small),
Err(Error::InvalidQueueSize)
));
assert!(matches!(
queue.post(&[1u8, 2], 0),
Err(Error::InvalidQueueSize)
));
assert!(matches!(
queue.post_from_isr(&[1u8, 2]),
Err(Error::InvalidQueueSize)
));
log_info!(TAG, "test_queue_undersized_buffers PASSED");
Ok(())
}
#[test]
fn test_queue_full_and_empty_from_isr() -> Result<()> {
log_info!(TAG, "Starting test_queue_full_and_empty_from_isr");
let queue = Queue::new(1, 4)?;
let mut buffer = [0u8; 4];
assert!(matches!(
queue.fetch_from_isr(&mut buffer),
Err(Error::Timeout)
));
queue.post_from_isr(&[7u8, 7, 7, 7])?;
assert!(matches!(
queue.post_from_isr(&[8u8, 8, 8, 8]),
Err(Error::QueueFull)
));
queue.fetch_from_isr(&mut buffer)?;
assert_eq!(buffer, [7, 7, 7, 7]);
log_info!(TAG, "test_queue_full_and_empty_from_isr PASSED");
Ok(())
}
#[test]
fn test_queue_blocking_forever_both_directions() -> Result<()> {
log_info!(TAG, "Starting test_queue_blocking_forever_both_directions");
let queue = Arc::new(Queue::new(1, 4)?);
let producer_queue = queue.clone();
let mut producer = Thread::new("q-producer", 8192, 5);
let spawned_producer = producer.spawn_simple(move || {
System::delay(40);
producer_queue.post(&[1u8, 2, 3, 4], 0)?;
Ok(Arc::new(()))
})?;
let mut buffer = [0u8; 4];
queue.fetch(&mut buffer, types::TickType::MAX)?;
assert_eq!(buffer, [1, 2, 3, 4]);
spawned_producer.join(null_mut())?;
queue.post(&[9u8, 9, 9, 9], 0)?;
let consumer_queue = queue.clone();
let mut consumer = Thread::new("q-consumer", 8192, 5);
let spawned_consumer = consumer.spawn_simple(move || {
System::delay(40);
let mut scratch = [0u8; 4];
consumer_queue.fetch(&mut scratch, 100)?;
Ok(Arc::new(()))
})?;
queue.post(&[5u8, 6, 7, 8], types::TickType::MAX)?;
spawned_consumer.join(null_mut())?;
queue.fetch(&mut buffer, 100)?;
assert_eq!(buffer, [5, 6, 7, 8]);
log_info!(TAG, "test_queue_blocking_forever_both_directions PASSED");
Ok(())
}
#[test]
fn test_queue_deadline_crosses_second_boundary() -> Result<()> {
log_info!(TAG, "Starting test_queue_deadline_crosses_second_boundary");
let queue = Queue::new(2, 4)?;
wait_for_late_subsecond();
queue.post(&[1u8, 2, 3, 4], 999)?;
let mut buffer = [0u8; 4];
wait_for_late_subsecond();
queue.fetch(&mut buffer, 999)?;
assert_eq!(buffer, [1, 2, 3, 4]);
log_info!(TAG, "test_queue_deadline_crosses_second_boundary PASSED");
Ok(())
}
#[test]
fn test_timer_after_delete() -> Result<()> {
log_info!(TAG, "Starting test_timer_after_delete");
let mut timer = Timer::new("dead-timer", 20, true, None, |_, param| Ok(param.unwrap_or(Arc::new(()))))?;
assert!(!timer.is_null());
assert_eq!(timer.delete(0), OsalRsBool::True);
assert!(timer.is_null());
assert_eq!(timer.start(0), OsalRsBool::False);
assert_eq!(timer.stop(0), OsalRsBool::False);
assert_eq!(timer.reset(0), OsalRsBool::False);
assert_eq!(timer.change_period(50, 0), OsalRsBool::False);
assert_eq!(timer.delete(0), OsalRsBool::False);
assert_eq!(timer.start_with_to_tick(Duration::from_millis(1)), OsalRsBool::False);
assert_eq!(timer.stop_with_to_tick(Duration::from_millis(1)), OsalRsBool::False);
assert_eq!(timer.reset_with_to_tick(Duration::from_millis(1)), OsalRsBool::False);
assert_eq!(
timer.change_period_with_to_tick(Duration::from_millis(50), Duration::from_millis(1)),
OsalRsBool::False
);
assert_eq!(timer.delete_with_to_tick(Duration::from_millis(1)), OsalRsBool::False);
log_info!(TAG, "test_timer_after_delete PASSED");
Ok(())
}
#[test]
fn test_thread_null_handle_guards() -> Result<()> {
log_info!(TAG, "Starting test_thread_null_handle_guards");
let unspawned = Thread::new("never-spawned", 1024, 3);
assert!(unspawned.is_null());
assert!(matches!(unspawned.join(null_mut()), Err(Error::NullPtr)));
assert!(matches!(
unspawned.notify(ThreadNotification::Increment),
Err(Error::NullPtr)
));
let mut woken = 0;
assert!(matches!(
unspawned.notify_from_isr(ThreadNotification::Increment, &mut woken),
Err(Error::NullPtr)
));
assert!(matches!(
unspawned.wait_notification(0, 0, 0),
Err(Error::NullPtr)
));
assert!(matches!(
unspawned.wait_notification_with_to_tick(0, 0, Duration::from_millis(1)),
Err(Error::NullPtr)
));
unspawned.suspend();
unspawned.resume();
let metadata = unspawned.get_metadata();
log_debug!(TAG, "unspawned metadata: {:?}", metadata);
assert_eq!(metadata.state, ThreadState::Invalid);
assert_eq!(metadata.name.as_str(), "never-spawned");
assert_eq!(metadata.priority, 3);
let from_zero = Thread::get_metadata_from_handle(0);
assert_eq!(from_zero.state, ThreadState::Invalid);
assert_eq!(from_zero.name.len(), 0);
struct Lowest;
impl ToPriority for Lowest {
fn to_priority(&self) -> types::UBaseType {
1
}
}
assert!(Thread::new_with_handle(0, "zero", 1024, 1).is_err());
assert!(Thread::new_with_handle_and_to_priority(0, "zero", 1024, Lowest).is_err());
log_info!(TAG, "test_thread_null_handle_guards PASSED");
Ok(())
}
#[test]
fn test_thread_notification_without_overwrite_conflict() -> Result<()> {
log_info!(TAG, "Starting test_thread_notification_without_overwrite_conflict");
let current = Thread::get_current();
current.notify(ThreadNotification::SetValueWithoutOverwrite(11))?;
assert!(matches!(
current.notify(ThreadNotification::SetValueWithoutOverwrite(22)),
Err(Error::QueueFull)
));
assert_eq!(current.wait_notification(0, 0, 0)?, 11);
current.notify(ThreadNotification::SetValueWithoutOverwrite(33))?;
assert_eq!(current.wait_notification(0, 0, 0)?, 33);
log_info!(TAG, "test_thread_notification_without_overwrite_conflict PASSED");
Ok(())
}
#[test]
fn test_thread_notification_all_actions() -> Result<()> {
log_info!(TAG, "Starting test_thread_notification_all_actions");
let current = Thread::get_current();
current.notify(ThreadNotification::NoAction)?;
assert_eq!(current.wait_notification(0, 0, 0)?, 0);
current.notify(ThreadNotification::SetBits(0b0101))?;
assert_eq!(current.wait_notification(0, 0, 0)?, 0b0101);
current.notify(ThreadNotification::SetBits(0b1010))?;
assert_eq!(current.wait_notification(0, 0, 0)?, 0b1111);
current.notify(ThreadNotification::SetBits(0))?;
assert_eq!(current.wait_notification(0b0001, 0, 0)?, 0b1110);
current.notify(ThreadNotification::SetBits(0))?;
assert_eq!(current.wait_notification(0, 0b0010, 0)?, 0b1110);
current.notify(ThreadNotification::SetBits(0))?;
assert_eq!(current.wait_notification(0, 0, 0)?, 0b1100);
current.notify(ThreadNotification::SetValueWithOverwrite(41))?;
assert_eq!(current.wait_notification(0, 0, 0)?, 41);
current.notify(ThreadNotification::Increment)?;
assert_eq!(current.wait_notification(0, 0, 0)?, 42);
assert!(matches!(
current.wait_notification(0, 0, 5),
Err(Error::Timeout)
));
log_info!(TAG, "test_thread_notification_all_actions PASSED");
Ok(())
}
#[test]
fn test_thread_wait_notification_forever() -> Result<()> {
log_info!(TAG, "Starting test_thread_wait_notification_forever");
let waiter = Arc::new(Thread::get_current());
let notifier_target = waiter.clone();
let mut notifier = Thread::new("notify-source", 8192, 5);
let spawned = notifier.spawn_simple(move || {
System::delay(40);
notifier_target.notify(ThreadNotification::SetValueWithOverwrite(0x5A5A))?;
Ok(Arc::new(()))
})?;
let value = waiter.wait_notification(0, 0, types::TickType::MAX)?;
log_debug!(TAG, "unbounded wait_notification got {:#x}", value);
assert_eq!(value, 0x5A5A);
spawned.join(null_mut())?;
log_info!(TAG, "test_thread_wait_notification_forever PASSED");
Ok(())
}
#[test]
fn test_thread_notification_deadline_crosses_second_boundary() -> Result<()> {
log_info!(TAG, "Starting test_thread_notification_deadline_crosses_second_boundary");
let current = Thread::get_current();
current.notify(ThreadNotification::SetValueWithOverwrite(1))?;
wait_for_late_subsecond();
assert_eq!(current.wait_notification(0, 0, 999)?, 1);
log_info!(TAG, "test_thread_notification_deadline_crosses_second_boundary PASSED");
Ok(())
}
fn wait_for_late_subsecond() {
loop {
let nanos = System::get_current_time().subsec_nanos();
if (10_000_000..900_000_000).contains(&nanos) {
return;
}
System::delay(1);
}
}
#[cfg(feature = "serde")]
mod broken_payloads {
use osal_rs::os::BytesHasLen;
use osal_rs_serde::{Deserialize, Deserializer, Error as SerdeError, Serialize, Serializer};
#[derive(Debug, Default, PartialEq)]
pub struct NeverDecodes(pub u8);
impl BytesHasLen for NeverDecodes {
fn len(&self) -> usize {
1
}
}
impl Serialize for NeverDecodes {
fn serialize<S: Serializer>(
&self,
name: &str,
serializer: &mut S,
) -> core::result::Result<(), S::Error> {
serializer.serialize_u8(name, self.0)
}
}
impl Deserialize for NeverDecodes {
fn deserialize<D: Deserializer>(
_deserializer: &mut D,
_name: &str,
) -> core::result::Result<Self, D::Error> {
Err(SerdeError::InvalidData.into())
}
}
#[derive(Debug, Default, PartialEq)]
pub struct UnderReportsLen(pub u32);
impl BytesHasLen for UnderReportsLen {
fn len(&self) -> usize {
1
}
}
impl Serialize for UnderReportsLen {
fn serialize<S: Serializer>(
&self,
name: &str,
serializer: &mut S,
) -> core::result::Result<(), S::Error> {
serializer.serialize_u32(name, self.0)
}
}
impl Deserialize for UnderReportsLen {
fn deserialize<D: Deserializer>(
deserializer: &mut D,
name: &str,
) -> core::result::Result<Self, D::Error> {
Ok(Self(deserializer.deserialize_u32(name)?))
}
}
}
#[cfg(feature = "serde")]
#[test]
fn test_queue_streamed_deserialization_failure() -> Result<()> {
use broken_payloads::NeverDecodes;
log_info!(TAG, "Starting test_queue_streamed_deserialization_failure");
let queue = QueueStreamed::<NeverDecodes>::new(2, 1)?;
queue.post(&NeverDecodes(7), 0)?;
let mut buffer = NeverDecodes::default();
let err = queue.fetch(&mut buffer, 10);
log_debug!(TAG, "fetch decode error: {:?}", err);
assert!(matches!(err, Err(Error::Unhandled(_))));
queue.post_from_isr(&NeverDecodes(9))?;
let err = queue.fetch_from_isr(&mut buffer);
log_debug!(TAG, "fetch_from_isr decode error: {:?}", err);
assert!(matches!(err, Err(Error::Unhandled(_))));
log_info!(TAG, "test_queue_streamed_deserialization_failure PASSED");
Ok(())
}
#[cfg(feature = "serde")]
#[test]
fn test_queue_streamed_serialization_failure() -> Result<()> {
use broken_payloads::UnderReportsLen;
log_info!(TAG, "Starting test_queue_streamed_serialization_failure");
let queue = QueueStreamed::<UnderReportsLen>::new(2, 1)?;
let err = queue.post(&UnderReportsLen(0xDEAD_BEEF), 0);
log_debug!(TAG, "post encode error: {:?}", err);
assert!(matches!(err, Err(Error::Unhandled(_))));
let err = queue.post_from_isr(&UnderReportsLen(0xDEAD_BEEF));
log_debug!(TAG, "post_from_isr encode error: {:?}", err);
assert!(matches!(err, Err(Error::Unhandled(_))));
let mut buffer = UnderReportsLen::default();
assert!(matches!(queue.fetch(&mut buffer, 10), Err(Error::Timeout)));
log_info!(TAG, "test_queue_streamed_serialization_failure PASSED");
Ok(())
}
#[test]
fn test_timer_clone_after_original_deleted() -> Result<()> {
log_info!(TAG, "Starting test_timer_clone_after_original_deleted");
let mut original = Timer::new("clone-src", 20, true, None, |_, param| {
Ok(param.unwrap_or(Arc::new(())))
})?;
let mut clone = original.clone();
assert_eq!(original.delete(0), OsalRsBool::True);
assert!(clone.is_null());
assert_eq!(clone.start(0), OsalRsBool::False);
assert_eq!(clone.stop(0), OsalRsBool::False);
assert_eq!(clone.reset(0), OsalRsBool::False);
assert_eq!(clone.change_period(40, 0), OsalRsBool::False);
assert_eq!(clone.delete(0), OsalRsBool::True);
assert_eq!(clone.delete(0), OsalRsBool::False);
log_info!(TAG, "test_timer_clone_after_original_deleted PASSED");
Ok(())
}
#[test]
fn test_timer_callback_returning_error() -> Result<()> {
log_info!(TAG, "Starting test_timer_callback_returning_error");
static FIRES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let mut timer = Timer::new("failing-cb", 20, true, None, |_, _param| {
FIRES.fetch_add(1, Ordering::SeqCst);
Err(Error::Unhandled("callback failed on purpose"))
})?;
assert_eq!(timer.start(0), OsalRsBool::True);
System::delay(120);
assert_eq!(timer.stop(0), OsalRsBool::True);
let fires = FIRES.load(Ordering::SeqCst);
log_debug!(TAG, "failing callback fired {} time(s)", fires);
assert!(fires >= 2, "auto-reload must survive a failing callback");
timer.delete(0);
log_info!(TAG, "test_timer_callback_returning_error PASSED");
Ok(())
}