1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::{LazyLock, Mutex};
3use std::time::SystemTime;
4
5type CurrentTimeFn = Box<dyn Fn() -> Option<SystemTime> + Send + Sync>;
7
8type EventTimeFn = Box<dyn Fn(i32) -> Option<SystemTime> + Send + Sync>;
10
11pub const EPICS_EPOCH_UNIX_SECS: u64 = 631_152_000;
14
15pub fn epics_epoch() -> SystemTime {
31 SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(EPICS_EPOCH_UNIX_SECS)
32}
33
34struct CurrentTimeProvider {
35 name: String,
36 priority: i32,
37 get_time: CurrentTimeFn,
38 interrupt_safe: bool,
42 is_os_default: bool,
45}
46
47struct EventTimeProvider {
48 name: String,
49 priority: i32,
50 get_event: EventTimeFn,
51 interrupt_safe: bool,
53}
54
55struct GeneralTimeInner {
56 current_providers: Vec<CurrentTimeProvider>,
57 event_providers: Vec<EventTimeProvider>,
58 last_provided_time: SystemTime,
60 event_times: [SystemTime; 256],
62 last_best_time: SystemTime,
64 last_current_name: Option<String>,
66 last_event_name: Option<String>,
68 use_osd_get_current: bool,
76 sync_hooks: Vec<Box<dyn Fn(SystemTime) + Send + Sync>>,
79}
80
81impl GeneralTimeInner {
82 fn new() -> Self {
83 let mut inner = Self {
84 current_providers: Vec::new(),
85 event_providers: Vec::new(),
86 last_provided_time: epics_epoch(),
87 event_times: [epics_epoch(); 256],
88 last_best_time: epics_epoch(),
89 last_current_name: None,
90 last_event_name: None,
91 use_osd_get_current: true,
92 sync_hooks: Vec::new(),
93 };
94 inner.current_providers.push(CurrentTimeProvider {
96 name: "OS Clock".to_string(),
97 priority: 999,
98 get_time: Box::new(|| Some(SystemTime::now())),
99 interrupt_safe: true,
100 is_os_default: true,
101 });
102 inner
103 }
104}
105
106static GENERAL_TIME: LazyLock<Mutex<GeneralTimeInner>> =
107 LazyLock::new(|| Mutex::new(GeneralTimeInner::new()));
108
109static ERROR_COUNTS: AtomicU64 = AtomicU64::new(0);
110
111pub fn register_current_provider(
113 name: impl Into<String>,
114 priority: i32,
115 get_time: impl Fn() -> Option<SystemTime> + Send + Sync + 'static,
116) {
117 register_current_provider_impl(name.into(), priority, Box::new(get_time), false);
118}
119
120pub fn register_int_current_provider(
127 name: impl Into<String>,
128 priority: i32,
129 get_time: impl Fn() -> Option<SystemTime> + Send + Sync + 'static,
130) {
131 register_current_provider_impl(name.into(), priority, Box::new(get_time), true);
132}
133
134fn register_current_provider_impl(
135 name: String,
136 priority: i32,
137 get_time: CurrentTimeFn,
138 interrupt_safe: bool,
139) {
140 let mut inner = GENERAL_TIME.lock().unwrap();
141 let provider = CurrentTimeProvider {
142 name,
143 priority,
144 get_time,
145 interrupt_safe,
146 is_os_default: false,
147 };
148 let pos = inner
149 .current_providers
150 .iter()
151 .position(|p| p.priority > priority)
152 .unwrap_or(inner.current_providers.len());
153 inner.current_providers.insert(pos, provider);
154 inner.use_osd_get_current = false;
158}
159
160pub fn register_event_provider(
162 name: impl Into<String>,
163 priority: i32,
164 get_event: impl Fn(i32) -> Option<SystemTime> + Send + Sync + 'static,
165) {
166 register_event_provider_impl(name.into(), priority, Box::new(get_event), false);
167}
168
169pub fn register_int_event_provider(
175 name: impl Into<String>,
176 priority: i32,
177 get_event: impl Fn(i32) -> Option<SystemTime> + Send + Sync + 'static,
178) {
179 register_event_provider_impl(name.into(), priority, Box::new(get_event), true);
180}
181
182fn register_event_provider_impl(
183 name: String,
184 priority: i32,
185 get_event: EventTimeFn,
186 interrupt_safe: bool,
187) {
188 let mut inner = GENERAL_TIME.lock().unwrap();
189 let provider = EventTimeProvider {
190 name,
191 priority,
192 get_event,
193 interrupt_safe,
194 };
195 let pos = inner
196 .event_providers
197 .iter()
198 .position(|p| p.priority > priority)
199 .unwrap_or(inner.event_providers.len());
200 inner.event_providers.insert(pos, provider);
201}
202
203pub fn register_clock_sync_hook<F>(hook: F)
219where
220 F: Fn(SystemTime) + Send + Sync + 'static,
221{
222 let mut inner = GENERAL_TIME.lock().unwrap();
223 inner.sync_hooks.push(Box::new(hook));
224}
225
226pub fn notify_clock_sync(t_synced: SystemTime) {
238 let inner = GENERAL_TIME.lock().unwrap();
239 for hook in &inner.sync_hooks {
240 hook(t_synced);
241 }
242}
243
244pub fn get_current() -> SystemTime {
257 let mut inner = GENERAL_TIME.lock().unwrap();
258
259 if inner.use_osd_get_current {
261 if let Some(idx) = inner.current_providers.iter().position(|p| p.is_os_default) {
262 if let Some(t) = (inner.current_providers[idx].get_time)() {
263 let name = inner.current_providers[idx].name.clone();
264 inner.last_provided_time = t;
265 inner.last_current_name = Some(name);
266 return t;
267 }
268 }
269 }
272
273 for i in 0..inner.current_providers.len() {
274 if let Some(t) = (inner.current_providers[i].get_time)() {
275 let name = inner.current_providers[i].name.clone();
276 if t >= inner.last_provided_time {
277 inner.last_provided_time = t;
278 inner.last_current_name = Some(name);
279 return t;
280 } else {
281 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
282 inner.last_current_name = Some(name);
283 return inner.last_provided_time;
284 }
285 }
286 }
287 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
289 inner.last_provided_time
290}
291
292pub fn get_current_except_priority(ignore_priority: i32) -> Option<(SystemTime, i32)> {
310 let inner = GENERAL_TIME.lock().unwrap();
311 for p in &inner.current_providers {
312 if (ignore_priority > 0 && p.priority == ignore_priority)
313 || (ignore_priority < 0 && p.priority != -ignore_priority)
314 {
315 continue;
316 }
317 if let Some(t) = (p.get_time)() {
318 return Some((t, p.priority));
319 }
320 }
321 None
322}
323
324pub fn get_current_int() -> Option<SystemTime> {
334 let inner = GENERAL_TIME.lock().unwrap();
335 for p in &inner.current_providers {
336 if !p.interrupt_safe {
337 continue;
338 }
339 if let Some(t) = (p.get_time)() {
340 return Some(t);
341 }
342 }
343 None
344}
345
346pub fn get_event_int(event: i32) -> Option<SystemTime> {
353 let inner = GENERAL_TIME.lock().unwrap();
354 for p in &inner.event_providers {
355 if !p.interrupt_safe {
356 continue;
357 }
358 if let Some(t) = (p.get_event)(event) {
359 return Some(t);
360 }
361 }
362 None
363}
364
365pub fn highest_current_name() -> Option<String> {
372 GENERAL_TIME
373 .lock()
374 .unwrap()
375 .current_providers
376 .first()
377 .map(|p| p.name.clone())
378}
379
380pub fn get_event(event: i32) -> Option<SystemTime> {
399 if event < -1 {
400 return None;
401 }
402 if event == 0 {
403 return Some(get_current());
404 }
405
406 let mut inner = GENERAL_TIME.lock().unwrap();
407
408 if event == -1 {
409 for i in 0..inner.current_providers.len() {
411 if let Some(t) = (inner.current_providers[i].get_time)() {
412 let name = inner.current_providers[i].name.clone();
413 if t >= inner.last_best_time {
414 inner.last_best_time = t;
415 inner.last_event_name = Some(name);
416 return Some(t);
417 } else {
418 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
419 inner.last_event_name = Some(name);
420 return Some(inner.last_best_time);
421 }
422 }
423 }
424 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
425 return Some(inner.last_best_time);
426 }
427
428 for i in 0..inner.event_providers.len() {
430 if let Some(t) = (inner.event_providers[i].get_event)(event) {
431 let name = inner.event_providers[i].name.clone();
432 inner.last_event_name = Some(name);
433
434 if (1..=255).contains(&event) {
435 let slot = event as usize;
436 if t >= inner.event_times[slot] {
437 inner.event_times[slot] = t;
438 return Some(t);
439 } else {
440 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
441 return Some(inner.event_times[slot]);
442 }
443 }
444 return Some(t);
446 }
447 }
448
449 None
451}
452
453pub fn install_last_resort_event_provider() {
467 register_event_provider("Last Resort Event", 999, |_| Some(SystemTime::now()));
468}
469
470pub fn error_counts() -> u64 {
472 ERROR_COUNTS.load(Ordering::Relaxed)
473}
474
475pub fn reset_error_counts() {
477 ERROR_COUNTS.store(0, Ordering::Relaxed);
478}
479
480pub fn current_provider_name() -> Option<String> {
482 GENERAL_TIME.lock().unwrap().last_current_name.clone()
483}
484
485pub fn event_provider_name() -> Option<String> {
487 GENERAL_TIME.lock().unwrap().last_event_name.clone()
488}
489
490fn format_time_sample(t: SystemTime) -> String {
497 let dt: chrono::DateTime<chrono::Local> = t.into();
498 dt.format("%Y-%m-%d %H:%M:%S.%6f").to_string()
499}
500
501static PROGRAM_START: LazyLock<SystemTime> = LazyLock::new(SystemTime::now);
509
510pub fn clock_time_report(level: i32) -> String {
526 let _ = level;
527 let mut out = format!(
528 "Program started at {}\n",
529 format_time_sample(*PROGRAM_START)
530 );
531 if cfg!(any(target_os = "vxworks", target_os = "rtems")) {
532 out.push_str("IOC's OS Clock synchronization thread is not running.\n");
533 }
534 out
535}
536
537pub fn report(level: i32) -> String {
553 let inner = GENERAL_TIME.lock().unwrap();
554 let mut out = String::new();
555
556 out.push_str(&format!(
558 "Backwards time errors prevented {} times.\n\n",
559 error_counts()
560 ));
561
562 out.push_str("Current Time Providers:\n");
564 if inner.current_providers.is_empty() {
565 out.push_str("\tNo Providers registered.\n");
566 } else {
567 for p in &inner.current_providers {
568 out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
569 if level != 0 {
570 match (p.get_time)() {
571 Some(t) => {
572 out.push_str(&format!("\tCurrent Time is {}.\n", format_time_sample(t)))
573 }
574 None => out.push_str("\tCurrent Time not available\n"),
575 }
576 }
577 }
578 out.push('\n');
580 }
581
582 out.push_str("Event Time Providers:\n");
584 if inner.event_providers.is_empty() {
585 out.push_str("\tNo Providers registered.\n");
586 } else {
587 for p in &inner.event_providers {
588 out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
589 }
590 out.push('\n');
591 }
592
593 out
594}
595
596#[cfg(test)]
598fn _reset_for_testing() {
599 let mut inner = GENERAL_TIME.lock().unwrap();
600 *inner = GeneralTimeInner::new();
601 ERROR_COUNTS.store(0, Ordering::Relaxed);
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607 use std::time::Duration;
608
609 static TEST_LOCK: Mutex<()> = Mutex::new(());
611
612 #[test]
613 fn os_clock_default_returns_reasonable_time() {
614 let _g = TEST_LOCK.lock().unwrap();
615 _reset_for_testing();
616 let t = get_current();
617 let secs = t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
618 assert!(secs > 1_577_836_800, "time should be after 2020");
620 }
621
622 #[test]
623 fn custom_provider_overrides_os_clock() {
624 let _g = TEST_LOCK.lock().unwrap();
625 _reset_for_testing();
626 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
627 register_current_provider("Test Clock", 10, move || Some(fixed));
628
629 let t = get_current();
630 assert_eq!(t, fixed);
631 assert_eq!(current_provider_name().as_deref(), Some("Test Clock"));
632 }
633
634 #[test]
635 fn provider_returning_none_falls_through() {
636 let _g = TEST_LOCK.lock().unwrap();
637 _reset_for_testing();
638 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
639 register_current_provider("Broken", 1, || None);
641 register_current_provider("Fallback", 50, move || Some(fixed));
643
644 let t = get_current();
645 assert_eq!(t, fixed);
646 assert_eq!(current_provider_name().as_deref(), Some("Fallback"));
647 }
648
649 #[test]
650 fn monotonic_enforcement() {
651 let _g = TEST_LOCK.lock().unwrap();
652 _reset_for_testing();
653 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
654 let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000); let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
657 let call_c = call.clone();
658
659 register_current_provider("Stepper", 10, move || {
660 let n = call_c.fetch_add(1, Ordering::Relaxed);
661 match n {
662 0 => Some(t1),
663 _ => Some(t2),
664 }
665 });
666
667 reset_error_counts();
668 let first = get_current();
669 assert_eq!(first, t1);
670 assert_eq!(error_counts(), 0);
671
672 let second = get_current();
673 assert_eq!(second, t1);
675 assert_eq!(error_counts(), 1);
676 }
677
678 #[test]
679 fn event_zero_delegates_to_get_current() {
680 let _g = TEST_LOCK.lock().unwrap();
681 _reset_for_testing();
682 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
683 register_current_provider("Fixed", 10, move || Some(fixed));
684
685 let t = get_event(0);
686 assert_eq!(t, Some(fixed));
687 }
688
689 #[test]
695 fn an_event_below_best_time_is_a_bad_event() {
696 let _g = TEST_LOCK.lock().unwrap();
697 _reset_for_testing();
698 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
699 register_current_provider("Fixed", 10, move || Some(fixed));
700 register_event_provider("EventSrc", 10, move |_ev| Some(fixed));
701
702 assert_eq!(get_event(-3), None);
703 assert_eq!(get_event(i16::MIN as i32), None);
704 assert_eq!(get_event(-1), Some(fixed));
706 assert_eq!(get_event(0), Some(fixed));
707 }
708
709 #[test]
716 fn an_event_no_provider_serves_is_not_the_current_time() {
717 let _g = TEST_LOCK.lock().unwrap();
718 _reset_for_testing();
719 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
720 register_current_provider("Fixed", 10, move || Some(fixed));
721
722 assert_eq!(get_event(42), None);
723 assert_eq!(get_event(300), None);
724 }
725
726 #[test]
727 fn event_per_slot_ratcheting() {
728 let _g = TEST_LOCK.lock().unwrap();
729 _reset_for_testing();
730 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
731 let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000); let t3 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_001_000); let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
735 let call_c = call.clone();
736
737 register_event_provider("EventSrc", 10, move |_ev| {
738 let n = call_c.fetch_add(1, Ordering::Relaxed);
739 match n {
740 0 => Some(t1),
741 1 => Some(t2),
742 _ => Some(t3),
743 }
744 });
745
746 reset_error_counts();
747 let first = get_event(42);
748 assert_eq!(first, Some(t1));
749
750 let second = get_event(42);
751 assert_eq!(second, Some(t1));
753 assert_eq!(error_counts(), 1);
754
755 let third = get_event(42);
756 assert_eq!(third, Some(t3));
757 }
758
759 #[test]
760 fn event_best_time_ratcheting() {
761 let _g = TEST_LOCK.lock().unwrap();
762 _reset_for_testing();
763 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
764 let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
765
766 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
767 let call_c = call.clone();
768
769 register_current_provider("BestSrc", 10, move || {
770 let n = call_c.fetch_add(1, Ordering::Relaxed);
771 match n {
772 0 => Some(t1),
773 _ => Some(t2),
774 }
775 });
776
777 reset_error_counts();
778 let first = get_event(-1);
779 assert_eq!(first, Some(t1));
780
781 let second = get_event(-1);
782 assert_eq!(second, Some(t1)); assert_eq!(error_counts(), 1);
784 }
785
786 #[test]
787 fn error_counts_reset() {
788 let _g = TEST_LOCK.lock().unwrap();
789 _reset_for_testing();
790 let t_back = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
791 register_current_provider("AlwaysBack", 10, move || Some(t_back));
792
793 reset_error_counts();
801 assert_eq!(error_counts(), 0);
802
803 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(3_000_000_000);
805 {
806 let mut inner = GENERAL_TIME.lock().unwrap();
807 inner.last_best_time = t_high;
808 }
809 let _ = get_event(-1);
811 assert!(error_counts() > 0);
812
813 reset_error_counts();
814 assert_eq!(error_counts(), 0);
815 }
816
817 #[test]
824 fn sync_hooks_fire_in_registration_order() {
825 use std::sync::{Arc, Mutex};
826 let captured: Arc<Mutex<Vec<(usize, SystemTime)>>> = Arc::new(Mutex::new(Vec::new()));
827
828 let cap1 = captured.clone();
829 register_clock_sync_hook(move |t| {
830 cap1.lock().unwrap().push((1, t));
831 });
832 let cap2 = captured.clone();
833 register_clock_sync_hook(move |t| {
834 cap2.lock().unwrap().push((2, t));
835 });
836
837 let synced = SystemTime::UNIX_EPOCH + Duration::from_secs(5_000_000_000);
838 notify_clock_sync(synced);
839
840 let log = captured.lock().unwrap();
841 let ours: Vec<_> = log.iter().filter(|(id, _)| *id == 1 || *id == 2).collect();
843 assert!(
844 ours.len() >= 2,
845 "both hooks must have fired at least once: {ours:?}"
846 );
847 let last1_idx = ours
851 .iter()
852 .rposition(|(id, t)| *id == 1 && *t == synced)
853 .expect("hook 1 fired with our synced timestamp");
854 let last2_idx = ours
855 .iter()
856 .rposition(|(id, t)| *id == 2 && *t == synced)
857 .expect("hook 2 fired with our synced timestamp");
858 assert!(
859 last1_idx < last2_idx,
860 "hook 1 must fire before hook 2 (registration order)"
861 );
862 }
863
864 #[test]
868 fn os_clock_only_bypasses_ratchet() {
869 let _g = TEST_LOCK.lock().unwrap();
870 _reset_for_testing();
871
872 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
876 let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
877 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
878 let call_c = call.clone();
879 {
880 let mut inner = GENERAL_TIME.lock().unwrap();
881 inner.current_providers.clear();
882 inner.current_providers.push(CurrentTimeProvider {
883 name: "OS Clock".to_string(),
884 priority: 999,
885 get_time: Box::new(move || {
886 let n = call_c.fetch_add(1, Ordering::Relaxed);
887 Some(if n == 0 { t_high } else { t_low })
888 }),
889 interrupt_safe: true,
890 is_os_default: true,
891 });
892 inner.use_osd_get_current = true;
893 }
894
895 reset_error_counts();
896 let first = get_current();
897 assert_eq!(first, t_high);
898 let second = get_current();
900 assert_eq!(
901 second, t_low,
902 "OS-clock-only path must follow a backward step (C useOsdGetCurrent)"
903 );
904 assert_eq!(
905 error_counts(),
906 0,
907 "OS-clock-only backward step must not count an error"
908 );
909 }
910
911 #[test]
914 fn registering_provider_enables_ratchet() {
915 let _g = TEST_LOCK.lock().unwrap();
916 _reset_for_testing();
917 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
918 let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
919 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
920 let call_c = call.clone();
921 register_current_provider("Stepper", 10, move || {
922 let n = call_c.fetch_add(1, Ordering::Relaxed);
923 Some(if n == 0 { t_high } else { t_low })
924 });
925
926 reset_error_counts();
927 assert_eq!(get_current(), t_high);
928 assert_eq!(get_current(), t_high);
931 assert_eq!(error_counts(), 1);
932 }
933
934 #[test]
937 fn except_priority_skips_named_provider() {
938 let _g = TEST_LOCK.lock().unwrap();
939 _reset_for_testing();
940 let t10 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
941 let t20 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_900_000_000);
942 register_current_provider("P10", 10, move || Some(t10));
943 register_current_provider("P20", 20, move || Some(t20));
944
945 let (t, prio) = get_current_except_priority(10).expect("P20 answers");
947 assert_eq!(t, t20);
948 assert_eq!(prio, 20);
949
950 let (t, prio) = get_current_except_priority(-10).expect("P10 answers");
952 assert_eq!(t, t10);
953 assert_eq!(prio, 10);
954 }
955
956 #[test]
959 fn int_providers_only_seen_by_int_queries() {
960 let _g = TEST_LOCK.lock().unwrap();
961 _reset_for_testing();
962 let t_int = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
963 register_int_current_provider("IntClock", 5, move || Some(t_int));
964 assert_eq!(get_current_int(), Some(t_int));
965
966 let t_evt = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_500);
967 register_int_event_provider("IntEvent", 5, move |_| Some(t_evt));
968 assert_eq!(get_event_int(7), Some(t_evt));
969 }
970
971 #[test]
974 fn int_event_query_none_without_int_provider() {
975 let _g = TEST_LOCK.lock().unwrap();
976 _reset_for_testing();
977 register_event_provider("Plain", 10, |_| {
979 Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1))
980 });
981 assert_eq!(get_event_int(3), None);
982 }
983
984 #[test]
987 fn highest_current_name_is_top_priority() {
988 let _g = TEST_LOCK.lock().unwrap();
989 _reset_for_testing();
990 assert_eq!(highest_current_name().as_deref(), Some("OS Clock"));
992 register_current_provider("Primary", 1, || None);
993 assert_eq!(highest_current_name().as_deref(), Some("Primary"));
994 }
995
996 #[test]
998 fn report_format_matches_general_time_report() {
999 let _g = TEST_LOCK.lock().unwrap();
1000 _reset_for_testing();
1001 let r = report(0);
1002 assert!(
1004 r.starts_with("Backwards time errors prevented 0 times.\n\n"),
1005 "report must lead with the backwards-error line: {r:?}"
1006 );
1007 assert!(r.contains("Current Time Providers:\n"));
1008 assert!(
1010 r.contains(" \"OS Clock\", priority = 999\n"),
1011 "provider line must use C `\"name\", priority = N` form: {r:?}"
1012 );
1013 assert!(
1015 r.contains("Event Time Providers:\n\tNo Providers registered.\n"),
1016 "empty event list must print the C placeholder: {r:?}"
1017 );
1018 assert!(!r.contains("\" priority "));
1020 assert!(!r.contains("(none)"));
1021 }
1022
1023 #[test]
1026 fn report_level_one_prints_time_sample() {
1027 let _g = TEST_LOCK.lock().unwrap();
1028 _reset_for_testing();
1029 let r = report(1);
1030 assert!(
1031 r.contains("\tCurrent Time is "),
1032 "level>0 report must print a per-provider time sample: {r:?}"
1033 );
1034 }
1035
1036 #[test]
1039 fn ratchet_seeds_at_epics_epoch() {
1040 let secs = epics_epoch()
1042 .duration_since(SystemTime::UNIX_EPOCH)
1043 .unwrap()
1044 .as_secs();
1045 assert_eq!(secs, EPICS_EPOCH_UNIX_SECS);
1046 assert_eq!(secs, 631_152_000);
1047
1048 let _g = TEST_LOCK.lock().unwrap();
1049 _reset_for_testing();
1050 let inner = GENERAL_TIME.lock().unwrap();
1051 assert_eq!(inner.last_provided_time, epics_epoch());
1052 assert_eq!(inner.last_best_time, epics_epoch());
1053 assert!(inner.event_times.iter().all(|t| *t == epics_epoch()));
1054 }
1055}