diff --git a/Cargo.lock b/Cargo.lock index 8d2349a..f4d9019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -723,6 +729,7 @@ dependencies = [ "nix", "nu-ansi-term 0.50.0", "once_cell", + "regex-lite", "serde", "serde_json", "subprocess", diff --git a/tokio-bin-process/Cargo.toml b/tokio-bin-process/Cargo.toml index 0a87b12..d73ccb1 100644 --- a/tokio-bin-process/Cargo.toml +++ b/tokio-bin-process/Cargo.toml @@ -21,6 +21,7 @@ itertools = "0.13.0" once_cell = "1.17.1" chrono = "0.4.24" cargo_metadata = "0.18.0" +regex-lite = "0.1.6" [dev-dependencies] -tokio = { version = "1.25.0", features = ["macros", "rt-multi-thread", "time"] } \ No newline at end of file +tokio = { version = "1.25.0", features = ["macros", "rt-multi-thread", "time"] } diff --git a/tokio-bin-process/single-crate-test/Cargo.lock b/tokio-bin-process/single-crate-test/Cargo.lock index d5d65aa..77a60b7 100644 --- a/tokio-bin-process/single-crate-test/Cargo.lock +++ b/tokio-bin-process/single-crate-test/Cargo.lock @@ -377,6 +377,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -539,6 +545,7 @@ dependencies = [ "nix", "nu-ansi-term", "once_cell", + "regex-lite", "serde", "serde_json", "subprocess", diff --git a/tokio-bin-process/src/event_matcher.rs b/tokio-bin-process/src/event_matcher.rs index 9c73fef..b5b4854 100644 --- a/tokio-bin-process/src/event_matcher.rs +++ b/tokio-bin-process/src/event_matcher.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use crate::event::{Event, Level}; use itertools::Itertools; +use regex_lite::Regex; /// Use to check for any matching [`Event`]'s among a list of events. #[derive(Debug)] @@ -63,6 +64,7 @@ impl Display for Events { pub struct EventMatcher { level: Matcher, message: Matcher, + message_regex: RegexMatcher, target: Matcher, pub(crate) count: Count, } @@ -91,6 +93,12 @@ impl EventMatcher { self } + /// Sets the matcher to only match an [`Event`] when it matches the provided regex pattern + pub fn with_message_regex(mut self, regex_pattern: &str) -> EventMatcher { + self.message_regex = RegexMatcher::new(regex_pattern); + self + } + /// Defines how many times the matcher must match to pass an assertion /// /// This is not used internally i.e. it has no effect on [`EventMatcher::matches`] @@ -104,6 +112,7 @@ impl EventMatcher { pub fn matches(&self, event: &Event) -> bool { self.level.matches(&event.level) && self.message.matches(&event.fields.message) + && self.message_regex.matches(&event.fields.message) && self.target.matches(&event.target) } } @@ -150,3 +159,25 @@ impl Matcher { } } } + +#[derive(Debug, Default)] +enum RegexMatcher { + Matches(Regex), + #[default] + Any, +} + +impl RegexMatcher { + fn new(pattern: &str) -> Self { + RegexMatcher::Matches(regex_lite::Regex::new(pattern).unwrap()) + } +} + +impl RegexMatcher { + fn matches(&self, value: &str) -> bool { + match self { + RegexMatcher::Matches(regex) => regex.is_match(value), + RegexMatcher::Any => true, + } + } +} diff --git a/tokio-bin-process/tests/test.rs b/tokio-bin-process/tests/test.rs index 0b55970..b79b9c9 100644 --- a/tokio-bin-process/tests/test.rs +++ b/tokio-bin-process/tests/test.rs @@ -44,6 +44,47 @@ async fn test_cooldb_by_binary_name_bench_profile() { cooldb.shutdown_and_then_consume_events(&[]).await; } +#[tokio::test(flavor = "multi_thread")] +async fn test_message_regex_match() { + // Setup cooldb with custom profile + let mut cooldb = cooldb(Some("bench")).await; + + // Assert that some functionality occured. + // Use a timeout to prevent the test hanging if no events occur. + timeout(Duration::from_secs(5), cooldb.consume_events(1, &[])) + .await + .unwrap() + .assert_contains( + &EventMatcher::new() + .with_level(Level::Info) + .with_message_regex("some .* occurs"), + ); + + // Shutdown cooldb asserting that it encountered no errors + cooldb.shutdown_and_then_consume_events(&[]).await; +} + +#[should_panic] +#[tokio::test(flavor = "multi_thread")] +async fn test_message_regex_no_match() { + // Setup cooldb with custom profile + let mut cooldb = cooldb(Some("bench")).await; + + // Assert that some functionality occured. + // Use a timeout to prevent the test hanging if no events occur. + timeout(Duration::from_secs(5), cooldb.consume_events(1, &[])) + .await + .unwrap() + .assert_contains( + &EventMatcher::new() + .with_level(Level::Info) + .with_message_regex("some .* does not occur"), + ); + + // Shutdown cooldb asserting that it encountered no errors + cooldb.shutdown_and_then_consume_events(&[]).await; +} + async fn cooldb(profile: Option<&'static str>) -> BinProcess { let mut cooldb = BinProcess::start_binary_name("cooldb", "cooldb", &["--log-format", "json"], profile).await;