1use crate::curuntime::KeyFrame;
2use core::fmt;
3use core::marker::PhantomData;
4use cu29_traits::CopperListTuple;
5use cu29_traits::{CuError, CuResult};
6use cu29_unifiedlog::{SectionStorage, UnifiedLogWrite};
7
8#[cfg(feature = "std")]
9use crate::copperlist::CopperList;
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12#[cfg(feature = "std")]
13use cu29_clock::RobotClockMock;
14#[cfg(feature = "std")]
15use std::vec::Vec;
16
17#[cfg(not(feature = "std"))]
18mod imp {
19 pub use alloc::boxed::Box;
20 pub use alloc::string::String;
21}
22
23#[cfg(feature = "std")]
24mod imp {
25 pub use crate::config::CuConfig;
26 pub use crate::simulation::SimOverride;
27 pub use cu29_clock::RobotClock;
28 pub use cu29_unifiedlog::memmap::MmapSectionStorage;
29 pub use std::sync::{Arc, Mutex};
30}
31
32use imp::*;
33
34#[cfg(feature = "std")]
36pub trait CuStdApplication:
37 CuApplication<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite>
38{
39}
40
41#[cfg(feature = "std")]
42impl<T> CuStdApplication for T where
43 T: CuApplication<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite>
44{
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct Subsystem {
50 id: Option<&'static str>,
51 code: u16,
52}
53
54impl Subsystem {
55 #[inline]
56 pub const fn new(id: Option<&'static str>, code: u16) -> Self {
57 Self { id, code }
58 }
59
60 #[inline]
61 pub const fn id(self) -> Option<&'static str> {
62 self.id
63 }
64
65 #[inline]
66 pub const fn code(self) -> u16 {
67 self.code
68 }
69}
70
71pub trait CuSubsystemMetadata {
73 fn subsystem() -> Subsystem;
75}
76
77pub trait CuApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> {
87 fn get_original_config() -> String;
90
91 #[deprecated(
98 since = "1.2.0",
99 note = "use the typed transition `start()` on the handle returned by `build()`"
100 )]
101 fn start_all_tasks(&mut self) -> CuResult<()>;
102
103 #[deprecated(
111 since = "1.2.0",
112 note = "use `run_one_iteration()` on the Running handle from `build()?.start()?`"
113 )]
114 fn run_one_iteration(&mut self) -> CuResult<()>;
115
116 #[deprecated(
125 since = "1.2.0",
126 note = "use the typed transition `run_until_shutdown()` on the handle returned by `build()`"
127 )]
128 fn run(&mut self) -> CuResult<()>;
129
130 #[deprecated(
140 since = "1.2.0",
141 note = "use the typed transition `stop()` on the Running handle"
142 )]
143 fn stop_all_tasks(&mut self) -> CuResult<()>;
144
145 fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()>;
147}
148
149#[cfg(feature = "std")]
157pub trait CuSimApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> {
158 type Step<'z>;
160
161 fn get_original_config() -> String;
164
165 fn mission_id() -> Option<&'static str> {
167 None
168 }
169
170 #[deprecated(
180 since = "1.2.0",
181 note = "use the typed transition `start()` on the handle returned by `build()`"
182 )]
183 fn start_all_tasks(
184 &mut self,
185 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
186 ) -> CuResult<()>;
187
188 #[deprecated(
198 since = "1.2.0",
199 note = "use `run_one_iteration()` on the Running handle from `build()?.start()?`"
200 )]
201 fn run_one_iteration(
202 &mut self,
203 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
204 ) -> CuResult<()>;
205
206 #[deprecated(
218 since = "1.2.0",
219 note = "use the typed transition `run_until_shutdown()` on the handle returned by `build()`"
220 )]
221 fn run(
222 &mut self,
223 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
224 ) -> CuResult<()>;
225
226 #[deprecated(
238 since = "1.2.0",
239 note = "use the typed transition `stop()` on the Running handle"
240 )]
241 fn stop_all_tasks(
242 &mut self,
243 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
244 ) -> CuResult<()>;
245
246 fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()>;
248}
249
250pub trait CurrentRuntimeCopperList<P: CopperListTuple> {
256 fn current_runtime_copperlist_bytes(&self) -> Option<&[u8]>;
257
258 fn set_current_runtime_copperlist_bytes(&mut self, snapshot: Option<Vec<u8>>) {
259 let _ = snapshot;
260 }
261}
262
263#[cfg(feature = "std")]
270pub trait CuRecordedReplayApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static>:
271 CuSimApplication<S, L>
272{
273 type RecordedDataSet: CopperListTuple;
275
276 #[cfg(feature = "clock-sync")]
278 fn restore_clock_sync(&mut self, _record: crate::clock_sync::ClockSyncRecord) -> CuResult<()> {
279 Ok(())
280 }
281
282 #[cfg(feature = "clock-sync")]
284 fn set_recorded_clock_time(
285 &mut self,
286 mock: &RobotClockMock,
287 time: cu29_clock::CuTime,
288 ) -> CuResult<()> {
289 mock.set_value(time.0);
290 Ok(())
291 }
292
293 fn replay_recorded_copperlist(
303 &mut self,
304 clock_mock: &RobotClockMock,
305 copperlist: &CopperList<Self::RecordedDataSet>,
306 keyframe: Option<&KeyFrame>,
307 ) -> CuResult<()>;
308}
309
310#[cfg(feature = "std")]
316pub trait CuDistributedReplayApplication<S: SectionStorage, L: UnifiedLogWrite<S> + 'static>:
317 CuRecordedReplayApplication<S, L> + CuSubsystemMetadata
318{
319 fn build_distributed_replay(
321 clock: RobotClock,
322 unified_logger: Arc<Mutex<L>>,
323 instance_id: u32,
324 config_override: Option<CuConfig>,
325 ) -> CuResult<Self>
326 where
327 Self: Sized;
328}
329
330pub struct Initialized;
332
333pub struct Running;
335
336pub struct Stopped;
339
340pub struct Faulted;
344
345mod sealed {
346 pub trait Sealed {}
349 impl Sealed for super::Initialized {}
350 impl Sealed for super::Running {}
351 impl Sealed for super::Stopped {}
352 impl Sealed for super::Faulted {}
353}
354
355#[diagnostic::on_unimplemented(
357 message = "the application cannot be started from the `{Self}` lifecycle state",
358 label = "`start_all_tasks` and `run` require an `Initialized` or `Stopped` application",
359 note = "a `Running` application is already started and a `Faulted` application must first be cleaned up with `stop_all_tasks`"
360)]
361pub trait Startable: sealed::Sealed {}
362impl Startable for Initialized {}
363impl Startable for Stopped {}
364
365#[diagnostic::on_unimplemented(
367 message = "the application cannot be stopped from the `{Self}` lifecycle state",
368 label = "`stop_all_tasks` requires a `Running` or `Faulted` application",
369 note = "start the application first with `start_all_tasks`"
370)]
371pub trait Stoppable: sealed::Sealed {}
372impl Stoppable for Running {}
373impl Stoppable for Faulted {}
374
375pub struct CuAppLifecycle<S, L, A, State = Initialized> {
411 app: Box<A>,
412 _lifecycle: PhantomData<(S, L, State)>,
413}
414
415impl<S, L, A, State> fmt::Debug for CuAppLifecycle<S, L, A, State> {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 f.debug_struct("CuAppLifecycle")
418 .field("state", &core::any::type_name::<State>())
419 .finish_non_exhaustive()
420 }
421}
422
423#[cfg(feature = "std")]
426pub type CuStdAppLifecycle<A, State = Initialized> =
427 CuAppLifecycle<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite, A, State>;
428
429pub type TransitionResult<S, L, A, Next> =
432 Result<CuAppLifecycle<S, L, A, Next>, LifecycleError<S, L, A>>;
433
434pub struct LifecycleError<S, L, A> {
440 pub error: CuError,
442 pub app: CuAppLifecycle<S, L, A, Faulted>,
444}
445
446impl<S, L, A> fmt::Debug for LifecycleError<S, L, A> {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 f.debug_struct("LifecycleError")
449 .field("error", &self.error)
450 .finish_non_exhaustive()
451 }
452}
453
454impl<S, L, A> fmt::Display for LifecycleError<S, L, A> {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 write!(f, "lifecycle transition failed: {}", self.error)
457 }
458}
459
460impl<S, L, A> core::error::Error for LifecycleError<S, L, A> {
461 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
462 Some(&self.error)
463 }
464}
465
466impl<S, L, A> From<LifecycleError<S, L, A>> for CuError {
469 fn from(value: LifecycleError<S, L, A>) -> Self {
470 value.error
471 }
472}
473
474impl<S, L, A, State> CuAppLifecycle<S, L, A, State> {
475 fn into_state<Next>(self) -> CuAppLifecycle<S, L, A, Next> {
478 CuAppLifecycle {
479 app: self.app,
480 _lifecycle: PhantomData,
481 }
482 }
483
484 pub fn inner(&self) -> &A {
486 &self.app
487 }
488
489 pub fn into_inner(self) -> A {
492 *self.app
493 }
494}
495
496impl<S, L, A> CuAppLifecycle<S, L, A, Initialized>
497where
498 S: SectionStorage,
499 L: UnifiedLogWrite<S> + 'static,
500 A: CuApplication<S, L>,
501{
502 pub fn new(app: A) -> Self {
504 CuAppLifecycle {
505 app: Box::new(app),
506 _lifecycle: PhantomData,
507 }
508 }
509
510 pub fn inner_mut(&mut self) -> &mut A {
517 &mut self.app
518 }
519}
520
521impl<S, L, A, State> CuAppLifecycle<S, L, A, State>
522where
523 S: SectionStorage,
524 L: UnifiedLogWrite<S> + 'static,
525 A: CuApplication<S, L>,
526 State: Startable,
527{
528 #[allow(deprecated)] pub fn start(mut self) -> TransitionResult<S, L, A, Running> {
534 match self.app.start_all_tasks() {
535 Ok(()) => Ok(self.into_state()),
536 Err(error) => Err(LifecycleError {
537 error,
538 app: self.into_state(),
539 }),
540 }
541 }
542
543 #[allow(deprecated)] pub fn run_until_shutdown(mut self) -> TransitionResult<S, L, A, Stopped> {
547 match self.app.run() {
548 Ok(()) => Ok(self.into_state()),
549 Err(error) => Err(LifecycleError {
550 error,
551 app: self.into_state(),
552 }),
553 }
554 }
555}
556
557impl<S, L, A> CuAppLifecycle<S, L, A, Running>
558where
559 S: SectionStorage,
560 L: UnifiedLogWrite<S> + 'static,
561 A: CuApplication<S, L>,
562{
563 #[allow(deprecated)] pub fn run_one_iteration(&mut self) -> CuResult<()> {
568 self.app.run_one_iteration()
569 }
570}
571
572impl<S, L, A, State> CuAppLifecycle<S, L, A, State>
573where
574 S: SectionStorage,
575 L: UnifiedLogWrite<S> + 'static,
576 A: CuApplication<S, L>,
577 State: Stoppable,
578{
579 #[allow(deprecated)] pub fn stop(mut self) -> TransitionResult<S, L, A, Stopped> {
584 match self.app.stop_all_tasks() {
585 Ok(()) => Ok(self.into_state()),
586 Err(error) => Err(LifecycleError {
587 error,
588 app: self.into_state(),
589 }),
590 }
591 }
592}
593
594impl<S, L, A, State> core::ops::Deref for CuAppLifecycle<S, L, A, State> {
597 type Target = A;
598
599 fn deref(&self) -> &A {
600 &self.app
601 }
602}
603
604impl<S, L, A> core::ops::DerefMut for CuAppLifecycle<S, L, A, Initialized> {
607 fn deref_mut(&mut self) -> &mut A {
608 &mut self.app
609 }
610}
611
612#[allow(deprecated)]
618impl<S, L, A> CuApplication<S, L> for CuAppLifecycle<S, L, A, Initialized>
619where
620 S: SectionStorage,
621 L: UnifiedLogWrite<S> + 'static,
622 A: CuApplication<S, L>,
623{
624 fn get_original_config() -> String {
625 A::get_original_config()
626 }
627
628 fn start_all_tasks(&mut self) -> CuResult<()> {
629 self.app.start_all_tasks()
630 }
631
632 fn run_one_iteration(&mut self) -> CuResult<()> {
633 self.app.run_one_iteration()
634 }
635
636 fn run(&mut self) -> CuResult<()> {
637 self.app.run()
638 }
639
640 fn stop_all_tasks(&mut self) -> CuResult<()> {
641 self.app.stop_all_tasks()
642 }
643
644 fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()> {
645 self.app.restore_keyframe(freezer)
646 }
647}
648
649#[cfg(feature = "std")]
656pub struct CuSimAppLifecycle<S, L, A, State = Initialized> {
657 app: Box<A>,
658 _lifecycle: PhantomData<(S, L, State)>,
659}
660
661#[cfg(feature = "std")]
662impl<S, L, A, State> fmt::Debug for CuSimAppLifecycle<S, L, A, State> {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 f.debug_struct("CuSimAppLifecycle")
665 .field("state", &core::any::type_name::<State>())
666 .finish_non_exhaustive()
667 }
668}
669
670#[cfg(feature = "std")]
673pub type CuStdSimAppLifecycle<A, State = Initialized> =
674 CuSimAppLifecycle<MmapSectionStorage, cu29_unifiedlog::UnifiedLoggerWrite, A, State>;
675
676#[cfg(feature = "std")]
680pub type SimTransitionResult<S, L, A, Next> =
681 Result<CuSimAppLifecycle<S, L, A, Next>, SimLifecycleError<S, L, A>>;
682
683#[cfg(feature = "std")]
685pub struct SimLifecycleError<S, L, A> {
686 pub error: CuError,
688 pub app: CuSimAppLifecycle<S, L, A, Faulted>,
690}
691
692#[cfg(feature = "std")]
693impl<S, L, A> fmt::Debug for SimLifecycleError<S, L, A> {
694 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
695 f.debug_struct("SimLifecycleError")
696 .field("error", &self.error)
697 .finish_non_exhaustive()
698 }
699}
700
701#[cfg(feature = "std")]
702impl<S, L, A> fmt::Display for SimLifecycleError<S, L, A> {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 write!(f, "lifecycle transition failed: {}", self.error)
705 }
706}
707
708#[cfg(feature = "std")]
709impl<S, L, A> core::error::Error for SimLifecycleError<S, L, A> {
710 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
711 Some(&self.error)
712 }
713}
714
715#[cfg(feature = "std")]
718impl<S, L, A> From<SimLifecycleError<S, L, A>> for CuError {
719 fn from(value: SimLifecycleError<S, L, A>) -> Self {
720 value.error
721 }
722}
723
724#[cfg(feature = "std")]
725impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State> {
726 fn into_state<Next>(self) -> CuSimAppLifecycle<S, L, A, Next> {
729 CuSimAppLifecycle {
730 app: self.app,
731 _lifecycle: PhantomData,
732 }
733 }
734
735 pub fn inner(&self) -> &A {
737 &self.app
738 }
739
740 pub fn into_inner(self) -> A {
743 *self.app
744 }
745}
746
747#[cfg(feature = "std")]
748impl<S, L, A> CuSimAppLifecycle<S, L, A, Initialized>
749where
750 S: SectionStorage,
751 L: UnifiedLogWrite<S> + 'static,
752 A: CuSimApplication<S, L>,
753{
754 pub fn new(app: A) -> Self {
756 CuSimAppLifecycle {
757 app: Box::new(app),
758 _lifecycle: PhantomData,
759 }
760 }
761}
762
763#[cfg(feature = "std")]
764impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State>
765where
766 S: SectionStorage,
767 L: UnifiedLogWrite<S> + 'static,
768 A: CuSimApplication<S, L>,
769 State: Startable,
770{
771 #[allow(deprecated)] pub fn start(
777 mut self,
778 sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
779 ) -> SimTransitionResult<S, L, A, Running> {
780 match self.app.start_all_tasks(sim_callback) {
781 Ok(()) => Ok(self.into_state()),
782 Err(error) => Err(SimLifecycleError {
783 error,
784 app: self.into_state(),
785 }),
786 }
787 }
788
789 #[allow(deprecated)] pub fn run_until_shutdown(
793 mut self,
794 sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
795 ) -> SimTransitionResult<S, L, A, Stopped> {
796 match self.app.run(sim_callback) {
797 Ok(()) => Ok(self.into_state()),
798 Err(error) => Err(SimLifecycleError {
799 error,
800 app: self.into_state(),
801 }),
802 }
803 }
804}
805
806#[cfg(feature = "std")]
807impl<S, L, A> CuSimAppLifecycle<S, L, A, Running>
808where
809 S: SectionStorage,
810 L: UnifiedLogWrite<S> + 'static,
811 A: CuSimApplication<S, L>,
812{
813 #[allow(deprecated)] pub fn run_one_iteration(
818 &mut self,
819 sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
820 ) -> CuResult<()> {
821 self.app.run_one_iteration(sim_callback)
822 }
823}
824
825#[cfg(feature = "std")]
826impl<S, L, A, State> CuSimAppLifecycle<S, L, A, State>
827where
828 S: SectionStorage,
829 L: UnifiedLogWrite<S> + 'static,
830 A: CuSimApplication<S, L>,
831 State: Stoppable,
832{
833 #[allow(deprecated)] pub fn stop(
838 mut self,
839 sim_callback: &mut impl for<'z> FnMut(<A as CuSimApplication<S, L>>::Step<'z>) -> SimOverride,
840 ) -> SimTransitionResult<S, L, A, Stopped> {
841 match self.app.stop_all_tasks(sim_callback) {
842 Ok(()) => Ok(self.into_state()),
843 Err(error) => Err(SimLifecycleError {
844 error,
845 app: self.into_state(),
846 }),
847 }
848 }
849}
850
851#[cfg(feature = "std")]
853impl<S, L, A, State> core::ops::Deref for CuSimAppLifecycle<S, L, A, State> {
854 type Target = A;
855
856 fn deref(&self) -> &A {
857 &self.app
858 }
859}
860
861#[cfg(feature = "std")]
864impl<S, L, A> core::ops::DerefMut for CuSimAppLifecycle<S, L, A, Initialized> {
865 fn deref_mut(&mut self) -> &mut A {
866 &mut self.app
867 }
868}
869
870#[cfg(feature = "std")]
874#[allow(deprecated)]
875impl<S, L, A> CuSimApplication<S, L> for CuSimAppLifecycle<S, L, A, Initialized>
876where
877 S: SectionStorage,
878 L: UnifiedLogWrite<S> + 'static,
879 A: CuSimApplication<S, L>,
880{
881 type Step<'z> = <A as CuSimApplication<S, L>>::Step<'z>;
882
883 fn get_original_config() -> String {
884 A::get_original_config()
885 }
886
887 fn mission_id() -> Option<&'static str> {
888 A::mission_id()
889 }
890
891 fn start_all_tasks(
892 &mut self,
893 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
894 ) -> CuResult<()> {
895 self.app.start_all_tasks(sim_callback)
896 }
897
898 fn run_one_iteration(
899 &mut self,
900 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
901 ) -> CuResult<()> {
902 self.app.run_one_iteration(sim_callback)
903 }
904
905 fn run(
906 &mut self,
907 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
908 ) -> CuResult<()> {
909 self.app.run(sim_callback)
910 }
911
912 fn stop_all_tasks(
913 &mut self,
914 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
915 ) -> CuResult<()> {
916 self.app.stop_all_tasks(sim_callback)
917 }
918
919 fn restore_keyframe(&mut self, freezer: &KeyFrame) -> CuResult<()> {
920 self.app.restore_keyframe(freezer)
921 }
922}
923
924#[cfg(all(test, feature = "std"))]
925mod lifecycle_tests {
926 use super::*;
927
928 #[derive(Default)]
929 struct MockApp {
930 started: u32,
931 stopped: u32,
932 iterations: u32,
933 runs: u32,
934 fail_start: bool,
935 fail_stop: bool,
936 }
937
938 #[allow(deprecated)] impl<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for MockApp {
940 fn get_original_config() -> String {
941 String::new()
942 }
943
944 fn start_all_tasks(&mut self) -> CuResult<()> {
945 if self.fail_start {
946 return Err("mock start failure".into());
947 }
948 self.started += 1;
949 Ok(())
950 }
951
952 fn run_one_iteration(&mut self) -> CuResult<()> {
953 self.iterations += 1;
954 Ok(())
955 }
956
957 fn run(&mut self) -> CuResult<()> {
958 if self.fail_start {
959 return Err("mock run failure".into());
960 }
961 self.runs += 1;
962 Ok(())
963 }
964
965 fn stop_all_tasks(&mut self) -> CuResult<()> {
966 if self.fail_stop {
967 return Err("mock stop failure".into());
968 }
969 self.stopped += 1;
970 Ok(())
971 }
972
973 fn restore_keyframe(&mut self, _freezer: &KeyFrame) -> CuResult<()> {
974 Ok(())
975 }
976 }
977
978 type Lifecycle = CuStdAppLifecycle<MockApp>;
979
980 #[test]
981 fn full_cycle_with_restart() {
982 let app = Lifecycle::new(MockApp::default());
983 let mut running = app.start().unwrap();
984 running.run_one_iteration().unwrap();
985 running.run_one_iteration().unwrap();
986 let stopped = running.stop().unwrap();
987
988 let running = stopped.start().unwrap();
990 let stopped = running.stop().unwrap();
991
992 let mock = stopped.into_inner();
993 assert_eq!(mock.started, 2);
994 assert_eq!(mock.iterations, 2);
995 assert_eq!(mock.stopped, 2);
996 }
997
998 #[test]
999 fn run_transitions_to_stopped_and_can_run_again() {
1000 let app = Lifecycle::new(MockApp::default());
1001 let stopped = app.run_until_shutdown().unwrap();
1002 let stopped = stopped.run_until_shutdown().unwrap();
1003 assert_eq!(stopped.inner().runs, 2);
1004 }
1005
1006 #[test]
1007 fn failed_start_hands_back_a_faulted_app_for_cleanup() {
1008 let app = Lifecycle::new(MockApp {
1009 fail_start: true,
1010 ..Default::default()
1011 });
1012 let err = app.start().unwrap_err();
1013 assert!(err.error.to_string().contains("mock start failure"));
1014
1015 let stopped = err.app.stop().unwrap();
1017 let mock = stopped.into_inner();
1018 assert_eq!(mock.started, 0);
1019 assert_eq!(mock.stopped, 1);
1020 }
1021
1022 #[test]
1023 fn failed_stop_hands_back_a_faulted_app() {
1024 let app = Lifecycle::new(MockApp {
1025 fail_stop: true,
1026 ..Default::default()
1027 });
1028 let running = app.start().unwrap();
1029 let err = running.stop().unwrap_err();
1030 assert!(err.error.to_string().contains("mock stop failure"));
1031 }
1032
1033 #[test]
1034 fn lifecycle_error_propagates_with_question_mark() {
1035 fn drive() -> CuResult<()> {
1036 let app = Lifecycle::new(MockApp {
1037 fail_start: true,
1038 ..Default::default()
1039 });
1040 let _running = app.start()?;
1041 Ok(())
1042 }
1043 let error = drive().unwrap_err();
1044 assert!(error.to_string().contains("mock start failure"));
1045 }
1046
1047 #[derive(Default)]
1048 struct MockSimApp {
1049 started: u32,
1050 stopped: u32,
1051 iterations: u32,
1052 fail_start: bool,
1053 }
1054
1055 struct MockStep;
1056
1057 #[allow(deprecated)] impl<S: SectionStorage, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for MockSimApp {
1059 type Step<'z> = MockStep;
1060
1061 fn get_original_config() -> String {
1062 String::new()
1063 }
1064
1065 fn start_all_tasks(
1066 &mut self,
1067 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
1068 ) -> CuResult<()> {
1069 if self.fail_start {
1070 return Err("mock sim start failure".into());
1071 }
1072 let _ = sim_callback(MockStep);
1073 self.started += 1;
1074 Ok(())
1075 }
1076
1077 fn run_one_iteration(
1078 &mut self,
1079 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
1080 ) -> CuResult<()> {
1081 let _ = sim_callback(MockStep);
1082 self.iterations += 1;
1083 Ok(())
1084 }
1085
1086 fn run(
1087 &mut self,
1088 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
1089 ) -> CuResult<()> {
1090 let _ = sim_callback(MockStep);
1091 Ok(())
1092 }
1093
1094 fn stop_all_tasks(
1095 &mut self,
1096 sim_callback: &mut impl for<'z> FnMut(Self::Step<'z>) -> SimOverride,
1097 ) -> CuResult<()> {
1098 let _ = sim_callback(MockStep);
1099 self.stopped += 1;
1100 Ok(())
1101 }
1102
1103 fn restore_keyframe(&mut self, _freezer: &KeyFrame) -> CuResult<()> {
1104 Ok(())
1105 }
1106 }
1107
1108 type SimLifecycle = CuStdSimAppLifecycle<MockSimApp>;
1109
1110 #[test]
1111 fn sim_full_cycle_threads_the_callback() {
1112 let mut callback_calls = 0u32;
1113 let mut cb = |_step: MockStep| -> SimOverride {
1114 callback_calls += 1;
1115 SimOverride::ExecuteByRuntime
1116 };
1117
1118 let app = SimLifecycle::new(MockSimApp::default());
1119 let mut running = app.start(&mut cb).unwrap();
1120 running.run_one_iteration(&mut cb).unwrap();
1121 let stopped = running.stop(&mut cb).unwrap();
1122
1123 let mock = stopped.into_inner();
1124 assert_eq!(mock.started, 1);
1125 assert_eq!(mock.iterations, 1);
1126 assert_eq!(mock.stopped, 1);
1127 assert_eq!(callback_calls, 3);
1128 }
1129
1130 #[test]
1131 fn sim_failed_start_hands_back_a_faulted_app_for_cleanup() {
1132 let mut cb = |_step: MockStep| -> SimOverride { SimOverride::ExecuteByRuntime };
1133 let app = SimLifecycle::new(MockSimApp {
1134 fail_start: true,
1135 ..Default::default()
1136 });
1137 let err = app.start(&mut cb).unwrap_err();
1138 assert!(err.error.to_string().contains("mock sim start failure"));
1139 let stopped = err.app.stop(&mut cb).unwrap();
1140 assert_eq!(stopped.inner().stopped, 1);
1141 }
1142}