From c3d7f42d63dfcd1d27b83a179445a8a7cdad9e1e Mon Sep 17 00:00:00 2001 From: bconn98 Date: Thu, 29 Feb 2024 23:44:48 -0500 Subject: [PATCH 1/4] chore: clippy fixes + msrv bump --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 6 +++--- README.md | 4 ++-- src/append/rolling_file/policy/compound/mod.rs | 2 +- src/config/raw.rs | 8 +++----- src/config/runtime.rs | 2 +- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8219b46..1f58427f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust_versions: ["stable", "1.69"] + rust_versions: ["stable", "1.70"] os: [ubuntu-latest, windows-latest] steps: - name: Checkout the source code @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - rust_versions: ["stable", "1.69"] + rust_versions: ["stable", "1.70"] steps: - name: Checkout the source code uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index ff9c9fd3..5826a3b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/estk/log4rs" readme = "README.md" keywords = ["log", "logger", "logging", "log4"] edition = "2018" -rust-version = "1.69" +rust-version = "1.70" [features] default = ["all_components", "config_parsing", "yaml_format"] @@ -62,13 +62,13 @@ fnv = "1.0" humantime = { version = "2.1", optional = true } log = { version = "0.4.20", features = ["std"] } log-mdc = { version = "0.1", optional = true } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { version = "1.0.196", optional = true, features = ["derive"] } serde-value = { version = "0.7", optional = true } thread-id = { version = "4", optional = true } typemap-ors = { version = "1.0.0", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } -toml = { version = "0.8", optional = true } +toml = { version = "0.8.10", optional = true } parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.8", optional = true} thiserror = "1.0.15" diff --git a/README.md b/README.md index 056d1e4d..b3c36ff2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/log4rs.svg)](https://crates.io/crates/log4rs) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) ![CI](https://github.com/estk/log4rs/workflows/CI/badge.svg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.69+-green.svg)](https://github.com/estk/log4rs#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.70+-green.svg)](https://github.com/estk/log4rs#rust-version-requirements) log4rs is a highly configurable logging framework modeled after Java's Logback and log4j libraries. @@ -71,7 +71,7 @@ fn main() { ## Rust Version Requirements -1.69 +1.70 ## Building for Dev diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 484af19c..3e14ae06 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -2,7 +2,7 @@ //! //! Requires the `compound_policy` feature. #[cfg(feature = "config_parsing")] -use serde::{self, de}; +use serde::de; #[cfg(feature = "config_parsing")] use serde_value::Value; #[cfg(feature = "config_parsing")] diff --git a/src/config/raw.rs b/src/config/raw.rs index a092d56b..9868b495 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -89,9 +89,7 @@ //! ``` #![allow(deprecated)] -use std::{ - borrow::ToOwned, collections::HashMap, fmt, marker::PhantomData, sync::Arc, time::Duration, -}; +use std::{collections::HashMap, fmt, marker::PhantomData, sync::Arc, time::Duration}; use anyhow::anyhow; use derivative::Derivative; @@ -288,9 +286,9 @@ impl Deserializers { } /// Deserializes a value of a specific type and kind. - pub fn deserialize(&self, kind: &str, config: Value) -> anyhow::Result> + pub fn deserialize(&self, kind: &str, config: Value) -> anyhow::Result> where - T: Deserializable, + T: Deserializable + ?Sized, { match self.0.get::>().and_then(|m| m.get(kind)) { Some(b) => b.deserialize(config, self), diff --git a/src/config/runtime.rs b/src/config/runtime.rs index 6b80019f..3d04947e 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -1,7 +1,7 @@ //! log4rs configuration use log::LevelFilter; -use std::{collections::HashSet, iter::IntoIterator}; +use std::collections::HashSet; use thiserror::Error; use crate::{append::Append, filter::Filter}; From 4486edd986f843bfe237e59425df7beb5f51d07f Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 19:08:56 -0500 Subject: [PATCH 2/4] chore: backout msrv bump, knock toml down --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 4 ++-- README.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1f58427f..b8219b46 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust_versions: ["stable", "1.70"] + rust_versions: ["stable", "1.69"] os: [ubuntu-latest, windows-latest] steps: - name: Checkout the source code @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - rust_versions: ["stable", "1.70"] + rust_versions: ["stable", "1.69"] steps: - name: Checkout the source code uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index 5826a3b9..2f44bb2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/estk/log4rs" readme = "README.md" keywords = ["log", "logger", "logging", "log4"] edition = "2018" -rust-version = "1.70" +rust-version = "1.69" [features] default = ["all_components", "config_parsing", "yaml_format"] @@ -68,7 +68,7 @@ thread-id = { version = "4", optional = true } typemap-ors = { version = "1.0.0", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } -toml = { version = "0.8.10", optional = true } +toml = { version = "0.8.9", optional = true } parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.8", optional = true} thiserror = "1.0.15" diff --git a/README.md b/README.md index b3c36ff2..056d1e4d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/log4rs.svg)](https://crates.io/crates/log4rs) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) ![CI](https://github.com/estk/log4rs/workflows/CI/badge.svg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.70+-green.svg)](https://github.com/estk/log4rs#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.69+-green.svg)](https://github.com/estk/log4rs#rust-version-requirements) log4rs is a highly configurable logging framework modeled after Java's Logback and log4j libraries. @@ -71,7 +71,7 @@ fn main() { ## Rust Version Requirements -1.70 +1.69 ## Building for Dev From 049e2cc5a6ebd86fc1278fa3a93d25a05b7b9bb0 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 20:40:04 -0500 Subject: [PATCH 3/4] limit toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2f44bb2e..8f7134ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ thread-id = { version = "4", optional = true } typemap-ors = { version = "1.0.0", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } -toml = { version = "0.8.9", optional = true } +toml = { version = "<0.8.10", optional = true } parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.8", optional = true} thiserror = "1.0.15" From 7ae84098ba2cbb9bca01831a9279f4a490a96450 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 20:50:22 -0500 Subject: [PATCH 4/4] chore: add new tests --- Cargo.toml | 1 + .../rolling_file/policy/compound/mod.rs | 98 ++++++ .../policy/compound/roll/delete.rs | 21 ++ .../policy/compound/roll/fixed_window.rs | 61 +++- .../policy/compound/trigger/size.rs | 284 +++++++++++++++++- .../policy/compound/trigger/time.rs | 192 +++++++++--- 6 files changed, 608 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f7134ca..9f648269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ streaming-stats = "0.2.3" humantime = "2.1" tempfile = "3.8" mock_instant = "0.3" +serde_test = "1.0.176" [[example]] name = "json_logger" diff --git a/src/append/rolling_file/policy/compound/mod.rs b/src/append/rolling_file/policy/compound/mod.rs index 3e14ae06..83ac48cc 100644 --- a/src/append/rolling_file/policy/compound/mod.rs +++ b/src/append/rolling_file/policy/compound/mod.rs @@ -159,3 +159,101 @@ impl Deserialize for CompoundPolicyDeserializer { Ok(Box::new(CompoundPolicy::new(trigger, roller))) } } + +#[cfg(test)] +mod test { + use self::{roll::delete::DeleteRoller, trigger::size::SizeTrigger}; + + use super::*; + use tempfile::NamedTempFile; + + #[cfg(feature = "config_parsing")] + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; + + fn create_policy() -> CompoundPolicy { + let trigger = SizeTrigger::new(1024); + let roller = DeleteRoller::new(); + CompoundPolicy::new(Box::new(trigger), Box::new(roller)) + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_trigger_deser() { + let mut cfg = vec![ + Token::Struct { + name: "Trigger", + len: 2, + }, + Token::Str("kind"), + Token::Str("size"), + Token::Str("limit"), + Token::U64(1024), + Token::StructEnd, + ]; + + assert_de_tokens( + &Trigger { + kind: "size".to_owned(), + config: Value::Map({ + let mut map = BTreeMap::new(); + map.insert(Value::String("limit".to_owned()), Value::U64(1024)); + map + }), + }, + &cfg, + ); + + // Intentionally break the config + cfg[1] = Token::Str("knd"); + assert_de_tokens_error::(&cfg, "missing field `kind`"); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_roller_deser() { + let mut cfg = vec![ + Token::Struct { + name: "Roller", + len: 1, + }, + Token::Str("kind"), + Token::Str("delete"), + Token::StructEnd, + ]; + + assert_de_tokens( + &Roller { + kind: "delete".to_owned(), + config: Value::Map(BTreeMap::new()), + }, + &cfg, + ); + + // Intentionally break the config + cfg[1] = Token::Str("knd"); + assert_de_tokens_error::(&cfg, "missing field `kind`"); + } + + #[test] + fn test_pre_process() { + let policy = create_policy(); + assert!(!policy.is_pre_process()); + } + + #[test] + fn test_process() { + let policy = create_policy(); + // Don't roll then roll + let file_sizes = vec![0, 2048]; + let tmp_file = NamedTempFile::new().unwrap(); + + for file_size in file_sizes { + let mut logfile = LogFile { + writer: &mut None, + path: tmp_file.as_ref(), + len: file_size, + }; + assert!(policy.process(&mut logfile).is_ok()); + } + } +} diff --git a/src/append/rolling_file/policy/compound/roll/delete.rs b/src/append/rolling_file/policy/compound/roll/delete.rs index 3c085273..c2ef5487 100644 --- a/src/append/rolling_file/policy/compound/roll/delete.rs +++ b/src/append/rolling_file/policy/compound/roll/delete.rs @@ -59,3 +59,24 @@ impl Deserialize for DeleteRollerDeserializer { Ok(Box::::default()) } } + +#[cfg(test)] +mod test { + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_roll() { + let roller = DeleteRoller::new(); + + let tmp_file = NamedTempFile::new().unwrap(); + let tmp_file = tmp_file.into_temp_path().keep().unwrap(); + // File exists, should be ok + let res = roller.roll(&tmp_file); + assert!(res.is_ok()); + + // File doesn't exist, should err + let res = roller.roll(&tmp_file); + assert!(res.is_err()); + } +} diff --git a/src/append/rolling_file/policy/compound/roll/fixed_window.rs b/src/append/rolling_file/policy/compound/roll/fixed_window.rs index 340ddd05..5f67459d 100644 --- a/src/append/rolling_file/policy/compound/roll/fixed_window.rs +++ b/src/append/rolling_file/policy/compound/roll/fixed_window.rs @@ -178,6 +178,10 @@ fn make_temp_file_name

(file: P) -> PathBuf where P: AsRef, { + #[cfg(test)] + let mut n = 0; + + #[cfg(not(test))] let mut n = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .unwrap_or_else(|_| std::time::Duration::from_secs(0)) @@ -354,7 +358,7 @@ mod test { fn wait_for_roller(_roller: &FixedWindowRoller) {} #[test] - fn rotation() { + fn test_test_rotation() { let dir = tempfile::tempdir().unwrap(); let base = dir.path().to_str().unwrap(); @@ -414,7 +418,7 @@ mod test { } #[test] - fn rotation_no_trivial_base() { + fn test_rotation_no_trivial_base() { let dir = tempfile::tempdir().unwrap(); let base = 3; let fname = "foo.log"; @@ -459,7 +463,7 @@ mod test { } #[test] - fn create_archive_unvaried() { + fn test_create_archive_unvaried() { let dir = tempfile::tempdir().unwrap(); let base = dir.path().join("log").join("archive"); @@ -487,7 +491,7 @@ mod test { } #[test] - fn create_archive_varied() { + fn test_create_archive_varied() { let dir = tempfile::tempdir().unwrap(); let base = dir.path().join("log").join("archive"); @@ -516,7 +520,7 @@ mod test { #[test] #[cfg_attr(feature = "gzip", ignore)] - fn unsupported_gzip() { + fn test_unsupported_gzip() { let dir = tempfile::tempdir().unwrap(); let pattern = dir.path().join("{}.gz"); @@ -529,7 +533,7 @@ mod test { #[cfg_attr(not(feature = "gzip"), ignore)] // or should we force windows user to install gunzip #[cfg(not(windows))] - fn supported_gzip() { + fn test_supported_gzip() { use std::process::Command; let dir = tempfile::tempdir().unwrap(); @@ -561,7 +565,7 @@ mod test { } #[test] - fn roll_with_env_var() { + fn test_roll_with_env_var() { std::env::set_var("LOG_DIR", "test_log_dir"); let fcontent = b"file1"; let dir = tempfile::tempdir().unwrap(); @@ -596,4 +600,47 @@ mod test { //Check the new rolled file has the same contents as the old one assert_eq!(contents, fcontent); } + + #[test] + fn test_invalid_pattern() { + let dir = tempfile::tempdir().unwrap(); + + let base = dir.path().to_str().unwrap(); + let roller = FixedWindowRoller::builder().build(&format!("{}/foo.log", base), 2); + + assert_eq!( + format!("{}", roller.unwrap_err()), + "pattern does not contain `{}`" + ); + } + + #[test] + fn test_rotate_to_del() { + let dir = tempfile::tempdir().unwrap(); + + let base = dir.path().to_str().unwrap(); + let roller = FixedWindowRoller::builder() + .build(&format!("{}/foo.log.{{}}", base), 0) + .unwrap(); + + let file = dir.path().join("foo.log"); + File::create(&file).unwrap().write_all(b"file1").unwrap(); + + roller.roll(&file).unwrap(); + wait_for_roller(&roller); + assert!(!file.exists()); + assert!(!dir.path().join("foo.log.0").exists()); + } + + #[test] + #[cfg(feature = "background_rotation")] + fn test_temp_file_exists() { + let dir = tempfile::tempdir().unwrap(); + let file = dir.path().join("foo.log"); + let temp_file = dir.path().join("foo.0"); + assert_eq!(make_temp_file_name(file.clone()), dir.path().join("foo.0")); + + let _ = File::create(temp_file).unwrap(); + assert_eq!(make_temp_file_name(file), dir.path().join("foo.1")); + } } diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index d399cb2f..46192c1f 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -158,9 +158,291 @@ impl Deserialize for SizeTriggerDeserializer { mod test { use super::*; + #[cfg(feature = "config_parsing")] + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; + + #[cfg(feature = "config_parsing")] + static BYTE_MULTIPLIER: u64 = 1024; + #[test] - fn pre_process() { + fn test_pre_process() { let trigger = SizeTrigger::new(2048); assert!(!trigger.is_pre_process()); } + + #[test] + fn test_trigger() { + let file = tempfile::tempdir().unwrap(); + let mut logfile = LogFile { + writer: &mut None, + path: file.path(), + len: 0, + }; + + let trigger_bytes = 5; + let trigger = SizeTrigger::new(trigger_bytes); + + // Logfile size is < trigger size, should never trigger + for size in 0..trigger_bytes { + logfile.len = size; + assert!(!trigger.trigger(&logfile).unwrap()); + } + + // Logfile size is == trigger size, should not trigger + logfile.len = trigger_bytes; + assert!(!trigger.trigger(&logfile).unwrap()); + + // Logfile size is >= trigger size, should trigger + logfile.len = trigger_bytes + 1; + assert!(trigger.trigger(&logfile).unwrap()); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_u64_deserialize() { + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER, + }; + assert_de_tokens( + &trigger, + &[ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::U64(1024), + Token::StructEnd, + ], + ); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_i64_deserialize() { + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER, + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::I64(1024), + Token::StructEnd, + ]; + + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::I64(-1024); + assert_de_tokens_error::( + &cfg, + "invalid value: integer `-1024`, expected a non-negative number", + ); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_float_deserialize() { + assert_de_tokens_error::( + &[ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::F32(2.0), + Token::StructEnd, + ], + "invalid type: floating point `2.0`, expected a size", + ); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_str_deserialize() { + // Test no unit (aka value in Bytes) + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER, + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1024"), + Token::StructEnd, + ]; + + assert_de_tokens(&trigger, &cfg); + + // Test not an unsigned number + cfg[2] = Token::Str("-1024"); + assert_de_tokens_error::( + &cfg, + "invalid value: string \"\", expected a number", + ); + + // Test not a valid unit + cfg[2] = Token::Str("1024 pb"); + assert_de_tokens_error::( + &cfg, + "invalid value: string \"pb\", expected a valid unit", + ); + + // u64::MAX which will overflow when converted to bytes + cfg[2] = Token::Str("18446744073709551615 kb"); + assert_de_tokens_error::( + &cfg, + "invalid value: string \"18446744073709551615 kb\", expected a byte size", + ); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_byte_deserialize() { + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER, + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1024b"), + Token::StructEnd, + ]; + + // Test spacing & b vs B + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1024 B"); + assert_de_tokens(&trigger, &cfg); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_kilobyte_deserialize() { + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER, + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1 kb"), + Token::StructEnd, + ]; + + // Test kb unit + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 KB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 kB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 Kb"); + assert_de_tokens(&trigger, &cfg); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_megabyte_deserialize() { + // Test mb unit + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER.pow(2), + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1 mb"), + Token::StructEnd, + ]; + + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 MB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 mB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 Mb"); + assert_de_tokens(&trigger, &cfg); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_gigabyte_deserialize() { + // Test gb unit + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER.pow(3), + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1 gb"), + Token::StructEnd, + ]; + + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 GB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 gB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 Gb"); + assert_de_tokens(&trigger, &cfg); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_terabyte_deserialize() { + // Test tb unit + let trigger = SizeTriggerConfig { + limit: BYTE_MULTIPLIER.pow(4), + }; + + let mut cfg = vec![ + Token::Struct { + name: "SizeTriggerConfig", + len: 1, + }, + Token::Str("limit"), + Token::Str("1 tb"), + Token::StructEnd, + ]; + + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 TB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 tB"); + assert_de_tokens(&trigger, &cfg); + + cfg[2] = Token::Str("1 Tb"); + assert_de_tokens(&trigger, &cfg); + } } diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index 4568a524..92e1590c 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -343,6 +343,9 @@ mod test { use mock_instant::MockClock; use std::time::Duration; + #[cfg(feature = "config_parsing")] + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; + fn trigger_with_time_and_modulate( interval: TimeTriggerInterval, modulate: bool, @@ -373,7 +376,7 @@ mod test { } #[test] - fn trigger() { + fn test_trigger() { let second_in_milli = 1000; let minute_in_milli = second_in_milli * 60; let hour_in_milli = minute_in_milli * 60; @@ -430,50 +433,128 @@ mod test { } #[test] - #[cfg(feature = "yaml_format")] - fn test_serde() { - let test_error = vec![ - "abc", // // str none none - "", // none - "5 das", // bad unit - "-1", // inegative integar - "2.0", //flaot + #[cfg(feature = "config_parsing")] + fn test_defaults_deserialize() { + let trigger = TimeTriggerConfig { + interval: TimeTriggerInterval::Second(1), + modulate: false, + max_random_delay: 0, + }; + + assert_de_tokens( + &trigger, + &[ + Token::Struct { + name: "TimeTriggerConfig", + len: 3, + }, + Token::Str("interval"), + Token::I64(1), + Token::Str("modulate"), + Token::Bool(false), + Token::Str("max_random_delay"), + Token::U64(0), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &trigger, + &[ + Token::Struct { + name: "TimeTriggerConfig", + len: 1, + }, + Token::Str("interval"), + Token::Str("1"), + Token::StructEnd, + ], + ); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_interval_deser_errors() { + let mut cfg = vec![ + Token::Struct { + name: "TimeTriggerConfig", + len: 1, + }, + Token::Str("interval"), + Token::Str("abc"), + Token::StructEnd, + ]; + + // Can't test with a STR "-1" because the negative sign parses as a + // non-ascii and the whole value goes into unit + let tests = vec![ + ( + Token::Str("abc"), + "invalid value: string \"\", expected a number", + ), + ( + Token::Str(""), + "invalid value: string \"\", expected a number", + ), + ( + Token::Str("5 das"), + "invalid value: string \"das\", expected a valid unit", + ), + ( + Token::I64(-1), + "invalid value: integer `-1`, expected a non-negative number", + ), + ( + Token::F32(2.0), + "invalid type: floating point `2.0`, expected a time", + ), ]; - for interval in test_error.iter() { - let error = ::serde_yaml::from_str::(&interval); - assert!(error.is_err()); + for (cfg_val, err_msg) in tests { + cfg[2] = cfg_val; + assert_de_tokens_error::(&cfg, err_msg); } + } - let test_ok = vec![ - // u64 - ("1", TimeTriggerInterval::Second(1)), - // str second - ("1 second", TimeTriggerInterval::Second(1)), - ("1 seconds", TimeTriggerInterval::Second(1)), - // str minute - ("1 minute", TimeTriggerInterval::Minute(1)), - ("1 minutes", TimeTriggerInterval::Minute(1)), - // str hour - ("1 hour", TimeTriggerInterval::Hour(1)), - ("1 hours", TimeTriggerInterval::Hour(1)), - // str day - ("1 day", TimeTriggerInterval::Day(1)), - ("1 days", TimeTriggerInterval::Day(1)), - // str week - ("1 week", TimeTriggerInterval::Week(1)), - ("1 weeks", TimeTriggerInterval::Week(1)), - // str month - ("1 month", TimeTriggerInterval::Month(1)), - ("1 months", TimeTriggerInterval::Month(1)), - // str year - ("1 year", TimeTriggerInterval::Year(1)), - ("1 years", TimeTriggerInterval::Year(1)), + #[test] + #[cfg(feature = "config_parsing")] + fn test_interval_deser() { + let mut trigger_cfg = TimeTriggerConfig { + interval: TimeTriggerInterval::Second(1), + modulate: false, + max_random_delay: 0, + }; + + let mut cfg = vec![ + Token::Struct { + name: "TimeTriggerConfig", + len: 1, + }, + Token::Str("interval"), + Token::U64(1), + Token::StructEnd, + ]; + + let tests = vec![ + (TimeTriggerInterval::Second(1), Token::U64(1)), + (TimeTriggerInterval::Second(1), Token::Str("1 second")), + (TimeTriggerInterval::Second(1), Token::Str("1 seconds")), + (TimeTriggerInterval::Minute(1), Token::Str("1 minute")), + (TimeTriggerInterval::Minute(1), Token::Str("1 minutes")), + (TimeTriggerInterval::Hour(1), Token::Str("1 hour")), + (TimeTriggerInterval::Hour(1), Token::Str("1 hours")), + (TimeTriggerInterval::Day(1), Token::Str("1 day")), + (TimeTriggerInterval::Day(1), Token::Str("1 days")), + (TimeTriggerInterval::Week(1), Token::Str("1 week")), + (TimeTriggerInterval::Week(1), Token::Str("1 weeks")), + (TimeTriggerInterval::Year(1), Token::Str("1 year")), + (TimeTriggerInterval::Year(1), Token::Str("1 years")), ]; - for (interval, expected) in test_ok.iter() { - let interval = format!("{}", interval); - let interval = ::serde_yaml::from_str::(&interval).unwrap(); - assert_eq!(interval, *expected); + + for (interval, cfg_val) in tests { + cfg[2] = cfg_val; + trigger_cfg.interval = interval; + assert_de_tokens(&trigger_cfg, &cfg); } } @@ -484,7 +565,7 @@ mod test { } #[test] - fn pre_process() { + fn test_is_pre_process() { let config = TimeTriggerConfig { interval: TimeTriggerInterval::Minute(2), modulate: true, @@ -493,4 +574,33 @@ mod test { let trigger = TimeTrigger::new(config); assert!(trigger.is_pre_process()); } + + #[test] + fn test_max_rand_delay() { + // Using a delay of 1 will test the block, but will always add a 0 allowing us + // to bypass the unknown of rand + let delays = vec![0, 1]; + + for delay in delays { + let trigger_cfg = TimeTriggerConfig { + interval: TimeTriggerInterval::Second(1), + modulate: false, + max_random_delay: delay, + }; + + let current = { + let now: std::time::Duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before Unix epoch"); + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64 + 1, now.subsec_nanos()) + .unwrap() + .and_local_timezone(Local) + .unwrap() + }; + + let trigger = TimeTrigger::new(trigger_cfg); + let trigger_time = trigger.next_roll_time.read().unwrap(); + assert_eq!(*trigger_time, current); + } + } }