From 0d6094c8b10da141d048ec23133df4525befbd44 Mon Sep 17 00:00:00 2001
From: "Andres O. Vela" <andresovela@gmail.com>
Date: Sun, 29 Oct 2023 19:49:52 +0100
Subject: [PATCH] time: add MockDriver for testing purposes

---
 embassy-time/Cargo.toml         |  3 ++
 embassy-time/src/driver_mock.rs | 73 +++++++++++++++++++++++++++++++++
 embassy-time/src/lib.rs         |  6 +++
 3 files changed, 82 insertions(+)
 create mode 100644 embassy-time/src/driver_mock.rs

diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml
index 62404863d..8b5d31ee1 100644
--- a/embassy-time/Cargo.toml
+++ b/embassy-time/Cargo.toml
@@ -59,6 +59,9 @@ generic-queue-32 = ["generic-queue"]
 generic-queue-64 = ["generic-queue"]
 generic-queue-128 = ["generic-queue"]
 
+# Create a `MockDriver` that can be manually advanced for testing purposes.
+mock-driver = ["tick-hz-1_000_000"]
+
 # Set the `embassy_time` tick rate.
 #
 # At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used.
diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs
new file mode 100644
index 000000000..4ae5a2d96
--- /dev/null
+++ b/embassy-time/src/driver_mock.rs
@@ -0,0 +1,73 @@
+use core::cell::Cell;
+
+use critical_section::Mutex as CsMutex;
+
+use crate::driver::{AlarmHandle, Driver};
+use crate::{Duration, Instant};
+
+/// A mock driver that can be manually advanced.
+/// This is useful for testing code that works with [`Instant`] and [`Duration`].
+///
+/// This driver cannot currently be used to test runtime functionality, such as
+/// timers, delays, etc.
+///
+/// # Example
+///
+/// ```ignore
+/// fn has_a_second_passed(reference: Instant) -> bool {
+///     Instant::now().duration_since(reference) > Duration::from_secs(1)
+/// }
+///
+/// fn test_second_passed() {
+///     let driver = embassy_time::MockDriver::get();
+///     let reference = Instant::now();
+///     assert_eq!(false, has_a_second_passed(reference));
+///     driver.advance(Duration::from_secs(1));
+///     assert_eq!(true, has_a_second_passed(reference));
+/// }
+/// ```
+pub struct MockDriver {
+    now: CsMutex<Cell<Instant>>,
+}
+
+crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver {
+    now: CsMutex::new(Cell::new(Instant::from_ticks(0))),
+});
+
+impl MockDriver {
+    /// Gets a reference to the global mock driver.
+    pub fn get() -> &'static MockDriver {
+        &DRIVER
+    }
+
+    /// Sets the current time of the mock driver.
+    pub fn set_current_time(&self, now: Instant) {
+        critical_section::with(|cs| self.now.borrow(cs).set(now))
+    }
+
+    /// Advances the time by the specified [`Duration`].
+    pub fn advance(&self, duration: Duration) {
+        critical_section::with(|cs| {
+            let now = self.now.borrow(cs).get().as_ticks();
+            self.now.borrow(cs).set(Instant::from_ticks(now + duration.as_ticks()));
+        });
+    }
+}
+
+impl Driver for MockDriver {
+    fn now(&self) -> u64 {
+        critical_section::with(|cs| self.now.borrow(cs).get().as_micros() as u64)
+    }
+
+    unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
+        unimplemented!("MockDriver does not support runtime features that require an executor");
+    }
+
+    fn set_alarm_callback(&self, _alarm: AlarmHandle, _callback: fn(*mut ()), _ctx: *mut ()) {
+        unimplemented!("MockDriver does not support runtime features that require an executor");
+    }
+
+    fn set_alarm(&self, _alarm: AlarmHandle, _timestamp: u64) -> bool {
+        unimplemented!("MockDriver does not support runtime features that require an executor");
+    }
+}
diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs
index 8f57eabcb..45c1e882b 100644
--- a/embassy-time/src/lib.rs
+++ b/embassy-time/src/lib.rs
@@ -15,6 +15,12 @@ pub mod queue;
 mod tick;
 mod timer;
 
+#[cfg(feature = "mock-driver")]
+mod driver_mock;
+
+#[cfg(feature = "mock-driver")]
+pub use driver_mock::MockDriver;
+
 #[cfg(feature = "std")]
 mod driver_std;
 #[cfg(feature = "wasm")]