diff --git a/Cargo.lock b/Cargo.lock index 5f63032e5..345af72fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1103,6 +1103,7 @@ dependencies = [ "brotli", "chrono", "criterion", + "deltachat-time", "deltachat_derive", "email", "encoded-words", @@ -1223,6 +1224,10 @@ dependencies = [ "yerpc", ] +[[package]] +name = "deltachat-time" +version = "1.0.0" + [[package]] name = "deltachat_derive" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index 3beb1d0f0..9299a4e67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ strip = true [dependencies] deltachat_derive = { path = "./deltachat_derive" } +deltachat-time = { path = "./deltachat-time" } format-flowed = { path = "./format-flowed" } ratelimit = { path = "./deltachat-ratelimit" } @@ -128,6 +129,7 @@ members = [ "deltachat-rpc-server", "deltachat-ratelimit", "deltachat-repl", + "deltachat-time", "format-flowed", ] diff --git a/deltachat-time/Cargo.toml b/deltachat-time/Cargo.toml new file mode 100644 index 000000000..a2931d726 --- /dev/null +++ b/deltachat-time/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "deltachat-time" +version = "1.0.0" +description = "Time-related tools" +edition = "2021" +license = "MPL-2.0" + +[dependencies] diff --git a/deltachat-time/src/lib.rs b/deltachat-time/src/lib.rs new file mode 100644 index 000000000..c8d7d6f6c --- /dev/null +++ b/deltachat-time/src/lib.rs @@ -0,0 +1,35 @@ +#![allow(missing_docs)] + +use std::sync::RwLock; +use std::time::{Duration, SystemTime}; + +static SYSTEM_TIME_SHIFT: RwLock = RwLock::new(Duration::new(0, 0)); + +/// Fake struct for mocking `SystemTime::now()` for test purposes. You still need to use +/// `SystemTime` as a struct representing a system time. +pub struct SystemTimeTools(); + +impl SystemTimeTools { + pub const UNIX_EPOCH: SystemTime = SystemTime::UNIX_EPOCH; + + pub fn now() -> SystemTime { + return SystemTime::now() + *SYSTEM_TIME_SHIFT.read().unwrap(); + } + + /// Simulates a system clock forward adjustment by `duration`. + pub fn shift(duration: Duration) { + *SYSTEM_TIME_SHIFT.write().unwrap() += duration; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + SystemTimeTools::shift(Duration::from_secs(60)); + let t = SystemTimeTools::now(); + assert!(t > SystemTime::now()); + } +} diff --git a/src/chat.rs b/src/chat.rs index 87b8163c3..a084add08 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -6,7 +6,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::time::{Duration, SystemTime}; +use std::time::Duration; use anyhow::{anyhow, bail, ensure, Context as _, Result}; use deltachat_derive::{FromSql, ToSql}; @@ -44,7 +44,7 @@ use crate::sync::{self, Sync::*, SyncData}; use crate::tools::{ buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input, - smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty, + smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty, SystemTime, }; use crate::webxdc::WEBXDC_SUFFIX; @@ -3656,7 +3656,7 @@ pub enum MuteDuration { Forever, /// Chat is muted for a limited period of time. - Until(SystemTime), + Until(std::time::SystemTime), } impl rusqlite::types::ToSql for MuteDuration { diff --git a/src/contact.rs b/src/contact.rs index 49f85435c..70db10b15 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -6,7 +6,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::ops::Deref; use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::UNIX_EPOCH; use anyhow::{bail, ensure, Context as _, Result}; use async_channel::{self as channel, Receiver, Sender}; @@ -36,7 +36,7 @@ use crate::sql::{self, params_iter}; use crate::sync::{self, Sync::*}; use crate::tools::{ duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time, - EmailAddress, + EmailAddress, SystemTime, }; use crate::{chat, stock_str}; diff --git a/src/context.rs b/src/context.rs index 3f3365f51..a297146d6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1217,7 +1217,7 @@ pub fn get_version_str() -> &'static str { #[cfg(test)] mod tests { - use std::time::{Duration, SystemTime}; + use std::time::Duration; use anyhow::Context as _; use strum::IntoEnumIterator; @@ -1234,7 +1234,7 @@ mod tests { use crate::mimeparser::SystemMessage; use crate::receive_imf::receive_imf; use crate::test_utils::{get_chat_msg, TestContext}; - use crate::tools::create_outgoing_rfc724_mid; + use crate::tools::{create_outgoing_rfc724_mid, SystemTime}; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_wrong_db() -> Result<()> { diff --git a/src/ephemeral.rs b/src/ephemeral.rs index 48e0a1ab1..719243f3e 100644 --- a/src/ephemeral.rs +++ b/src/ephemeral.rs @@ -67,7 +67,7 @@ use std::collections::BTreeSet; use std::convert::{TryFrom, TryInto}; use std::num::ParseIntError; use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, UNIX_EPOCH}; use anyhow::{ensure, Result}; use async_channel::Receiver; @@ -85,7 +85,7 @@ use crate::message::{Message, MessageState, MsgId, Viewtype}; use crate::mimeparser::SystemMessage; use crate::sql::{self, params_iter}; use crate::stock_str; -use crate::tools::{duration_to_str, time}; +use crate::tools::{duration_to_str, time, SystemTime}; /// Ephemeral timer value. #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] diff --git a/src/sql.rs b/src/sql.rs index 9333ba161..2909eab2d 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -21,7 +21,7 @@ use crate::message::{Message, MsgId, Viewtype}; use crate::param::{Param, Params}; use crate::peerstate::{deduplicate_peerstates, Peerstate}; use crate::stock_str; -use crate::tools::{delete_file, time}; +use crate::tools::{delete_file, time, SystemTime}; /// Extension to [`rusqlite::ToSql`] trait /// which also includes [`Send`] and [`Sync`]. @@ -850,9 +850,9 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> { Ok(mut dir_handle) => { /* avoid deletion of files that are just created to build a message object */ let diff = std::time::Duration::from_secs(60 * 60); - let keep_files_newer_than = std::time::SystemTime::now() + let keep_files_newer_than = SystemTime::now() .checked_sub(diff) - .unwrap_or(std::time::SystemTime::UNIX_EPOCH); + .unwrap_or(SystemTime::UNIX_EPOCH); while let Ok(Some(entry)) = dir_handle.next_entry().await { let name_f = entry.file_name(); diff --git a/src/sync.rs b/src/sync.rs index e719562a5..b63f919f9 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -319,7 +319,7 @@ impl Context { #[cfg(test)] mod tests { - use std::time::{Duration, SystemTime}; + use std::time::Duration; use anyhow::bail; @@ -329,6 +329,7 @@ mod tests { use crate::contact::{Contact, Origin}; use crate::test_utils::TestContext; use crate::token::Namespace; + use crate::tools::SystemTime; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_config_sync_msgs() -> Result<()> { diff --git a/src/tests/verified_chats.rs b/src/tests/verified_chats.rs index 06adeba0a..a07e093eb 100644 --- a/src/tests/verified_chats.rs +++ b/src/tests/verified_chats.rs @@ -12,6 +12,7 @@ use crate::mimeparser::SystemMessage; use crate::receive_imf::receive_imf; use crate::stock_str; use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager}; +use crate::tools::SystemTime; use crate::{e2ee, message}; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -792,10 +793,9 @@ async fn test_create_protected_grp_multidev() -> Result<()> { ); let sent = alice.send_text(group_id, "Hey").await; - // This sleep is necessary to reproduce the bug when the original message is sorted over the - // "protection enabled" message so that these messages have different timestamps. The better way - // would be to adjust the system time here if we could mock the system clock for the tests. - tokio::time::sleep(std::time::Duration::from_millis(2000)).await; + // This time shift is necessary to reproduce the bug when the original message is sorted over + // the "protection enabled" message so that these messages have different timestamps. + SystemTime::shift(std::time::Duration::from_secs(3600)); let msg = alice1.recv_msg(&sent).await; let group1 = Chat::load_from_db(alice1, msg.chat_id).await?; assert_eq!(group1.get_type(), Chattype::Group); diff --git a/src/timesmearing.rs b/src/timesmearing.rs index 21c3c57f8..6bd4dbbba 100644 --- a/src/timesmearing.rs +++ b/src/timesmearing.rs @@ -79,11 +79,11 @@ impl SmearedTimestamp { #[cfg(test)] mod tests { - use std::time::SystemTime; - use super::*; use crate::test_utils::TestContext; - use crate::tools::{create_smeared_timestamp, create_smeared_timestamps, smeared_time, time}; + use crate::tools::{ + create_smeared_timestamp, create_smeared_timestamps, smeared_time, time, SystemTime, + }; #[test] fn test_smeared_timestamp() { diff --git a/src/tools.rs b/src/tools.rs index 7563ab139..865299522 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -15,12 +15,16 @@ use std::str::from_utf8; // while being in deep sleep mode, we use `SystemTime` instead, but add an alias for it to document // why `Instant` isn't used in those places. Also this can help to switch to another clock impl if // we find any. +use std::time::Duration; pub use std::time::SystemTime as Time; -use std::time::{Duration, SystemTime}; +#[cfg(not(test))] +pub use std::time::SystemTime; use anyhow::{bail, Context as _, Result}; use base64::Engine as _; use chrono::{Local, NaiveDateTime, NaiveTime, TimeZone}; +#[cfg(test)] +pub use deltachat_time::SystemTimeTools as SystemTime; use futures::{StreamExt, TryStreamExt}; use mailparse::dateparse; use mailparse::headers::Headers;