From 133a55035a7c167dc03aa671fff58eb6706c2d7a Mon Sep 17 00:00:00 2001 From: Albert Armea Date: Sun, 28 Dec 2025 21:26:54 -0500 Subject: [PATCH] Add clock mocking mechanism for dev use only --- README.md | 2 + crates/shepherd-api/src/events.rs | 2 +- crates/shepherd-core/src/engine.rs | 2 +- crates/shepherd-core/src/session.rs | 4 +- crates/shepherd-hud/src/app.rs | 44 +++ crates/shepherd-hud/src/state.rs | 4 +- crates/shepherd-launcher-ui/src/app.rs | 2 +- crates/shepherd-launcher-ui/src/client.rs | 4 +- crates/shepherd-launcher-ui/src/state.rs | 4 +- crates/shepherd-store/src/audit.rs | 2 +- crates/shepherd-util/src/time.rs | 319 +++++++++++++++++++++- crates/shepherdd/src/main.rs | 9 +- crates/shepherdd/tests/integration.rs | 17 +- 13 files changed, 388 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index bc7c1f2..b000985 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ TODO ## Development TODO: `./run-dev`, options +* Set the `SHEPHERD_MOCK_TIME` environment variable to mock the time, + such as `SHEPHERD_MOCK_TIME="2025-12-25 15:30:00" ./run-dev` ## Contributing diff --git a/crates/shepherd-api/src/events.rs b/crates/shepherd-api/src/events.rs index e1ca83a..7ea70a2 100644 --- a/crates/shepherd-api/src/events.rs +++ b/crates/shepherd-api/src/events.rs @@ -19,7 +19,7 @@ impl Event { pub fn new(payload: EventPayload) -> Self { Self { api_version: API_VERSION, - timestamp: Local::now(), + timestamp: shepherd_util::now(), payload, } } diff --git a/crates/shepherd-core/src/engine.rs b/crates/shepherd-core/src/engine.rs index e8b91fd..aaaa3e7 100644 --- a/crates/shepherd-core/src/engine.rs +++ b/crates/shepherd-core/src/engine.rs @@ -479,7 +479,7 @@ impl CoreEngine { }); // Build entry views for the snapshot - let entries = self.list_entries(Local::now()); + let entries = self.list_entries(shepherd_util::now()); DaemonStateSnapshot { api_version: API_VERSION, diff --git a/crates/shepherd-core/src/session.rs b/crates/shepherd-core/src/session.rs index 281ffcc..54f3b01 100644 --- a/crates/shepherd-core/src/session.rs +++ b/crates/shepherd-core/src/session.rs @@ -213,7 +213,7 @@ mod tests { #[test] fn test_session_creation() { let plan = make_test_plan(300); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); let session = ActiveSession::new(plan, now, now_mono); @@ -261,7 +261,7 @@ mod tests { #[test] fn test_pending_warnings() { let plan = make_test_plan(300); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); let mut session = ActiveSession::new(plan, now, now_mono); diff --git a/crates/shepherd-hud/src/app.rs b/crates/shepherd-hud/src/app.rs index 04809c8..13e5a16 100644 --- a/crates/shepherd-hud/src/app.rs +++ b/crates/shepherd-hud/src/app.rs @@ -179,6 +179,32 @@ fn build_hud_content(state: SharedState) -> gtk4::Box { .halign(gtk4::Align::End) .build(); + // Wall clock display (shows mock time indicator in debug builds) + let clock_box = gtk4::Box::builder() + .orientation(gtk4::Orientation::Horizontal) + .spacing(4) + .build(); + + let clock_icon = gtk4::Image::from_icon_name("preferences-system-time-symbolic"); + clock_icon.set_pixel_size(20); + clock_box.append(&clock_icon); + + let clock_label = gtk4::Label::new(Some("--:--")); + clock_label.add_css_class("clock-label"); + clock_box.append(&clock_label); + + // Add mock indicator if mock time is active (debug builds only) + #[cfg(debug_assertions)] + { + if shepherd_util::is_mock_time_active() { + let mock_indicator = gtk4::Label::new(Some("(MOCK)")); + mock_indicator.add_css_class("mock-time-indicator"); + clock_box.append(&mock_indicator); + } + } + + right_box.append(&clock_box); + // Volume control with slider let volume_box = gtk4::Box::builder() .orientation(gtk4::Orientation::Horizontal) @@ -345,8 +371,13 @@ fn build_hud_content(state: SharedState) -> gtk4::Box { let volume_slider_clone = volume_slider.clone(); let volume_label_clone = volume_label.clone(); let slider_changing_for_update = slider_changing.clone(); + let clock_label_clone = clock_label.clone(); glib::timeout_add_local(Duration::from_millis(500), move || { + // Update wall clock display + let current_time = shepherd_util::now(); + clock_label_clone.set_text(&shepherd_util::format_clock_time(¤t_time)); + // Update session state let session_state = state.session_state(); match &session_state { @@ -567,6 +598,19 @@ fn load_css() { min-width: 3em; text-align: right; } + + .clock-label { + font-family: monospace; + font-size: 14px; + color: var(--text-primary); + } + + .mock-time-indicator { + font-size: 10px; + font-weight: bold; + color: var(--color-warning); + margin-left: 4px; + } "#; let provider = gtk4::CssProvider::new(); diff --git a/crates/shepherd-hud/src/state.rs b/crates/shepherd-hud/src/state.rs index e481c98..1d16a08 100644 --- a/crates/shepherd-hud/src/state.rs +++ b/crates/shepherd-hud/src/state.rs @@ -182,7 +182,7 @@ impl SharedState { label, deadline, } => { - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = deadline.and_then(|d| { if d > now { @@ -244,7 +244,7 @@ impl SharedState { EventPayload::StateChanged(snapshot) => { if let Some(session) = &snapshot.current_session { - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = session.deadline.and_then(|d| { if d > now { diff --git a/crates/shepherd-launcher-ui/src/app.rs b/crates/shepherd-launcher-ui/src/app.rs index 02259d5..06ac9e0 100644 --- a/crates/shepherd-launcher-ui/src/app.rs +++ b/crates/shepherd-launcher-ui/src/app.rs @@ -190,7 +190,7 @@ impl LauncherApp { match payload { shepherd_api::ResponsePayload::LaunchApproved { session_id, deadline } => { info!(session_id = %session_id, "Launch approved, setting SessionActive"); - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = deadline.and_then(|d| { if d > now { diff --git a/crates/shepherd-launcher-ui/src/client.rs b/crates/shepherd-launcher-ui/src/client.rs index b04bd62..a389689 100644 --- a/crates/shepherd-launcher-ui/src/client.rs +++ b/crates/shepherd-launcher-ui/src/client.rs @@ -139,7 +139,7 @@ impl DaemonClient { match payload { ResponsePayload::State(snapshot) => { if let Some(session) = snapshot.current_session { - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = session.deadline.and_then(|d| { if d > now { @@ -166,7 +166,7 @@ impl DaemonClient { } } ResponsePayload::LaunchApproved { session_id, deadline } => { - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = deadline.and_then(|d| { if d > now { diff --git a/crates/shepherd-launcher-ui/src/state.rs b/crates/shepherd-launcher-ui/src/state.rs index 5b97745..a14b675 100644 --- a/crates/shepherd-launcher-ui/src/state.rs +++ b/crates/shepherd-launcher-ui/src/state.rs @@ -73,7 +73,7 @@ impl SharedState { deadline, } => { tracing::info!(session_id = %session_id, label = %label, "Session started event"); - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = deadline.and_then(|d| { if d > now { @@ -123,7 +123,7 @@ impl SharedState { fn apply_snapshot(&self, snapshot: DaemonStateSnapshot) { if let Some(session) = snapshot.current_session { - let now = chrono::Local::now(); + let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None let time_remaining = session.deadline.and_then(|d| { if d > now { diff --git a/crates/shepherd-store/src/audit.rs b/crates/shepherd-store/src/audit.rs index bcc810e..6697101 100644 --- a/crates/shepherd-store/src/audit.rs +++ b/crates/shepherd-store/src/audit.rs @@ -86,7 +86,7 @@ impl AuditEvent { pub fn new(event: AuditEventType) -> Self { Self { id: 0, // Will be set by store - timestamp: Local::now(), + timestamp: shepherd_util::now(), event, } } diff --git a/crates/shepherd-util/src/time.rs b/crates/shepherd-util/src/time.rs index 7c6d128..e0a6baa 100644 --- a/crates/shepherd-util/src/time.rs +++ b/crates/shepherd-util/src/time.rs @@ -2,11 +2,103 @@ //! //! Provides both monotonic time (for countdown enforcement) and //! wall-clock time (for availability windows). +//! +//! # Mock Time for Development +//! +//! In debug builds, the `SHEPHERD_MOCK_TIME` environment variable can be set +//! to override the system time for all time-sensitive operations. This is useful +//! for testing availability windows and time-based policies. +//! +//! Format: `YYYY-MM-DD HH:MM:SS` (e.g., `2025-12-25 14:30:00`) +//! +//! Example: +//! ```bash +//! SHEPHERD_MOCK_TIME="2025-12-25 14:30:00" ./run-dev +//! ``` -use chrono::{DateTime, Datelike, Local, NaiveTime, Timelike, Weekday}; +use chrono::{DateTime, Datelike, Local, NaiveDateTime, NaiveTime, TimeZone, Timelike, Weekday}; use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; use std::time::{Duration, Instant}; +/// Environment variable name for mock time (debug builds only) +pub const MOCK_TIME_ENV_VAR: &str = "SHEPHERD_MOCK_TIME"; + +/// Cached mock time offset from the real time when the process started. +/// This allows mock time to advance naturally. +static MOCK_TIME_OFFSET: OnceLock> = OnceLock::new(); + +/// Initialize the mock time offset based on the environment variable. +/// Returns the offset between mock time and real time at process start. +fn get_mock_time_offset() -> Option { + *MOCK_TIME_OFFSET.get_or_init(|| { + #[cfg(debug_assertions)] + { + if let Ok(mock_time_str) = std::env::var(MOCK_TIME_ENV_VAR) { + // Parse the mock time string + if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&mock_time_str, "%Y-%m-%d %H:%M:%S") { + if let Some(mock_dt) = Local.from_local_datetime(&naive_dt).single() { + let real_now = chrono::Local::now(); + let offset = mock_dt.signed_duration_since(real_now); + tracing::info!( + mock_time = %mock_time_str, + offset_secs = offset.num_seconds(), + "Mock time enabled" + ); + return Some(offset); + } else { + tracing::warn!( + mock_time = %mock_time_str, + "Failed to convert mock time to local timezone" + ); + } + } else { + tracing::warn!( + mock_time = %mock_time_str, + expected_format = "%Y-%m-%d %H:%M:%S", + "Invalid mock time format" + ); + } + } + None + } + #[cfg(not(debug_assertions))] + { + None + } + }) +} + +/// Returns whether mock time is currently active. +pub fn is_mock_time_active() -> bool { + get_mock_time_offset().is_some() +} + +/// Get the current local time, respecting mock time settings in debug builds. +/// +/// In release builds, this always returns the real system time. +/// In debug builds, if `SHEPHERD_MOCK_TIME` is set, this returns a time +/// that advances from the mock time at the same rate as real time. +pub fn now() -> DateTime { + let real_now = chrono::Local::now(); + + if let Some(offset) = get_mock_time_offset() { + real_now + offset + } else { + real_now + } +} + +/// Format a DateTime for display in the HUD clock. +pub fn format_clock_time(dt: &DateTime) -> String { + dt.format("%H:%M").to_string() +} + +/// Format a DateTime for display with full date and time. +pub fn format_datetime_full(dt: &DateTime) -> String { + dt.format("%Y-%m-%d %H:%M:%S").to_string() +} + /// Represents a point in monotonic time for countdown enforcement. /// This is immune to wall-clock changes. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -298,4 +390,229 @@ mod tests { assert!(t2 > t1); assert!(t2.duration_since(t1) >= Duration::from_millis(10)); } + + #[test] + fn test_format_clock_time() { + let dt = Local.with_ymd_and_hms(2025, 12, 25, 14, 30, 45).unwrap(); + assert_eq!(format_clock_time(&dt), "14:30"); + } + + #[test] + fn test_format_datetime_full() { + let dt = Local.with_ymd_and_hms(2025, 12, 25, 14, 30, 45).unwrap(); + assert_eq!(format_datetime_full(&dt), "2025-12-25 14:30:45"); + } + + #[test] + fn test_now_returns_time() { + // Basic test that now() returns a valid time + let t = now(); + // Should be a reasonable year (after 2020, before 2100) + assert!(t.year() >= 2020); + assert!(t.year() <= 2100); + } + + #[test] + fn test_mock_time_env_var_name() { + // Verify the environment variable name is correct + assert_eq!(MOCK_TIME_ENV_VAR, "SHEPHERD_MOCK_TIME"); + } + + #[test] + fn test_parse_mock_time_format() { + // Test that the expected format parses correctly + let valid_formats = [ + "2025-12-25 14:30:00", + "2025-01-01 00:00:00", + "2025-12-31 23:59:59", + "2020-06-15 12:00:00", + ]; + + for format_str in &valid_formats { + let result = NaiveDateTime::parse_from_str(format_str, "%Y-%m-%d %H:%M:%S"); + assert!( + result.is_ok(), + "Expected '{}' to parse successfully, got {:?}", + format_str, + result + ); + } + } + + #[test] + fn test_parse_mock_time_invalid_formats() { + // Test that invalid formats are rejected + let invalid_formats = [ + "2025-12-25", // Missing time + "14:30:00", // Missing date + "2025/12/25 14:30:00", // Wrong date separator + "2025-12-25T14:30:00", // ISO format (not supported) + "Dec 25, 2025 14:30", // Wrong format + "25-12-2025 14:30:00", // Wrong date order + "", // Empty string + "not a date", // Invalid string + ]; + + for format_str in &invalid_formats { + let result = NaiveDateTime::parse_from_str(format_str, "%Y-%m-%d %H:%M:%S"); + assert!( + result.is_err(), + "Expected '{}' to fail parsing, but it succeeded", + format_str + ); + } + } + + #[test] + fn test_mock_time_offset_calculation() { + // Test that the offset calculation works correctly + let mock_time_str = "2025-12-25 14:30:00"; + let naive_dt = NaiveDateTime::parse_from_str(mock_time_str, "%Y-%m-%d %H:%M:%S").unwrap(); + let mock_dt = Local.from_local_datetime(&naive_dt).single().unwrap(); + let real_now = chrono::Local::now(); + + let offset = mock_dt.signed_duration_since(real_now); + + // The offset should be applied correctly + let simulated_now = real_now + offset; + + // The simulated time should be very close to the mock time + // (within a second, accounting for test execution time) + let diff = (simulated_now - mock_dt).num_seconds().abs(); + assert!( + diff <= 1, + "Expected simulated time to be within 1 second of mock time, got {} seconds difference", + diff + ); + } + + #[test] + fn test_mock_time_advances_with_real_time() { + // Test that mock time advances at the same rate as real time + // This tests the concept, not the actual implementation (since OnceLock is static) + + let mock_time_str = "2025-12-25 14:30:00"; + let naive_dt = NaiveDateTime::parse_from_str(mock_time_str, "%Y-%m-%d %H:%M:%S").unwrap(); + let mock_dt = Local.from_local_datetime(&naive_dt).single().unwrap(); + + let real_t1 = chrono::Local::now(); + let offset = mock_dt.signed_duration_since(real_t1); + + // Simulate time passing + std::thread::sleep(Duration::from_millis(100)); + + let real_t2 = chrono::Local::now(); + let simulated_t1 = real_t1 + offset; + let simulated_t2 = real_t2 + offset; + + // The simulated times should have advanced by the same amount as real times + let real_elapsed = real_t2.signed_duration_since(real_t1); + let simulated_elapsed = simulated_t2.signed_duration_since(simulated_t1); + + assert_eq!( + real_elapsed.num_milliseconds(), + simulated_elapsed.num_milliseconds(), + "Mock time should advance at the same rate as real time" + ); + } + + #[test] + fn test_availability_with_specific_time() { + // Test that availability windows work correctly with a specific time + // This validates that the mock time would affect availability checks + + let window = TimeWindow::new( + DaysOfWeek::ALL_DAYS, + WallClock::new(14, 0).unwrap(), // 2 PM + WallClock::new(18, 0).unwrap(), // 6 PM + ); + + // Time within window + let in_window = Local.with_ymd_and_hms(2025, 12, 25, 15, 0, 0).unwrap(); + assert!(window.contains(&in_window), "15:00 should be within 14:00-18:00 window"); + + // Time before window + let before_window = Local.with_ymd_and_hms(2025, 12, 25, 10, 0, 0).unwrap(); + assert!(!window.contains(&before_window), "10:00 should be before 14:00-18:00 window"); + + // Time after window + let after_window = Local.with_ymd_and_hms(2025, 12, 25, 20, 0, 0).unwrap(); + assert!(!window.contains(&after_window), "20:00 should be after 14:00-18:00 window"); + } + + #[test] + fn test_availability_with_day_restriction() { + // Test that day-of-week restrictions work correctly + let window = TimeWindow::new( + DaysOfWeek::WEEKDAYS, + WallClock::new(14, 0).unwrap(), + WallClock::new(18, 0).unwrap(), + ); + + // Thursday at 3 PM - should be available (weekday, in time window) + let thursday = Local.with_ymd_and_hms(2025, 12, 25, 15, 0, 0).unwrap(); // Christmas 2025 is Thursday + assert!(window.contains(&thursday), "Thursday 15:00 should be in weekday afternoon window"); + + // Saturday at 3 PM - should NOT be available (weekend) + let saturday = Local.with_ymd_and_hms(2025, 12, 27, 15, 0, 0).unwrap(); + assert!(!window.contains(&saturday), "Saturday should not be in weekday window"); + + // Sunday at 3 PM - should NOT be available (weekend) + let sunday = Local.with_ymd_and_hms(2025, 12, 28, 15, 0, 0).unwrap(); + assert!(!window.contains(&sunday), "Sunday should not be in weekday window"); + } +} + +/// Tests that require running in a separate process to test environment variable handling. +/// These are integration-style tests for the mock time feature. +#[cfg(test)] +mod mock_time_integration_tests { + use super::*; + + /// This test documents the expected behavior of the mock time feature. + /// Due to the static OnceLock, actual integration testing requires + /// running with the environment variable set externally. + /// + /// To manually test: + /// ```bash + /// SHEPHERD_MOCK_TIME="2025-12-25 14:30:00" cargo test + /// ``` + #[test] + fn test_mock_time_documentation() { + // This test verifies the mock time constants and expected behavior + assert_eq!(MOCK_TIME_ENV_VAR, "SHEPHERD_MOCK_TIME"); + + // The expected format is documented + let expected_format = "%Y-%m-%d %H:%M:%S"; + let example = "2025-12-25 14:30:00"; + assert!(NaiveDateTime::parse_from_str(example, expected_format).is_ok()); + } + + #[test] + #[cfg(debug_assertions)] + fn test_is_mock_time_active_in_debug() { + // In debug mode, is_mock_time_active() should return based on env var + // Since we can't control the env var within a single test run due to OnceLock, + // we just verify the function doesn't panic + let _ = is_mock_time_active(); + } + + #[test] + fn test_now_consistency() { + // now() should return consistent, advancing times + let t1 = now(); + std::thread::sleep(Duration::from_millis(50)); + let t2 = now(); + + // t2 should be after t1 + assert!(t2 > t1, "Time should advance forward"); + + // The difference should be approximately 50ms (with some tolerance) + let diff = t2.signed_duration_since(t1); + assert!( + diff.num_milliseconds() >= 40 && diff.num_milliseconds() <= 200, + "Expected ~50ms difference, got {}ms", + diff.num_milliseconds() + ); + } } diff --git a/crates/shepherdd/src/main.rs b/crates/shepherdd/src/main.rs index 5ce9a6c..1338452 100644 --- a/crates/shepherdd/src/main.rs +++ b/crates/shepherdd/src/main.rs @@ -10,7 +10,6 @@ //! - Volume control use anyhow::{Context, Result}; -use chrono::Local; use clap::Parser; use shepherd_api::{ Command, DaemonStateSnapshot, ErrorCode, ErrorInfo, Event, EventPayload, HealthStatus, @@ -176,7 +175,7 @@ impl Daemon { // Tick timer - check warnings and expiry _ = tick_timer.tick() => { let now_mono = MonotonicInstant::now(); - let now = Local::now(); + let now = shepherd_util::now(); let events = { let mut engine = engine.lock().await; @@ -207,7 +206,7 @@ impl Daemon { ipc: &Arc, event: CoreEvent, _now_mono: MonotonicInstant, - _now: chrono::DateTime, + _now: chrono::DateTime, ) { match &event { CoreEvent::Warning { @@ -322,7 +321,7 @@ impl Daemon { match event { HostEvent::Exited { handle, status } => { let now_mono = MonotonicInstant::now(); - let now = Local::now(); + let now = shepherd_util::now(); info!( session_id = %handle.session_id, @@ -454,7 +453,7 @@ impl Daemon { request_id: u64, command: Command, ) -> Response { - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); match command { diff --git a/crates/shepherdd/tests/integration.rs b/crates/shepherdd/tests/integration.rs index b325141..e5b1d9c 100644 --- a/crates/shepherdd/tests/integration.rs +++ b/crates/shepherdd/tests/integration.rs @@ -2,13 +2,12 @@ //! //! These tests verify the end-to-end behavior of the daemon. -use chrono::Local; use shepherd_api::{EntryKind, WarningSeverity, WarningThreshold}; use shepherd_config::{AvailabilityPolicy, Entry, LimitsPolicy, Policy}; use shepherd_core::{CoreEngine, CoreEvent, LaunchDecision}; use shepherd_host_api::{HostCapabilities, MockHost}; use shepherd_store::{SqliteStore, Store}; -use shepherd_util::{EntryId, MonotonicInstant}; +use shepherd_util::{self, EntryId, MonotonicInstant}; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -73,7 +72,7 @@ fn test_entry_listing() { let caps = HostCapabilities::minimal(); let engine = CoreEngine::new(policy, store, caps); - let entries = engine.list_entries(Local::now()); + let entries = engine.list_entries(shepherd_util::now()); assert_eq!(entries.len(), 1); assert!(entries[0].enabled); @@ -89,7 +88,7 @@ fn test_launch_approval() { let engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let decision = engine.request_launch(&entry_id, Local::now()); + let decision = engine.request_launch(&entry_id, shepherd_util::now()); assert!(matches!(decision, LaunchDecision::Approved(plan) if plan.max_duration == Some(Duration::from_secs(10)))); } @@ -102,7 +101,7 @@ fn test_session_lifecycle() { let mut engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); // Launch @@ -131,7 +130,7 @@ fn test_warning_emission() { let mut engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); // Start session @@ -170,7 +169,7 @@ fn test_session_expiry() { let mut engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); // Start session @@ -198,7 +197,7 @@ fn test_usage_accounting() { let mut engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); // Start session @@ -302,7 +301,7 @@ fn test_session_extension() { let mut engine = CoreEngine::new(policy, store, caps); let entry_id = EntryId::new("test-game"); - let now = Local::now(); + let now = shepherd_util::now(); let now_mono = MonotonicInstant::now(); // Start session