diff --git a/src/concepts/filter.rs b/src/concepts/filter.rs index d79be4f..99d83c6 100644 --- a/src/concepts/filter.rs +++ b/src/concepts/filter.rs @@ -354,6 +354,7 @@ impl Filter { retry_period: Option<&str>, stream_name: &str, name: &str, + duplicate: Duplicate, config_patterns: &Patterns, ) -> Self { let mut filter = Self { @@ -361,6 +362,7 @@ impl Filter { regex: regex.into_iter().map(|s| s.into()).collect(), retry, retry_period: retry_period.map(|s| s.into()), + duplicate, ..Default::default() }; filter.setup(stream_name, name, config_patterns).unwrap(); @@ -374,6 +376,7 @@ impl Filter { retry_period: Option<&str>, stream_name: &str, name: &str, + duplicate: Duplicate, config_patterns: &Patterns, ) -> &'static Self { Box::leak(Box::new(Self::new( @@ -383,6 +386,7 @@ impl Filter { retry_period, stream_name, name, + duplicate, config_patterns, ))) } diff --git a/src/daemon/filter/state.rs b/src/daemon/filter/state.rs index a67039d..f5d9692 100644 --- a/src/daemon/filter/state.rs +++ b/src/daemon/filter/state.rs @@ -232,7 +232,7 @@ mod tests { use chrono::{DateTime, Local, TimeDelta}; use crate::{ - concepts::{filter_tests::ok_filter, Action, Filter, Pattern}, + concepts::{filter_tests::ok_filter, Action, Duplicate, Filter, Pattern}, daemon::filter::state::State, tests::TempDatabase, }; @@ -256,6 +256,7 @@ mod tests { Some("2s"), "s1", "f1", + Duplicate::default(), &patterns, ); @@ -384,6 +385,7 @@ mod tests { Some("2s"), "s1", "f1", + Duplicate::default(), &patterns, ); diff --git a/src/daemon/filter/tests.rs b/src/daemon/filter/tests.rs index 44a4a02..5d095d3 100644 --- a/src/daemon/filter/tests.rs +++ b/src/daemon/filter/tests.rs @@ -11,7 +11,7 @@ use tokio::sync::Semaphore; use super::{state::filter_ordered_times_db_name, FilterManager, React}; use crate::{ - concepts::{Action, Filter, Pattern, Patterns, Time}, + concepts::{Action, Duplicate, Filter, Pattern, Patterns, Time}, daemon::shutdown::ShutdownController, tests::TempDatabase, }; @@ -83,10 +83,142 @@ impl TestBed2 { #[tokio::test] async fn three_matches_then_action_then_delayed_action() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![ - Action::new( + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![ + Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + None, + false, + "test", + "test", + "a1", + &bed.az_patterns, + ), + Action::new( + vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], + Some("100ms"), + false, + "test", + "test", + "a2", + &bed.az_patterns, + ), + ], + vec!["test "], + Some(3), + Some("2s"), + "test", + "test", + dup, + &bed.az_patterns, + ); + + let bed = bed.part2(filter, Local::now(), None).await; + + let now = bed.now; + let now1s = bed.now + TimeDelta::seconds(1); + let now2s = bed.now + TimeDelta::seconds(2); + + // No match + assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); + bed.assert_empty_trees(); + + // First match + let one = vec!["one".to_string()]; + assert_eq!(bed.manager.handle_line("test one", now), React::Match); + { + let state = bed.manager.state.lock().unwrap(); + assert_eq!( + state.matches, + BTreeMap::from([(one.clone(), BTreeSet::from([now]))]), + "the match has been added to matches" + ); + assert_eq!( + state.ordered_times.tree(), + &BTreeMap::from([(now, one.clone())]), + "the match has been added to ordered_times" + ); + assert!(state.triggers.is_empty(), "triggers is still empty"); + } + + // Second match + assert_eq!(bed.manager.handle_line("test one", now1s), React::Match); + { + let state = bed.manager.state.lock().unwrap(); + assert_eq!( + state.matches, + BTreeMap::from([(one.clone(), BTreeSet::from([now, now1s]))]), + "a second match is present in matches" + ); + assert_eq!( + state.ordered_times.tree(), + &BTreeMap::from([(now, one.clone()), (now1s, one.clone())]), + "a second match is present in ordered_times" + ); + assert!(state.triggers.is_empty(), "triggers is still empty"); + } + + // Third match, exec + let _block = bed.semaphore.acquire().await.unwrap(); + assert_eq!(bed.manager.handle_line("test one", now2s), React::Trigger); + { + let state = bed.manager.state.lock().unwrap(); + assert!( + state.matches.is_empty(), + "matches are emptied after trigger" + ); + assert!( + state.ordered_times.is_empty(), + "ordered_times are emptied after trigger" + ); + assert_eq!( + state.triggers.tree(), + &BTreeMap::from([(one.clone(), BTreeMap::from([(now2s, 1)]))]), + // 1 and not 2 because the decrement_trigger() doesn't wait for the semaphore + "triggers now contain the triggered match with 1 action left" + ); + } + drop(_block); + + // Now the first action executes + tokio::time::sleep(Duration::from_millis(40)).await; + // Check first action + assert_eq!( + bed.manager.state.lock().unwrap().triggers.tree(), + &BTreeMap::from([(one.clone(), BTreeMap::from([(now2s, 1)]))]), + "triggers still contain the triggered match with 1 action left" + ); + assert_eq!( + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the first action" + ); + + // Now the second action executes + tokio::time::sleep(Duration::from_millis(100)).await; + // Check second action + assert!( + bed.manager.state.lock().unwrap().triggers.is_empty(), + "triggers are empty again" + ); + assert_eq!( + "a1 one\na2 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the 2 actions" + ); + + bed.assert_empty_trees(); + } +} + +#[tokio::test] +async fn one_match_one_action() { + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![Action::new( vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], None, false, @@ -94,326 +226,209 @@ async fn three_matches_then_action_then_delayed_action() { "test", "a1", &bed.az_patterns, - ), - Action::new( - vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], - Some("100ms"), - false, - "test", - "test", - "a2", - &bed.az_patterns, - ), - ], - vec!["test "], - Some(3), - Some("2s"), - "test", - "test", - &bed.az_patterns, - ); - - let bed = bed.part2(filter, Local::now(), None).await; - - let now = bed.now; - let now1s = bed.now + TimeDelta::seconds(1); - let now2s = bed.now + TimeDelta::seconds(2); - - // No match - assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); - bed.assert_empty_trees(); - - // First match - let one = vec!["one".to_string()]; - assert_eq!(bed.manager.handle_line("test one", now), React::Match); - { - let state = bed.manager.state.lock().unwrap(); - assert_eq!( - state.matches, - BTreeMap::from([(one.clone(), BTreeSet::from([now]))]), - "the match has been added to matches" - ); - assert_eq!( - state.ordered_times.tree(), - &BTreeMap::from([(now, one.clone())]), - "the match has been added to ordered_times" - ); - assert!(state.triggers.is_empty(), "triggers is still empty"); - } - - // Second match - assert_eq!(bed.manager.handle_line("test one", now1s), React::Match); - { - let state = bed.manager.state.lock().unwrap(); - assert_eq!( - state.matches, - BTreeMap::from([(one.clone(), BTreeSet::from([now, now1s]))]), - "a second match is present in matches" - ); - assert_eq!( - state.ordered_times.tree(), - &BTreeMap::from([(now, one.clone()), (now1s, one.clone())]), - "a second match is present in ordered_times" - ); - assert!(state.triggers.is_empty(), "triggers is still empty"); - } - - // Third match, exec - let _block = bed.semaphore.acquire().await.unwrap(); - assert_eq!(bed.manager.handle_line("test one", now2s), React::Trigger); - { - let state = bed.manager.state.lock().unwrap(); - assert!( - state.matches.is_empty(), - "matches are emptied after trigger" - ); - assert!( - state.ordered_times.is_empty(), - "ordered_times are emptied after trigger" - ); - assert_eq!( - state.triggers.tree(), - &BTreeMap::from([(one.clone(), BTreeMap::from([(now2s, 1)]))]), - // 1 and not 2 because the decrement_trigger() doesn't wait for the semaphore - "triggers now contain the triggered match with 1 action left" - ); - } - drop(_block); - - // Now the first action executes - tokio::time::sleep(Duration::from_millis(40)).await; - // Check first action - assert_eq!( - bed.manager.state.lock().unwrap().triggers.tree(), - &BTreeMap::from([(one.clone(), BTreeMap::from([(now2s, 1)]))]), - "triggers still contain the triggered match with 1 action left" - ); - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the first action" - ); - - // Now the second action executes - tokio::time::sleep(Duration::from_millis(100)).await; - // Check second action - assert!( - bed.manager.state.lock().unwrap().triggers.is_empty(), - "triggers are empty again" - ); - assert_eq!( - "a1 one\na2 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the 2 actions" - ); - - bed.assert_empty_trees(); -} - -#[tokio::test] -async fn one_match_one_action() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + )], + vec!["test "], + None, None, - false, "test", "test", - "a1", + dup, &bed.az_patterns, - )], - vec!["test "], - None, - None, - "test", - "test", - &bed.az_patterns, - ); + ); - let bed = bed.part2(filter, Local::now(), None).await; - let now = bed.now; + let bed = bed.part2(filter, Local::now(), None).await; + let now = bed.now; - // No match - assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); - bed.assert_empty_trees(); + // No match + assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); + bed.assert_empty_trees(); - // match - assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); - bed.assert_empty_trees(); + // match + assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); + bed.assert_empty_trees(); - // the action executes - tokio::time::sleep(Duration::from_millis(40)).await; - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the first action" - ); + // the action executes + tokio::time::sleep(Duration::from_millis(40)).await; + assert_eq!( + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the first action" + ); - bed.assert_empty_trees(); + bed.assert_empty_trees(); + } } #[tokio::test] async fn one_match_one_delayed_action() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], - Some("100ms"), - false, + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + Some("100ms"), + false, + "test", + "test", + "a1", + &bed.az_patterns, + )], + vec!["test "], + None, + None, "test", "test", - "a1", + dup, &bed.az_patterns, - )], - vec!["test "], - None, - None, - "test", - "test", - &bed.az_patterns, - ); - - let bed = bed.part2(filter, Local::now(), None).await; - let now = bed.now; - - // No match - assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); - bed.assert_empty_trees(); - - // Match - let one = vec!["one".to_string()]; - assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); - { - let state = bed.manager.state.lock().unwrap(); - assert!(state.matches.is_empty(), "matches stay empty"); - assert!(state.ordered_times.is_empty(), "ordered_times stay empty"); - assert_eq!( - state.triggers.tree(), - &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), - "triggers still contain the triggered match with 1 action left" ); + + let bed = bed.part2(filter, Local::now(), None).await; + let now = bed.now; + + // No match + assert_eq!(bed.manager.handle_line("test 131", now), React::NoMatch); + bed.assert_empty_trees(); + + // Match + let one = vec!["one".to_string()]; + assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); + { + let state = bed.manager.state.lock().unwrap(); + assert!(state.matches.is_empty(), "matches stay empty"); + assert!(state.ordered_times.is_empty(), "ordered_times stay empty"); + assert_eq!( + state.triggers.tree(), + &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), + "triggers still contain the triggered match with 1 action left" + ); + } + assert_eq!( + "", + &read_to_string(&bed.out_file).unwrap(), + "the output file is empty" + ); + + // The action executes + tokio::time::sleep(Duration::from_millis(140)).await; + assert!( + bed.manager.state.lock().unwrap().triggers.is_empty(), + "triggers are empty again" + ); + assert_eq!( + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the action" + ); + + bed.assert_empty_trees(); } - assert_eq!( - "", - &read_to_string(&bed.out_file).unwrap(), - "the output file is empty" - ); - - // The action executes - tokio::time::sleep(Duration::from_millis(140)).await; - assert!( - bed.manager.state.lock().unwrap().triggers.is_empty(), - "triggers are empty again" - ); - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the action" - ); - - bed.assert_empty_trees(); } #[tokio::test] async fn one_db_match_one_runtime_match_one_action() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], - None, - false, + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + None, + false, + "test", + "test", + "a1", + &bed.az_patterns, + )], + vec!["test "], + Some(2), + Some("2s"), "test", "test", - "a1", + dup, &bed.az_patterns, - )], - vec!["test "], - Some(2), - Some("2s"), - "test", - "test", - &bed.az_patterns, - ); - - let mut db = TempDatabase::default().await; - - // Pre-add match - let now = Local::now(); - let one = vec!["one".to_string()]; - let now1s = now - TimeDelta::seconds(1); - - db.set_loaded_db(HashMap::from([( - filter_ordered_times_db_name(filter), - HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), - )])); - - // Finish setup - let bed = bed.part2(filter, now, Some(db)).await; - - { - let state = bed.manager.state.lock().unwrap(); - assert_eq!( - state.matches, - BTreeMap::from([(one.clone(), BTreeSet::from([now1s]))]), - "the match previously added to matches" ); + + let mut db = TempDatabase::default().await; + + // Pre-add match + let now = Local::now(); + let one = vec!["one".to_string()]; + let now1s = now - TimeDelta::seconds(1); + + db.set_loaded_db(HashMap::from([( + filter_ordered_times_db_name(filter), + HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), + )])); + + // Finish setup + let bed = bed.part2(filter, now, Some(db)).await; + + { + let state = bed.manager.state.lock().unwrap(); + assert_eq!( + state.matches, + BTreeMap::from([(one.clone(), BTreeSet::from([now1s]))]), + "the match previously added to matches" + ); + assert_eq!( + state.ordered_times.tree(), + &BTreeMap::from([(now1s, one.clone())]), + "the match previously added to matches" + ); + assert!(state.triggers.is_empty(), "triggers stay empty"); + } + + // match + assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); + bed.assert_empty_trees(); + // the action executes + tokio::time::sleep(Duration::from_millis(40)).await; assert_eq!( - state.ordered_times.tree(), - &BTreeMap::from([(now1s, one.clone())]), - "the match previously added to matches" + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the action" ); - assert!(state.triggers.is_empty(), "triggers stay empty"); } - - // match - assert_eq!(bed.manager.handle_line("test one", now), React::Trigger); - bed.assert_empty_trees(); - // the action executes - tokio::time::sleep(Duration::from_millis(40)).await; - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the action" - ); } #[tokio::test] async fn one_outdated_db_match() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], - None, - false, + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + None, + false, + "test", + "test", + "a1", + &bed.az_patterns, + )], + vec!["test "], + Some(2), + Some("1s"), "test", "test", - "a1", + dup, &bed.az_patterns, - )], - vec!["test "], - Some(2), - Some("1s"), - "test", - "test", - &bed.az_patterns, - ); + ); - let mut db = TempDatabase::default().await; + let mut db = TempDatabase::default().await; - // Pre-add match - let now = Local::now(); - let one = vec!["one".to_string()]; - let now1s = now - TimeDelta::milliseconds(1001); + // Pre-add match + let now = Local::now(); + let one = vec!["one".to_string()]; + let now1s = now - TimeDelta::milliseconds(1001); - db.set_loaded_db(HashMap::from([( - filter_ordered_times_db_name(filter), - HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), - )])); + db.set_loaded_db(HashMap::from([( + filter_ordered_times_db_name(filter), + HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), + )])); - // Finish setup - let bed = bed.part2(filter, now, Some(db)).await; - bed.assert_empty_trees(); + // Finish setup + let bed = bed.part2(filter, now, Some(db)).await; + bed.assert_empty_trees(); + } } #[tokio::test] @@ -423,145 +438,149 @@ async fn flush() { #[tokio::test] async fn trigger_unmatched_pattern() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![ - Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], - None, - false, - "test", - "test", - "a1", - &bed.az_patterns, - ), - Action::new( - vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], - Some("200ms"), - false, - "test", - "test", - "a2", - &bed.az_patterns, - ), - ], - vec!["test "], - Some(2), - Some("1s"), - "test", - "test", - &bed.az_patterns, - ); + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![ + Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + None, + false, + "test", + "test", + "a1", + &bed.az_patterns, + ), + Action::new( + vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], + Some("200ms"), + false, + "test", + "test", + "a2", + &bed.az_patterns, + ), + ], + vec!["test "], + Some(2), + Some("1s"), + "test", + "test", + dup, + &bed.az_patterns, + ); - let now = Local::now(); - let one = vec!["one".to_string()]; - let bed = bed.part2(filter, now, None).await; + let now = Local::now(); + let one = vec!["one".to_string()]; + let bed = bed.part2(filter, now, None).await; - bed.manager - .handle_trigger( - // az_pattern: "one" - bed.az_patterns - .values() - .cloned() - .map(|pattern| (pattern, one[0].clone())) - .collect(), - now, - ) - .unwrap(); + bed.manager + .handle_trigger( + // az_pattern: "one" + bed.az_patterns + .values() + .cloned() + .map(|pattern| (pattern, one[0].clone())) + .collect(), + now, + ) + .unwrap(); - // the action executes - tokio::time::sleep(Duration::from_millis(40)).await; + // the action executes + tokio::time::sleep(Duration::from_millis(40)).await; - // No matches, one action registered - { - let state = bed.manager.state.lock().unwrap(); - assert!(state.matches.is_empty()); - assert!(state.ordered_times.is_empty()); + // No matches, one action registered + { + let state = bed.manager.state.lock().unwrap(); + assert!(state.matches.is_empty()); + assert!(state.ordered_times.is_empty()); + assert_eq!( + state.triggers.tree(), + &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), + ); + } assert_eq!( - state.triggers.tree(), - &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the action" ); } - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the action" - ); } #[tokio::test] async fn trigger_matched_pattern() { - let bed = TestBed::new(); - let filter = Filter::new_static( - vec![ - Action::new( - vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], - None, - false, - "test", - "test", - "a1", - &bed.az_patterns, - ), - Action::new( - vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], - Some("200ms"), - false, - "test", - "test", - "a2", - &bed.az_patterns, - ), - ], - vec!["test "], - Some(2), - Some("1s"), - "test", - "test", - &bed.az_patterns, - ); + for dup in [Duplicate::Rerun, Duplicate::Extend, Duplicate::Ignore] { + let bed = TestBed::new(); + let filter = Filter::new_static( + vec![ + Action::new( + vec!["sh", "-c", &format!("echo a1 >> {}", &bed.out_file)], + None, + false, + "test", + "test", + "a1", + &bed.az_patterns, + ), + Action::new( + vec!["sh", "-c", &format!("echo a2 >> {}", &bed.out_file)], + Some("200ms"), + false, + "test", + "test", + "a2", + &bed.az_patterns, + ), + ], + vec!["test "], + Some(2), + Some("1s"), + "test", + "test", + dup, + &bed.az_patterns, + ); - let now = Local::now(); - let now1s = now - TimeDelta::milliseconds(10); - let one = vec!["one".to_string()]; + let now = Local::now(); + let now1s = now - TimeDelta::milliseconds(10); + let one = vec!["one".to_string()]; - let mut db = TempDatabase::default().await; - db.set_loaded_db(HashMap::from([( - filter_ordered_times_db_name(filter), - HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), - )])); - let bed = bed.part2(filter, now, Some(db)).await; + let mut db = TempDatabase::default().await; + db.set_loaded_db(HashMap::from([( + filter_ordered_times_db_name(filter), + HashMap::from([(now1s.to_rfc3339().into(), one.clone().into())]), + )])); + let bed = bed.part2(filter, now, Some(db)).await; - bed.manager - .handle_trigger( - // az_pattern: "one" - bed.az_patterns - .values() - .cloned() - .map(|pattern| (pattern, one[0].clone())) - .collect(), - now, - ) - .unwrap(); + bed.manager + .handle_trigger( + // az_pattern: "one" + bed.az_patterns + .values() + .cloned() + .map(|pattern| (pattern, one[0].clone())) + .collect(), + now, + ) + .unwrap(); - // the action executes - tokio::time::sleep(Duration::from_millis(40)).await; + // the action executes + tokio::time::sleep(Duration::from_millis(40)).await; - // No matches, one action registered - { - let state = bed.manager.state.lock().unwrap(); - assert!(state.matches.is_empty()); - assert!(state.ordered_times.is_empty()); + // No matches, one action registered + { + let state = bed.manager.state.lock().unwrap(); + assert!(state.matches.is_empty()); + assert!(state.ordered_times.is_empty()); + assert_eq!( + state.triggers.tree(), + &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), + ); + } assert_eq!( - state.triggers.tree(), - &BTreeMap::from([(one.clone(), BTreeMap::from([(now, 1)]))]), + "a1 one\n", + &read_to_string(&bed.out_file).unwrap(), + "the output file contains the result of the action" ); } - assert_eq!( - "a1 one\n", - &read_to_string(&bed.out_file).unwrap(), - "the output file contains the result of the action" - ); } - -// TODO test State functions