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> {
398 if event < -1 {
399 return None;
400 }
401 if event == 0 {
402 return Some(get_current());
403 }
404
405 let mut inner = GENERAL_TIME.lock().unwrap();
406
407 if event == -1 {
408 for i in 0..inner.current_providers.len() {
410 if let Some(t) = (inner.current_providers[i].get_time)() {
411 let name = inner.current_providers[i].name.clone();
412 if t >= inner.last_best_time {
413 inner.last_best_time = t;
414 inner.last_event_name = Some(name);
415 return Some(t);
416 } else {
417 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
418 inner.last_event_name = Some(name);
419 return Some(inner.last_best_time);
420 }
421 }
422 }
423 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
424 return Some(inner.last_best_time);
425 }
426
427 for i in 0..inner.event_providers.len() {
429 if let Some(t) = (inner.event_providers[i].get_event)(event) {
430 let name = inner.event_providers[i].name.clone();
431 inner.last_event_name = Some(name);
432
433 if (1..=255).contains(&event) {
434 let slot = event as usize;
435 if t >= inner.event_times[slot] {
436 inner.event_times[slot] = t;
437 return Some(t);
438 } else {
439 ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
440 return Some(inner.event_times[slot]);
441 }
442 }
443 return Some(t);
445 }
446 }
447
448 None
450}
451
452pub fn install_last_resort_event_provider() {
466 register_event_provider("Last Resort Event", 999, |_| Some(SystemTime::now()));
467}
468
469pub fn error_counts() -> u64 {
471 ERROR_COUNTS.load(Ordering::Relaxed)
472}
473
474pub fn reset_error_counts() {
476 ERROR_COUNTS.store(0, Ordering::Relaxed);
477}
478
479pub fn current_provider_name() -> Option<String> {
481 GENERAL_TIME.lock().unwrap().last_current_name.clone()
482}
483
484pub fn event_provider_name() -> Option<String> {
486 GENERAL_TIME.lock().unwrap().last_event_name.clone()
487}
488
489fn format_time_sample(t: SystemTime) -> String {
496 let dt: chrono::DateTime<chrono::Local> = t.into();
497 dt.format("%Y-%m-%d %H:%M:%S.%6f").to_string()
498}
499
500pub fn report(level: i32) -> String {
516 let inner = GENERAL_TIME.lock().unwrap();
517 let mut out = String::new();
518
519 out.push_str(&format!(
521 "Backwards time errors prevented {} times.\n\n",
522 error_counts()
523 ));
524
525 out.push_str("Current Time Providers:\n");
527 if inner.current_providers.is_empty() {
528 out.push_str("\tNo Providers registered.\n");
529 } else {
530 for p in &inner.current_providers {
531 out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
532 if level != 0 {
533 match (p.get_time)() {
534 Some(t) => {
535 out.push_str(&format!("\tCurrent Time is {}.\n", format_time_sample(t)))
536 }
537 None => out.push_str("\tCurrent Time not available\n"),
538 }
539 }
540 }
541 out.push('\n');
543 }
544
545 out.push_str("Event Time Providers:\n");
547 if inner.event_providers.is_empty() {
548 out.push_str("\tNo Providers registered.\n");
549 } else {
550 for p in &inner.event_providers {
551 out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
552 }
553 out.push('\n');
554 }
555
556 out
557}
558
559#[cfg(test)]
561fn _reset_for_testing() {
562 let mut inner = GENERAL_TIME.lock().unwrap();
563 *inner = GeneralTimeInner::new();
564 ERROR_COUNTS.store(0, Ordering::Relaxed);
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570 use std::time::Duration;
571
572 static TEST_LOCK: Mutex<()> = Mutex::new(());
574
575 #[test]
576 fn os_clock_default_returns_reasonable_time() {
577 let _g = TEST_LOCK.lock().unwrap();
578 _reset_for_testing();
579 let t = get_current();
580 let secs = t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
581 assert!(secs > 1_577_836_800, "time should be after 2020");
583 }
584
585 #[test]
586 fn custom_provider_overrides_os_clock() {
587 let _g = TEST_LOCK.lock().unwrap();
588 _reset_for_testing();
589 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
590 register_current_provider("Test Clock", 10, move || Some(fixed));
591
592 let t = get_current();
593 assert_eq!(t, fixed);
594 assert_eq!(current_provider_name().as_deref(), Some("Test Clock"));
595 }
596
597 #[test]
598 fn provider_returning_none_falls_through() {
599 let _g = TEST_LOCK.lock().unwrap();
600 _reset_for_testing();
601 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
602 register_current_provider("Broken", 1, || None);
604 register_current_provider("Fallback", 50, move || Some(fixed));
606
607 let t = get_current();
608 assert_eq!(t, fixed);
609 assert_eq!(current_provider_name().as_deref(), Some("Fallback"));
610 }
611
612 #[test]
613 fn monotonic_enforcement() {
614 let _g = TEST_LOCK.lock().unwrap();
615 _reset_for_testing();
616 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
617 let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000); let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
620 let call_c = call.clone();
621
622 register_current_provider("Stepper", 10, move || {
623 let n = call_c.fetch_add(1, Ordering::Relaxed);
624 match n {
625 0 => Some(t1),
626 _ => Some(t2),
627 }
628 });
629
630 reset_error_counts();
631 let first = get_current();
632 assert_eq!(first, t1);
633 assert_eq!(error_counts(), 0);
634
635 let second = get_current();
636 assert_eq!(second, t1);
638 assert_eq!(error_counts(), 1);
639 }
640
641 #[test]
642 fn event_zero_delegates_to_get_current() {
643 let _g = TEST_LOCK.lock().unwrap();
644 _reset_for_testing();
645 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
646 register_current_provider("Fixed", 10, move || Some(fixed));
647
648 let t = get_event(0);
649 assert_eq!(t, Some(fixed));
650 }
651
652 #[test]
658 fn an_event_below_best_time_is_a_bad_event() {
659 let _g = TEST_LOCK.lock().unwrap();
660 _reset_for_testing();
661 let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
662 register_current_provider("Fixed", 10, move || Some(fixed));
663 register_event_provider("EventSrc", 10, move |_ev| Some(fixed));
664
665 assert_eq!(get_event(-3), None);
666 assert_eq!(get_event(i16::MIN as i32), None);
667 assert_eq!(get_event(-1), Some(fixed));
669 assert_eq!(get_event(0), Some(fixed));
670 }
671
672 #[test]
679 fn an_event_no_provider_serves_is_not_the_current_time() {
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 assert_eq!(get_event(42), None);
686 assert_eq!(get_event(300), None);
687 }
688
689 #[test]
690 fn event_per_slot_ratcheting() {
691 let _g = TEST_LOCK.lock().unwrap();
692 _reset_for_testing();
693 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
694 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));
698 let call_c = call.clone();
699
700 register_event_provider("EventSrc", 10, move |_ev| {
701 let n = call_c.fetch_add(1, Ordering::Relaxed);
702 match n {
703 0 => Some(t1),
704 1 => Some(t2),
705 _ => Some(t3),
706 }
707 });
708
709 reset_error_counts();
710 let first = get_event(42);
711 assert_eq!(first, Some(t1));
712
713 let second = get_event(42);
714 assert_eq!(second, Some(t1));
716 assert_eq!(error_counts(), 1);
717
718 let third = get_event(42);
719 assert_eq!(third, Some(t3));
720 }
721
722 #[test]
723 fn event_best_time_ratcheting() {
724 let _g = TEST_LOCK.lock().unwrap();
725 _reset_for_testing();
726 let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
727 let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
728
729 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
730 let call_c = call.clone();
731
732 register_current_provider("BestSrc", 10, move || {
733 let n = call_c.fetch_add(1, Ordering::Relaxed);
734 match n {
735 0 => Some(t1),
736 _ => Some(t2),
737 }
738 });
739
740 reset_error_counts();
741 let first = get_event(-1);
742 assert_eq!(first, Some(t1));
743
744 let second = get_event(-1);
745 assert_eq!(second, Some(t1)); assert_eq!(error_counts(), 1);
747 }
748
749 #[test]
750 fn error_counts_reset() {
751 let _g = TEST_LOCK.lock().unwrap();
752 _reset_for_testing();
753 let t_back = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
754 register_current_provider("AlwaysBack", 10, move || Some(t_back));
755
756 reset_error_counts();
764 assert_eq!(error_counts(), 0);
765
766 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(3_000_000_000);
768 {
769 let mut inner = GENERAL_TIME.lock().unwrap();
770 inner.last_best_time = t_high;
771 }
772 let _ = get_event(-1);
774 assert!(error_counts() > 0);
775
776 reset_error_counts();
777 assert_eq!(error_counts(), 0);
778 }
779
780 #[test]
787 fn sync_hooks_fire_in_registration_order() {
788 use std::sync::{Arc, Mutex};
789 let captured: Arc<Mutex<Vec<(usize, SystemTime)>>> = Arc::new(Mutex::new(Vec::new()));
790
791 let cap1 = captured.clone();
792 register_clock_sync_hook(move |t| {
793 cap1.lock().unwrap().push((1, t));
794 });
795 let cap2 = captured.clone();
796 register_clock_sync_hook(move |t| {
797 cap2.lock().unwrap().push((2, t));
798 });
799
800 let synced = SystemTime::UNIX_EPOCH + Duration::from_secs(5_000_000_000);
801 notify_clock_sync(synced);
802
803 let log = captured.lock().unwrap();
804 let ours: Vec<_> = log.iter().filter(|(id, _)| *id == 1 || *id == 2).collect();
806 assert!(
807 ours.len() >= 2,
808 "both hooks must have fired at least once: {ours:?}"
809 );
810 let last1_idx = ours
814 .iter()
815 .rposition(|(id, t)| *id == 1 && *t == synced)
816 .expect("hook 1 fired with our synced timestamp");
817 let last2_idx = ours
818 .iter()
819 .rposition(|(id, t)| *id == 2 && *t == synced)
820 .expect("hook 2 fired with our synced timestamp");
821 assert!(
822 last1_idx < last2_idx,
823 "hook 1 must fire before hook 2 (registration order)"
824 );
825 }
826
827 #[test]
831 fn os_clock_only_bypasses_ratchet() {
832 let _g = TEST_LOCK.lock().unwrap();
833 _reset_for_testing();
834
835 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
839 let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
840 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
841 let call_c = call.clone();
842 {
843 let mut inner = GENERAL_TIME.lock().unwrap();
844 inner.current_providers.clear();
845 inner.current_providers.push(CurrentTimeProvider {
846 name: "OS Clock".to_string(),
847 priority: 999,
848 get_time: Box::new(move || {
849 let n = call_c.fetch_add(1, Ordering::Relaxed);
850 Some(if n == 0 { t_high } else { t_low })
851 }),
852 interrupt_safe: true,
853 is_os_default: true,
854 });
855 inner.use_osd_get_current = true;
856 }
857
858 reset_error_counts();
859 let first = get_current();
860 assert_eq!(first, t_high);
861 let second = get_current();
863 assert_eq!(
864 second, t_low,
865 "OS-clock-only path must follow a backward step (C useOsdGetCurrent)"
866 );
867 assert_eq!(
868 error_counts(),
869 0,
870 "OS-clock-only backward step must not count an error"
871 );
872 }
873
874 #[test]
877 fn registering_provider_enables_ratchet() {
878 let _g = TEST_LOCK.lock().unwrap();
879 _reset_for_testing();
880 let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
881 let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
882 let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
883 let call_c = call.clone();
884 register_current_provider("Stepper", 10, move || {
885 let n = call_c.fetch_add(1, Ordering::Relaxed);
886 Some(if n == 0 { t_high } else { t_low })
887 });
888
889 reset_error_counts();
890 assert_eq!(get_current(), t_high);
891 assert_eq!(get_current(), t_high);
894 assert_eq!(error_counts(), 1);
895 }
896
897 #[test]
900 fn except_priority_skips_named_provider() {
901 let _g = TEST_LOCK.lock().unwrap();
902 _reset_for_testing();
903 let t10 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
904 let t20 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_900_000_000);
905 register_current_provider("P10", 10, move || Some(t10));
906 register_current_provider("P20", 20, move || Some(t20));
907
908 let (t, prio) = get_current_except_priority(10).expect("P20 answers");
910 assert_eq!(t, t20);
911 assert_eq!(prio, 20);
912
913 let (t, prio) = get_current_except_priority(-10).expect("P10 answers");
915 assert_eq!(t, t10);
916 assert_eq!(prio, 10);
917 }
918
919 #[test]
922 fn int_providers_only_seen_by_int_queries() {
923 let _g = TEST_LOCK.lock().unwrap();
924 _reset_for_testing();
925 let t_int = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
926 register_int_current_provider("IntClock", 5, move || Some(t_int));
927 assert_eq!(get_current_int(), Some(t_int));
928
929 let t_evt = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_500);
930 register_int_event_provider("IntEvent", 5, move |_| Some(t_evt));
931 assert_eq!(get_event_int(7), Some(t_evt));
932 }
933
934 #[test]
937 fn int_event_query_none_without_int_provider() {
938 let _g = TEST_LOCK.lock().unwrap();
939 _reset_for_testing();
940 register_event_provider("Plain", 10, |_| {
942 Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1))
943 });
944 assert_eq!(get_event_int(3), None);
945 }
946
947 #[test]
950 fn highest_current_name_is_top_priority() {
951 let _g = TEST_LOCK.lock().unwrap();
952 _reset_for_testing();
953 assert_eq!(highest_current_name().as_deref(), Some("OS Clock"));
955 register_current_provider("Primary", 1, || None);
956 assert_eq!(highest_current_name().as_deref(), Some("Primary"));
957 }
958
959 #[test]
961 fn report_format_matches_general_time_report() {
962 let _g = TEST_LOCK.lock().unwrap();
963 _reset_for_testing();
964 let r = report(0);
965 assert!(
967 r.starts_with("Backwards time errors prevented 0 times.\n\n"),
968 "report must lead with the backwards-error line: {r:?}"
969 );
970 assert!(r.contains("Current Time Providers:\n"));
971 assert!(
973 r.contains(" \"OS Clock\", priority = 999\n"),
974 "provider line must use C `\"name\", priority = N` form: {r:?}"
975 );
976 assert!(
978 r.contains("Event Time Providers:\n\tNo Providers registered.\n"),
979 "empty event list must print the C placeholder: {r:?}"
980 );
981 assert!(!r.contains("\" priority "));
983 assert!(!r.contains("(none)"));
984 }
985
986 #[test]
989 fn report_level_one_prints_time_sample() {
990 let _g = TEST_LOCK.lock().unwrap();
991 _reset_for_testing();
992 let r = report(1);
993 assert!(
994 r.contains("\tCurrent Time is "),
995 "level>0 report must print a per-provider time sample: {r:?}"
996 );
997 }
998
999 #[test]
1002 fn ratchet_seeds_at_epics_epoch() {
1003 let secs = epics_epoch()
1005 .duration_since(SystemTime::UNIX_EPOCH)
1006 .unwrap()
1007 .as_secs();
1008 assert_eq!(secs, EPICS_EPOCH_UNIX_SECS);
1009 assert_eq!(secs, 631_152_000);
1010
1011 let _g = TEST_LOCK.lock().unwrap();
1012 _reset_for_testing();
1013 let inner = GENERAL_TIME.lock().unwrap();
1014 assert_eq!(inner.last_provided_time, epics_epoch());
1015 assert_eq!(inner.last_best_time, epics_epoch());
1016 assert!(inner.event_times.iter().all(|t| *t == epics_epoch()));
1017 }
1018}