From 2b25ae4006ffcc276a8093326d0f43255716834f Mon Sep 17 00:00:00 2001
From: bconn98 <brc5229@rit.edu>
Date: Sat, 2 Mar 2024 21:41:57 -0500
Subject: [PATCH] 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 e1019686..9f648269 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,19 +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 }
-<<<<<<< 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 {
         <W as Write>::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::<EncoderConfig>(
+            &[
+                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<Chunk> = 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<Chunk> = 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<Chunk> = 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<Chunk> = 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<Chunk> = 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<Chunk> = 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<Chunk> = 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<ColorMode> = 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<ColorMode> = OnceCell::new();
+
+fn set_color_mode(
+    no_color: Result<String, env::VarError>,
+    clicolor_force: Result<String, env::VarError>,
+    clicolor: Result<String, env::VarError>,
+) -> 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<Writer> {
             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<Writer> {
             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<ColorMode> = 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<ColorMode> = 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<ColorMode> = 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<ColorMode> = 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");
-    }
-}