feat: Mock SystemTime::now() for the tests

Add a new crate `deltachat_time` with a fake `struct SystemTimeTools` for mocking
`SystemTime::now()` for test purposes. One still needs to use `std::time::SystemTime` as a struct
representing a system time. I think such a minimalistic approach is ok -- even if somebody uses the
original `SystemTime::now()` instead of the mock by mistake, that could break only tests but not the
program itself. The worst thing that can happen is that tests using `SystemTime::shift()` and
checking messages timestamps f.e. wouldn't catch the corresponding bugs, but now we don't have such
tests at all which is much worse.
This commit is contained in:
iequidoo
2023-12-15 23:19:37 -03:00
committed by iequidoo
parent 3b0e740c17
commit 6e55f0c6e3
13 changed files with 76 additions and 21 deletions

5
Cargo.lock generated
View File

@@ -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"

View File

@@ -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",
]

View File

@@ -0,0 +1,8 @@
[package]
name = "deltachat-time"
version = "1.0.0"
description = "Time-related tools"
edition = "2021"
license = "MPL-2.0"
[dependencies]

35
deltachat-time/src/lib.rs Normal file
View File

@@ -0,0 +1,35 @@
#![allow(missing_docs)]
use std::sync::RwLock;
use std::time::{Duration, SystemTime};
static SYSTEM_TIME_SHIFT: RwLock<Duration> = 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());
}
}

View File

@@ -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 {

View File

@@ -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};

View File

@@ -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<()> {

View File

@@ -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)]

View File

@@ -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();

View File

@@ -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<()> {

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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;