diff --git a/.gitignore b/.gitignore index f4ad7734..ccddebfe 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock *.iml .vscode/ info.log +log/ diff --git a/Cargo.toml b/Cargo.toml index 119b3a98..27cfac13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ compound_policy = [] delete_roller = [] fixed_window_roller = [] size_trigger = [] -time_trigger = [] +time_trigger = ["rand"] json_encoder = ["serde", "serde_json", "chrono", "log-mdc", "log/serde", "thread-id"] pattern_encoder = ["chrono", "log-mdc", "thread-id"] ansi_writer = [] @@ -70,6 +70,7 @@ serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } toml = { version = "0.8", optional = true } parking_lot = { version = "0.12.0", optional = true } +rand = { version = "0.8", optional = true} thiserror = "1.0.15" anyhow = "1.0.28" derivative = "2.2" diff --git a/docs/Configuration.md b/docs/Configuration.md index 65225639..d1ae5563 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -168,13 +168,13 @@ my_rolling_appender: pattern: "logs/test.{}.log" ``` -The new component is the _policy_ field. A policy must have `kind` like most +The new component is the _policy_ field. A policy must have _kind_ field like most other components, the default (and only supported) policy is `kind: compound`. The _trigger_ field is used to dictate when the log file should be rolled. It -supports two types: `size`, and `time`. They both require a `limit` field. +supports two types: `size`, and `time`. -For `size`, the `limit` field is a string which defines the maximum file size +For `size`, it require a _limit_ field. The _limit_ field is a string which defines the maximum file size prior to a rolling of the file. The limit field requires one of the following units in bytes, case does not matter: @@ -192,8 +192,10 @@ trigger: limit: 10 mb ``` -For `time`, the `limit` field is a string which defines the time to roll the -file. The limit field supports the following units(second will be used if the +For `time`, it has three field, _interval_, _modulate_ and _max_random_delay_. + +The _interval_ field is a string which defines the time to roll the +file. The interval field supports the following units(second will be used if the unit is not specified), case does not matter: - second[s] @@ -204,18 +206,26 @@ unit is not specified), case does not matter: - month[s] - year[s] -> Note: The log file will be rolled at the integer time. For example, if the -> `limit` is set to `2 day`, the log file will be rolled at 0:00 every other a -> day, regardless of the time `log4rs` was started or the log file was created. -> This means that the initial log file will be likely rolled before the limit -> is reached. +The _modulate_ field is an optional boolean.It indicates whether the interval should +be adjusted to cause the next rollover to occur on the interval boundary. For example, +if the item is hours, the current hour is 3 am and the interval is 4 then the first +rollover will occur at 4 am and then next ones will occur at 8 am, noon, 4pm, etc. +The default value is false. + +The _max_random_delay_ field is an optional integar.Indicates the maximum number +of seconds to randomly delay a rollover. By default, this is 0 which indicates no +delay. This setting is useful on servers where multiple applications are configured +to rollover log files at the same time and can spread the load of doing so across +time. i.e. ```yml trigger: kind: time - limit: 7 day + interval: 1 day + modulate: false + max_random_delay: 0 ``` The _roller_ field supports two types: delete, and fixed_window. The delete diff --git a/examples/sample_config.yml b/examples/sample_config.yml index 52a95f37..84bb9133 100644 --- a/examples/sample_config.yml +++ b/examples/sample_config.yml @@ -19,7 +19,7 @@ appenders: policy: trigger: kind: time - limit: 1 minute + interval: 1 minute roller: kind: fixed_window pattern: "log/old-rolling_file-{}.log" diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 360cfa6e..9e6d35ee 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -391,7 +391,7 @@ appenders: policy: trigger: kind: time - limit: 2 minutes + interval: 2 minutes roller: kind: delete bar: diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index 0e031e48..344103f9 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -2,9 +2,9 @@ //! //! Requires the `time_trigger` feature. -use chrono::{DateTime, Datelike, Duration, Local, TimeZone, Timelike}; #[cfg(test)] -use chrono::{NaiveDateTime, Utc}; +use chrono::NaiveDateTime; +use chrono::{DateTime, Datelike, Duration, Local, TimeZone, Timelike}; #[cfg(test)] use mock_instant::{SystemTime, UNIX_EPOCH}; #[cfg(feature = "config_parsing")] @@ -18,226 +18,293 @@ use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; -/// Configuration for the time trigger. #[cfg(feature = "config_parsing")] +/// Configuration for the time trigger. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] #[serde(deny_unknown_fields)] -pub(crate) struct TimeTriggerConfig { - #[serde(deserialize_with = "deserialize_limit")] - limit: TimeTriggerLimit, +pub struct TimeTriggerConfig { + interval: TimeTriggerInterval, + #[serde(default)] + modulate: bool, + #[serde(default)] + max_random_delay: u64, } -#[cfg(feature = "config_parsing")] -fn deserialize_limit<'de, D>(d: D) -> Result -where - D: de::Deserializer<'de>, -{ - struct V; - - impl<'de2> de::Visitor<'de2> for V { - type Value = TimeTriggerLimit; - - fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str("a time") - } - - fn visit_u64(self, v: u64) -> Result - where - E: de::Error, - { - Ok(TimeTriggerLimit::Second(v)) - } - - fn visit_i64(self, v: i64) -> Result - where - E: de::Error, - { - if v < 0 { - return Err(E::invalid_value( - de::Unexpected::Signed(v), - &"a non-negative number", - )); - } - - Ok(TimeTriggerLimit::Second(v as u64)) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - let (number, unit) = match v.find(|c: char| !c.is_ascii_digit()) { - Some(n) => (v[..n].trim(), Some(v[n..].trim())), - None => (v.trim(), None), - }; - - let number = match number.parse::() { - Ok(n) => n, - Err(_) => return Err(E::invalid_value(de::Unexpected::Str(number), &"a number")), - }; - - let unit = match unit { - Some(u) => u, - None => return Ok(TimeTriggerLimit::Second(number)), - }; - - let result = if unit.eq_ignore_ascii_case("second") - || unit.eq_ignore_ascii_case("seconds") - { - Some(TimeTriggerLimit::Second(number)) - } else if unit.eq_ignore_ascii_case("minute") || unit.eq_ignore_ascii_case("minutes") { - Some(TimeTriggerLimit::Minute(number)) - } else if unit.eq_ignore_ascii_case("hour") || unit.eq_ignore_ascii_case("hours") { - Some(TimeTriggerLimit::Hour(number)) - } else if unit.eq_ignore_ascii_case("day") || unit.eq_ignore_ascii_case("days") { - Some(TimeTriggerLimit::Day(number)) - } else if unit.eq_ignore_ascii_case("week") || unit.eq_ignore_ascii_case("weeks") { - Some(TimeTriggerLimit::Week(number)) - } else if unit.eq_ignore_ascii_case("month") || unit.eq_ignore_ascii_case("months") { - Some(TimeTriggerLimit::Month(number)) - } else if unit.eq_ignore_ascii_case("year") || unit.eq_ignore_ascii_case("years") { - Some(TimeTriggerLimit::Year(number)) - } else { - return Err(E::invalid_value(de::Unexpected::Str(unit), &"a valid unit")); - }; - - match result { - Some(n) => Ok(n), - None => Err(E::invalid_value(de::Unexpected::Str(v), &"a time")), - } - } - } - - d.deserialize_any(V) +#[cfg(not(feature = "config_parsing"))] +/// Configuration for the time trigger. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct TimeTriggerConfig { + interval: TimeTriggerInterval, + modulate: bool, + max_random_delay: u64, } + /// A trigger which rolls the log once it has passed a certain time. #[derive(Debug)] pub struct TimeTrigger { - limit: TimeTriggerLimit, - time_start: RwLock>, + config: TimeTriggerConfig, + next_roll_time: RwLock>, } /// The TimeTriger have the following units are supported (case insensitive): /// "second", "seconds", "minute", "minutes", "hour", "hours", "day", "days", "week", "weeks", "month", "months", "year", "years". The unit defaults to -/// week if not specified. +/// second if not specified. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub enum TimeTriggerLimit { +pub enum TimeTriggerInterval { /// TimeTriger in second(s). - Second(u64), + Second(i64), /// TimeTriger in minute(s). - Minute(u64), + Minute(i64), /// TimeTriger in hour(s). - Hour(u64), + Hour(i64), /// TimeTriger in day(s). - Day(u64), + Day(i64), /// TimeTriger in week(s). - Week(u64), + Week(i64), /// TimeTriger in month(s). - Month(u64), + Month(i64), /// TimeTriger in year(s). - Year(u64), + Year(i64), } -impl Default for TimeTriggerLimit { +impl Default for TimeTriggerInterval { fn default() -> Self { - TimeTriggerLimit::Week(1) + TimeTriggerInterval::Second(1) + } +} + +#[cfg(feature = "config_parsing")] +impl<'de> serde::Deserialize<'de> for TimeTriggerInterval { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct V; + + impl<'de2> de::Visitor<'de2> for V { + type Value = TimeTriggerInterval; + + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a time") + } + + fn visit_u64(self, v: u64) -> Result + where + E: de::Error, + { + Ok(TimeTriggerInterval::Second(v as i64)) + } + + fn visit_i64(self, v: i64) -> Result + where + E: de::Error, + { + if v < 0 { + return Err(E::invalid_value( + de::Unexpected::Signed(v), + &"a non-negative number", + )); + } + + Ok(TimeTriggerInterval::Second(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let (number, unit) = match v.find(|c: char| !c.is_ascii_digit()) { + Some(n) => (v[..n].trim(), Some(v[n..].trim())), + None => (v.trim(), None), + }; + + let number = match number.parse::() { + Ok(n) => { + if n < 0 { + return Err(E::invalid_value( + de::Unexpected::Signed(n), + &"a non-negative number", + )); + } + n + } + Err(_) => { + return Err(E::invalid_value(de::Unexpected::Str(number), &"a number")) + } + }; + + let unit = match unit { + Some(u) => u, + None => return Ok(TimeTriggerInterval::Second(number)), + }; + + let result = if unit.eq_ignore_ascii_case("second") + || unit.eq_ignore_ascii_case("seconds") + { + Some(TimeTriggerInterval::Second(number)) + } else if unit.eq_ignore_ascii_case("minute") + || unit.eq_ignore_ascii_case("minutes") + { + Some(TimeTriggerInterval::Minute(number)) + } else if unit.eq_ignore_ascii_case("hour") || unit.eq_ignore_ascii_case("hours") { + Some(TimeTriggerInterval::Hour(number)) + } else if unit.eq_ignore_ascii_case("day") || unit.eq_ignore_ascii_case("days") { + Some(TimeTriggerInterval::Day(number)) + } else if unit.eq_ignore_ascii_case("week") || unit.eq_ignore_ascii_case("weeks") { + Some(TimeTriggerInterval::Week(number)) + } else if unit.eq_ignore_ascii_case("month") || unit.eq_ignore_ascii_case("months") + { + Some(TimeTriggerInterval::Month(number)) + } else if unit.eq_ignore_ascii_case("year") || unit.eq_ignore_ascii_case("years") { + Some(TimeTriggerInterval::Year(number)) + } else { + return Err(E::invalid_value(de::Unexpected::Str(unit), &"a valid unit")); + }; + + match result { + Some(n) => Ok(n), + None => Err(E::invalid_value(de::Unexpected::Str(v), &"a time")), + } + } + } + + d.deserialize_any(V) } } impl TimeTrigger { /// Returns a new trigger which rolls the log once it has passed the /// specified time. - pub fn new(limit: TimeTriggerLimit) -> TimeTrigger { + pub fn new(config: TimeTriggerConfig) -> TimeTrigger { #[cfg(test)] - let time = { + let current = { let now: std::time::Duration = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time before Unix epoch"); - let naive_time = - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) - .unwrap(); - Utc.from_utc_datetime(&naive_time) + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap() + .and_local_timezone(Local) + .unwrap() }; #[cfg(not(test))] - let time = Local::now(); - let year = time.year(); - let month = time.month(); - let day = time.day(); - let weekday = time.weekday(); - let hour = time.hour(); - let min = time.minute(); - let sec = time.second(); - - let time_new = match limit { - TimeTriggerLimit::Second(_) => Local - .with_ymd_and_hms(year, month, day, hour, min, sec) - .unwrap(), - TimeTriggerLimit::Minute(_) => Local - .with_ymd_and_hms(year, month, day, hour, min, 0) - .unwrap(), - TimeTriggerLimit::Hour(_) => Local - .with_ymd_and_hms(year, month, day, hour, 0, 0) - .unwrap(), - TimeTriggerLimit::Day(_) => Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(), - TimeTriggerLimit::Week(_) => { - Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap() - - Duration::days(weekday.num_days_from_monday() as i64) - } - TimeTriggerLimit::Month(_) => Local.with_ymd_and_hms(year, month, 1, 0, 0, 0).unwrap(), - TimeTriggerLimit::Year(_) => Local.with_ymd_and_hms(year, 1, 1, 0, 0, 0).unwrap(), + let current = Local::now(); + + let next_time = TimeTrigger::get_next_time(current, config.interval, config.modulate); + + let next_roll_time = if config.max_random_delay > 0 { + let random_delay = rand::random::() % config.max_random_delay; + next_time + Duration::seconds(random_delay as i64) + } else { + next_time }; TimeTrigger { - limit, - time_start: RwLock::new(time_new), + config, + next_roll_time: RwLock::new(next_roll_time), + } + } + + fn get_next_time( + current: DateTime, + interval: TimeTriggerInterval, + modulate: bool, + ) -> DateTime { + let year = current.year(); + if let TimeTriggerInterval::Year(n) = interval { + let n = n as i32; + let increment = if modulate { n - year % n } else { n }; + let year_new = year + increment; + return Local.with_ymd_and_hms(year_new, 1, 1, 0, 0, 0).unwrap(); + } + + if let TimeTriggerInterval::Month(n) = interval { + let month0 = current.month0(); + let n = n as u32; + let increment = if modulate { n - month0 % n } else { n }; + let num_months = (year as u32) * 12 + month0; + let num_months_new = num_months + increment; + let year_new = (num_months_new / 12) as i32; + let month_new = (num_months_new) % 12 + 1; + return Local + .with_ymd_and_hms(year_new, month_new, 1, 0, 0, 0) + .unwrap(); + } + + let month = current.month(); + let day = current.day(); + if let TimeTriggerInterval::Week(n) = interval { + let week0 = current.iso_week().week0(); + let weekday = current.weekday().num_days_from_sunday() as i64; + // let time = NaiveDate::from_isoywd_opt(year, week0, Weekday::Mon).unwrap().and_hms_opt(0, 0, 0).unwrap().and_local_timezone(Local).unwrap(); + let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); + let increment = if modulate { n - (week0 as i64) % n } else { n }; + return time + Duration::weeks(increment) - Duration::days(weekday); // Set Sunday as the first day of the week + } + + if let TimeTriggerInterval::Day(n) = interval { + let ordinal0 = current.ordinal0(); + let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); + let increment = if modulate { + n - (ordinal0 as i64) % n + } else { + n + }; + return time + Duration::days(increment); + } + + let hour = current.hour(); + if let TimeTriggerInterval::Hour(n) = interval { + let time = Local + .with_ymd_and_hms(year, month, day, hour, 0, 0) + .unwrap(); + let increment = if modulate { n - (hour as i64) % n } else { n }; + return time + Duration::hours(increment); + } + + let min = current.minute(); + if let TimeTriggerInterval::Minute(n) = interval { + let time = Local + .with_ymd_and_hms(year, month, day, hour, min, 0) + .unwrap(); + let increment = if modulate { n - (min as i64) % n } else { n }; + return time + Duration::minutes(increment); + } + + let sec = current.second(); + if let TimeTriggerInterval::Second(n) = interval { + let time = Local + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap(); + let increment = if modulate { n - (sec as i64) % n } else { n }; + return time + Duration::seconds(increment); } + panic!("Should not reach here!"); } } impl Trigger for TimeTrigger { fn trigger(&self, _file: &LogFile) -> anyhow::Result { #[cfg(test)] - let time_now = { + let current = { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time before Unix epoch"); - let naive_time = - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) - .unwrap(); - Utc.from_utc_datetime(&naive_time) + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap() + .and_local_timezone(Local) + .unwrap() }; #[cfg(not(test))] - let time_now: DateTime = Local::now(); - let mut time_start = self.time_start.write().unwrap(); - let duration = time_now.signed_duration_since(*time_start); - let is_triger = match self.limit { - TimeTriggerLimit::Second(num) => duration.num_seconds() as u64 >= num, - TimeTriggerLimit::Minute(num) => duration.num_minutes() as u64 >= num, - TimeTriggerLimit::Hour(num) => duration.num_hours() as u64 >= num, - TimeTriggerLimit::Day(num) => duration.num_days() as u64 >= num, - TimeTriggerLimit::Week(num) => duration.num_weeks() as u64 >= num, - TimeTriggerLimit::Month(num) => { - let num_years_start = time_start.year() as u64; - let num_months_start = num_years_start * 12 + time_start.month() as u64; - let num_years_now = time_now.year() as u64; - let num_months_now = num_years_now * 12 + time_now.month() as u64; - - num_months_now - num_months_start >= num - } - TimeTriggerLimit::Year(num) => { - let num_years_start = time_start.year() as u64; - let num_years_now = time_now.year() as u64; - - num_years_now - num_years_start >= num - } - }; + let current: DateTime = Local::now(); + let mut next_roll_time = self.next_roll_time.write().unwrap(); + let is_triger = current >= *next_roll_time; if is_triger { - let tmp = TimeTrigger::new(self.limit); - let time_new = tmp.time_start.read().unwrap(); - *time_start = *time_new; + let tmp = TimeTrigger::new(self.config); + let time_new = tmp.next_roll_time.read().unwrap(); + *next_roll_time = *time_new; } Ok(is_triger) } @@ -254,10 +321,10 @@ impl Trigger for TimeTrigger { /// ```yaml /// kind: time /// -/// # The time limit in seconds. The following units are supported (case insensitive): +/// # The time interval. The following units are supported (case insensitive): /// # "second(s)", "minute(s)", "hour(s)", "day(s)", "week(s)", "month(s)", "year(s)". The unit defaults to /// # second if not specified. -/// limit: 7 day +/// interval: 7 day /// ``` #[cfg(feature = "config_parsing")] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] @@ -274,7 +341,7 @@ impl Deserialize for TimeTriggerDeserializer { config: TimeTriggerConfig, _: &Deserializers, ) -> anyhow::Result> { - Ok(Box::new(TimeTrigger::new(config.limit))) + Ok(Box::new(TimeTrigger::new(config))) } } @@ -284,7 +351,11 @@ mod test { use mock_instant::MockClock; use std::time::Duration; - fn trigger_with_time(limit: TimeTriggerLimit, millis: u64) -> (bool, bool) { + fn trigger_with_time_and_modulate( + interval: TimeTriggerInterval, + modulate: bool, + millis: u64, + ) -> (bool, bool) { let file = tempfile::tempdir().unwrap(); let logfile = LogFile { writer: &mut None, @@ -292,7 +363,13 @@ mod test { len: 0, }; - let trigger = TimeTrigger::new(limit); + let config = TimeTriggerConfig { + interval, + modulate, + max_random_delay: 0, + }; + + let trigger = TimeTrigger::new(config); MockClock::advance_system_time(Duration::from_millis(millis / 2)); let result1 = trigger.trigger(&logfile).unwrap(); @@ -305,7 +382,6 @@ mod test { #[test] fn trigger() { - // Second let second_in_milli = 1000; let minute_in_milli = second_in_milli * 60; let hour_in_milli = minute_in_milli * 60; @@ -313,53 +389,61 @@ mod test { let week_in_milli = day_in_milli * 7; let month_in_milli = day_in_milli * 31; let year_in_milli = day_in_milli * 365; + let modulate = false; + //Second MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Second(1), second_in_milli), + trigger_with_time_and_modulate( + TimeTriggerInterval::Second(1), + modulate, + second_in_milli + ), (false, true) ); // Minute MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Minute(1), minute_in_milli), + trigger_with_time_and_modulate( + TimeTriggerInterval::Minute(1), + modulate, + minute_in_milli + ), (false, true) ); // Hour MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Hour(1), hour_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Hour(1), modulate, hour_in_milli), (false, true) ); // Day MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Day(1), day_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Day(1), modulate, day_in_milli), (false, true) ); // Week - MockClock::set_system_time(Duration::from_millis(4 * day_in_milli)); // Monday + MockClock::set_system_time(Duration::from_millis(3 * day_in_milli)); // Sunday assert_eq!( - trigger_with_time(TimeTriggerLimit::Week(1), week_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Week(1), modulate, week_in_milli), (false, true) ); // Month MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Month(1), month_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Month(1), modulate, month_in_milli), (false, true) ); // Year MockClock::set_system_time(Duration::from_millis(0)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Year(1), year_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Year(1), modulate, year_in_milli), (false, true) ); } #[test] fn trigger2() { - // This test may fail if it runs in the following timezones (UTC+12, UTC-12) - // Second let second_in_milli = 1000; let minute_in_milli = second_in_milli * 60; let hour_in_milli = minute_in_milli * 60; @@ -367,45 +451,55 @@ mod test { let week_in_milli = day_in_milli * 7; let month_in_milli = day_in_milli * 31; let year_in_milli = day_in_milli * 365; + let modulate = false; + // Second MockClock::set_system_time(Duration::from_millis(second_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Second(1), second_in_milli), + trigger_with_time_and_modulate( + TimeTriggerInterval::Second(1), + modulate, + second_in_milli + ), (true, false) ); // Minute MockClock::set_system_time(Duration::from_millis(minute_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Minute(1), minute_in_milli), + trigger_with_time_and_modulate( + TimeTriggerInterval::Minute(1), + modulate, + minute_in_milli + ), (true, false) ); // Hour MockClock::set_system_time(Duration::from_millis(hour_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Hour(1), hour_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Hour(1), modulate, hour_in_milli), (true, false) ); // Day MockClock::set_system_time(Duration::from_millis(day_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Day(1), day_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Day(1), modulate, day_in_milli), (true, false) ); // Week - MockClock::set_system_time(Duration::from_millis(4 * day_in_milli + week_in_milli / 2)); // Monday + MockClock::set_system_time(Duration::from_millis(3 * day_in_milli + week_in_milli / 2)); // Sunday assert_eq!( - trigger_with_time(TimeTriggerLimit::Week(1), week_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Week(1), modulate, week_in_milli), (true, false) ); // Month MockClock::set_system_time(Duration::from_millis(month_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Month(1), month_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Month(1), modulate, month_in_milli), (true, false) ); // Year MockClock::set_system_time(Duration::from_millis(year_in_milli / 2)); assert_eq!( - trigger_with_time(TimeTriggerLimit::Year(1), year_in_milli), + trigger_with_time_and_modulate(TimeTriggerInterval::Year(1), modulate, year_in_milli), (true, false) ); } @@ -414,103 +508,108 @@ mod test { #[cfg(feature = "yaml_format")] fn test_serde() { // str none - let limit = format!("limit: abc",); - let error = ::serde_yaml::from_str::(&limit); + let interval = format!("abc",); + let error = ::serde_yaml::from_str::(&interval); assert!(error.is_err()); // none - let limit = format!("limit: ",); - let error = ::serde_yaml::from_str::(&limit); + let interval = format!("",); + let error = ::serde_yaml::from_str::(&interval); assert!(error.is_err()); // bad unit - let limit = format!("limit: 5 das",); - let error = ::serde_yaml::from_str::(&limit); + let interval = format!("5 das",); + let error = ::serde_yaml::from_str::(&interval); assert!(error.is_err()); // i64 - let limit = format!("limit: -1",); - let error = ::serde_yaml::from_str::(&limit); + let interval = format!("-1",); + let error = ::serde_yaml::from_str::(&interval); assert!(error.is_err()); // u64 - let limit = format!("limit: 1",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + let interval = format!("1",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Second(1)); // str second - let limit = format!("limit: 1 second",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + let interval = format!("1 second",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Second(1)); - let limit = format!("limit: 1 seconds",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + let interval = format!("1 seconds",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Second(1)); // str minute - let limit = format!("limit: 1 minute",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Minute(1)); + let interval = format!("1 minute",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Minute(1)); - let limit = format!("limit: 1 minutes",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Minute(1)); + let interval = format!("1 minutes",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Minute(1)); // str hour - let limit = format!("limit: 1 hour",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Hour(1)); + let interval = format!("1 hour",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Hour(1)); - let limit = format!("limit: 1 hours",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Hour(1)); + let interval = format!("1 hours",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Hour(1)); // str day - let limit = format!("limit: 1 day",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Day(1)); + let interval = format!("1 day",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Day(1)); - let limit = format!("limit: 1 days",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Day(1)); + let interval = format!("1 days",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Day(1)); // str week - let limit = format!("limit: 1 week",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Week(1)); + let interval = format!("1 week",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Week(1)); - let limit = format!("limit: 1 weeks",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Week(1)); + let interval = format!("1 weeks",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Week(1)); // str month - let limit = format!("limit: 1 month",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Month(1)); + let interval = format!("1 month",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Month(1)); - let limit = format!("limit: 1 months",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Month(1)); + let interval = format!("1 months",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Month(1)); // str year - let limit = format!("limit: 1 year",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Year(1)); + let interval = format!("1 year",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Year(1)); - let limit = format!("limit: 1 years",); - let limit = ::serde_yaml::from_str::(&limit).unwrap(); - assert_eq!(limit.limit, TimeTriggerLimit::Year(1)); + let interval = format!("1 years",); + let interval = ::serde_yaml::from_str::(&interval).unwrap(); + assert_eq!(interval, TimeTriggerInterval::Year(1)); } #[test] fn test_time_trigger_limit_default() { - let limit = TimeTriggerLimit::default(); - assert_eq!(limit, TimeTriggerLimit::Week(1)); + let interval = TimeTriggerInterval::default(); + assert_eq!(interval, TimeTriggerInterval::Second(1)); } #[test] fn pre_process() { - let trigger = TimeTrigger::new(TimeTriggerLimit::Minute(2)); + let config = TimeTriggerConfig { + interval: TimeTriggerInterval::Minute(2), + modulate: true, + max_random_delay: 0, + }; + let trigger = TimeTrigger::new(config); assert!(trigger.is_pre_process()); } }