Skip to content

Commit c4a2f58

Browse files
committed
feat: Mock SystemTime::now() for the tests
Add a fake `struct SystemTime` for mocking `SystemTime::now()` etc. 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::elapsed()` or `now()` instead of mocks 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.
1 parent 3b8609c commit c4a2f58

File tree

9 files changed

+51
-21
lines changed

9 files changed

+51
-21
lines changed

src/chat.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::convert::{TryFrom, TryInto};
66
use std::fmt;
77
use std::path::{Path, PathBuf};
88
use std::str::FromStr;
9-
use std::time::{Duration, SystemTime};
9+
use std::time::Duration;
1010

1111
use anyhow::{anyhow, bail, ensure, Context as _, Result};
1212
use deltachat_derive::{FromSql, ToSql};
@@ -44,7 +44,7 @@ use crate::sync::{self, Sync::*, SyncData};
4444
use crate::tools::{
4545
buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
4646
create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input,
47-
smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty,
47+
smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty, SystemTime,
4848
};
4949
use crate::webxdc::WEBXDC_SUFFIX;
5050

@@ -3641,7 +3641,7 @@ pub enum MuteDuration {
36413641
Forever,
36423642

36433643
/// Chat is muted for a limited period of time.
3644-
Until(SystemTime),
3644+
Until(std::time::SystemTime),
36453645
}
36463646

36473647
impl rusqlite::types::ToSql for MuteDuration {

src/contact.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::convert::{TryFrom, TryInto};
66
use std::fmt;
77
use std::ops::Deref;
88
use std::path::{Path, PathBuf};
9-
use std::time::{SystemTime, UNIX_EPOCH};
9+
use std::time::UNIX_EPOCH;
1010

1111
use anyhow::{bail, ensure, Context as _, Result};
1212
use async_channel::{self as channel, Receiver, Sender};
@@ -35,7 +35,7 @@ use crate::sql::{self, params_iter};
3535
use crate::sync::{self, Sync::*, SyncData};
3636
use crate::tools::{
3737
duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time,
38-
EmailAddress,
38+
EmailAddress, SystemTime,
3939
};
4040
use crate::{chat, stock_str};
4141

src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ pub fn get_version_str() -> &'static str {
10511051

10521052
#[cfg(test)]
10531053
mod tests {
1054-
use std::time::{Duration, SystemTime};
1054+
use std::time::Duration;
10551055

10561056
use anyhow::Context as _;
10571057
use strum::IntoEnumIterator;
@@ -1067,7 +1067,7 @@ mod tests {
10671067
use crate::message::{Message, Viewtype};
10681068
use crate::receive_imf::receive_imf;
10691069
use crate::test_utils::TestContext;
1070-
use crate::tools::create_outgoing_rfc724_mid;
1070+
use crate::tools::{create_outgoing_rfc724_mid, SystemTime};
10711071

10721072
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10731073
async fn test_wrong_db() -> Result<()> {

src/ephemeral.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ use std::collections::BTreeSet;
6767
use std::convert::{TryFrom, TryInto};
6868
use std::num::ParseIntError;
6969
use std::str::FromStr;
70-
use std::time::{Duration, SystemTime, UNIX_EPOCH};
70+
use std::time::{Duration, UNIX_EPOCH};
7171

7272
use anyhow::{ensure, Result};
7373
use async_channel::Receiver;
@@ -85,7 +85,7 @@ use crate::message::{Message, MessageState, MsgId, Viewtype};
8585
use crate::mimeparser::SystemMessage;
8686
use crate::sql::{self, params_iter};
8787
use crate::stock_str;
88-
use crate::tools::{duration_to_str, time};
88+
use crate::tools::{duration_to_str, time, SystemTime};
8989

9090
/// Ephemeral timer value.
9191
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]

src/sql.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::message::{Message, MsgId, Viewtype};
2121
use crate::param::{Param, Params};
2222
use crate::peerstate::{deduplicate_peerstates, Peerstate};
2323
use crate::stock_str;
24-
use crate::tools::{delete_file, time};
24+
use crate::tools::{delete_file, time, SystemTime};
2525

2626
/// Extension to [`rusqlite::ToSql`] trait
2727
/// which also includes [`Send`] and [`Sync`].
@@ -848,9 +848,9 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
848848
Ok(mut dir_handle) => {
849849
/* avoid deletion of files that are just created to build a message object */
850850
let diff = std::time::Duration::from_secs(60 * 60);
851-
let keep_files_newer_than = std::time::SystemTime::now()
851+
let keep_files_newer_than = SystemTime::now()
852852
.checked_sub(diff)
853-
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
853+
.unwrap_or(SystemTime::UNIX_EPOCH);
854854

855855
while let Ok(Some(entry)) = dir_handle.next_entry().await {
856856
let name_f = entry.file_name();

src/sync.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ impl Context {
319319

320320
#[cfg(test)]
321321
mod tests {
322-
use std::time::{Duration, SystemTime};
322+
use std::time::Duration;
323323

324324
use anyhow::bail;
325325

@@ -329,6 +329,7 @@ mod tests {
329329
use crate::contact::{Contact, Origin};
330330
use crate::test_utils::TestContext;
331331
use crate::token::Namespace;
332+
use crate::tools::SystemTime;
332333

333334
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
334335
async fn test_config_sync_msgs() -> Result<()> {

src/tests/verified_chats.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::mimeparser::SystemMessage;
1212
use crate::receive_imf::receive_imf;
1313
use crate::stock_str;
1414
use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager};
15+
use crate::tools::SystemTime;
1516
use crate::{e2ee, message};
1617

1718
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -792,10 +793,7 @@ async fn test_create_protected_grp_multidev() -> Result<()> {
792793
);
793794

794795
let sent = alice.send_text(group_id, "Hey").await;
795-
// This sleep is necessary to reproduce the bug when the original message is sorted over the
796-
// "protection enabled" message so that these messages have different timestamps. The better way
797-
// would be to adjust the system time here if we could mock the system clock for the tests.
798-
tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
796+
SystemTime::shift(std::time::Duration::from_secs(3600));
799797
let msg = alice1.recv_msg(&sent).await;
800798
let group1 = Chat::load_from_db(alice1, msg.chat_id).await?;
801799
assert_eq!(group1.get_type(), Chattype::Group);

src/timesmearing.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ impl SmearedTimestamp {
7979

8080
#[cfg(test)]
8181
mod tests {
82-
use std::time::SystemTime;
83-
8482
use super::*;
8583
use crate::test_utils::TestContext;
86-
use crate::tools::{create_smeared_timestamp, create_smeared_timestamps, smeared_time, time};
84+
use crate::tools::{
85+
create_smeared_timestamp, create_smeared_timestamps, smeared_time, time, SystemTime,
86+
};
8787

8888
#[test]
8989
fn test_smeared_timestamp() {

src/tools.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ use std::io::{Cursor, Write};
99
use std::mem;
1010
use std::path::{Path, PathBuf};
1111
use std::str::from_utf8;
12+
#[cfg(test)]
13+
use std::sync::RwLock;
1214
// If a time value doesn't need to be sent to another host, saved to the db or otherwise used across
1315
// program restarts, a monotonically nondecreasing clock (`Instant`) should be used. But as
1416
// `Instant` may use `libc::clock_gettime(CLOCK_MONOTONIC)`, e.g. on Android, and does not advance
1517
// while being in deep sleep mode, we use `SystemTime` instead, but add an alias for it to document
1618
// why `Instant` isn't used in those places. Also this can help to switch to another clock impl if
1719
// we find any.
1820
pub use std::time::SystemTime as Time;
19-
use std::time::{Duration, SystemTime};
21+
use std::time::{Duration, SystemTimeError};
2022

2123
use anyhow::{bail, Context as _, Result};
2224
use base64::Engine as _;
@@ -482,6 +484,35 @@ pub async fn read_dir(path: &Path) -> Result<Vec<fs::DirEntry>> {
482484
Ok(res)
483485
}
484486

487+
#[cfg(test)]
488+
static SYSTEM_TIME_SHIFT: RwLock<Duration> = RwLock::new(Duration::new(0, 0));
489+
490+
/// Fake struct for mocking `SystemTime::now()` etc. for test purposes. You still need to use
491+
/// `std::time::SystemTime` as a struct representing a system time.
492+
pub(crate) struct SystemTime();
493+
494+
impl SystemTime {
495+
pub const UNIX_EPOCH: std::time::SystemTime = std::time::SystemTime::UNIX_EPOCH;
496+
497+
#[allow(unused)]
498+
pub fn elapsed(t: &std::time::SystemTime) -> Result<Duration, SystemTimeError> {
499+
Self::now().duration_since(*t)
500+
}
501+
502+
pub fn now() -> std::time::SystemTime {
503+
#[cfg(not(test))]
504+
return std::time::SystemTime::now();
505+
#[cfg(test)]
506+
return std::time::SystemTime::now() + *SYSTEM_TIME_SHIFT.read().unwrap();
507+
}
508+
509+
/// Simulates a system clock forward adjustment by `duration`.
510+
#[cfg(test)]
511+
pub fn shift(duration: Duration) {
512+
*SYSTEM_TIME_SHIFT.write().unwrap() += duration;
513+
}
514+
}
515+
485516
pub(crate) fn time() -> i64 {
486517
SystemTime::now()
487518
.duration_since(SystemTime::UNIX_EPOCH)

0 commit comments

Comments
 (0)