Test existing FilterManager tests for each Duplicate enum

This commit is contained in:
ppom 2025-07-14 12:00:00 +02:00
commit 6f346ff371
No known key found for this signature in database
3 changed files with 439 additions and 414 deletions

View file

@ -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,
)))
}

View file

@ -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,
);

View file

@ -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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
),
Action::new(
vec!["sh", "-c", &format!("echo a2 <az> >> {}", &bed.out_file)],
Some("100ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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 <az> >> {}", &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 <az> >> {}", &bed.out_file)],
Some("100ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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 <az> >> {}", &bed.out_file)],
)],
vec!["test <az>"],
None,
None,
false,
"test",
"test",
"a1",
dup,
&bed.az_patterns,
)],
vec!["test <az>"],
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 <az> >> {}", &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 <az> >> {}", &bed.out_file)],
Some("100ms"),
false,
"test",
"test",
"a1",
&bed.az_patterns,
)],
vec!["test <az>"],
None,
None,
"test",
"test",
"a1",
dup,
&bed.az_patterns,
)],
vec!["test <az>"],
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 <az> >> {}", &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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
)],
vec!["test <az>"],
Some(2),
Some("2s"),
"test",
"test",
"a1",
dup,
&bed.az_patterns,
)],
vec!["test <az>"],
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 <az> >> {}", &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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
)],
vec!["test <az>"],
Some(2),
Some("1s"),
"test",
"test",
"a1",
dup,
&bed.az_patterns,
)],
vec!["test <az>"],
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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
),
Action::new(
vec!["sh", "-c", &format!("echo a2 <az> >> {}", &bed.out_file)],
Some("200ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
),
Action::new(
vec!["sh", "-c", &format!("echo a2 <az> >> {}", &bed.out_file)],
Some("200ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
),
Action::new(
vec!["sh", "-c", &format!("echo a2 <az> >> {}", &bed.out_file)],
Some("200ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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 <az> >> {}", &bed.out_file)],
None,
false,
"test",
"test",
"a1",
&bed.az_patterns,
),
Action::new(
vec!["sh", "-c", &format!("echo a2 <az> >> {}", &bed.out_file)],
Some("200ms"),
false,
"test",
"test",
"a2",
&bed.az_patterns,
),
],
vec!["test <az>"],
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