From 2eb3185e82a8f45d496f22969dea56638eb825e8 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Thu, 29 Feb 2024 23:44:48 -0500 Subject: [PATCH 1/5] chore: clippy fixes + msrv bump --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 6 +++++- README.md | 4 ++-- 3 files changed, 9 insertions(+), 5 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 fbc77abd..9b528e42 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"] @@ -70,7 +70,11 @@ 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 } +<<<<<<< HEAD toml = { version = "<0.8.10", optional = true } +======= +toml = { version = "0.8.10", optional = true } +>>>>>>> c3d7f42 (chore: clippy fixes + msrv bump) 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 c35baf4d..496a7fa1 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. @@ -54,7 +54,7 @@ fn main() { ## Rust Version Requirements -1.69 +1.70 ## Building for Dev From 7adf7d0c4365c7be06696d10bf4b4a2565e28ad7 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 19:08:56 -0500 Subject: [PATCH 2/5] chore: backout msrv bump, knock toml down --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 6 +++++- README.md | 4 ++-- 3 files changed, 9 insertions(+), 5 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 9b528e42..2ca4f1d8 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"] @@ -71,10 +71,14 @@ typemap-ors = { version = "1.0.0", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } <<<<<<< HEAD +<<<<<<< HEAD toml = { version = "<0.8.10", optional = true } ======= toml = { version = "0.8.10", optional = true } >>>>>>> c3d7f42 (chore: clippy fixes + msrv bump) +======= +toml = { version = "0.8.9", optional = true } +>>>>>>> 4486edd (chore: backout msrv bump, knock toml down) 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 496a7fa1..c35baf4d 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. @@ -54,7 +54,7 @@ fn main() { ## Rust Version Requirements -1.70 +1.69 ## Building for Dev From c88ae6e5b7fa7484bf3b941b0386e460456c6d70 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 20:40:04 -0500 Subject: [PATCH 3/5] limit toml --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 2ca4f1d8..93473f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD toml = { version = "<0.8.10", optional = true } ======= toml = { version = "0.8.10", optional = true } @@ -79,6 +80,9 @@ toml = { version = "0.8.10", optional = true } ======= toml = { version = "0.8.9", optional = true } >>>>>>> 4486edd (chore: backout msrv bump, knock toml down) +======= +toml = { version = "<0.8.10", optional = true } +>>>>>>> 049e2cc (limit toml) parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.8", optional = true} thiserror = "1.0.15" From 896fca1a9f9c19c085650f573e7db672b9c02c4d Mon Sep 17 00:00:00 2001 From: bconn98 Date: Sat, 2 Mar 2024 21:41:57 -0500 Subject: [PATCH 4/5] tests: add encode tests --- Cargo.toml | 12 -- src/encode/json.rs | 34 +++- src/encode/mod.rs | 77 ++++++++ src/encode/pattern/mod.rs | 337 +++++++++++++++++++++++++++++++---- src/encode/pattern/parser.rs | 88 +++++++++ src/encode/writer/ansi.rs | 22 ++- src/encode/writer/console.rs | 236 +++++++++++++++++++----- tests/color_control.rs | 24 --- 8 files changed, 712 insertions(+), 118 deletions(-) delete mode 100644 tests/color_control.rs diff --git a/Cargo.toml b/Cargo.toml index 93473f67..fbc77abd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,19 +70,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 } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD toml = { version = "<0.8.10", optional = true } -======= -toml = { version = "0.8.10", optional = true } ->>>>>>> c3d7f42 (chore: clippy fixes + msrv bump) -======= -toml = { version = "0.8.9", optional = true } ->>>>>>> 4486edd (chore: backout msrv bump, knock toml down) -======= -toml = { version = "<0.8.10", optional = true } ->>>>>>> 049e2cc (limit toml) parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.8", optional = true} thiserror = "1.0.15" diff --git a/src/encode/json.rs b/src/encode/json.rs index 784a8739..2674db69 100644 --- a/src/encode/json.rs +++ b/src/encode/json.rs @@ -85,7 +85,15 @@ impl JsonEncoder { impl Encode for JsonEncoder { fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()> { - self.encode_inner(w, Local::now(), record) + #[cfg(test)] + let time = DateTime::parse_from_rfc3339("2016-03-20T14:22:20.644420340-08:00") + .unwrap() + .with_timezone(&Local); + + #[cfg(not(test))] + let time = Local::now(); + + self.encode_inner(w, time, record) } } @@ -168,13 +176,17 @@ impl Deserialize for JsonEncoderDeserializer { mod test { #[cfg(feature = "chrono")] use chrono::{DateTime, Local}; - use log::Level; + use log::{Level, Record}; use super::*; + + #[cfg(feature = "config_parsing")] + use crate::config::Deserializers; + use crate::encode::writer::simple::SimpleWriter; #[test] - fn default() { + fn test_json_encode() { let time = DateTime::parse_from_rfc3339("2016-03-20T14:22:20.644420340-08:00") .unwrap() .with_timezone(&Local); @@ -184,16 +196,15 @@ mod test { let file = "file"; let line = 100; let message = "message"; - let thread = "encode::json::test::default"; + let thread = "encode::json::test::test_json_encode"; log_mdc::insert("foo", "bar"); let encoder = JsonEncoder::new(); let mut buf = vec![]; encoder - .encode_inner( + .encode( &mut SimpleWriter(&mut buf), - time, &Record::builder() .level(level) .target(target) @@ -221,4 +232,15 @@ mod test { ); assert_eq!(expected, String::from_utf8(buf).unwrap().trim()); } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_cfg_deserializer() { + let json_cfg = JsonEncoderConfig { _p: () }; + + let deserializer = JsonEncoderDeserializer; + + let res = deserializer.deserialize(json_cfg, &Deserializers::default()); + assert!(res.is_ok()); + } } diff --git a/src/encode/mod.rs b/src/encode/mod.rs index aa290b3c..efe386c4 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -154,3 +154,80 @@ impl<'a, W: Write + ?Sized> Write for &'a mut W { ::set_style(*self, style) } } + +#[cfg(test)] +mod test { + #[cfg(feature = "config_parsing")] + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; + + #[test] + #[cfg(feature = "config_parsing")] + fn test_cfg_deserialize() { + use super::*; + use std::collections::BTreeMap; + + let pattern = "[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}".to_owned(); + + let mut config = BTreeMap::new(); + config.insert(Value::String("pattern".to_owned()), Value::String(pattern)); + + let encoder_cfg = EncoderConfig { + kind: "pattern".to_owned(), + config: Value::Map(config), + }; + + assert_de_tokens( + &encoder_cfg, + &[ + Token::Struct { + name: "EncoderConfig", + len: 2, + }, + Token::Str("kind"), + Token::Str("pattern"), + Token::Str("pattern"), + Token::Str("[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}"), + Token::StructEnd, + ], + ); + + // No pattern defined, should fail to deserializez into a map + assert_de_tokens_error::( + &[ + Token::Struct { + name: "EncoderConfig", + len: 2, + }, + Token::Str("kind"), + Token::Str("pattern"), + Token::Str("pattern"), + Token::StructEnd, + ], + "deserialization did not expect this token: StructEnd", + ); + } + + #[test] + #[cfg(feature = "console_writer")] + fn test_set_console_writer_style() { + use super::*; + use crate::encode::writer::console::ConsoleWriter; + + let w = match ConsoleWriter::stdout() { + Some(w) => w, + None => return, + }; + let mut w = w.lock(); + + assert!(w + .set_style( + Style::new() + .text(Color::Red) + .background(Color::Blue) + .intense(true), + ) + .is_ok()); + + w.set_style(&Style::new()).unwrap(); + } +} diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 5215f2ec..15c42dc8 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -744,18 +744,16 @@ impl Deserialize for PatternEncoderDeserializer { #[cfg(test)] mod tests { + #[cfg(feature = "config_parsing")] + use crate::config::Deserializers; #[cfg(feature = "simple_writer")] - use log::{Level, Record}; + use crate::encode::{writer::simple::SimpleWriter, Encode, Write as EncodeWrite}; #[cfg(feature = "simple_writer")] - use std::process; + use log::{Level, Record}; #[cfg(feature = "simple_writer")] - use std::thread; + use std::{io::Write, process, thread}; - use super::{Chunk, PatternEncoder}; - #[cfg(feature = "simple_writer")] - use crate::encode::writer::simple::SimpleWriter; - #[cfg(feature = "simple_writer")] - use crate::encode::Encode; + use super::*; fn error_free(encoder: &PatternEncoder) -> bool { encoder.chunks.iter().all(|c| match *c { @@ -765,18 +763,18 @@ mod tests { } #[test] - fn invalid_formatter() { + fn test_invalid_formatter() { assert!(!error_free(&PatternEncoder::new("{x}"))); } #[test] - fn unclosed_delimiter() { + fn test_unclosed_delimiter() { assert!(!error_free(&PatternEncoder::new("{d(%Y-%m-%d)"))); } #[test] #[cfg(feature = "simple_writer")] - fn log() { + fn test_log() { let pw = PatternEncoder::new("{l} {m} at {M} in {f}:{L}"); let mut buf = vec![]; pw.encode( @@ -796,7 +794,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn unnamed_thread() { + fn test_unnamed_thread() { thread::spawn(|| { let pw = PatternEncoder::new("{T}"); let mut buf = vec![]; @@ -810,7 +808,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn named_thread() { + fn test_named_thread() { thread::Builder::new() .name("foobar".to_string()) .spawn(|| { @@ -827,7 +825,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn thread_id_field() { + fn test_thread_id_field() { thread::spawn(|| { let pw = PatternEncoder::new("{I}"); let mut buf = vec![]; @@ -841,7 +839,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn process_id() { + fn test_process_id() { let pw = PatternEncoder::new("{P}"); let mut buf = vec![]; @@ -853,7 +851,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn system_thread_id() { + fn test_system_thread_id() { let pw = PatternEncoder::new("{i}"); let mut buf = vec![]; @@ -865,13 +863,13 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn default_okay() { + fn test_default_okay() { assert!(error_free(&PatternEncoder::default())); } #[test] #[cfg(feature = "simple_writer")] - fn left_align() { + fn test_left_align() { let pw = PatternEncoder::new("{m:~<5.6}"); let mut buf = vec![]; @@ -893,7 +891,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn right_align() { + fn test_right_align() { let pw = PatternEncoder::new("{m:~>5.6}"); let mut buf = vec![]; @@ -915,7 +913,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn left_align_formatter() { + fn test_left_align_formatter() { let pw = PatternEncoder::new("{({l} {m}):15}"); let mut buf = vec![]; @@ -932,7 +930,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn right_align_formatter() { + fn test_right_align_formatter() { let pw = PatternEncoder::new("{({l} {m}):>15}"); let mut buf = vec![]; @@ -948,27 +946,27 @@ mod tests { } #[test] - fn custom_date_format() { + fn test_custom_date_format() { assert!(error_free(&PatternEncoder::new( "{d(%Y-%m-%d %H:%M:%S)} {m}{n}" ))); } #[test] - fn timezones() { + fn test_timezones() { assert!(error_free(&PatternEncoder::new("{d(%+)(utc)}"))); assert!(error_free(&PatternEncoder::new("{d(%+)(local)}"))); assert!(!error_free(&PatternEncoder::new("{d(%+)(foo)}"))); } #[test] - fn unescaped_parens() { + fn test_unescaped_parens() { assert!(!error_free(&PatternEncoder::new("(hi)"))); } #[test] #[cfg(feature = "simple_writer")] - fn escaped_chars() { + fn test_escaped_chars() { let pw = PatternEncoder::new("{{{m}(())}}"); let mut buf = vec![]; @@ -982,7 +980,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn quote_braces_with_backslash() { + fn test_quote_braces_with_backslash() { let pw = PatternEncoder::new(r"\{\({l}\)\}\\"); let mut buf = vec![]; @@ -996,7 +994,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn mdc() { + fn test_mdc() { let pw = PatternEncoder::new("{X(user_id)}"); log_mdc::insert("user_id", "mdc value"); @@ -1009,7 +1007,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn mdc_missing_default() { + fn test_mdc_missing_default() { let pw = PatternEncoder::new("{X(user_id)}"); let mut buf = vec![]; @@ -1021,7 +1019,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn mdc_missing_custom() { + fn test_mdc_missing_custom() { let pw = PatternEncoder::new("{X(user_id)(missing value)}"); let mut buf = vec![]; @@ -1033,7 +1031,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn debug_release() { + fn test_debug_release() { let debug_pat = "{D({l})}"; let release_pat = "{R({l})}"; @@ -1063,4 +1061,283 @@ mod tests { assert!(debug_buf.is_empty()); } } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_max_width_writer() { + let mut buf = vec![]; + let mut w = SimpleWriter(&mut buf); + + let mut w = MaxWidthWriter { + remaining: 2, + w: &mut w, + }; + + let res = w.write(b"test write"); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 2); + assert_eq!(w.remaining, 0); + assert!(w.flush().is_ok()); + assert!(w.set_style(&Style::new()).is_ok()); + assert_eq!(buf, b"te"); + + let mut buf = vec![]; + let mut w = SimpleWriter(&mut buf); + + let mut w = MaxWidthWriter { + remaining: 15, + w: &mut w, + }; + let res = w.write(b"test write"); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 10); + assert_eq!(w.remaining, 5); + assert_eq!(buf, b"test write"); + } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_left_align_writer() { + let mut buf = vec![]; + let mut w = SimpleWriter(&mut buf); + + let mut w = LeftAlignWriter { + to_fill: 4, + fill: ' ', + w: &mut w, + }; + + let res = w.write(b"test write"); + assert!(res.is_ok()); + assert!(w.flush().is_ok()); + assert!(w.set_style(&Style::new()).is_ok()); + } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_right_align_writer() { + let mut write_buf = vec![]; + let buf = vec![BufferedOutput::Style(Style::new())]; + let mut w = SimpleWriter(&mut write_buf); + + let mut w = RightAlignWriter { + to_fill: 4, + fill: ' ', + w: &mut w, + buf, + }; + + let res = w.write(b"test write"); + assert!(res.is_ok()); + assert!(w.flush().is_ok()); + assert!(w.set_style(&Style::new()).is_ok()); + assert!(w.finish().is_ok()); + } + + #[test] + #[cfg(feature = "config_parsing")] + fn test_cfg_deserializer() { + let pattern_cfg = PatternEncoderConfig { + pattern: Some("[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}".to_owned()), + }; + + let deserializer = PatternEncoderDeserializer; + + let res = deserializer.deserialize(pattern_cfg, &Deserializers::default()); + assert!(res.is_ok()); + + let pattern_cfg = PatternEncoderConfig { pattern: None }; + + let res = deserializer.deserialize(pattern_cfg, &Deserializers::default()); + assert!(res.is_ok()); + } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_chunk_no_min_width() { + let mut buf = vec![]; + let pattern = "[{h({l}):<.5} {M}]"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + for chunk in chunks { + assert!(chunk + .encode( + &mut SimpleWriter(&mut buf), + &Record::builder() + .level(Level::Debug) + .args(format_args!("the message")) + .module_path(Some("path")) + .file(Some("file")) + .line(Some(132)) + .build() + ) + .is_ok()) + } + assert!(!String::from_utf8(buf).unwrap().contains("ERROR")); + } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_chunk_encode_err() { + let mut buf = vec![]; + let pattern = "[{h({l):<.5}]"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + for chunk in chunks { + assert!(chunk + .encode( + &mut SimpleWriter(&mut buf), + &Record::builder() + .level(Level::Debug) + .args(format_args!("the message")) + .module_path(Some("path")) + .file(Some("file")) + .line(Some(132)) + .build() + ) + .is_ok()) + } + assert!(String::from_utf8(buf).unwrap().contains("ERROR")); + } + + #[test] + fn test_from_piece_to_chunk() { + // Test 3 args passed to date + let pattern = "[{d(%Y-%m-%d %H:%M:%S %Z)(utc)(local)}]"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + match chunks.get(1).unwrap() { + Chunk::Error(err) => assert_eq!(err, "expected at most two arguments"), + _ => assert!(false), + } + + // Test unexepected formatter + let pattern = "[{d({l} %Y-%m-%d %H:%M:%S %Z)}]"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + match chunks.get(1).unwrap() { + Chunk::Formatted { chunk, .. } => match chunk { + FormattedChunk::Time(value, _tz) => { + assert_eq!(value, "{ERROR: unexpected formatter} %Y-%m-%d %H:%M:%S %Z") + } + _ => assert!(false), + }, + _ => assert!(false), + } + + let tests = vec![ + ("[{d(%Y-%m-%d %H:%M:%S %Z)(zulu)}]", "invalid timezone"), + ("[{d(%Y-%m-%d %H:%M:%S %Z)({l})}]", "invalid timezone"), + ("[{d(%Y-%m-%d %H:%M:%S %Z)()}]", "invalid timezone"), + ("[{h({l})({M}):<5.5}]", "expected exactly one argument"), + ( + "[{D({l})({M}):<5.5}{R({l})({M}):<5.5}]", + "expected exactly one argument", + ), + ( + "[{X(user_id)(foobar)(test):<5.5}]", + "expected at most two arguments", + ), + ("[{X({l user_id):<5.5}]", "expected '}'"), + ("[{X({l} user_id):<5.5}]", "invalid MDC key"), + ("[{X:<5.5}]", "missing MDC key"), + ("[{X(user_id)({l):<5.5}]", "expected '}'"), + ("[{X(user_id)({l}):<5.5}]", "invalid MDC default"), + ("[{X(user_id)():<5.5} {M}]", "invalid MDC default"), + ]; + + for (pattern, error_msg) in tests { + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + match chunks.get(1).unwrap() { + Chunk::Error(err) => assert!(err.contains(error_msg)), + _ => assert!(false), + } + } + + // Test expected 1 arg + let pattern = "{({l} {m})()}"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + match chunks.get(0).unwrap() { + Chunk::Error(err) => assert!(err.contains("expected exactly one argument")), + _ => assert!(false), + } + + // Test no_args + let pattern = "{l()}"; + let chunks: Vec = Parser::new(pattern).map(From::from).collect(); + match chunks.get(0).unwrap() { + Chunk::Error(err) => assert!(err.contains("unexpected arguments")), + _ => assert!(false), + } + } + + #[test] + #[cfg(feature = "simple_writer")] + fn test_encode_formatted_chunk() { + // Each test gets a new buf and writer to allow for checking the + // buffer and utilizing completely clean buffers. + + let record = Record::builder() + .level(Level::Info) + .args(format_args!("the message")) + .module_path(Some("path")) + .file(Some("file")) + .line(None) + .target("target") + .build(); + + // Limit the time tests to the year. Just need to verify that time can + // be written. Don't need to be precise. This should limit potential + // race condition failures. + + // Test UTC Time + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Time("%Y".to_owned(), Timezone::Utc); + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, Utc::now().format("%Y").to_string().as_bytes()); + + // Test Local Time + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Time("%Y".to_owned(), Timezone::Local); + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, Local::now().format("%Y").to_string().as_bytes()); + + // Test missing Line + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Line; + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, b"???"); + + // Test Target + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Target; + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, b"target"); + + // Test Newline + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Newline; + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, NEWLINE.as_bytes()); + + // Loop over to hit each possible styling + for level in Level::iter() { + let record = Record::builder() + .level(level) + .args(format_args!("the message")) + .module_path(Some("path")) + .file(Some("file")) + .line(None) + .target("target") + .build(); + + let mut write_buf = vec![]; + let mut w = SimpleWriter(&mut write_buf); + let chunk = FormattedChunk::Highlight(vec![Chunk::Text("Text".to_owned())]); + chunk.encode(&mut w, &record).unwrap(); + assert_eq!(write_buf, b"Text"); + // No style updates in the buffer to check for + } + } } diff --git a/src/encode/pattern/parser.rs b/src/encode/pattern/parser.rs index 8e91e8ec..aefa9377 100644 --- a/src/encode/pattern/parser.rs +++ b/src/encode/pattern/parser.rs @@ -268,3 +268,91 @@ impl<'a> Iterator for Parser<'a> { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_arg_parser() { + let pattern = "(%Y-%m-%dT%H:%M:%S%.6f"; + let mut parser = Parser::new(pattern); + + let arg = parser.arg(); + assert!(arg.is_err()); + + let pattern = "(%Y-%m-%dT%H:%M:%S%.6f)"; + let mut parser = Parser::new(pattern); + + let arg = parser.arg(); + assert!(arg.is_ok()); + + let pattern = "[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}"; + let mut parser = Parser::new(pattern); + + let arg = parser.arg(); + assert!(arg.is_ok()); + assert!(arg.unwrap().is_empty()); + } + + #[test] + fn test_name() { + // match up to first non alpha numberic + let pattern = "test["; + let mut parser = Parser::new(pattern); + let name = parser.name(); + assert_eq!(name, "test"); + + // match up to first non alpha numberic, so empty string + let pattern = "["; + let mut parser = Parser::new(pattern); + let name = parser.name(); + assert_eq!(name, ""); + + // match up to first non alpha numberic, so empty string + let pattern = "test"; + let mut parser = Parser::new(pattern); + let name = parser.name(); + assert_eq!(name, "test"); + } + + #[test] + fn test_argument_invalid_and_valid() { + let pattern = "(%Y-%m-%dT%H:%M:%S%.6f"; + let mut parser = Parser::new(pattern); + + let piece = parser.argument(); + assert!(match piece { + Piece::Error(_) => true, + _ => false, + }); + + let pattern = "[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}"; + let mut parser = Parser::new(pattern); + + let piece = parser.argument(); + assert!(match piece { + Piece::Argument { .. } => true, + _ => false, + }); + } + + #[test] + fn test_unmatched_bracket() { + let pattern = "d}"; + let parser = Parser::new(pattern); + let mut iter = parser.into_iter(); + + // First parse the d + assert!(match iter.next().unwrap() { + Piece::Text { .. } => true, + _ => false, + }); + + // Next try and parse the } but it's unmatched + assert!(match iter.next().unwrap() { + Piece::Error { .. } => true, + _ => false, + }); + } +} diff --git a/src/encode/writer/ansi.rs b/src/encode/writer/ansi.rs index 8b8b4226..9d35d247 100644 --- a/src/encode/writer/ansi.rs +++ b/src/encode/writer/ansi.rs @@ -87,11 +87,11 @@ mod test { use crate::encode::{Color, Style, Write as EncodeWrite}; #[test] - fn basic() { + fn test_ansi_writer() { let stdout = io::stdout(); let mut w = AnsiWriter(stdout.lock()); - w.write_all(b"normal ").unwrap(); + w.write(b"normal ").unwrap(); w.set_style( Style::new() .text(Color::Red) @@ -100,10 +100,24 @@ mod test { ) .unwrap(); w.write_all(b"styled").unwrap(); - w.set_style(Style::new().text(Color::Green)).unwrap(); + // Call out intense false here to hit else case + w.set_style(Style::new().text(Color::Green).intense(false)) + .unwrap(); w.write_all(b" styled2").unwrap(); w.set_style(&Style::new()).unwrap(); - w.write_all(b" normal\n").unwrap(); + w.write_fmt(format_args!(" {} \n", "normal")).unwrap(); w.flush().unwrap(); } + + #[test] + fn test_color_enum() { + assert_eq!(color_byte(Color::Black), b'0'); + assert_eq!(color_byte(Color::Red), b'1'); + assert_eq!(color_byte(Color::Green), b'2'); + assert_eq!(color_byte(Color::Yellow), b'3'); + assert_eq!(color_byte(Color::Blue), b'4'); + assert_eq!(color_byte(Color::Magenta), b'5'); + assert_eq!(color_byte(Color::Cyan), b'6'); + assert_eq!(color_byte(Color::White), b'7'); + } } diff --git a/src/encode/writer/console.rs b/src/encode/writer/console.rs index 6209fb97..eaaf0974 100644 --- a/src/encode/writer/console.rs +++ b/src/encode/writer/console.rs @@ -2,36 +2,37 @@ //! //! Requires the `console_writer` feature. -use std::{fmt, io}; +use std::{env, fmt, io}; use crate::encode::{self, Style}; -use once_cell::sync::Lazy; - -static COLOR_MODE: Lazy = Lazy::new(|| { - let no_color = std::env::var("NO_COLOR") - .map(|var| var != "0") - .unwrap_or(false); - let clicolor_force = std::env::var("CLICOLOR_FORCE") - .map(|var| var != "0") - .unwrap_or(false); +use once_cell::sync::OnceCell; + +static COLOR_MODE: OnceCell = OnceCell::new(); + +fn set_color_mode( + no_color: Result, + clicolor_force: Result, + clicolor: Result, +) -> ColorMode { + let no_color = no_color.map(|var| var != "0").unwrap_or(false); + let clicolor_force = clicolor_force.map(|var| var != "0").unwrap_or(false); + if no_color { ColorMode::Never } else if clicolor_force { ColorMode::Always } else { - let clicolor = std::env::var("CLICOLOR") - .map(|var| var != "0") - .unwrap_or(true); + let clicolor = clicolor.map(|var| var != "0").unwrap_or(true); if clicolor { ColorMode::Auto } else { ColorMode::Never } } -}); +} /// The color output mode for a `ConsoleAppender` -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Default, Debug, PartialEq)] pub enum ColorMode { /// Print color only if the output is recognized as a console #[default] @@ -121,14 +122,14 @@ impl<'a> encode::Write for ConsoleWriterLock<'a> { #[cfg(unix)] mod imp { - use std::{fmt, io}; + use std::{env, fmt, io}; use crate::{ encode::{ self, writer::{ ansi::AnsiWriter, - console::{ColorMode, COLOR_MODE}, + console::{set_color_mode, ColorMode, COLOR_MODE}, }, Style, }, @@ -140,7 +141,13 @@ mod imp { impl Writer { pub fn stdout() -> Option { let writer = || Writer(AnsiWriter(StdWriter::stdout())); - match *COLOR_MODE { + let color_mode_init = { + let no_color = env::var("NO_COLOR"); + let clicolor_force = env::var("CLICOLOR_FORCE"); + let clicolor = env::var("CLICOLOR"); + set_color_mode(no_color, clicolor_force, clicolor) + }; + match COLOR_MODE.get_or_init(|| color_mode_init) { ColorMode::Auto => { if unsafe { libc::isatty(libc::STDOUT_FILENO) } != 1 { None @@ -155,7 +162,13 @@ mod imp { pub fn stderr() -> Option { let writer = || Writer(AnsiWriter(StdWriter::stderr())); - match *COLOR_MODE { + let color_mode_init = { + let no_color = env::var("NO_COLOR"); + let clicolor_force = env::var("CLICOLOR_FORCE"); + let clicolor = env::var("CLICOLOR"); + set_color_mode(no_color, clicolor_force, clicolor) + }; + match COLOR_MODE.get_or_init(|| color_mode_init) { ColorMode::Auto => { if unsafe { libc::isatty(libc::STDERR_FILENO) } != 1 { None @@ -227,7 +240,7 @@ mod imp { #[cfg(windows)] mod imp { use std::{ - fmt, + env, fmt, io::{self, Write}, mem, }; @@ -239,7 +252,7 @@ mod imp { use crate::{ encode::{ self, - writer::console::{ColorMode, COLOR_MODE}, + writer::console::{set_color_mode, ColorMode, COLOR_MODE}, Color, Style, }, priv_io::{StdWriter, StdWriterLock}, @@ -335,7 +348,13 @@ mod imp { inner: StdWriter::stdout(), }; - match *COLOR_MODE { + let color_mode_init = { + let no_color = env::var("NO_COLOR"); + let clicolor_force = env::var("CLICOLOR_FORCE"); + let clicolor = env::var("CLICOLOR"); + set_color_mode(no_color, clicolor_force, clicolor) + }; + match COLOR_MODE.get_or_init(|| color_mode_init) { ColorMode::Auto | ColorMode::Always => Some(writer), ColorMode::Never => None, } @@ -362,7 +381,13 @@ mod imp { inner: StdWriter::stdout(), }; - match *COLOR_MODE { + let color_mode_init = { + let no_color = env::var("NO_COLOR"); + let clicolor_force = env::var("CLICOLOR_FORCE"); + let clicolor = env::var("CLICOLOR"); + set_color_mode(no_color, clicolor_force, clicolor) + }; + match COLOR_MODE.get_or_init(|| color_mode_init) { ColorMode::Auto | ColorMode::Always => Some(writer), ColorMode::Never => None, } @@ -435,32 +460,159 @@ mod imp { #[cfg(test)] mod test { - use std::io::Write; - use super::*; use crate::encode::{Color, Style, Write as EncodeWrite}; + use std::{env::VarError, io::Write}; + // Unable to test the non locked Console as by definition, the unlocked + // console results in race conditions. Codecov tooling does not seem to + // see this test as coverage of the ConsoleWritterLock or WriterLock + // class, however, it should completely cover either. #[test] - fn basic() { + fn test_stdout_console_writer_lock() { let w = match ConsoleWriter::stdout() { Some(w) => w, None => return, }; - let mut w = w.lock(); - - w.write_all(b"normal ").unwrap(); - w.set_style( - Style::new() - .text(Color::Red) - .background(Color::Blue) - .intense(true), - ) - .unwrap(); - w.write_all(b"styled").unwrap(); - w.set_style(Style::new().text(Color::Green)).unwrap(); - w.write_all(b" styled2").unwrap(); - w.set_style(&Style::new()).unwrap(); - w.write_all(b" normal\n").unwrap(); - w.flush().unwrap(); + let mut writer = w.lock(); + + writer.write(b"normal ").unwrap(); + writer + .set_style( + Style::new() + .text(Color::Red) + .background(Color::Blue) + .intense(true), + ) + .unwrap(); + writer.write_all(b"styled").unwrap(); + writer + .set_style(&Style::new().text(Color::Green).intense(false)) + .unwrap(); + writer.write_all(b" styled2").unwrap(); + writer.set_style(&Style::new()).unwrap(); + writer.write_fmt(format_args!(" {} \n", "normal")).unwrap(); + writer.flush().unwrap(); + } + + #[test] + fn test_stderr_console_writer_lock() { + let w = match ConsoleWriter::stderr() { + Some(w) => w, + None => return, + }; + + // Do not operate on the stderr writer. Doing so results in undefined + // test result behavior due to tests running in parallel + let _ = w.lock(); + } + + #[test] + fn test_color_mode_default() { + let no_color = Err(VarError::NotPresent); + let clicolor_force = Err(VarError::NotPresent); + let clicolor = Err(VarError::NotPresent); + + let color_mode: OnceCell = OnceCell::new(); + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Auto + ); + } + + // Note that NO_COLOR has priority over all other fields + #[test] + fn test_no_color() { + let no_color = Ok("1".to_owned()); + let clicolor_force = Err(VarError::NotPresent); + let clicolor = Err(VarError::NotPresent); + + let mut color_mode: OnceCell = OnceCell::new(); + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Never + ); + + let no_color = Ok("1".to_owned()); + let clicolor_force = Ok("1".to_owned()); + let clicolor = Ok("1".to_owned()); + + let _ = color_mode.take(); // Clear the owned value + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Never + ); + } + + #[test] + fn test_cli_force() { + // CLICOLOR_FORCE is the only set field + let no_color = Err(VarError::NotPresent); + let clicolor_force = Ok("1".to_owned()); + let clicolor = Err(VarError::NotPresent); + + let mut color_mode: OnceCell = OnceCell::new(); + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Always + ); + + // Although NO_COLOR has priority, when set to 0 next in line + // is CLICOLOR_FORCE which maintains precedence over clicolor + // regardless of how it's set. Attempt both settings below + let no_color = Ok("0".to_owned()); + let clicolor_force = Ok("1".to_owned()); + let clicolor = Ok("1".to_owned()); + + let _ = color_mode.take(); // Clear the owned value + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Always + ); + + let no_color = Ok("0".to_owned()); + let clicolor_force = Ok("1".to_owned()); + let clicolor = Ok("0".to_owned()); + + let _ = color_mode.take(); // Clear the owned value + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Always + ); + } + + #[test] + fn test_cli_on() { + // CLICOLOR is the only set field + let no_color = Err(VarError::NotPresent); + let clicolor_force = Err(VarError::NotPresent); + let clicolor = Ok("1".to_owned()); + + let mut color_mode: OnceCell = OnceCell::new(); + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Auto + ); + + let no_color = Err(VarError::NotPresent); + let clicolor_force = Err(VarError::NotPresent); + let clicolor = Ok("0".to_owned()); + + let _ = color_mode.take(); // Clear the owned value + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Never + ); + + // CLICOLOR_FORCE is disabled + let no_color = Err(VarError::NotPresent); + let clicolor_force = Ok("0".to_owned()); + let clicolor = Ok("1".to_owned()); + + let _ = color_mode.take(); // Clear the owned value + assert_eq!( + color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), + &ColorMode::Auto + ); } } diff --git a/tests/color_control.rs b/tests/color_control.rs deleted file mode 100644 index 344f032f..00000000 --- a/tests/color_control.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::process::Command; - -fn execute_test(env_key: &str, env_val: &str) { - let mut child_proc = Command::new("cargo") - .args(&["run", "--example", "compile_time_config"]) - .env(env_key, env_val) - .spawn() - .expect("Cargo command failed to start"); - - let ecode = child_proc.wait().expect("failed to wait on child"); - - assert!(ecode.success()); -} - -// Maintaining as a single test to avoid blocking calls to the package cache -#[test] -fn test_no_color() { - let keys = vec!["NO_COLOR", "CLICOLOR_FORCE", "CLICOLOR"]; - - for key in keys { - execute_test(key, "1"); - execute_test(key, "0"); - } -} From 6dcf08cadb0b1fe47d75f5e0db38a599145ee096 Mon Sep 17 00:00:00 2001 From: bconn98 Date: Mon, 1 Apr 2024 21:38:15 -0400 Subject: [PATCH 5/5] backout: console changes and pattern changes for separate PR --- src/encode/pattern/mod.rs | 337 ++++------------------------------- src/encode/writer/console.rs | 236 +++++------------------- 2 files changed, 72 insertions(+), 501 deletions(-) diff --git a/src/encode/pattern/mod.rs b/src/encode/pattern/mod.rs index 15c42dc8..5215f2ec 100644 --- a/src/encode/pattern/mod.rs +++ b/src/encode/pattern/mod.rs @@ -744,16 +744,18 @@ impl Deserialize for PatternEncoderDeserializer { #[cfg(test)] mod tests { - #[cfg(feature = "config_parsing")] - use crate::config::Deserializers; - #[cfg(feature = "simple_writer")] - use crate::encode::{writer::simple::SimpleWriter, Encode, Write as EncodeWrite}; #[cfg(feature = "simple_writer")] use log::{Level, Record}; #[cfg(feature = "simple_writer")] - use std::{io::Write, process, thread}; + use std::process; + #[cfg(feature = "simple_writer")] + use std::thread; - use super::*; + use super::{Chunk, PatternEncoder}; + #[cfg(feature = "simple_writer")] + use crate::encode::writer::simple::SimpleWriter; + #[cfg(feature = "simple_writer")] + use crate::encode::Encode; fn error_free(encoder: &PatternEncoder) -> bool { encoder.chunks.iter().all(|c| match *c { @@ -763,18 +765,18 @@ mod tests { } #[test] - fn test_invalid_formatter() { + fn invalid_formatter() { assert!(!error_free(&PatternEncoder::new("{x}"))); } #[test] - fn test_unclosed_delimiter() { + fn unclosed_delimiter() { assert!(!error_free(&PatternEncoder::new("{d(%Y-%m-%d)"))); } #[test] #[cfg(feature = "simple_writer")] - fn test_log() { + fn log() { let pw = PatternEncoder::new("{l} {m} at {M} in {f}:{L}"); let mut buf = vec![]; pw.encode( @@ -794,7 +796,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_unnamed_thread() { + fn unnamed_thread() { thread::spawn(|| { let pw = PatternEncoder::new("{T}"); let mut buf = vec![]; @@ -808,7 +810,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_named_thread() { + fn named_thread() { thread::Builder::new() .name("foobar".to_string()) .spawn(|| { @@ -825,7 +827,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_thread_id_field() { + fn thread_id_field() { thread::spawn(|| { let pw = PatternEncoder::new("{I}"); let mut buf = vec![]; @@ -839,7 +841,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_process_id() { + fn process_id() { let pw = PatternEncoder::new("{P}"); let mut buf = vec![]; @@ -851,7 +853,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_system_thread_id() { + fn system_thread_id() { let pw = PatternEncoder::new("{i}"); let mut buf = vec![]; @@ -863,13 +865,13 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_default_okay() { + fn default_okay() { assert!(error_free(&PatternEncoder::default())); } #[test] #[cfg(feature = "simple_writer")] - fn test_left_align() { + fn left_align() { let pw = PatternEncoder::new("{m:~<5.6}"); let mut buf = vec![]; @@ -891,7 +893,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_right_align() { + fn right_align() { let pw = PatternEncoder::new("{m:~>5.6}"); let mut buf = vec![]; @@ -913,7 +915,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_left_align_formatter() { + fn left_align_formatter() { let pw = PatternEncoder::new("{({l} {m}):15}"); let mut buf = vec![]; @@ -930,7 +932,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_right_align_formatter() { + fn right_align_formatter() { let pw = PatternEncoder::new("{({l} {m}):>15}"); let mut buf = vec![]; @@ -946,27 +948,27 @@ mod tests { } #[test] - fn test_custom_date_format() { + fn custom_date_format() { assert!(error_free(&PatternEncoder::new( "{d(%Y-%m-%d %H:%M:%S)} {m}{n}" ))); } #[test] - fn test_timezones() { + fn timezones() { assert!(error_free(&PatternEncoder::new("{d(%+)(utc)}"))); assert!(error_free(&PatternEncoder::new("{d(%+)(local)}"))); assert!(!error_free(&PatternEncoder::new("{d(%+)(foo)}"))); } #[test] - fn test_unescaped_parens() { + fn unescaped_parens() { assert!(!error_free(&PatternEncoder::new("(hi)"))); } #[test] #[cfg(feature = "simple_writer")] - fn test_escaped_chars() { + fn escaped_chars() { let pw = PatternEncoder::new("{{{m}(())}}"); let mut buf = vec![]; @@ -980,7 +982,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_quote_braces_with_backslash() { + fn quote_braces_with_backslash() { let pw = PatternEncoder::new(r"\{\({l}\)\}\\"); let mut buf = vec![]; @@ -994,7 +996,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_mdc() { + fn mdc() { let pw = PatternEncoder::new("{X(user_id)}"); log_mdc::insert("user_id", "mdc value"); @@ -1007,7 +1009,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_mdc_missing_default() { + fn mdc_missing_default() { let pw = PatternEncoder::new("{X(user_id)}"); let mut buf = vec![]; @@ -1019,7 +1021,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_mdc_missing_custom() { + fn mdc_missing_custom() { let pw = PatternEncoder::new("{X(user_id)(missing value)}"); let mut buf = vec![]; @@ -1031,7 +1033,7 @@ mod tests { #[test] #[cfg(feature = "simple_writer")] - fn test_debug_release() { + fn debug_release() { let debug_pat = "{D({l})}"; let release_pat = "{R({l})}"; @@ -1061,283 +1063,4 @@ mod tests { assert!(debug_buf.is_empty()); } } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_max_width_writer() { - let mut buf = vec![]; - let mut w = SimpleWriter(&mut buf); - - let mut w = MaxWidthWriter { - remaining: 2, - w: &mut w, - }; - - let res = w.write(b"test write"); - assert!(res.is_ok()); - assert_eq!(res.unwrap(), 2); - assert_eq!(w.remaining, 0); - assert!(w.flush().is_ok()); - assert!(w.set_style(&Style::new()).is_ok()); - assert_eq!(buf, b"te"); - - let mut buf = vec![]; - let mut w = SimpleWriter(&mut buf); - - let mut w = MaxWidthWriter { - remaining: 15, - w: &mut w, - }; - let res = w.write(b"test write"); - assert!(res.is_ok()); - assert_eq!(res.unwrap(), 10); - assert_eq!(w.remaining, 5); - assert_eq!(buf, b"test write"); - } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_left_align_writer() { - let mut buf = vec![]; - let mut w = SimpleWriter(&mut buf); - - let mut w = LeftAlignWriter { - to_fill: 4, - fill: ' ', - w: &mut w, - }; - - let res = w.write(b"test write"); - assert!(res.is_ok()); - assert!(w.flush().is_ok()); - assert!(w.set_style(&Style::new()).is_ok()); - } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_right_align_writer() { - let mut write_buf = vec![]; - let buf = vec![BufferedOutput::Style(Style::new())]; - let mut w = SimpleWriter(&mut write_buf); - - let mut w = RightAlignWriter { - to_fill: 4, - fill: ' ', - w: &mut w, - buf, - }; - - let res = w.write(b"test write"); - assert!(res.is_ok()); - assert!(w.flush().is_ok()); - assert!(w.set_style(&Style::new()).is_ok()); - assert!(w.finish().is_ok()); - } - - #[test] - #[cfg(feature = "config_parsing")] - fn test_cfg_deserializer() { - let pattern_cfg = PatternEncoderConfig { - pattern: Some("[{d(%Y-%m-%dT%H:%M:%S%.6f)} {h({l}):<5.5} {M}] {m}{n}".to_owned()), - }; - - let deserializer = PatternEncoderDeserializer; - - let res = deserializer.deserialize(pattern_cfg, &Deserializers::default()); - assert!(res.is_ok()); - - let pattern_cfg = PatternEncoderConfig { pattern: None }; - - let res = deserializer.deserialize(pattern_cfg, &Deserializers::default()); - assert!(res.is_ok()); - } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_chunk_no_min_width() { - let mut buf = vec![]; - let pattern = "[{h({l}):<.5} {M}]"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - for chunk in chunks { - assert!(chunk - .encode( - &mut SimpleWriter(&mut buf), - &Record::builder() - .level(Level::Debug) - .args(format_args!("the message")) - .module_path(Some("path")) - .file(Some("file")) - .line(Some(132)) - .build() - ) - .is_ok()) - } - assert!(!String::from_utf8(buf).unwrap().contains("ERROR")); - } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_chunk_encode_err() { - let mut buf = vec![]; - let pattern = "[{h({l):<.5}]"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - for chunk in chunks { - assert!(chunk - .encode( - &mut SimpleWriter(&mut buf), - &Record::builder() - .level(Level::Debug) - .args(format_args!("the message")) - .module_path(Some("path")) - .file(Some("file")) - .line(Some(132)) - .build() - ) - .is_ok()) - } - assert!(String::from_utf8(buf).unwrap().contains("ERROR")); - } - - #[test] - fn test_from_piece_to_chunk() { - // Test 3 args passed to date - let pattern = "[{d(%Y-%m-%d %H:%M:%S %Z)(utc)(local)}]"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - match chunks.get(1).unwrap() { - Chunk::Error(err) => assert_eq!(err, "expected at most two arguments"), - _ => assert!(false), - } - - // Test unexepected formatter - let pattern = "[{d({l} %Y-%m-%d %H:%M:%S %Z)}]"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - match chunks.get(1).unwrap() { - Chunk::Formatted { chunk, .. } => match chunk { - FormattedChunk::Time(value, _tz) => { - assert_eq!(value, "{ERROR: unexpected formatter} %Y-%m-%d %H:%M:%S %Z") - } - _ => assert!(false), - }, - _ => assert!(false), - } - - let tests = vec![ - ("[{d(%Y-%m-%d %H:%M:%S %Z)(zulu)}]", "invalid timezone"), - ("[{d(%Y-%m-%d %H:%M:%S %Z)({l})}]", "invalid timezone"), - ("[{d(%Y-%m-%d %H:%M:%S %Z)()}]", "invalid timezone"), - ("[{h({l})({M}):<5.5}]", "expected exactly one argument"), - ( - "[{D({l})({M}):<5.5}{R({l})({M}):<5.5}]", - "expected exactly one argument", - ), - ( - "[{X(user_id)(foobar)(test):<5.5}]", - "expected at most two arguments", - ), - ("[{X({l user_id):<5.5}]", "expected '}'"), - ("[{X({l} user_id):<5.5}]", "invalid MDC key"), - ("[{X:<5.5}]", "missing MDC key"), - ("[{X(user_id)({l):<5.5}]", "expected '}'"), - ("[{X(user_id)({l}):<5.5}]", "invalid MDC default"), - ("[{X(user_id)():<5.5} {M}]", "invalid MDC default"), - ]; - - for (pattern, error_msg) in tests { - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - match chunks.get(1).unwrap() { - Chunk::Error(err) => assert!(err.contains(error_msg)), - _ => assert!(false), - } - } - - // Test expected 1 arg - let pattern = "{({l} {m})()}"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - match chunks.get(0).unwrap() { - Chunk::Error(err) => assert!(err.contains("expected exactly one argument")), - _ => assert!(false), - } - - // Test no_args - let pattern = "{l()}"; - let chunks: Vec = Parser::new(pattern).map(From::from).collect(); - match chunks.get(0).unwrap() { - Chunk::Error(err) => assert!(err.contains("unexpected arguments")), - _ => assert!(false), - } - } - - #[test] - #[cfg(feature = "simple_writer")] - fn test_encode_formatted_chunk() { - // Each test gets a new buf and writer to allow for checking the - // buffer and utilizing completely clean buffers. - - let record = Record::builder() - .level(Level::Info) - .args(format_args!("the message")) - .module_path(Some("path")) - .file(Some("file")) - .line(None) - .target("target") - .build(); - - // Limit the time tests to the year. Just need to verify that time can - // be written. Don't need to be precise. This should limit potential - // race condition failures. - - // Test UTC Time - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Time("%Y".to_owned(), Timezone::Utc); - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, Utc::now().format("%Y").to_string().as_bytes()); - - // Test Local Time - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Time("%Y".to_owned(), Timezone::Local); - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, Local::now().format("%Y").to_string().as_bytes()); - - // Test missing Line - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Line; - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, b"???"); - - // Test Target - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Target; - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, b"target"); - - // Test Newline - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Newline; - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, NEWLINE.as_bytes()); - - // Loop over to hit each possible styling - for level in Level::iter() { - let record = Record::builder() - .level(level) - .args(format_args!("the message")) - .module_path(Some("path")) - .file(Some("file")) - .line(None) - .target("target") - .build(); - - let mut write_buf = vec![]; - let mut w = SimpleWriter(&mut write_buf); - let chunk = FormattedChunk::Highlight(vec![Chunk::Text("Text".to_owned())]); - chunk.encode(&mut w, &record).unwrap(); - assert_eq!(write_buf, b"Text"); - // No style updates in the buffer to check for - } - } } diff --git a/src/encode/writer/console.rs b/src/encode/writer/console.rs index eaaf0974..6209fb97 100644 --- a/src/encode/writer/console.rs +++ b/src/encode/writer/console.rs @@ -2,37 +2,36 @@ //! //! Requires the `console_writer` feature. -use std::{env, fmt, io}; +use std::{fmt, io}; use crate::encode::{self, Style}; -use once_cell::sync::OnceCell; - -static COLOR_MODE: OnceCell = OnceCell::new(); - -fn set_color_mode( - no_color: Result, - clicolor_force: Result, - clicolor: Result, -) -> ColorMode { - let no_color = no_color.map(|var| var != "0").unwrap_or(false); - let clicolor_force = clicolor_force.map(|var| var != "0").unwrap_or(false); - +use once_cell::sync::Lazy; + +static COLOR_MODE: Lazy = Lazy::new(|| { + let no_color = std::env::var("NO_COLOR") + .map(|var| var != "0") + .unwrap_or(false); + let clicolor_force = std::env::var("CLICOLOR_FORCE") + .map(|var| var != "0") + .unwrap_or(false); if no_color { ColorMode::Never } else if clicolor_force { ColorMode::Always } else { - let clicolor = clicolor.map(|var| var != "0").unwrap_or(true); + let clicolor = std::env::var("CLICOLOR") + .map(|var| var != "0") + .unwrap_or(true); if clicolor { ColorMode::Auto } else { ColorMode::Never } } -} +}); /// The color output mode for a `ConsoleAppender` -#[derive(Clone, Copy, Default, Debug, PartialEq)] +#[derive(Clone, Copy, Default)] pub enum ColorMode { /// Print color only if the output is recognized as a console #[default] @@ -122,14 +121,14 @@ impl<'a> encode::Write for ConsoleWriterLock<'a> { #[cfg(unix)] mod imp { - use std::{env, fmt, io}; + use std::{fmt, io}; use crate::{ encode::{ self, writer::{ ansi::AnsiWriter, - console::{set_color_mode, ColorMode, COLOR_MODE}, + console::{ColorMode, COLOR_MODE}, }, Style, }, @@ -141,13 +140,7 @@ mod imp { impl Writer { pub fn stdout() -> Option { let writer = || Writer(AnsiWriter(StdWriter::stdout())); - let color_mode_init = { - let no_color = env::var("NO_COLOR"); - let clicolor_force = env::var("CLICOLOR_FORCE"); - let clicolor = env::var("CLICOLOR"); - set_color_mode(no_color, clicolor_force, clicolor) - }; - match COLOR_MODE.get_or_init(|| color_mode_init) { + match *COLOR_MODE { ColorMode::Auto => { if unsafe { libc::isatty(libc::STDOUT_FILENO) } != 1 { None @@ -162,13 +155,7 @@ mod imp { pub fn stderr() -> Option { let writer = || Writer(AnsiWriter(StdWriter::stderr())); - let color_mode_init = { - let no_color = env::var("NO_COLOR"); - let clicolor_force = env::var("CLICOLOR_FORCE"); - let clicolor = env::var("CLICOLOR"); - set_color_mode(no_color, clicolor_force, clicolor) - }; - match COLOR_MODE.get_or_init(|| color_mode_init) { + match *COLOR_MODE { ColorMode::Auto => { if unsafe { libc::isatty(libc::STDERR_FILENO) } != 1 { None @@ -240,7 +227,7 @@ mod imp { #[cfg(windows)] mod imp { use std::{ - env, fmt, + fmt, io::{self, Write}, mem, }; @@ -252,7 +239,7 @@ mod imp { use crate::{ encode::{ self, - writer::console::{set_color_mode, ColorMode, COLOR_MODE}, + writer::console::{ColorMode, COLOR_MODE}, Color, Style, }, priv_io::{StdWriter, StdWriterLock}, @@ -348,13 +335,7 @@ mod imp { inner: StdWriter::stdout(), }; - let color_mode_init = { - let no_color = env::var("NO_COLOR"); - let clicolor_force = env::var("CLICOLOR_FORCE"); - let clicolor = env::var("CLICOLOR"); - set_color_mode(no_color, clicolor_force, clicolor) - }; - match COLOR_MODE.get_or_init(|| color_mode_init) { + match *COLOR_MODE { ColorMode::Auto | ColorMode::Always => Some(writer), ColorMode::Never => None, } @@ -381,13 +362,7 @@ mod imp { inner: StdWriter::stdout(), }; - let color_mode_init = { - let no_color = env::var("NO_COLOR"); - let clicolor_force = env::var("CLICOLOR_FORCE"); - let clicolor = env::var("CLICOLOR"); - set_color_mode(no_color, clicolor_force, clicolor) - }; - match COLOR_MODE.get_or_init(|| color_mode_init) { + match *COLOR_MODE { ColorMode::Auto | ColorMode::Always => Some(writer), ColorMode::Never => None, } @@ -460,159 +435,32 @@ mod imp { #[cfg(test)] mod test { + use std::io::Write; + use super::*; use crate::encode::{Color, Style, Write as EncodeWrite}; - use std::{env::VarError, io::Write}; - // Unable to test the non locked Console as by definition, the unlocked - // console results in race conditions. Codecov tooling does not seem to - // see this test as coverage of the ConsoleWritterLock or WriterLock - // class, however, it should completely cover either. #[test] - fn test_stdout_console_writer_lock() { + fn basic() { let w = match ConsoleWriter::stdout() { Some(w) => w, None => return, }; - let mut writer = w.lock(); - - writer.write(b"normal ").unwrap(); - writer - .set_style( - Style::new() - .text(Color::Red) - .background(Color::Blue) - .intense(true), - ) - .unwrap(); - writer.write_all(b"styled").unwrap(); - writer - .set_style(&Style::new().text(Color::Green).intense(false)) - .unwrap(); - writer.write_all(b" styled2").unwrap(); - writer.set_style(&Style::new()).unwrap(); - writer.write_fmt(format_args!(" {} \n", "normal")).unwrap(); - writer.flush().unwrap(); - } - - #[test] - fn test_stderr_console_writer_lock() { - let w = match ConsoleWriter::stderr() { - Some(w) => w, - None => return, - }; - - // Do not operate on the stderr writer. Doing so results in undefined - // test result behavior due to tests running in parallel - let _ = w.lock(); - } - - #[test] - fn test_color_mode_default() { - let no_color = Err(VarError::NotPresent); - let clicolor_force = Err(VarError::NotPresent); - let clicolor = Err(VarError::NotPresent); - - let color_mode: OnceCell = OnceCell::new(); - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Auto - ); - } - - // Note that NO_COLOR has priority over all other fields - #[test] - fn test_no_color() { - let no_color = Ok("1".to_owned()); - let clicolor_force = Err(VarError::NotPresent); - let clicolor = Err(VarError::NotPresent); - - let mut color_mode: OnceCell = OnceCell::new(); - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Never - ); - - let no_color = Ok("1".to_owned()); - let clicolor_force = Ok("1".to_owned()); - let clicolor = Ok("1".to_owned()); - - let _ = color_mode.take(); // Clear the owned value - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Never - ); - } - - #[test] - fn test_cli_force() { - // CLICOLOR_FORCE is the only set field - let no_color = Err(VarError::NotPresent); - let clicolor_force = Ok("1".to_owned()); - let clicolor = Err(VarError::NotPresent); - - let mut color_mode: OnceCell = OnceCell::new(); - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Always - ); - - // Although NO_COLOR has priority, when set to 0 next in line - // is CLICOLOR_FORCE which maintains precedence over clicolor - // regardless of how it's set. Attempt both settings below - let no_color = Ok("0".to_owned()); - let clicolor_force = Ok("1".to_owned()); - let clicolor = Ok("1".to_owned()); - - let _ = color_mode.take(); // Clear the owned value - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Always - ); - - let no_color = Ok("0".to_owned()); - let clicolor_force = Ok("1".to_owned()); - let clicolor = Ok("0".to_owned()); - - let _ = color_mode.take(); // Clear the owned value - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Always - ); - } - - #[test] - fn test_cli_on() { - // CLICOLOR is the only set field - let no_color = Err(VarError::NotPresent); - let clicolor_force = Err(VarError::NotPresent); - let clicolor = Ok("1".to_owned()); - - let mut color_mode: OnceCell = OnceCell::new(); - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Auto - ); - - let no_color = Err(VarError::NotPresent); - let clicolor_force = Err(VarError::NotPresent); - let clicolor = Ok("0".to_owned()); - - let _ = color_mode.take(); // Clear the owned value - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Never - ); - - // CLICOLOR_FORCE is disabled - let no_color = Err(VarError::NotPresent); - let clicolor_force = Ok("0".to_owned()); - let clicolor = Ok("1".to_owned()); - - let _ = color_mode.take(); // Clear the owned value - assert_eq!( - color_mode.get_or_init(|| set_color_mode(no_color, clicolor_force, clicolor)), - &ColorMode::Auto - ); + let mut w = w.lock(); + + w.write_all(b"normal ").unwrap(); + w.set_style( + Style::new() + .text(Color::Red) + .background(Color::Blue) + .intense(true), + ) + .unwrap(); + w.write_all(b"styled").unwrap(); + w.set_style(Style::new().text(Color::Green)).unwrap(); + w.write_all(b" styled2").unwrap(); + w.set_style(&Style::new()).unwrap(); + w.write_all(b" normal\n").unwrap(); + w.flush().unwrap(); } }