use crate::curuntime::KeyFrame;
use core::fmt;
use core::marker::PhantomData;
use cu29_traits::CopperListTuple;
use cu29_traits::{CuError, CuResult};
use cu29_unifiedlog::{SectionStorage, UnifiedLogWrite};
#[cfg(feature = "std")]
use crate::copperlist::CopperList;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use cu29_clock::RobotClockMock;
#[cfg(feature = "std")]
use std::vec::Vec;
#[cfg(not(feature = "std"))]
mod imp {
pub use alloc::boxed::Box;
pub use alloc::string::String;
}
#[cfg(feature = "std")]
mod imp {
pub use crate::config::CuConfig;
pub use crate::simulation::SimOverride;
pub use cu29_clock::RobotClock;
pub use cu29_unifiedlog::memmap::MmapSectionStorage;
pub use std::sync::{Arc, Mutex};
}
use imp::*;
#[cfg(feature = "std")]
pub trait CuStdApplication:
CuApplication<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite>
{
}
#[cfg(feature = "std")]
impl<T> CuStdApplication for T where
T: CuApplication<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite>
{
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Subsystem {
id: Option<&'static str>,
code: u16,
}
impl Subsystem {
#[inline]
pub const fn new(id: Option<&'static str>, code: u16) -> Self {
Self { id, code }
}
#[inline]
pub const fn id(self) -> Option<&'static str> {
self.id
}
#[inline]
pub const fn code(self) -> u16 {
self.code
}
}
pub trait CuSubsystemMetadata {
fn subsystem() -> Subsystem;
}
pub trait CuApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> {
fn get_original_config() -> String;
#[deprecated(
since = "1.2.0",
note = "use the typed transition `start()` on the handle returned by `build()`"
)]
fn start_all_tasks(&mut self) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use `run_one_iteration()` on the Running handle from `build()?.start()?`"
)]
fn run_one_iteration(&mut self) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use the typed transition `run_until_shutdown()` on the handle returned by `build()`"
)]
fn run(&mut self) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use the typed transition `stop()` on the Running handle"
)]
fn stop_all_tasks(&mut self) -> CuResult<()>;
fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()>;
}
#[cfg(feature = "std")]
pub trait CuSimApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> {
type Step<'z>;
fn get_original_config() -> String;
fn mission_id() -> Option<&'static str> {
None
}
#[deprecated(
since = "1.2.0",
note = "use the typed transition `start()` on the handle returned by `build()`"
)]
fn start_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use `run_one_iteration()` on the Running handle from `build()?.start()?`"
)]
fn run_one_iteration(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use the typed transition `run_until_shutdown()` on the handle returned by `build()`"
)]
fn run(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()>;
#[deprecated(
since = "1.2.0",
note = "use the typed transition `stop()` on the Running handle"
)]
fn stop_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()>;
fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()>;
}
pub trait CurrentRuntimeCopperList<P: CopperListTuple> {
fn current_runtime_copperlist_bytes(&self) -> Option<&[u8]>;
fn set_current_runtime_copperlist_bytes(&mut self, snapshot: Option<Vec<u8>>) {
let _ = snapshot;
}
}
#[cfg(feature = "std")]
pub trait CuRecordedReplayApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static>:
CuSimApplication<S, L>
{
type RecordedDataSet: CopperListTuple;
fn replay_recorded_copperlist(
&mut self,
clock_mock: &RobotClockMock,
copperlist: &CopperList<Self::RecordedDataSet>,
keyframe: Option<&KeyFrame>,
) -> CuResult<()>;
}
#[cfg(feature = "std")]
pub trait CuDistributedReplayApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static>:
CuRecordedReplayApplication<S, L> + CuSubsystemMetadata
{
fn build_distributed_replay(
clock: RobotClock,
unified_logger: Arc<Mutex<L>>,
instance_id: u32,
config_override: Option<CuConfig>,
) -> CuResult<Self>
where
Self: Sized;
}
pub struct Initialized;
pub struct Running;
pub struct Stopped;
pub struct Faulted;
mod sealed {
pub trait Sealed {}
impl Sealed for super::Initialized {}
impl Sealed for super::Running {}
impl Sealed for super::Stopped {}
impl Sealed for super::Faulted {}
}
#[diagnostic::on_unimplemented(
message = "the application cannot be started from the `{Self}` lifecycle state",
label = "`start_all_tasks` and `run` require an `Initialized` or `Stopped` application",
note = "a `Running` application is already started and a `Faulted` application must first be cleaned up with `stop_all_tasks`"
)]
pub trait Startable: sealed::Sealed {}
impl Startable for Initialized {}
impl Startable for Stopped {}
#[diagnostic::on_unimplemented(
message = "the application cannot be stopped from the `{Self}` lifecycle state",
label = "`stop_all_tasks` requires a `Running` or `Faulted` application",
note = "start the application first with `start_all_tasks`"
)]
pub trait Stoppable: sealed::Sealed {}
impl Stoppable for Running {}
impl Stoppable for Faulted {}
pub struct CuAppLifecycle<S, L, A, State = Initialized> {
app: Box<A>,
_lifecycle: PhantomData<(S, L, State)>,
}
impl<S, L, A, State> fmt::Debug for CuAppLifecycle<S, L, A, State> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CuAppLifecycle")
.field("state", &core::any::type_name::<State>())
.finish_non_exhaustive()
}
}
#[cfg(feature = "std")]
pub type CuStdAppLifecycle<A, State = Initialized> =
CuAppLifecycle<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite, A, State>;
pub type TransitionResult<S, L, A, Next> =
Result<CuAppLifecycle<S, L, A, Next>, LifecycleError<S, L, A>>;
pub struct LifecycleError<S, L, A> {
pub error: CuError,
pub app: CuAppLifecycle<S, L, A, Faulted>,
}
impl<S, L, A> fmt::Debug for LifecycleError<S, L, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LifecycleError")
.field("error", &self.error)
.finish_non_exhaustive()
}
}
impl<S, L, A> fmt::Display for LifecycleError<S, L, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "lifecycle transition failed: {}", self.error)
}
}
impl<S, L, A> core::error::Error for LifecycleError<S, L, A> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
impl<S, L, A> From<LifecycleError<S, L, A>> for CuError {
fn from(value: LifecycleError<S, L, A>) -> Self {
value.error
}
}
impl<S, L, A, State> CuAppLifecycle<S, L, A, State> {
fn into_state<Next>(self) -> CuAppLifecycle<S, L, A, Next> {
CuAppLifecycle {
app: self.app,
_lifecycle: PhantomData,
}
}
pub fn inner(&self) -> &A {
&self.app
}
pub fn into_inner(self) -> A {
*self.app
}
}
impl<S, L, A> CuAppLifecycle<S, L, A, Initialized>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuApplication<S, L>,
{
pub fn new(app: A) -> Self {
CuAppLifecycle {
app: Box::new(app),
_lifecycle: PhantomData,
}
}
pub fn inner_mut(&mut self) -> &mut A {
&mut self.app
}
}
impl<S, L, A, State> CuAppLifecycle<S, L, A, State>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuApplication<S, L>,
State: Startable,
{
#[allow(deprecated)] pub fn start(mut self) -> TransitionResult<S, L, A, Running> {
match self.app.start_all_tasks() {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(LifecycleError {
error,
app: self.into_state(),
}),
}
}
#[allow(deprecated)] pub fn run_until_shutdown(mut self) -> TransitionResult<S, L, A, Stopped> {
match self.app.run() {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(LifecycleError {
error,
app: self.into_state(),
}),
}
}
}
impl<S, L, A> CuAppLifecycle<S, L, A, Running>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuApplication<S, L>,
{
#[allow(deprecated)] pub fn run_one_iteration(&mut self) -> CuResult<()> {
self.app.run_one_iteration()
}
}
impl<S, L, A, State> CuAppLifecycle<S, L, A, State>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuApplication<S, L>,
State: Stoppable,
{
#[allow(deprecated)] pub fn stop(mut self) -> TransitionResult<S, L, A, Stopped> {
match self.app.stop_all_tasks() {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(LifecycleError {
error,
app: self.into_state(),
}),
}
}
}
impl<S, L, A, State> core::ops::Deref for CuAppLifecycle<S, L, A, State> {
type Target = A;
fn deref(&self) -> &A {
&self.app
}
}
impl<S, L, A> core::ops::DerefMut for CuAppLifecycle<S, L, A, Initialized> {
fn deref_mut(&mut self) -> &mut A {
&mut self.app
}
}
#[allow(deprecated)]
impl<S, L, A> CuApplication<S, L> for CuAppLifecycle<S, L, A, Initialized>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuApplication<S, L>,
{
fn get_original_config() -> String {
A::get_original_config()
}
fn start_all_tasks(&mut self) -> CuResult<()> {
self.app.start_all_tasks()
}
fn run_one_iteration(&mut self) -> CuResult<()> {
self.app.run_one_iteration()
}
fn run(&mut self) -> CuResult<()> {
self.app.run()
}
fn stop_all_tasks(&mut self) -> CuResult<()> {
self.app.stop_all_tasks()
}
fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()> {
self.app.restore_keyframe(freezer)
}
}
#[cfg(feature = "std")]
pub struct CuSimAppLifecycle<S, L, A, State = Initialized> {
app: Box<A>,
_lifecycle: PhantomData<(S, L, State)>,
}
#[cfg(feature = "std")]
impl<S, L, A, State> fmt::Debug for CuSimAppLifecycle<S, L, A, State> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CuSimAppLifecycle")
.field("state", &core::any::type_name::<State>())
.finish_non_exhaustive()
}
}
#[cfg(feature = "std")]
pub type CuStdSimAppLifecycle<A, State = Initialized> =
CuSimAppLifecycle<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite, A, State>;
#[cfg(feature = "std")]
pub type SimTransitionResult<S, L, A, Next> =
Result<CuSimAppLifecycle<S, L, A, Next>, SimLifecycleError<S, L, A>>;
#[cfg(feature = "std")]
pub struct SimLifecycleError<S, L, A> {
pub error: CuError,
pub app: CuSimAppLifecycle<S, L, A, Faulted>,
}
#[cfg(feature = "std")]
impl<S, L, A> fmt::Debug for SimLifecycleError<S, L, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SimLifecycleError")
.field("error", &self.error)
.finish_non_exhaustive()
}
}
#[cfg(feature = "std")]
impl<S, L, A> fmt::Display for SimLifecycleError<S, L, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "lifecycle transition failed: {}", self.error)
}
}
#[cfg(feature = "std")]
impl<S, L, A> core::error::Error for SimLifecycleError<S, L, A> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
#[cfg(feature = "std")]
impl<S, L, A> From<SimLifecycleError<S, L, A>> for CuError {
fn from(value: SimLifecycleError<S, L, A>) -> Self {
value.error
}
}
#[cfg(feature = "std")]
impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State> {
fn into_state<Next>(self) -> CuSimAppLifecycle<S, L, A, Next> {
CuSimAppLifecycle {
app: self.app,
_lifecycle: PhantomData,
}
}
pub fn inner(&self) -> &A {
&self.app
}
pub fn into_inner(self) -> A {
*self.app
}
}
#[cfg(feature = "std")]
impl<S, L, A> CuSimAppLifecycle<S, L, A, Initialized>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuSimApplication<S, L>,
{
pub fn new(app: A) -> Self {
CuSimAppLifecycle {
app: Box::new(app),
_lifecycle: PhantomData,
}
}
}
#[cfg(feature = "std")]
impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuSimApplication<S, L>,
State: Startable,
{
#[allow(deprecated)] pub fn start(
mut self,
sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
) -> SimTransitionResult<S, L, A, Running> {
match self.app.start_all_tasks(sim_callback) {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(SimLifecycleError {
error,
app: self.into_state(),
}),
}
}
#[allow(deprecated)] pub fn run_until_shutdown(
mut self,
sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
) -> SimTransitionResult<S, L, A, Stopped> {
match self.app.run(sim_callback) {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(SimLifecycleError {
error,
app: self.into_state(),
}),
}
}
}
#[cfg(feature = "std")]
impl<S, L, A> CuSimAppLifecycle<S, L, A, Running>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuSimApplication<S, L>,
{
#[allow(deprecated)] pub fn run_one_iteration(
&mut self,
sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
) -> CuResult<()> {
self.app.run_one_iteration(sim_callback)
}
}
#[cfg(feature = "std")]
impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuSimApplication<S, L>,
State: Stoppable,
{
#[allow(deprecated)] pub fn stop(
mut self,
sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
) -> SimTransitionResult<S, L, A, Stopped> {
match self.app.stop_all_tasks(sim_callback) {
Ok(()) => Ok(self.into_state()),
Err(error) => Err(SimLifecycleError {
error,
app: self.into_state(),
}),
}
}
}
#[cfg(feature = "std")]
impl<S, L, A, State> core::ops::Deref for CuSimAppLifecycle<S, L, A, State> {
type Target = A;
fn deref(&self) -> &A {
&self.app
}
}
#[cfg(feature = "std")]
impl<S, L, A> core::ops::DerefMut for CuSimAppLifecycle<S, L, A, Initialized> {
fn deref_mut(&mut self) -> &mut A {
&mut self.app
}
}
#[cfg(feature = "std")]
#[allow(deprecated)]
impl<S, L, A> CuSimApplication<S, L> for CuSimAppLifecycle<S, L, A, Initialized>
where
S: SectionStorage,
L: UnifiedLogWrite<S> + 'static,
A: CuSimApplication<S, L>,
{
type Step<'z> = <A as CuSimApplication<S, L>>::Step<'z>;
fn get_original_config() -> String {
A::get_original_config()
}
fn mission_id() -> Option<&'static str> {
A::mission_id()
}
fn start_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
self.app.start_all_tasks(sim_callback)
}
fn run_one_iteration(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
self.app.run_one_iteration(sim_callback)
}
fn run(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
self.app.run(sim_callback)
}
fn stop_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
self.app.stop_all_tasks(sim_callback)
}
fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()> {
self.app.restore_keyframe(freezer)
}
}
#[cfg(all(test, feature = "std"))]
mod lifecycle_tests {
use super::*;
#[derive(Default)]
struct MockApp {
started: u32,
stopped: u32,
iterations: u32,
runs: u32,
fail_start: bool,
fail_stop: bool,
}
#[allow(deprecated)] impl<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for MockApp {
fn get_original_config() -> String {
String::new()
}
fn start_all_tasks(&mut self) -> CuResult<()> {
if self.fail_start {
return Err("mock start failure".into());
}
self.started += 1;
Ok(())
}
fn run_one_iteration(&mut self) -> CuResult<()> {
self.iterations += 1;
Ok(())
}
fn run(&mut self) -> CuResult<()> {
if self.fail_start {
return Err("mock run failure".into());
}
self.runs += 1;
Ok(())
}
fn stop_all_tasks(&mut self) -> CuResult<()> {
if self.fail_stop {
return Err("mock stop failure".into());
}
self.stopped += 1;
Ok(())
}
fn restore_keyframe(&mut self, _freezer: &KeyFrame) -> CuResult<()> {
Ok(())
}
}
type Lifecycle = CuStdAppLifecycle<MockApp>;
#[test]
fn full_cycle_with_restart() {
let app = Lifecycle::new(MockApp::default());
let mut running = app.start().unwrap();
running.run_one_iteration().unwrap();
running.run_one_iteration().unwrap();
let stopped = running.stop().unwrap();
let running = stopped.start().unwrap();
let stopped = running.stop().unwrap();
let mock = stopped.into_inner();
assert_eq!(mock.started, 2);
assert_eq!(mock.iterations, 2);
assert_eq!(mock.stopped, 2);
}
#[test]
fn run_transitions_to_stopped_and_can_run_again() {
let app = Lifecycle::new(MockApp::default());
let stopped = app.run_until_shutdown().unwrap();
let stopped = stopped.run_until_shutdown().unwrap();
assert_eq!(stopped.inner().runs, 2);
}
#[test]
fn failed_start_hands_back_a_faulted_app_for_cleanup() {
let app = Lifecycle::new(MockApp {
fail_start: true,
..Default::default()
});
let err = app.start().unwrap_err();
assert!(err.error.to_string().contains("mock start failure"));
let stopped = err.app.stop().unwrap();
let mock = stopped.into_inner();
assert_eq!(mock.started, 0);
assert_eq!(mock.stopped, 1);
}
#[test]
fn failed_stop_hands_back_a_faulted_app() {
let app = Lifecycle::new(MockApp {
fail_stop: true,
..Default::default()
});
let running = app.start().unwrap();
let err = running.stop().unwrap_err();
assert!(err.error.to_string().contains("mock stop failure"));
}
#[test]
fn lifecycle_error_propagates_with_question_mark() {
fn drive() -> CuResult<()> {
let app = Lifecycle::new(MockApp {
fail_start: true,
..Default::default()
});
let _running = app.start()?;
Ok(())
}
let error = drive().unwrap_err();
assert!(error.to_string().contains("mock start failure"));
}
#[derive(Default)]
struct MockSimApp {
started: u32,
stopped: u32,
iterations: u32,
fail_start: bool,
}
struct MockStep;
#[allow(deprecated)] impl<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for MockSimApp {
type Step<'z> = MockStep;
fn get_original_config() -> String {
String::new()
}
fn start_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
if self.fail_start {
return Err("mock sim start failure".into());
}
let _ = sim_callback(MockStep);
self.started += 1;
Ok(())
}
fn run_one_iteration(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
let _ = sim_callback(MockStep);
self.iterations += 1;
Ok(())
}
fn run(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
let _ = sim_callback(MockStep);
Ok(())
}
fn stop_all_tasks(
&mut self,
sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
) -> CuResult<()> {
let _ = sim_callback(MockStep);
self.stopped += 1;
Ok(())
}
fn restore_keyframe(&mut self, _freezer: &KeyFrame) -> CuResult<()> {
Ok(())
}
}
type SimLifecycle = CuStdSimAppLifecycle<MockSimApp>;
#[test]
fn sim_full_cycle_threads_the_callback() {
let mut callback_calls = 0u32;
let mut cb = |_step: MockStep| -> SimOverride {
callback_calls += 1;
SimOverride::ExecuteByRuntime
};
let app = SimLifecycle::new(MockSimApp::default());
let mut running = app.start(&mut cb).unwrap();
running.run_one_iteration(&mut cb).unwrap();
let stopped = running.stop(&mut cb).unwrap();
let mock = stopped.into_inner();
assert_eq!(mock.started, 1);
assert_eq!(mock.iterations, 1);
assert_eq!(mock.stopped, 1);
assert_eq!(callback_calls, 3);
}
#[test]
fn sim_failed_start_hands_back_a_faulted_app_for_cleanup() {
let mut cb = |_step: MockStep| -> SimOverride { SimOverride::ExecuteByRuntime };
let app = SimLifecycle::new(MockSimApp {
fail_start: true,
..Default::default()
});
let err = app.start(&mut cb).unwrap_err();
assert!(err.error.to_string().contains("mock sim start failure"));
let stopped = err.app.stop(&mut cb).unwrap();
assert_eq!(stopped.inner().stopped, 1);
}
}