From ab26ab9abbcfbd22db132d393f87c23fe82c5de8 Mon Sep 17 00:00:00 2001 From: Cameron Garnham Date: Sun, 17 Mar 2024 11:23:25 +0800 Subject: [PATCH] dev: extract clock to new package --- Cargo.lock | 54 ++- Cargo.toml | 1 + cSpell.json | 2 + packages/clock/Cargo.toml | 24 ++ packages/clock/README.md | 11 + packages/clock/src/clock/mod.rs | 72 ++++ packages/clock/src/clock/stopped/mod.rs | 210 ++++++++++ packages/clock/src/clock/working/mod.rs | 18 + packages/clock/src/conv/mod.rs | 82 ++++ packages/clock/src/lib.rs | 53 +++ .../clock/src/static_time/mod.rs | 0 .../clock/src/time_extent/mod.rs | 99 +++-- packages/clock/tests/clock/mod.rs | 16 + packages/clock/tests/integration.rs | 19 + packages/configuration/src/lib.rs | 3 + packages/torrent-repository/Cargo.toml | 5 +- packages/torrent-repository/src/entry/mod.rs | 6 +- .../torrent-repository/src/entry/single.rs | 299 +++++++++++++ packages/torrent-repository/src/lib.rs | 13 + src/bootstrap/app.rs | 2 +- src/core/auth.rs | 20 +- src/core/databases/mod.rs | 4 +- src/core/mod.rs | 15 +- src/core/peer_tests.rs | 8 +- src/core/torrent/mod.rs | 298 +------------ src/lib.rs | 24 ++ .../apis/v1/context/auth_key/resources.rs | 15 +- src/servers/http/mod.rs | 2 +- src/servers/http/v1/handlers/announce.rs | 5 +- src/servers/udp/connection_cookie.rs | 24 +- src/servers/udp/handlers.rs | 5 +- src/servers/udp/peer_builder.rs | 5 +- src/shared/clock/mod.rs | 393 ------------------ src/shared/clock/utils.rs | 1 - src/shared/mod.rs | 2 - tests/common/clock.rs | 16 + tests/common/mod.rs | 1 + tests/integration.rs | 13 + 38 files changed, 1050 insertions(+), 790 deletions(-) create mode 100644 packages/clock/Cargo.toml create mode 100644 packages/clock/README.md create mode 100644 packages/clock/src/clock/mod.rs create mode 100644 packages/clock/src/clock/stopped/mod.rs create mode 100644 packages/clock/src/clock/working/mod.rs create mode 100644 packages/clock/src/conv/mod.rs create mode 100644 packages/clock/src/lib.rs rename src/shared/clock/static_time.rs => packages/clock/src/static_time/mod.rs (100%) rename src/shared/clock/time_extent.rs => packages/clock/src/time_extent/mod.rs (85%) create mode 100644 packages/clock/tests/clock/mod.rs create mode 100644 packages/clock/tests/integration.rs delete mode 100644 src/shared/clock/mod.rs delete mode 100644 src/shared/clock/utils.rs create mode 100644 tests/common/clock.rs diff --git a/Cargo.lock b/Cargo.lock index b8437326c..e28278abb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1209,6 +1209,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" @@ -2560,6 +2566,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "rend" version = "0.4.2" @@ -2676,6 +2688,35 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.53", + "unicode-ident", +] + [[package]] name = "rusqlite" version = "0.31.0" @@ -3490,6 +3531,7 @@ dependencies = [ "serde_repr", "thiserror", "tokio", + "torrust-tracker-clock", "torrust-tracker-configuration", "torrust-tracker-contrib-bencode", "torrust-tracker-located-error", @@ -3503,6 +3545,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "torrust-tracker-clock" +version = "3.0.0-alpha.12-develop" +dependencies = [ + "chrono", + "lazy_static", + "torrust-tracker-primitives", +] + [[package]] name = "torrust-tracker-configuration" version = "3.0.0-alpha.12-develop" @@ -3561,8 +3612,9 @@ version = "3.0.0-alpha.12-develop" dependencies = [ "criterion", "futures", - "serde", + "rstest", "tokio", + "torrust-tracker-clock", "torrust-tracker-configuration", "torrust-tracker-primitives", ] diff --git a/Cargo.toml b/Cargo.toml index 9610fffc2..99b7a334a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ serde_repr = "0" thiserror = "1" tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] } torrust-tracker-configuration = { version = "3.0.0-alpha.12-develop", path = "packages/configuration" } +torrust-tracker-clock = { version = "3.0.0-alpha.12-develop", path = "packages/clock" } torrust-tracker-contrib-bencode = { version = "3.0.0-alpha.12-develop", path = "contrib/bencode" } torrust-tracker-located-error = { version = "3.0.0-alpha.12-develop", path = "packages/located-error" } torrust-tracker-primitives = { version = "3.0.0-alpha.12-develop", path = "packages/primitives" } diff --git a/cSpell.json b/cSpell.json index 6d5f71b85..1e276dbc2 100644 --- a/cSpell.json +++ b/cSpell.json @@ -34,6 +34,7 @@ "completei", "connectionless", "Containerfile", + "conv", "curr", "Cyberneering", "dashmap", @@ -116,6 +117,7 @@ "rngs", "rosegment", "routable", + "rstest", "rusqlite", "RUSTDOCFLAGS", "RUSTFLAGS", diff --git a/packages/clock/Cargo.toml b/packages/clock/Cargo.toml new file mode 100644 index 000000000..d7192b6e4 --- /dev/null +++ b/packages/clock/Cargo.toml @@ -0,0 +1,24 @@ +[package] +description = "A library to a clock for the torrust tracker." +keywords = ["library", "clock", "torrents"] +name = "torrust-tracker-clock" +readme = "README.md" + +authors.workspace = true +categories.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[dependencies] +lazy_static = "1" +chrono = { version = "0", default-features = false, features = ["clock"] } + +torrust-tracker-primitives = { version = "3.0.0-alpha.12-develop", path = "../primitives" } + +[dev-dependencies] diff --git a/packages/clock/README.md b/packages/clock/README.md new file mode 100644 index 000000000..98d7d922b --- /dev/null +++ b/packages/clock/README.md @@ -0,0 +1,11 @@ +# Torrust Tracker Configuration + +A library to provide torrent repository to the [Torrust Tracker](https://github.com/torrust/torrust-tracker). + +## Documentation + +[Crate documentation](https://docs.rs/torrust-tracker-torrent-repository). + +## License + +The project is licensed under the terms of the [GNU AFFERO GENERAL PUBLIC LICENSE](./LICENSE). diff --git a/packages/clock/src/clock/mod.rs b/packages/clock/src/clock/mod.rs new file mode 100644 index 000000000..50afbc9db --- /dev/null +++ b/packages/clock/src/clock/mod.rs @@ -0,0 +1,72 @@ +use std::time::Duration; + +use torrust_tracker_primitives::DurationSinceUnixEpoch; + +use self::stopped::StoppedClock; +use self::working::WorkingClock; + +pub mod stopped; +pub mod working; + +/// A generic structure that represents a clock. +/// +/// It can be either the working clock (production) or the stopped clock +/// (testing). It implements the `Time` trait, which gives you the current time. +#[derive(Debug)] +pub struct Clock { + clock: std::marker::PhantomData, +} + +/// The working clock. It returns the current time. +pub type Working = Clock; +/// The stopped clock. It returns always the same fixed time. +pub type Stopped = Clock; + +/// Trait for types that can be used as a timestamp clock. +pub trait Time: Sized { + fn now() -> DurationSinceUnixEpoch; + + fn dbg_clock_type() -> String; + + #[must_use] + fn now_add(add_time: &Duration) -> Option { + Self::now().checked_add(*add_time) + } + #[must_use] + fn now_sub(sub_time: &Duration) -> Option { + Self::now().checked_sub(*sub_time) + } +} + +#[cfg(test)] +mod tests { + use std::any::TypeId; + use std::time::Duration; + + use crate::clock::{self, Stopped, Time, Working}; + use crate::CurrentClock; + + #[test] + fn it_should_be_the_stopped_clock_as_default_when_testing() { + // We are testing, so we should default to the fixed time. + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_eq!(Stopped::now(), CurrentClock::now()); + } + + #[test] + fn it_should_have_different_times() { + assert_ne!(TypeId::of::(), TypeId::of::()); + assert_ne!(Stopped::now(), Working::now()); + } + + #[test] + fn it_should_use_stopped_time_for_testing() { + assert_eq!(CurrentClock::dbg_clock_type(), "Stopped".to_owned()); + + let time = CurrentClock::now(); + std::thread::sleep(Duration::from_millis(50)); + let time_2 = CurrentClock::now(); + + assert_eq!(time, time_2); + } +} diff --git a/packages/clock/src/clock/stopped/mod.rs b/packages/clock/src/clock/stopped/mod.rs new file mode 100644 index 000000000..57655ab75 --- /dev/null +++ b/packages/clock/src/clock/stopped/mod.rs @@ -0,0 +1,210 @@ +/// Trait for types that can be used as a timestamp clock stopped +/// at a given time. + +#[allow(clippy::module_name_repetitions)] +pub struct StoppedClock {} + +#[allow(clippy::module_name_repetitions)] +pub trait Stopped: clock::Time { + /// It sets the clock to a given time. + fn local_set(unix_time: &DurationSinceUnixEpoch); + + /// It sets the clock to the Unix Epoch. + fn local_set_to_unix_epoch() { + Self::local_set(&DurationSinceUnixEpoch::ZERO); + } + + /// It sets the clock to the time the application started. + fn local_set_to_app_start_time(); + + /// It sets the clock to the current system time. + fn local_set_to_system_time_now(); + + /// It adds a `Duration` to the clock. + /// + /// # Errors + /// + /// Will return `IntErrorKind` if `duration` would overflow the internal `Duration`. + fn local_add(duration: &Duration) -> Result<(), IntErrorKind>; + + /// It subtracts a `Duration` from the clock. + /// # Errors + /// + /// Will return `IntErrorKind` if `duration` would underflow the internal `Duration`. + fn local_sub(duration: &Duration) -> Result<(), IntErrorKind>; + + /// It resets the clock to default fixed time that is application start time (or the unix epoch when testing). + fn local_reset(); +} + +use std::num::IntErrorKind; +use std::time::Duration; + +use super::{DurationSinceUnixEpoch, Time}; +use crate::clock; + +impl Time for clock::Stopped { + fn now() -> DurationSinceUnixEpoch { + detail::FIXED_TIME.with(|time| { + return *time.borrow(); + }) + } + + fn dbg_clock_type() -> String { + "Stopped".to_owned() + } +} + +impl Stopped for clock::Stopped { + fn local_set(unix_time: &DurationSinceUnixEpoch) { + detail::FIXED_TIME.with(|time| { + *time.borrow_mut() = *unix_time; + }); + } + + fn local_set_to_app_start_time() { + Self::local_set(&detail::get_app_start_time()); + } + + fn local_set_to_system_time_now() { + Self::local_set(&detail::get_app_start_time()); + } + + fn local_add(duration: &Duration) -> Result<(), IntErrorKind> { + detail::FIXED_TIME.with(|time| { + let time_borrowed = *time.borrow(); + *time.borrow_mut() = match time_borrowed.checked_add(*duration) { + Some(time) => time, + None => { + return Err(IntErrorKind::PosOverflow); + } + }; + Ok(()) + }) + } + + fn local_sub(duration: &Duration) -> Result<(), IntErrorKind> { + detail::FIXED_TIME.with(|time| { + let time_borrowed = *time.borrow(); + *time.borrow_mut() = match time_borrowed.checked_sub(*duration) { + Some(time) => time, + None => { + return Err(IntErrorKind::NegOverflow); + } + }; + Ok(()) + }) + } + + fn local_reset() { + Self::local_set(&detail::get_default_fixed_time()); + } +} + +#[cfg(test)] +mod tests { + use std::thread; + use std::time::Duration; + + use torrust_tracker_primitives::DurationSinceUnixEpoch; + + use crate::clock::stopped::Stopped as _; + use crate::clock::{Stopped, Time, Working}; + + #[test] + fn it_should_default_to_zero_when_testing() { + assert_eq!(Stopped::now(), DurationSinceUnixEpoch::ZERO); + } + + #[test] + fn it_should_possible_to_set_the_time() { + // Check we start with ZERO. + assert_eq!(Stopped::now(), Duration::ZERO); + + // Set to Current Time and Check + let timestamp = Working::now(); + Stopped::local_set(×tamp); + assert_eq!(Stopped::now(), timestamp); + + // Elapse the Current Time and Check + Stopped::local_add(×tamp).unwrap(); + assert_eq!(Stopped::now(), timestamp + timestamp); + + // Reset to ZERO and Check + Stopped::local_reset(); + assert_eq!(Stopped::now(), Duration::ZERO); + } + + #[test] + fn it_should_default_to_zero_on_thread_exit() { + assert_eq!(Stopped::now(), Duration::ZERO); + let after5 = Working::now_add(&Duration::from_secs(5)).unwrap(); + Stopped::local_set(&after5); + assert_eq!(Stopped::now(), after5); + + let t = thread::spawn(move || { + // each thread starts out with the initial value of ZERO + assert_eq!(Stopped::now(), Duration::ZERO); + + // and gets set to the current time. + let timestamp = Working::now(); + Stopped::local_set(×tamp); + assert_eq!(Stopped::now(), timestamp); + }); + + // wait for the thread to complete and bail out on panic + t.join().unwrap(); + + // we retain our original value of current time + 5sec despite the child thread + assert_eq!(Stopped::now(), after5); + + // Reset to ZERO and Check + Stopped::local_reset(); + assert_eq!(Stopped::now(), Duration::ZERO); + } +} + +mod detail { + use std::cell::RefCell; + use std::time::SystemTime; + + use torrust_tracker_primitives::DurationSinceUnixEpoch; + + use crate::static_time; + + thread_local!(pub static FIXED_TIME: RefCell = RefCell::new(get_default_fixed_time())); + + pub fn get_app_start_time() -> DurationSinceUnixEpoch { + (*static_time::TIME_AT_APP_START) + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + } + + #[cfg(not(test))] + pub fn get_default_fixed_time() -> DurationSinceUnixEpoch { + get_app_start_time() + } + + #[cfg(test)] + pub fn get_default_fixed_time() -> DurationSinceUnixEpoch { + DurationSinceUnixEpoch::ZERO + } + + #[cfg(test)] + mod tests { + use std::time::Duration; + + use crate::clock::stopped::detail::{get_app_start_time, get_default_fixed_time}; + + #[test] + fn it_should_get_the_zero_start_time_when_testing() { + assert_eq!(get_default_fixed_time(), Duration::ZERO); + } + + #[test] + fn it_should_get_app_start_time() { + const TIME_AT_WRITING_THIS_TEST: Duration = Duration::new(1_662_983_731, 22312); + assert!(get_app_start_time() > TIME_AT_WRITING_THIS_TEST); + } + } +} diff --git a/packages/clock/src/clock/working/mod.rs b/packages/clock/src/clock/working/mod.rs new file mode 100644 index 000000000..6d0b4dcf7 --- /dev/null +++ b/packages/clock/src/clock/working/mod.rs @@ -0,0 +1,18 @@ +use std::time::SystemTime; + +use torrust_tracker_primitives::DurationSinceUnixEpoch; + +use crate::clock; + +#[allow(clippy::module_name_repetitions)] +pub struct WorkingClock; + +impl clock::Time for clock::Working { + fn now() -> DurationSinceUnixEpoch { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap() + } + + fn dbg_clock_type() -> String { + "Working".to_owned() + } +} diff --git a/packages/clock/src/conv/mod.rs b/packages/clock/src/conv/mod.rs new file mode 100644 index 000000000..f70950c38 --- /dev/null +++ b/packages/clock/src/conv/mod.rs @@ -0,0 +1,82 @@ +use std::str::FromStr; + +use chrono::{DateTime, Utc}; +use torrust_tracker_primitives::DurationSinceUnixEpoch; + +/// It converts a string in ISO 8601 format to a timestamp. +/// For example, the string `1970-01-01T00:00:00.000Z` which is the Unix Epoch +/// will be converted to a timestamp of 0: `DurationSinceUnixEpoch::ZERO`. +/// +/// # Panics +/// +/// Will panic if the input time cannot be converted to `DateTime::`, internally using the `i64` type. +/// (this will naturally happen in 292.5 billion years) +#[must_use] +pub fn convert_from_iso_8601_to_timestamp(iso_8601: &str) -> DurationSinceUnixEpoch { + convert_from_datetime_utc_to_timestamp(&DateTime::::from_str(iso_8601).unwrap()) +} + +/// It converts a `DateTime::` to a timestamp. +/// For example, the `DateTime::` of the Unix Epoch will be converted to a +/// timestamp of 0: `DurationSinceUnixEpoch::ZERO`. +/// +/// # Panics +/// +/// Will panic if the input time overflows the `u64` type. +/// (this will naturally happen in 584.9 billion years) +#[must_use] +pub fn convert_from_datetime_utc_to_timestamp(datetime_utc: &DateTime) -> DurationSinceUnixEpoch { + DurationSinceUnixEpoch::from_secs(u64::try_from(datetime_utc.timestamp()).expect("Overflow of u64 seconds, very future!")) +} + +/// It converts a timestamp to a `DateTime::`. +/// For example, the timestamp of 0: `DurationSinceUnixEpoch::ZERO` will be +/// converted to the `DateTime::` of the Unix Epoch. +/// +/// # Panics +/// +/// Will panic if the input time overflows the `u64` seconds overflows the `i64` type. +/// (this will naturally happen in 292.5 billion years) +#[must_use] +pub fn convert_from_timestamp_to_datetime_utc(duration: DurationSinceUnixEpoch) -> DateTime { + DateTime::from_timestamp( + i64::try_from(duration.as_secs()).expect("Overflow of i64 seconds, very future!"), + duration.subsec_nanos(), + ) + .unwrap() +} + +#[cfg(test)] + +mod tests { + use chrono::DateTime; + use torrust_tracker_primitives::DurationSinceUnixEpoch; + + use crate::conv::{ + convert_from_datetime_utc_to_timestamp, convert_from_iso_8601_to_timestamp, convert_from_timestamp_to_datetime_utc, + }; + + #[test] + fn should_be_converted_to_datetime_utc() { + let timestamp = DurationSinceUnixEpoch::ZERO; + assert_eq!( + convert_from_timestamp_to_datetime_utc(timestamp), + DateTime::from_timestamp(0, 0).unwrap() + ); + } + + #[test] + fn should_be_converted_from_datetime_utc() { + let datetime = DateTime::from_timestamp(0, 0).unwrap(); + assert_eq!( + convert_from_datetime_utc_to_timestamp(&datetime), + DurationSinceUnixEpoch::ZERO + ); + } + + #[test] + fn should_be_converted_from_datetime_utc_in_iso_8601() { + let iso_8601 = "1970-01-01T00:00:00.000Z".to_string(); + assert_eq!(convert_from_iso_8601_to_timestamp(&iso_8601), DurationSinceUnixEpoch::ZERO); + } +} diff --git a/packages/clock/src/lib.rs b/packages/clock/src/lib.rs new file mode 100644 index 000000000..9fc67cb54 --- /dev/null +++ b/packages/clock/src/lib.rs @@ -0,0 +1,53 @@ +//! Time related functions and types. +//! +//! It's usually a good idea to control where the time comes from +//! in an application so that it can be mocked for testing and it can be +//! controlled in production so we get the intended behavior without +//! relying on the specific time zone for the underlying system. +//! +//! Clocks use the type `DurationSinceUnixEpoch` which is a +//! `std::time::Duration` since the Unix Epoch (timestamp). +//! +//! ```text +//! Local time: lun 2023-03-27 16:12:00 WEST +//! Universal time: lun 2023-03-27 15:12:00 UTC +//! Time zone: Atlantic/Canary (WEST, +0100) +//! Timestamp: 1679929914 +//! Duration: 1679929914.10167426 +//! ``` +//! +//! > **NOTICE**: internally the `Duration` is stores it's main unit as seconds in a `u64` and it will +//! overflow in 584.9 billion years. +//! +//! > **NOTICE**: the timestamp does not depend on the time zone. That gives you +//! the ability to use the clock regardless of the underlying system time zone +//! configuration. See [Unix time Wikipedia entry](https://en.wikipedia.org/wiki/Unix_time). + +pub mod clock; +pub mod conv; +pub mod static_time; +pub mod time_extent; + +#[macro_use] +extern crate lazy_static; + +/// This code needs to be copied into each crate. +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Working; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Stopped; + +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type DefaultTimeExtentMaker = time_extent::WorkingTimeExtentMaker; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type DefaultTimeExtentMaker = time_extent::StoppedTimeExtentMaker; diff --git a/src/shared/clock/static_time.rs b/packages/clock/src/static_time/mod.rs similarity index 100% rename from src/shared/clock/static_time.rs rename to packages/clock/src/static_time/mod.rs diff --git a/src/shared/clock/time_extent.rs b/packages/clock/src/time_extent/mod.rs similarity index 85% rename from src/shared/clock/time_extent.rs rename to packages/clock/src/time_extent/mod.rs index 168224eda..c51849f21 100644 --- a/src/shared/clock/time_extent.rs +++ b/packages/clock/src/time_extent/mod.rs @@ -65,7 +65,7 @@ use std::num::{IntErrorKind, TryFromIntError}; use std::time::Duration; -use super::{Stopped, TimeNow, Type, Working}; +use crate::clock::{self, Stopped, Working}; /// This trait defines the operations that can be performed on a `TimeExtent`. pub trait Extent: Sized + Default { @@ -199,10 +199,10 @@ impl Extent for TimeExtent { /// It gives you the time in time extents. pub trait Make: Sized where - Clock: TimeNow, + Clock: clock::Time, { /// It gives you the current time extent (with a certain increment) for - /// the current time. It gets the current timestamp front he `Clock`. + /// the current time. It gets the current timestamp front the `Clock`. /// /// For example: /// @@ -223,12 +223,12 @@ where }) } - /// Same as [`now`](crate::shared::clock::time_extent::Make::now), but it + /// Same as [`now`](crate::time_extent::Make::now), but it /// will add an extra duration to the current time before calculating the /// time extent. It gives you a time extent for a time in the future. #[must_use] fn now_after(increment: &Base, add_time: &Duration) -> Option> { - match Clock::add(add_time) { + match Clock::now_add(add_time) { None => None, Some(time) => time .as_nanos() @@ -240,12 +240,12 @@ where } } - /// Same as [`now`](crate::shared::clock::time_extent::Make::now), but it + /// Same as [`now`](crate::time_extent::Make::now), but it /// will subtract a duration to the current time before calculating the /// time extent. It gives you a time extent for a time in the past. #[must_use] fn now_before(increment: &Base, sub_time: &Duration) -> Option> { - match Clock::sub(sub_time) { + match Clock::now_sub(sub_time) { None => None, Some(time) => time .as_nanos() @@ -262,38 +262,30 @@ where /// /// It's a clock which measures time in `TimeExtents`. #[derive(Debug)] -pub struct Maker {} +pub struct Maker { + clock: std::marker::PhantomData, +} /// A `TimeExtent` maker which makes `TimeExtents` from the `Working` clock. -pub type WorkingTimeExtentMaker = Maker<{ Type::WorkingClock as usize }>; +pub type WorkingTimeExtentMaker = Maker; /// A `TimeExtent` maker which makes `TimeExtents` from the `Stopped` clock. -pub type StoppedTimeExtentMaker = Maker<{ Type::StoppedClock as usize }>; - -impl Make for WorkingTimeExtentMaker {} -impl Make for StoppedTimeExtentMaker {} +pub type StoppedTimeExtentMaker = Maker; -/// The default `TimeExtent` maker. It is `WorkingTimeExtentMaker` in production -/// and `StoppedTimeExtentMaker` in tests. -#[cfg(not(test))] -pub type DefaultTimeExtentMaker = WorkingTimeExtentMaker; - -/// The default `TimeExtent` maker. It is `WorkingTimeExtentMaker` in production -/// and `StoppedTimeExtentMaker` in tests. -#[cfg(test)] -pub type DefaultTimeExtentMaker = StoppedTimeExtentMaker; +impl Make for WorkingTimeExtentMaker {} +impl Make for StoppedTimeExtentMaker {} #[cfg(test)] mod test { - use crate::shared::clock::time_extent::TimeExtent; + use crate::time_extent::TimeExtent; const TIME_EXTENT_VAL: TimeExtent = TimeExtent::from_sec(2, &239_812_388_723); mod fn_checked_duration_from_nanos { use std::time::Duration; - use crate::shared::clock::time_extent::checked_duration_from_nanos; - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::checked_duration_from_nanos; + use crate::time_extent::test::TIME_EXTENT_VAL; const NANOS_PER_SEC: u32 = 1_000_000_000; @@ -334,7 +326,7 @@ mod test { mod time_extent { mod fn_default { - use crate::shared::clock::time_extent::{TimeExtent, ZERO}; + use crate::time_extent::{TimeExtent, ZERO}; #[test] fn it_should_default_initialize_to_zero() { @@ -343,8 +335,8 @@ mod test { } mod fn_from_sec { - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Multiplier, TimeExtent, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Multiplier, TimeExtent, ZERO}; #[test] fn it_should_make_empty_for_zero() { @@ -360,8 +352,8 @@ mod test { } mod fn_new { - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Base, Extent, Multiplier, TimeExtent, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Base, Extent, Multiplier, TimeExtent, ZERO}; #[test] fn it_should_make_empty_for_zero() { @@ -383,8 +375,8 @@ mod test { mod fn_increase { use std::num::IntErrorKind; - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Extent, TimeExtent, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Extent, TimeExtent, ZERO}; #[test] fn it_should_not_increase_for_zero() { @@ -411,8 +403,8 @@ mod test { mod fn_decrease { use std::num::IntErrorKind; - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Extent, TimeExtent, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Extent, TimeExtent, ZERO}; #[test] fn it_should_not_decrease_for_zero() { @@ -437,8 +429,8 @@ mod test { } mod fn_total { - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; #[test] fn it_should_be_zero_for_zero() { @@ -485,8 +477,8 @@ mod test { } mod fn_total_next { - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; #[test] fn it_should_be_zero_for_zero() { @@ -544,9 +536,10 @@ mod test { mod fn_now { use torrust_tracker_primitives::DurationSinceUnixEpoch; - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Base, DefaultTimeExtentMaker, Make, TimeExtent}; - use crate::shared::clock::{Current, StoppedTime}; + use crate::clock::stopped::Stopped as _; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Base, Make, TimeExtent}; + use crate::{CurrentClock, DefaultTimeExtentMaker}; #[test] fn it_should_give_a_time_extent() { @@ -558,7 +551,7 @@ mod test { } ); - Current::local_set(&DurationSinceUnixEpoch::from_secs(TIME_EXTENT_VAL.amount * 2)); + CurrentClock::local_set(&DurationSinceUnixEpoch::from_secs(TIME_EXTENT_VAL.amount * 2)); assert_eq!( DefaultTimeExtentMaker::now(&TIME_EXTENT_VAL.increment).unwrap().unwrap(), @@ -573,7 +566,7 @@ mod test { #[test] fn it_should_fail_if_amount_exceeds_bounds() { - Current::local_set(&DurationSinceUnixEpoch::MAX); + CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); assert_eq!( DefaultTimeExtentMaker::now(&Base::from_millis(1)).unwrap().unwrap_err(), u64::try_from(u128::MAX).unwrap_err() @@ -586,9 +579,10 @@ mod test { use torrust_tracker_primitives::DurationSinceUnixEpoch; - use crate::shared::clock::time_extent::test::TIME_EXTENT_VAL; - use crate::shared::clock::time_extent::{Base, DefaultTimeExtentMaker, Make}; - use crate::shared::clock::{Current, StoppedTime}; + use crate::clock::stopped::Stopped as _; + use crate::time_extent::test::TIME_EXTENT_VAL; + use crate::time_extent::{Base, Make}; + use crate::{CurrentClock, DefaultTimeExtentMaker}; #[test] fn it_should_give_a_time_extent() { @@ -607,13 +601,13 @@ mod test { fn it_should_fail_for_zero() { assert_eq!(DefaultTimeExtentMaker::now_after(&Base::ZERO, &Duration::ZERO), None); - Current::local_set(&DurationSinceUnixEpoch::MAX); + CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); assert_eq!(DefaultTimeExtentMaker::now_after(&Base::ZERO, &Duration::MAX), None); } #[test] fn it_should_fail_if_amount_exceeds_bounds() { - Current::local_set(&DurationSinceUnixEpoch::MAX); + CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); assert_eq!( DefaultTimeExtentMaker::now_after(&Base::from_millis(1), &Duration::ZERO) .unwrap() @@ -627,12 +621,13 @@ mod test { use torrust_tracker_primitives::DurationSinceUnixEpoch; - use crate::shared::clock::time_extent::{Base, DefaultTimeExtentMaker, Make, TimeExtent}; - use crate::shared::clock::{Current, StoppedTime}; + use crate::clock::stopped::Stopped as _; + use crate::time_extent::{Base, Make, TimeExtent}; + use crate::{CurrentClock, DefaultTimeExtentMaker}; #[test] fn it_should_give_a_time_extent() { - Current::local_set(&DurationSinceUnixEpoch::MAX); + CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); assert_eq!( DefaultTimeExtentMaker::now_before( @@ -657,7 +652,7 @@ mod test { #[test] fn it_should_fail_if_amount_exceeds_bounds() { - Current::local_set(&DurationSinceUnixEpoch::MAX); + CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); assert_eq!( DefaultTimeExtentMaker::now_before(&Base::from_millis(1), &Duration::ZERO) .unwrap() diff --git a/packages/clock/tests/clock/mod.rs b/packages/clock/tests/clock/mod.rs new file mode 100644 index 000000000..5d94bb83d --- /dev/null +++ b/packages/clock/tests/clock/mod.rs @@ -0,0 +1,16 @@ +use std::time::Duration; + +use torrust_tracker_clock::clock::Time; + +use crate::CurrentClock; + +#[test] +fn it_should_use_stopped_time_for_testing() { + assert_eq!(CurrentClock::dbg_clock_type(), "Stopped".to_owned()); + + let time = CurrentClock::now(); + std::thread::sleep(Duration::from_millis(50)); + let time_2 = CurrentClock::now(); + + assert_eq!(time, time_2); +} diff --git a/packages/clock/tests/integration.rs b/packages/clock/tests/integration.rs new file mode 100644 index 000000000..fa500227a --- /dev/null +++ b/packages/clock/tests/integration.rs @@ -0,0 +1,19 @@ +//! Integration tests. +//! +//! ```text +//! cargo test --test integration +//! ``` + +//mod common; +mod clock; + +/// This code needs to be copied into each crate. +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type CurrentClock = torrust_tracker_clock::clock::Working; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type CurrentClock = torrust_tracker_clock::clock::Stopped; diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index b3b146717..549c73a31 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -243,6 +243,9 @@ use thiserror::Error; use torrust_tracker_located_error::{DynError, Located, LocatedError}; use torrust_tracker_primitives::{DatabaseDriver, TrackerMode}; +/// The maximum number of returned peers for a torrent. +pub const TORRENT_PEERS_LIMIT: usize = 74; + #[derive(Copy, Clone, Debug, PartialEq, Default, Constructor)] pub struct TrackerPolicy { pub remove_peerless_torrents: bool, diff --git a/packages/torrent-repository/Cargo.toml b/packages/torrent-repository/Cargo.toml index b53b9a15e..c36ae1440 100644 --- a/packages/torrent-repository/Cargo.toml +++ b/packages/torrent-repository/Cargo.toml @@ -1,5 +1,5 @@ [package] -description = "A library to provide error decorator with the location and the source of the original error." +description = "A library that provides a repository of torrents files and their peers." keywords = ["torrents", "repository", "library"] name = "torrust-tracker-torrent-repository" readme = "README.md" @@ -20,10 +20,11 @@ futures = "0.3.29" tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] } torrust-tracker-primitives = { version = "3.0.0-alpha.12-develop", path = "../primitives" } torrust-tracker-configuration = { version = "3.0.0-alpha.12-develop", path = "../configuration" } -serde = { version = "1", features = ["derive"] } +torrust-tracker-clock = { version = "3.0.0-alpha.12-develop", path = "../clock" } [dev-dependencies] criterion = { version = "0", features = ["async_tokio"] } +rstest = "0" [[bench]] harness = false diff --git a/packages/torrent-repository/src/entry/mod.rs b/packages/torrent-repository/src/entry/mod.rs index 04aa597df..11352a8fa 100644 --- a/packages/torrent-repository/src/entry/mod.rs +++ b/packages/torrent-repository/src/entry/mod.rs @@ -1,7 +1,7 @@ use std::fmt::Debug; use std::sync::Arc; -use serde::{Deserialize, Serialize}; +//use serde::{Deserialize, Serialize}; use torrust_tracker_configuration::TrackerPolicy; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; @@ -88,10 +88,10 @@ pub trait EntryAsync { /// This is the tracker entry for a given torrent and contains the swarm data, /// that's the list of all the peers trying to download the same torrent. /// The tracker keeps one entry like this for every torrent. -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Torrent { /// The swarm: a network of peers that are all trying to download the torrent associated to this entry - #[serde(skip)] + // #[serde(skip)] pub(crate) peers: std::collections::BTreeMap>, /// The number of peers that have ever completed downloading the torrent associated to this entry pub(crate) completed: u32, diff --git a/packages/torrent-repository/src/entry/single.rs b/packages/torrent-repository/src/entry/single.rs index 7a5cf6240..85fdc6cf0 100644 --- a/packages/torrent-repository/src/entry/single.rs +++ b/packages/torrent-repository/src/entry/single.rs @@ -103,3 +103,302 @@ impl Entry for EntrySingle { .retain(|_, peer| peer::ReadInfo::get_updated(peer) > current_cutoff); } } + +#[cfg(test)] +mod tests { + mod torrent_entry { + + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::ops::Sub; + use std::sync::Arc; + use std::time::Duration; + + use torrust_tracker_clock::clock::stopped::Stopped as _; + use torrust_tracker_clock::clock::{self, Time}; + use torrust_tracker_configuration::TORRENT_PEERS_LIMIT; + use torrust_tracker_primitives::announce_event::AnnounceEvent; + use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch, NumberOfBytes}; + + use crate::entry::Entry; + use crate::{CurrentClock, EntrySingle}; + + struct TorrentPeerBuilder { + peer: peer::Peer, + } + + impl TorrentPeerBuilder { + pub fn default() -> TorrentPeerBuilder { + let default_peer = peer::Peer { + peer_id: peer::Id([0u8; 20]), + peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), + updated: CurrentClock::now(), + uploaded: NumberOfBytes(0), + downloaded: NumberOfBytes(0), + left: NumberOfBytes(0), + event: AnnounceEvent::Started, + }; + TorrentPeerBuilder { peer: default_peer } + } + + pub fn with_event_completed(mut self) -> Self { + self.peer.event = AnnounceEvent::Completed; + self + } + + pub fn with_peer_address(mut self, peer_addr: SocketAddr) -> Self { + self.peer.peer_addr = peer_addr; + self + } + + pub fn with_peer_id(mut self, peer_id: peer::Id) -> Self { + self.peer.peer_id = peer_id; + self + } + + pub fn with_number_of_bytes_left(mut self, left: i64) -> Self { + self.peer.left = NumberOfBytes(left); + self + } + + pub fn updated_at(mut self, updated: DurationSinceUnixEpoch) -> Self { + self.peer.updated = updated; + self + } + + pub fn into(self) -> peer::Peer { + self.peer + } + } + + /// A torrent seeder is a peer with 0 bytes left to download which + /// has not announced it has stopped + fn a_torrent_seeder() -> peer::Peer { + TorrentPeerBuilder::default() + .with_number_of_bytes_left(0) + .with_event_completed() + .into() + } + + /// A torrent leecher is a peer that is not a seeder. + /// Leecher: left > 0 OR event = Stopped + fn a_torrent_leecher() -> peer::Peer { + TorrentPeerBuilder::default() + .with_number_of_bytes_left(1) + .with_event_completed() + .into() + } + + #[test] + fn the_default_torrent_entry_should_contain_an_empty_list_of_peers() { + let torrent_entry = EntrySingle::default(); + + assert_eq!(torrent_entry.get_peers(None).len(), 0); + } + + #[test] + fn a_new_peer_can_be_added_to_a_torrent_entry() { + let mut torrent_entry = EntrySingle::default(); + let torrent_peer = TorrentPeerBuilder::default().into(); + + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + assert_eq!(*torrent_entry.get_peers(None)[0], torrent_peer); + assert_eq!(torrent_entry.get_peers(None).len(), 1); + } + + #[test] + fn a_torrent_entry_should_contain_the_list_of_peers_that_were_added_to_the_torrent() { + let mut torrent_entry = EntrySingle::default(); + let torrent_peer = TorrentPeerBuilder::default().into(); + + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + assert_eq!(torrent_entry.get_peers(None), vec![Arc::new(torrent_peer)]); + } + + #[test] + fn a_peer_can_be_updated_in_a_torrent_entry() { + let mut torrent_entry = EntrySingle::default(); + let mut torrent_peer = TorrentPeerBuilder::default().into(); + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + torrent_peer.event = AnnounceEvent::Completed; // Update the peer + torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry + + assert_eq!(torrent_entry.get_peers(None)[0].event, AnnounceEvent::Completed); + } + + #[test] + fn a_peer_should_be_removed_from_a_torrent_entry_when_the_peer_announces_it_has_stopped() { + let mut torrent_entry = EntrySingle::default(); + let mut torrent_peer = TorrentPeerBuilder::default().into(); + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + torrent_peer.event = AnnounceEvent::Stopped; // Update the peer + torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry + + assert_eq!(torrent_entry.get_peers(None).len(), 0); + } + + #[test] + fn torrent_stats_change_when_a_previously_known_peer_announces_it_has_completed_the_torrent() { + let mut torrent_entry = EntrySingle::default(); + let mut torrent_peer = TorrentPeerBuilder::default().into(); + + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + torrent_peer.event = AnnounceEvent::Completed; // Update the peer + let stats_have_changed = torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry + + assert!(stats_have_changed); + } + + #[test] + fn torrent_stats_should_not_change_when_a_peer_announces_it_has_completed_the_torrent_if_it_is_the_first_announce_from_the_peer( + ) { + let mut torrent_entry = EntrySingle::default(); + let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into(); + + // Add a peer that did not exist before in the entry + let torrent_stats_have_not_changed = !torrent_entry.insert_or_update_peer(&torrent_peer_announcing_complete_event); + + assert!(torrent_stats_have_not_changed); + } + + #[test] + fn a_torrent_entry_should_return_the_list_of_peers_for_a_given_peer_filtering_out_the_client_that_is_making_the_request() + { + let mut torrent_entry = EntrySingle::default(); + let peer_socket_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let torrent_peer = TorrentPeerBuilder::default().with_peer_address(peer_socket_address).into(); + torrent_entry.insert_or_update_peer(&torrent_peer); // Add peer + + // Get peers excluding the one we have just added + let peers = torrent_entry.get_peers_for_peer(&torrent_peer, None); + + assert_eq!(peers.len(), 0); + } + + #[test] + fn two_peers_with_the_same_ip_but_different_port_should_be_considered_different_peers() { + let mut torrent_entry = EntrySingle::default(); + + let peer_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + + // Add peer 1 + let torrent_peer_1 = TorrentPeerBuilder::default() + .with_peer_address(SocketAddr::new(peer_ip, 8080)) + .into(); + torrent_entry.insert_or_update_peer(&torrent_peer_1); + + // Add peer 2 + let torrent_peer_2 = TorrentPeerBuilder::default() + .with_peer_address(SocketAddr::new(peer_ip, 8081)) + .into(); + torrent_entry.insert_or_update_peer(&torrent_peer_2); + + // Get peers for peer 1 + let peers = torrent_entry.get_peers_for_peer(&torrent_peer_1, None); + + // The peer 2 using the same IP but different port should be included + assert_eq!(peers[0].peer_addr.ip(), Ipv4Addr::new(127, 0, 0, 1)); + assert_eq!(peers[0].peer_addr.port(), 8081); + } + + fn peer_id_from_i32(number: i32) -> peer::Id { + let peer_id = number.to_le_bytes(); + peer::Id([ + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, peer_id[0], peer_id[1], + peer_id[2], peer_id[3], + ]) + } + + #[test] + fn the_tracker_should_limit_the_list_of_peers_to_74_when_clients_scrape_torrents() { + let mut torrent_entry = EntrySingle::default(); + + // We add one more peer than the scrape limit + for peer_number in 1..=74 + 1 { + let torrent_peer = TorrentPeerBuilder::default() + .with_peer_id(peer_id_from_i32(peer_number)) + .into(); + torrent_entry.insert_or_update_peer(&torrent_peer); + } + + let peers = torrent_entry.get_peers(Some(TORRENT_PEERS_LIMIT)); + + assert_eq!(peers.len(), 74); + } + + #[test] + fn torrent_stats_should_have_the_number_of_seeders_for_a_torrent() { + let mut torrent_entry = EntrySingle::default(); + let torrent_seeder = a_torrent_seeder(); + + torrent_entry.insert_or_update_peer(&torrent_seeder); // Add seeder + + assert_eq!(torrent_entry.get_stats().complete, 1); + } + + #[test] + fn torrent_stats_should_have_the_number_of_leechers_for_a_torrent() { + let mut torrent_entry = EntrySingle::default(); + let torrent_leecher = a_torrent_leecher(); + + torrent_entry.insert_or_update_peer(&torrent_leecher); // Add leecher + + assert_eq!(torrent_entry.get_stats().incomplete, 1); + } + + #[test] + fn torrent_stats_should_have_the_number_of_peers_that_having_announced_at_least_two_events_the_latest_one_is_the_completed_event( + ) { + let mut torrent_entry = EntrySingle::default(); + let mut torrent_peer = TorrentPeerBuilder::default().into(); + torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer + + // Announce "Completed" torrent download event. + torrent_peer.event = AnnounceEvent::Completed; + torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer + + let number_of_previously_known_peers_with_completed_torrent = torrent_entry.get_stats().complete; + + assert_eq!(number_of_previously_known_peers_with_completed_torrent, 1); + } + + #[test] + fn torrent_stats_should_not_include_a_peer_in_the_completed_counter_if_the_peer_has_announced_only_one_event() { + let mut torrent_entry = EntrySingle::default(); + let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into(); + + // Announce "Completed" torrent download event. + // It's the first event announced from this peer. + torrent_entry.insert_or_update_peer(&torrent_peer_announcing_complete_event); // Add the peer + + let number_of_peers_with_completed_torrent = torrent_entry.get_stats().downloaded; + + assert_eq!(number_of_peers_with_completed_torrent, 0); + } + + #[test] + fn a_torrent_entry_should_remove_a_peer_not_updated_after_a_timeout_in_seconds() { + let mut torrent_entry = EntrySingle::default(); + + let timeout = 120u32; + + let now = clock::Working::now(); + clock::Stopped::local_set(&now); + + let timeout_seconds_before_now = now.sub(Duration::from_secs(u64::from(timeout))); + let inactive_peer = TorrentPeerBuilder::default() + .updated_at(timeout_seconds_before_now.sub(Duration::from_secs(1))) + .into(); + torrent_entry.insert_or_update_peer(&inactive_peer); // Add the peer + + let current_cutoff = CurrentClock::now_sub(&Duration::from_secs(u64::from(timeout))).unwrap_or_default(); + torrent_entry.remove_inactive_peers(current_cutoff); + + assert_eq!(torrent_entry.get_peers_len(), 0); + } + } +} diff --git a/packages/torrent-repository/src/lib.rs b/packages/torrent-repository/src/lib.rs index 903e1405e..8bb1b6def 100644 --- a/packages/torrent-repository/src/lib.rs +++ b/packages/torrent-repository/src/lib.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use torrust_tracker_clock::clock; + pub mod entry; pub mod repository; @@ -13,3 +15,14 @@ pub type TorrentsRwLockStdMutexTokio = repository::RwLockStd; pub type TorrentsRwLockTokio = repository::RwLockTokio; pub type TorrentsRwLockTokioMutexStd = repository::RwLockTokio; pub type TorrentsRwLockTokioMutexTokio = repository::RwLockTokio; + +/// This code needs to be copied into each crate. +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Working; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Stopped; diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 09b624566..396e63682 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -13,13 +13,13 @@ //! 4. Initialize the domain tracker. use std::sync::Arc; +use torrust_tracker_clock::static_time; use torrust_tracker_configuration::Configuration; use super::config::initialize_configuration; use crate::bootstrap; use crate::core::services::tracker_factory; use crate::core::Tracker; -use crate::shared::clock::static_time; use crate::shared::crypto::ephemeral_instance_keys; /// It loads the configuration from the environment and builds the main domain [`Tracker`] struct. diff --git a/src/core/auth.rs b/src/core/auth.rs index a7bb91aa4..b5326a373 100644 --- a/src/core/auth.rs +++ b/src/core/auth.rs @@ -47,11 +47,13 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use torrust_tracker_clock::clock::Time; +use torrust_tracker_clock::conv::convert_from_timestamp_to_datetime_utc; use torrust_tracker_located_error::{DynError, LocatedError}; use torrust_tracker_primitives::DurationSinceUnixEpoch; use crate::shared::bit_torrent::common::AUTH_KEY_LENGTH; -use crate::shared::clock::{convert_from_timestamp_to_datetime_utc, Current, Time, TimeNow}; +use crate::CurrentClock; #[must_use] /// It generates a new random 32-char authentication [`ExpiringKey`] @@ -70,7 +72,7 @@ pub fn generate(lifetime: Duration) -> ExpiringKey { ExpiringKey { key: random_id.parse::().unwrap(), - valid_until: Current::add(&lifetime).unwrap(), + valid_until: CurrentClock::now_add(&lifetime).unwrap(), } } @@ -82,7 +84,7 @@ pub fn generate(lifetime: Duration) -> ExpiringKey { /// /// Will return `Error::KeyInvalid` if `auth_key.valid_until` is past the `None`. pub fn verify(auth_key: &ExpiringKey) -> Result<(), Error> { - let current_time: DurationSinceUnixEpoch = Current::now(); + let current_time: DurationSinceUnixEpoch = CurrentClock::now(); if auth_key.valid_until < current_time { Err(Error::KeyExpired { @@ -213,8 +215,10 @@ mod tests { use std::str::FromStr; use std::time::Duration; + use torrust_tracker_clock::clock; + use torrust_tracker_clock::clock::stopped::Stopped as _; + use crate::core::auth; - use crate::shared::clock::{Current, StoppedTime}; #[test] fn should_be_parsed_from_an_string() { @@ -228,7 +232,7 @@ mod tests { #[test] fn should_be_displayed() { // Set the time to the current time. - Current::local_set_to_unix_epoch(); + clock::Stopped::local_set_to_unix_epoch(); let expiring_key = auth::generate(Duration::from_secs(0)); @@ -248,18 +252,18 @@ mod tests { #[test] fn should_be_generate_and_verified() { // Set the time to the current time. - Current::local_set_to_system_time_now(); + clock::Stopped::local_set_to_system_time_now(); // Make key that is valid for 19 seconds. let expiring_key = auth::generate(Duration::from_secs(19)); // Mock the time has passed 10 sec. - Current::local_add(&Duration::from_secs(10)).unwrap(); + clock::Stopped::local_add(&Duration::from_secs(10)).unwrap(); assert!(auth::verify(&expiring_key).is_ok()); // Mock the time has passed another 10 sec. - Current::local_add(&Duration::from_secs(10)).unwrap(); + clock::Stopped::local_add(&Duration::from_secs(10)).unwrap(); assert!(auth::verify(&expiring_key).is_err()); } diff --git a/src/core/databases/mod.rs b/src/core/databases/mod.rs index b708ef4dc..20a45cf83 100644 --- a/src/core/databases/mod.rs +++ b/src/core/databases/mod.rs @@ -117,9 +117,9 @@ pub trait Database: Sync + Send { /// /// It returns an array of tuples with the torrent /// [`InfoHash`] and the - /// [`completed`](torrust_tracker_torrent_repository::entry::Entry::completed) counter + /// [`completed`](torrust_tracker_torrent_repository::entry::Torrent::completed) counter /// which is the number of times the torrent has been downloaded. - /// See [`Entry::completed`](torrust_tracker_torrent_repository::entry::Entry::completed). + /// See [`Entry::completed`](torrust_tracker_torrent_repository::entry::Torrent::completed). /// /// # Context: Torrent Metrics /// diff --git a/src/core/mod.rs b/src/core/mod.rs index f94c46543..21cd1b501 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -444,7 +444,8 @@ use std::time::Duration; use derive_more::Constructor; use log::debug; use tokio::sync::mpsc::error::SendError; -use torrust_tracker_configuration::{AnnouncePolicy, Configuration, TrackerPolicy}; +use torrust_tracker_clock::clock::Time; +use torrust_tracker_configuration::{AnnouncePolicy, Configuration, TrackerPolicy, TORRENT_PEERS_LIMIT}; use torrust_tracker_primitives::info_hash::InfoHash; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; use torrust_tracker_primitives::torrent_metrics::TorrentsMetrics; @@ -456,10 +457,7 @@ use self::auth::Key; use self::error::Error; use self::torrent::Torrents; use crate::core::databases::Database; -use crate::shared::clock::{self, TimeNow}; - -/// The maximum number of returned peers for a torrent. -pub const TORRENT_PEERS_LIMIT: usize = 74; +use crate::CurrentClock; /// The domain layer tracker service. /// @@ -741,7 +739,7 @@ impl Tracker { self.torrents.remove_peerless_torrents(&self.policy); } else { let current_cutoff = - clock::Current::sub(&Duration::from_secs(u64::from(self.policy.max_peer_timeout))).unwrap_or_default(); + CurrentClock::now_sub(&Duration::from_secs(u64::from(self.policy.max_peer_timeout))).unwrap_or_default(); self.torrents.remove_inactive_peers(current_cutoff); } } @@ -1592,8 +1590,11 @@ mod tests { use std::str::FromStr; use std::time::Duration; + use torrust_tracker_clock::clock::Time; + use crate::core::auth; use crate::core::tests::the_tracker::private_tracker; + use crate::CurrentClock; #[tokio::test] async fn it_should_generate_the_expiring_authentication_keys() { @@ -1601,7 +1602,7 @@ mod tests { let key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); - assert_eq!(key.valid_until, Duration::from_secs(100)); + assert_eq!(key.valid_until, CurrentClock::now_add(&Duration::from_secs(100)).unwrap()); } #[tokio::test] diff --git a/src/core/peer_tests.rs b/src/core/peer_tests.rs index 9e5b4be01..d30d73db3 100644 --- a/src/core/peer_tests.rs +++ b/src/core/peer_tests.rs @@ -2,17 +2,21 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use torrust_tracker_clock::clock::stopped::Stopped as _; +use torrust_tracker_clock::clock::{self, Time}; use torrust_tracker_primitives::announce_event::AnnounceEvent; use torrust_tracker_primitives::{peer, NumberOfBytes}; -use crate::shared::clock::{self, Time}; +use crate::CurrentClock; #[test] fn it_should_be_serializable() { + clock::Stopped::local_set_to_unix_epoch(); + let torrent_peer = peer::Peer { peer_id: peer::Id(*b"-qB0000-000000000000"), peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080), - updated: clock::Current::now(), + updated: CurrentClock::now(), uploaded: NumberOfBytes(0), downloaded: NumberOfBytes(0), left: NumberOfBytes(0), diff --git a/src/core/torrent/mod.rs b/src/core/torrent/mod.rs index b5a2b4c07..2b3f9cbf7 100644 --- a/src/core/torrent/mod.rs +++ b/src/core/torrent/mod.rs @@ -31,300 +31,4 @@ use torrust_tracker_torrent_repository::TorrentsRwLockStdMutexStd; pub type Torrents = TorrentsRwLockStdMutexStd; // Currently Used #[cfg(test)] -mod tests { - - mod torrent_entry { - - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use std::ops::Sub; - use std::sync::Arc; - use std::time::Duration; - - use torrust_tracker_primitives::announce_event::AnnounceEvent; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch, NumberOfBytes}; - use torrust_tracker_torrent_repository::entry::Entry; - use torrust_tracker_torrent_repository::EntrySingle; - - use crate::core::TORRENT_PEERS_LIMIT; - use crate::shared::clock::{self, StoppedTime, Time, TimeNow}; - - struct TorrentPeerBuilder { - peer: peer::Peer, - } - - impl TorrentPeerBuilder { - pub fn default() -> TorrentPeerBuilder { - let default_peer = peer::Peer { - peer_id: peer::Id([0u8; 20]), - peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), - updated: clock::Current::now(), - uploaded: NumberOfBytes(0), - downloaded: NumberOfBytes(0), - left: NumberOfBytes(0), - event: AnnounceEvent::Started, - }; - TorrentPeerBuilder { peer: default_peer } - } - - pub fn with_event_completed(mut self) -> Self { - self.peer.event = AnnounceEvent::Completed; - self - } - - pub fn with_peer_address(mut self, peer_addr: SocketAddr) -> Self { - self.peer.peer_addr = peer_addr; - self - } - - pub fn with_peer_id(mut self, peer_id: peer::Id) -> Self { - self.peer.peer_id = peer_id; - self - } - - pub fn with_number_of_bytes_left(mut self, left: i64) -> Self { - self.peer.left = NumberOfBytes(left); - self - } - - pub fn updated_at(mut self, updated: DurationSinceUnixEpoch) -> Self { - self.peer.updated = updated; - self - } - - pub fn into(self) -> peer::Peer { - self.peer - } - } - - /// A torrent seeder is a peer with 0 bytes left to download which - /// has not announced it has stopped - fn a_torrent_seeder() -> peer::Peer { - TorrentPeerBuilder::default() - .with_number_of_bytes_left(0) - .with_event_completed() - .into() - } - - /// A torrent leecher is a peer that is not a seeder. - /// Leecher: left > 0 OR event = Stopped - fn a_torrent_leecher() -> peer::Peer { - TorrentPeerBuilder::default() - .with_number_of_bytes_left(1) - .with_event_completed() - .into() - } - - #[test] - fn the_default_torrent_entry_should_contain_an_empty_list_of_peers() { - let torrent_entry = EntrySingle::default(); - - assert_eq!(torrent_entry.get_peers(None).len(), 0); - } - - #[test] - fn a_new_peer_can_be_added_to_a_torrent_entry() { - let mut torrent_entry = EntrySingle::default(); - let torrent_peer = TorrentPeerBuilder::default().into(); - - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - assert_eq!(*torrent_entry.get_peers(None)[0], torrent_peer); - assert_eq!(torrent_entry.get_peers(None).len(), 1); - } - - #[test] - fn a_torrent_entry_should_contain_the_list_of_peers_that_were_added_to_the_torrent() { - let mut torrent_entry = EntrySingle::default(); - let torrent_peer = TorrentPeerBuilder::default().into(); - - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - assert_eq!(torrent_entry.get_peers(None), vec![Arc::new(torrent_peer)]); - } - - #[test] - fn a_peer_can_be_updated_in_a_torrent_entry() { - let mut torrent_entry = EntrySingle::default(); - let mut torrent_peer = TorrentPeerBuilder::default().into(); - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - torrent_peer.event = AnnounceEvent::Completed; // Update the peer - torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry - - assert_eq!(torrent_entry.get_peers(None)[0].event, AnnounceEvent::Completed); - } - - #[test] - fn a_peer_should_be_removed_from_a_torrent_entry_when_the_peer_announces_it_has_stopped() { - let mut torrent_entry = EntrySingle::default(); - let mut torrent_peer = TorrentPeerBuilder::default().into(); - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - torrent_peer.event = AnnounceEvent::Stopped; // Update the peer - torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry - - assert_eq!(torrent_entry.get_peers(None).len(), 0); - } - - #[test] - fn torrent_stats_change_when_a_previously_known_peer_announces_it_has_completed_the_torrent() { - let mut torrent_entry = EntrySingle::default(); - let mut torrent_peer = TorrentPeerBuilder::default().into(); - - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - torrent_peer.event = AnnounceEvent::Completed; // Update the peer - let stats_have_changed = torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer in the torrent entry - - assert!(stats_have_changed); - } - - #[test] - fn torrent_stats_should_not_change_when_a_peer_announces_it_has_completed_the_torrent_if_it_is_the_first_announce_from_the_peer( - ) { - let mut torrent_entry = EntrySingle::default(); - let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into(); - - // Add a peer that did not exist before in the entry - let torrent_stats_have_not_changed = !torrent_entry.insert_or_update_peer(&torrent_peer_announcing_complete_event); - - assert!(torrent_stats_have_not_changed); - } - - #[test] - fn a_torrent_entry_should_return_the_list_of_peers_for_a_given_peer_filtering_out_the_client_that_is_making_the_request() - { - let mut torrent_entry = EntrySingle::default(); - let peer_socket_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let torrent_peer = TorrentPeerBuilder::default().with_peer_address(peer_socket_address).into(); - torrent_entry.insert_or_update_peer(&torrent_peer); // Add peer - - // Get peers excluding the one we have just added - let peers = torrent_entry.get_peers_for_peer(&torrent_peer, None); - - assert_eq!(peers.len(), 0); - } - - #[test] - fn two_peers_with_the_same_ip_but_different_port_should_be_considered_different_peers() { - let mut torrent_entry = EntrySingle::default(); - - let peer_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - - // Add peer 1 - let torrent_peer_1 = TorrentPeerBuilder::default() - .with_peer_address(SocketAddr::new(peer_ip, 8080)) - .into(); - torrent_entry.insert_or_update_peer(&torrent_peer_1); - - // Add peer 2 - let torrent_peer_2 = TorrentPeerBuilder::default() - .with_peer_address(SocketAddr::new(peer_ip, 8081)) - .into(); - torrent_entry.insert_or_update_peer(&torrent_peer_2); - - // Get peers for peer 1 - let peers = torrent_entry.get_peers_for_peer(&torrent_peer_1, None); - - // The peer 2 using the same IP but different port should be included - assert_eq!(peers[0].peer_addr.ip(), Ipv4Addr::new(127, 0, 0, 1)); - assert_eq!(peers[0].peer_addr.port(), 8081); - } - - fn peer_id_from_i32(number: i32) -> peer::Id { - let peer_id = number.to_le_bytes(); - peer::Id([ - 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, peer_id[0], peer_id[1], - peer_id[2], peer_id[3], - ]) - } - - #[test] - fn the_tracker_should_limit_the_list_of_peers_to_74_when_clients_scrape_torrents() { - let mut torrent_entry = EntrySingle::default(); - - // We add one more peer than the scrape limit - for peer_number in 1..=74 + 1 { - let torrent_peer = TorrentPeerBuilder::default() - .with_peer_id(peer_id_from_i32(peer_number)) - .into(); - torrent_entry.insert_or_update_peer(&torrent_peer); - } - - let peers = torrent_entry.get_peers(Some(TORRENT_PEERS_LIMIT)); - - assert_eq!(peers.len(), 74); - } - - #[test] - fn torrent_stats_should_have_the_number_of_seeders_for_a_torrent() { - let mut torrent_entry = EntrySingle::default(); - let torrent_seeder = a_torrent_seeder(); - - torrent_entry.insert_or_update_peer(&torrent_seeder); // Add seeder - - assert_eq!(torrent_entry.get_stats().complete, 1); - } - - #[test] - fn torrent_stats_should_have_the_number_of_leechers_for_a_torrent() { - let mut torrent_entry = EntrySingle::default(); - let torrent_leecher = a_torrent_leecher(); - - torrent_entry.insert_or_update_peer(&torrent_leecher); // Add leecher - - assert_eq!(torrent_entry.get_stats().incomplete, 1); - } - - #[test] - fn torrent_stats_should_have_the_number_of_peers_that_having_announced_at_least_two_events_the_latest_one_is_the_completed_event( - ) { - let mut torrent_entry = EntrySingle::default(); - let mut torrent_peer = TorrentPeerBuilder::default().into(); - torrent_entry.insert_or_update_peer(&torrent_peer); // Add the peer - - // Announce "Completed" torrent download event. - torrent_peer.event = AnnounceEvent::Completed; - torrent_entry.insert_or_update_peer(&torrent_peer); // Update the peer - - let number_of_previously_known_peers_with_completed_torrent = torrent_entry.get_stats().complete; - - assert_eq!(number_of_previously_known_peers_with_completed_torrent, 1); - } - - #[test] - fn torrent_stats_should_not_include_a_peer_in_the_completed_counter_if_the_peer_has_announced_only_one_event() { - let mut torrent_entry = EntrySingle::default(); - let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into(); - - // Announce "Completed" torrent download event. - // It's the first event announced from this peer. - torrent_entry.insert_or_update_peer(&torrent_peer_announcing_complete_event); // Add the peer - - let number_of_peers_with_completed_torrent = torrent_entry.get_stats().downloaded; - - assert_eq!(number_of_peers_with_completed_torrent, 0); - } - - #[test] - fn a_torrent_entry_should_remove_a_peer_not_updated_after_a_timeout_in_seconds() { - let mut torrent_entry = EntrySingle::default(); - - let timeout = 120u32; - - let now = clock::Working::now(); - clock::Stopped::local_set(&now); - - let timeout_seconds_before_now = now.sub(Duration::from_secs(u64::from(timeout))); - let inactive_peer = TorrentPeerBuilder::default() - .updated_at(timeout_seconds_before_now.sub(Duration::from_secs(1))) - .into(); - torrent_entry.insert_or_update_peer(&inactive_peer); // Add the peer - - let current_cutoff = clock::Current::sub(&Duration::from_secs(u64::from(timeout))).unwrap_or_default(); - torrent_entry.remove_inactive_peers(current_cutoff); - - assert_eq!(torrent_entry.get_peers_len(), 0); - } - } -} +mod tests {} diff --git a/src/lib.rs b/src/lib.rs index b4ad298ac..064f50eb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -469,6 +469,9 @@ //! //! In addition to the production code documentation you can find a lot of //! examples on the integration and unit tests. + +use torrust_tracker_clock::{clock, time_extent}; + pub mod app; pub mod bootstrap; pub mod console; @@ -478,3 +481,24 @@ pub mod shared; #[macro_use] extern crate lazy_static; + +/// This code needs to be copied into each crate. +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Working; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Stopped; + +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type DefaultTimeExtentMaker = time_extent::WorkingTimeExtentMaker; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type DefaultTimeExtentMaker = time_extent::StoppedTimeExtentMaker; diff --git a/src/servers/apis/v1/context/auth_key/resources.rs b/src/servers/apis/v1/context/auth_key/resources.rs index 99e93aaf9..3671438c2 100644 --- a/src/servers/apis/v1/context/auth_key/resources.rs +++ b/src/servers/apis/v1/context/auth_key/resources.rs @@ -1,9 +1,9 @@ //! API resources for the [`auth_key`](crate::servers::apis::v1::context::auth_key) API context. use serde::{Deserialize, Serialize}; +use torrust_tracker_clock::conv::convert_from_iso_8601_to_timestamp; use crate::core::auth::{self, Key}; -use crate::shared::clock::convert_from_iso_8601_to_timestamp; /// A resource that represents an authentication key. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] @@ -41,9 +41,12 @@ impl From for AuthKey { mod tests { use std::time::Duration; + use torrust_tracker_clock::clock::stopped::Stopped as _; + use torrust_tracker_clock::clock::{self, Time}; + use super::AuthKey; use crate::core::auth::{self, Key}; - use crate::shared::clock::{Current, TimeNow}; + use crate::CurrentClock; struct TestTime { pub timestamp: u64, @@ -65,6 +68,8 @@ mod tests { #[test] #[allow(deprecated)] fn it_should_be_convertible_into_an_auth_key() { + clock::Stopped::local_set_to_unix_epoch(); + let auth_key_resource = AuthKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line valid_until: one_hour_after_unix_epoch().timestamp, @@ -75,7 +80,7 @@ mod tests { auth::ExpiringKey::from(auth_key_resource), auth::ExpiringKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".parse::().unwrap(), // cspell:disable-line - valid_until: Current::add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap() + valid_until: CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap() } ); } @@ -83,9 +88,11 @@ mod tests { #[test] #[allow(deprecated)] fn it_should_be_convertible_from_an_auth_key() { + clock::Stopped::local_set_to_unix_epoch(); + let auth_key = auth::ExpiringKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".parse::().unwrap(), // cspell:disable-line - valid_until: Current::add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap(), + valid_until: CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap(), }; assert_eq!( diff --git a/src/servers/http/mod.rs b/src/servers/http/mod.rs index 6e8b5a40e..3ef85e600 100644 --- a/src/servers/http/mod.rs +++ b/src/servers/http/mod.rs @@ -71,7 +71,7 @@ //! is behind a reverse proxy. //! //! > **NOTICE**: the maximum number of peers that the tracker can return is -//! `74`. Defined with a hardcoded const [`TORRENT_PEERS_LIMIT`](crate::core::TORRENT_PEERS_LIMIT). +//! `74`. Defined with a hardcoded const [`TORRENT_PEERS_LIMIT`](torrust_tracker_configuration::TORRENT_PEERS_LIMIT). //! Refer to [issue 262](https://github.com/torrust/torrust-tracker/issues/262) //! for more information about this limitation. //! diff --git a/src/servers/http/v1/handlers/announce.rs b/src/servers/http/v1/handlers/announce.rs index 215acbad8..e9198f20c 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -12,6 +12,7 @@ use std::sync::Arc; use axum::extract::State; use axum::response::{IntoResponse, Response}; use log::debug; +use torrust_tracker_clock::clock::Time; use torrust_tracker_primitives::announce_event::AnnounceEvent; use torrust_tracker_primitives::{peer, NumberOfBytes}; @@ -25,7 +26,7 @@ use crate::servers::http::v1::requests::announce::{Announce, Compact, Event}; use crate::servers::http::v1::responses::{self}; use crate::servers::http::v1::services::peer_ip_resolver::ClientIpSources; use crate::servers::http::v1::services::{self, peer_ip_resolver}; -use crate::shared::clock::{Current, Time}; +use crate::CurrentClock; /// It handles the `announce` request when the HTTP tracker does not require /// authentication (no PATH `key` parameter required). @@ -134,7 +135,7 @@ fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> peer::Pee peer::Peer { peer_id: announce_request.peer_id, peer_addr: SocketAddr::new(*peer_ip, announce_request.port), - updated: Current::now(), + updated: CurrentClock::now(), uploaded: NumberOfBytes(announce_request.uploaded.unwrap_or(0)), downloaded: NumberOfBytes(announce_request.downloaded.unwrap_or(0)), left: NumberOfBytes(announce_request.left.unwrap_or(0)), diff --git a/src/servers/udp/connection_cookie.rs b/src/servers/udp/connection_cookie.rs index 19e61f14e..49ea6261b 100644 --- a/src/servers/udp/connection_cookie.rs +++ b/src/servers/udp/connection_cookie.rs @@ -70,9 +70,9 @@ use std::net::SocketAddr; use std::panic::Location; use aquatic_udp_protocol::ConnectionId; +use torrust_tracker_clock::time_extent::{Extent, TimeExtent}; use super::error::Error; -use crate::shared::clock::time_extent::{Extent, TimeExtent}; pub type Cookie = [u8; 8]; @@ -133,9 +133,11 @@ mod cookie_builder { use std::hash::{Hash, Hasher}; use std::net::SocketAddr; + use torrust_tracker_clock::time_extent::{Extent, Make, TimeExtent}; + use super::{Cookie, SinceUnixEpochTimeExtent, COOKIE_LIFETIME}; - use crate::shared::clock::time_extent::{DefaultTimeExtentMaker, Extent, Make, TimeExtent}; use crate::shared::crypto::keys::seeds::{Current, Keeper}; + use crate::DefaultTimeExtentMaker; pub(super) fn get_last_time_extent() -> SinceUnixEpochTimeExtent { DefaultTimeExtentMaker::now(&COOKIE_LIFETIME.increment) @@ -162,10 +164,12 @@ mod cookie_builder { mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + use torrust_tracker_clock::clock::stopped::Stopped as _; + use torrust_tracker_clock::clock::{self}; + use torrust_tracker_clock::time_extent::{self, Extent}; + use super::cookie_builder::{self}; use crate::servers::udp::connection_cookie::{check, make, Cookie, COOKIE_LIFETIME}; - use crate::shared::clock::time_extent::{self, Extent}; - use crate::shared::clock::{Stopped, StoppedTime}; // #![feature(const_socketaddr)] // const REMOTE_ADDRESS_IPV4_ZERO: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); @@ -176,6 +180,8 @@ mod tests { const ID_COOKIE_OLD: Cookie = [23, 204, 198, 29, 48, 180, 62, 19]; const ID_COOKIE_NEW: Cookie = [41, 166, 45, 246, 249, 24, 108, 203]; + clock::Stopped::local_set_to_unix_epoch(); + let cookie = make(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)); assert!(cookie == ID_COOKIE_OLD || cookie == ID_COOKIE_NEW); @@ -276,7 +282,7 @@ mod tests { let cookie = make(&remote_address); - Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); + clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); let cookie_next = make(&remote_address); @@ -298,7 +304,7 @@ mod tests { let cookie = make(&remote_address); - Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); + clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); check(&remote_address, &cookie).unwrap(); } @@ -307,9 +313,11 @@ mod tests { fn it_should_be_valid_for_the_last_time_extent() { let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + clock::Stopped::local_set_to_unix_epoch(); + let cookie = make(&remote_address); - Stopped::local_set(&COOKIE_LIFETIME.total().unwrap().unwrap()); + clock::Stopped::local_set(&COOKIE_LIFETIME.total().unwrap().unwrap()); check(&remote_address, &cookie).unwrap(); } @@ -321,7 +329,7 @@ mod tests { let cookie = make(&remote_address); - Stopped::local_set(&COOKIE_LIFETIME.total_next().unwrap().unwrap()); + clock::Stopped::local_set(&COOKIE_LIFETIME.total_next().unwrap().unwrap()); check(&remote_address, &cookie).unwrap(); } diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index 8f6e6d8b4..59aec0ff3 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -318,6 +318,7 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; + use torrust_tracker_clock::clock::Time; use torrust_tracker_configuration::Configuration; use torrust_tracker_primitives::announce_event::AnnounceEvent; use torrust_tracker_primitives::{peer, NumberOfBytes}; @@ -325,7 +326,7 @@ mod tests { use crate::core::services::tracker_factory; use crate::core::Tracker; - use crate::shared::clock::{Current, Time}; + use crate::CurrentClock; fn tracker_configuration() -> Configuration { default_testing_tracker_configuration() @@ -376,7 +377,7 @@ mod tests { let default_peer = peer::Peer { peer_id: peer::Id([255u8; 20]), peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080), - updated: Current::now(), + updated: CurrentClock::now(), uploaded: NumberOfBytes(0), downloaded: NumberOfBytes(0), left: NumberOfBytes(0), diff --git a/src/servers/udp/peer_builder.rs b/src/servers/udp/peer_builder.rs index 8c8fa10a5..f7eb935a0 100644 --- a/src/servers/udp/peer_builder.rs +++ b/src/servers/udp/peer_builder.rs @@ -1,11 +1,12 @@ //! Logic to extract the peer info from the announce request. use std::net::{IpAddr, SocketAddr}; +use torrust_tracker_clock::clock::Time; use torrust_tracker_primitives::announce_event::AnnounceEvent; use torrust_tracker_primitives::{peer, NumberOfBytes}; use super::request::AnnounceWrapper; -use crate::shared::clock::{Current, Time}; +use crate::CurrentClock; /// Extracts the [`peer::Peer`] info from the /// announce request. @@ -20,7 +21,7 @@ pub fn from_request(announce_wrapper: &AnnounceWrapper, peer_ip: &IpAddr) -> pee peer::Peer { peer_id: peer::Id(announce_wrapper.announce_request.peer_id.0), peer_addr: SocketAddr::new(*peer_ip, announce_wrapper.announce_request.port.0), - updated: Current::now(), + updated: CurrentClock::now(), uploaded: NumberOfBytes(announce_wrapper.announce_request.bytes_uploaded.0), downloaded: NumberOfBytes(announce_wrapper.announce_request.bytes_downloaded.0), left: NumberOfBytes(announce_wrapper.announce_request.bytes_left.0), diff --git a/src/shared/clock/mod.rs b/src/shared/clock/mod.rs deleted file mode 100644 index a73878466..000000000 --- a/src/shared/clock/mod.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! Time related functions and types. -//! -//! It's usually a good idea to control where the time comes from -//! in an application so that it can be mocked for testing and it can be -//! controlled in production so we get the intended behavior without -//! relying on the specific time zone for the underlying system. -//! -//! Clocks use the type `DurationSinceUnixEpoch` which is a -//! `std::time::Duration` since the Unix Epoch (timestamp). -//! -//! ```text -//! Local time: lun 2023-03-27 16:12:00 WEST -//! Universal time: lun 2023-03-27 15:12:00 UTC -//! Time zone: Atlantic/Canary (WEST, +0100) -//! Timestamp: 1679929914 -//! Duration: 1679929914.10167426 -//! ``` -//! -//! > **NOTICE**: internally the `Duration` is stores it's main unit as seconds in a `u64` and it will -//! overflow in 584.9 billion years. -//! -//! > **NOTICE**: the timestamp does not depend on the time zone. That gives you -//! the ability to use the clock regardless of the underlying system time zone -//! configuration. See [Unix time Wikipedia entry](https://en.wikipedia.org/wiki/Unix_time). -pub mod static_time; -pub mod time_extent; -pub mod utils; - -use std::num::IntErrorKind; -use std::str::FromStr; -use std::time::Duration; - -use chrono::{DateTime, Utc}; -use torrust_tracker_primitives::DurationSinceUnixEpoch; - -/// Clock types. -#[derive(Debug)] -pub enum Type { - /// Clock that returns the current time. - WorkingClock, - /// Clock that returns always the same fixed time. - StoppedClock, -} - -/// A generic structure that represents a clock. -/// -/// It can be either the working clock (production) or the stopped clock -/// (testing). It implements the `Time` trait, which gives you the current time. -#[derive(Debug)] -pub struct Clock; - -/// The working clock. It returns the current time. -pub type Working = Clock<{ Type::WorkingClock as usize }>; -/// The stopped clock. It returns always the same fixed time. -pub type Stopped = Clock<{ Type::StoppedClock as usize }>; - -/// The current clock. Defined at compilation time. -/// It can be either the working clock (production) or the stopped clock (testing). -#[cfg(not(test))] -pub type Current = Working; - -/// The current clock. Defined at compilation time. -/// It can be either the working clock (production) or the stopped clock (testing). -#[cfg(test)] -pub type Current = Stopped; - -/// Trait for types that can be used as a timestamp clock. -pub trait Time: Sized { - fn now() -> DurationSinceUnixEpoch; -} - -/// Trait for types that can be manipulate the current time in order to -/// get time in the future or in the past after or before a duration of time. -pub trait TimeNow: Time { - #[must_use] - fn add(add_time: &Duration) -> Option { - Self::now().checked_add(*add_time) - } - #[must_use] - fn sub(sub_time: &Duration) -> Option { - Self::now().checked_sub(*sub_time) - } -} - -/// It converts a string in ISO 8601 format to a timestamp. -/// For example, the string `1970-01-01T00:00:00.000Z` which is the Unix Epoch -/// will be converted to a timestamp of 0: `DurationSinceUnixEpoch::ZERO`. -/// -/// # Panics -/// -/// Will panic if the input time cannot be converted to `DateTime::`, internally using the `i64` type. -/// (this will naturally happen in 292.5 billion years) -#[must_use] -pub fn convert_from_iso_8601_to_timestamp(iso_8601: &str) -> DurationSinceUnixEpoch { - convert_from_datetime_utc_to_timestamp(&DateTime::::from_str(iso_8601).unwrap()) -} - -/// It converts a `DateTime::` to a timestamp. -/// For example, the `DateTime::` of the Unix Epoch will be converted to a -/// timestamp of 0: `DurationSinceUnixEpoch::ZERO`. -/// -/// # Panics -/// -/// Will panic if the input time overflows the `u64` type. -/// (this will naturally happen in 584.9 billion years) -#[must_use] -pub fn convert_from_datetime_utc_to_timestamp(datetime_utc: &DateTime) -> DurationSinceUnixEpoch { - DurationSinceUnixEpoch::from_secs(u64::try_from(datetime_utc.timestamp()).expect("Overflow of u64 seconds, very future!")) -} - -/// It converts a timestamp to a `DateTime::`. -/// For example, the timestamp of 0: `DurationSinceUnixEpoch::ZERO` will be -/// converted to the `DateTime::` of the Unix Epoch. -/// -/// # Panics -/// -/// Will panic if the input time overflows the `u64` seconds overflows the `i64` type. -/// (this will naturally happen in 292.5 billion years) -#[must_use] -pub fn convert_from_timestamp_to_datetime_utc(duration: DurationSinceUnixEpoch) -> DateTime { - DateTime::from_timestamp( - i64::try_from(duration.as_secs()).expect("Overflow of i64 seconds, very future!"), - duration.subsec_nanos(), - ) - .unwrap() -} - -#[cfg(test)] -mod tests { - use std::any::TypeId; - - use crate::shared::clock::{Current, Stopped, Time, Working}; - - #[test] - fn it_should_be_the_stopped_clock_as_default_when_testing() { - // We are testing, so we should default to the fixed time. - assert_eq!(TypeId::of::(), TypeId::of::()); - assert_eq!(Stopped::now(), Current::now()); - } - - #[test] - fn it_should_have_different_times() { - assert_ne!(TypeId::of::(), TypeId::of::()); - assert_ne!(Stopped::now(), Working::now()); - } - - mod timestamp { - use chrono::DateTime; - - use crate::shared::clock::{ - convert_from_datetime_utc_to_timestamp, convert_from_iso_8601_to_timestamp, convert_from_timestamp_to_datetime_utc, - DurationSinceUnixEpoch, - }; - - #[test] - fn should_be_converted_to_datetime_utc() { - let timestamp = DurationSinceUnixEpoch::ZERO; - assert_eq!( - convert_from_timestamp_to_datetime_utc(timestamp), - DateTime::from_timestamp(0, 0).unwrap() - ); - } - - #[test] - fn should_be_converted_from_datetime_utc() { - let datetime = DateTime::from_timestamp(0, 0).unwrap(); - assert_eq!( - convert_from_datetime_utc_to_timestamp(&datetime), - DurationSinceUnixEpoch::ZERO - ); - } - - #[test] - fn should_be_converted_from_datetime_utc_in_iso_8601() { - let iso_8601 = "1970-01-01T00:00:00.000Z".to_string(); - assert_eq!(convert_from_iso_8601_to_timestamp(&iso_8601), DurationSinceUnixEpoch::ZERO); - } - } -} - -mod working_clock { - use std::time::SystemTime; - - use super::{DurationSinceUnixEpoch, Time, TimeNow, Working}; - - impl Time for Working { - fn now() -> DurationSinceUnixEpoch { - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap() - } - } - - impl TimeNow for Working {} -} - -/// Trait for types that can be used as a timestamp clock stopped -/// at a given time. -pub trait StoppedTime: TimeNow { - /// It sets the clock to a given time. - fn local_set(unix_time: &DurationSinceUnixEpoch); - - /// It sets the clock to the Unix Epoch. - fn local_set_to_unix_epoch() { - Self::local_set(&DurationSinceUnixEpoch::ZERO); - } - - /// It sets the clock to the time the application started. - fn local_set_to_app_start_time(); - - /// It sets the clock to the current system time. - fn local_set_to_system_time_now(); - - /// It adds a `Duration` to the clock. - /// - /// # Errors - /// - /// Will return `IntErrorKind` if `duration` would overflow the internal `Duration`. - fn local_add(duration: &Duration) -> Result<(), IntErrorKind>; - - /// It subtracts a `Duration` from the clock. - /// # Errors - /// - /// Will return `IntErrorKind` if `duration` would underflow the internal `Duration`. - fn local_sub(duration: &Duration) -> Result<(), IntErrorKind>; - - /// It resets the clock to default fixed time that is application start time (or the unix epoch when testing). - fn local_reset(); -} - -mod stopped_clock { - use std::num::IntErrorKind; - use std::time::Duration; - - use super::{DurationSinceUnixEpoch, Stopped, StoppedTime, Time, TimeNow}; - - impl Time for Stopped { - fn now() -> DurationSinceUnixEpoch { - detail::FIXED_TIME.with(|time| { - return *time.borrow(); - }) - } - } - - impl TimeNow for Stopped {} - - impl StoppedTime for Stopped { - fn local_set(unix_time: &DurationSinceUnixEpoch) { - detail::FIXED_TIME.with(|time| { - *time.borrow_mut() = *unix_time; - }); - } - - fn local_set_to_app_start_time() { - Self::local_set(&detail::get_app_start_time()); - } - - fn local_set_to_system_time_now() { - Self::local_set(&detail::get_app_start_time()); - } - - fn local_add(duration: &Duration) -> Result<(), IntErrorKind> { - detail::FIXED_TIME.with(|time| { - let time_borrowed = *time.borrow(); - *time.borrow_mut() = match time_borrowed.checked_add(*duration) { - Some(time) => time, - None => { - return Err(IntErrorKind::PosOverflow); - } - }; - Ok(()) - }) - } - - fn local_sub(duration: &Duration) -> Result<(), IntErrorKind> { - detail::FIXED_TIME.with(|time| { - let time_borrowed = *time.borrow(); - *time.borrow_mut() = match time_borrowed.checked_sub(*duration) { - Some(time) => time, - None => { - return Err(IntErrorKind::NegOverflow); - } - }; - Ok(()) - }) - } - - fn local_reset() { - Self::local_set(&detail::get_default_fixed_time()); - } - } - - #[cfg(test)] - mod tests { - use std::thread; - use std::time::Duration; - - use crate::shared::clock::{DurationSinceUnixEpoch, Stopped, StoppedTime, Time, TimeNow, Working}; - - #[test] - fn it_should_default_to_zero_when_testing() { - assert_eq!(Stopped::now(), DurationSinceUnixEpoch::ZERO); - } - - #[test] - fn it_should_possible_to_set_the_time() { - // Check we start with ZERO. - assert_eq!(Stopped::now(), Duration::ZERO); - - // Set to Current Time and Check - let timestamp = Working::now(); - Stopped::local_set(×tamp); - assert_eq!(Stopped::now(), timestamp); - - // Elapse the Current Time and Check - Stopped::local_add(×tamp).unwrap(); - assert_eq!(Stopped::now(), timestamp + timestamp); - - // Reset to ZERO and Check - Stopped::local_reset(); - assert_eq!(Stopped::now(), Duration::ZERO); - } - - #[test] - fn it_should_default_to_zero_on_thread_exit() { - assert_eq!(Stopped::now(), Duration::ZERO); - let after5 = Working::add(&Duration::from_secs(5)).unwrap(); - Stopped::local_set(&after5); - assert_eq!(Stopped::now(), after5); - - let t = thread::spawn(move || { - // each thread starts out with the initial value of ZERO - assert_eq!(Stopped::now(), Duration::ZERO); - - // and gets set to the current time. - let timestamp = Working::now(); - Stopped::local_set(×tamp); - assert_eq!(Stopped::now(), timestamp); - }); - - // wait for the thread to complete and bail out on panic - t.join().unwrap(); - - // we retain our original value of current time + 5sec despite the child thread - assert_eq!(Stopped::now(), after5); - - // Reset to ZERO and Check - Stopped::local_reset(); - assert_eq!(Stopped::now(), Duration::ZERO); - } - } - - mod detail { - use std::cell::RefCell; - use std::time::SystemTime; - - use crate::shared::clock::{static_time, DurationSinceUnixEpoch}; - - pub fn get_app_start_time() -> DurationSinceUnixEpoch { - (*static_time::TIME_AT_APP_START) - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - } - - #[cfg(not(test))] - pub fn get_default_fixed_time() -> DurationSinceUnixEpoch { - get_app_start_time() - } - - #[cfg(test)] - pub fn get_default_fixed_time() -> DurationSinceUnixEpoch { - DurationSinceUnixEpoch::ZERO - } - - thread_local!(pub static FIXED_TIME: RefCell = RefCell::new(get_default_fixed_time())); - - #[cfg(test)] - mod tests { - use std::time::Duration; - - use crate::shared::clock::stopped_clock::detail::{get_app_start_time, get_default_fixed_time}; - - #[test] - fn it_should_get_the_zero_start_time_when_testing() { - assert_eq!(get_default_fixed_time(), Duration::ZERO); - } - - #[test] - fn it_should_get_app_start_time() { - const TIME_AT_WRITING_THIS_TEST: Duration = Duration::new(1_662_983_731, 22312); - assert!(get_app_start_time() > TIME_AT_WRITING_THIS_TEST); - } - } - } -} diff --git a/src/shared/clock/utils.rs b/src/shared/clock/utils.rs deleted file mode 100644 index 8b1378917..000000000 --- a/src/shared/clock/utils.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/shared/mod.rs b/src/shared/mod.rs index f016ba913..8c95effe1 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -1,8 +1,6 @@ //! Modules with generic logic used by several modules. //! //! - [`bit_torrent`]: `BitTorrent` protocol related logic. -//! - [`clock`]: Times services. //! - [`crypto`]: Encryption related logic. pub mod bit_torrent; -pub mod clock; pub mod crypto; diff --git a/tests/common/clock.rs b/tests/common/clock.rs new file mode 100644 index 000000000..5d94bb83d --- /dev/null +++ b/tests/common/clock.rs @@ -0,0 +1,16 @@ +use std::time::Duration; + +use torrust_tracker_clock::clock::Time; + +use crate::CurrentClock; + +#[test] +fn it_should_use_stopped_time_for_testing() { + assert_eq!(CurrentClock::dbg_clock_type(), "Stopped".to_owned()); + + let time = CurrentClock::now(); + std::thread::sleep(Duration::from_millis(50)); + let time_2 = CurrentClock::now(); + + assert_eq!(time, time_2); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b57996292..281c1fb9c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,4 @@ +pub mod clock; pub mod fixtures; pub mod http; pub mod udp; diff --git a/tests/integration.rs b/tests/integration.rs index 5d66d9074..8e3d46826 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3,5 +3,18 @@ //! ```text //! cargo test --test integration //! ``` + +use torrust_tracker_clock::clock; mod common; mod servers; + +/// This code needs to be copied into each crate. +/// Working version, for production. +#[cfg(not(test))] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Working; + +/// Stopped version, for testing. +#[cfg(test)] +#[allow(dead_code)] +pub(crate) type CurrentClock = clock::Stopped;