Replace event queue with a priority queue (#302)
Solves several issues, generally through delivering signals before filesystem events, preventing situations where an overwhelming amount of events makes it impossible to quit. Does _not_ solve the problem of a queue full of lower-priority events not accepting an urgent message, but that's a rarer issue that's more complicated to overcome. Changes the Filterer trait: adds Priority to `check_event()` Makes some events unfilterable (Urgent priority): SIGINT, SIGTERM, and CTRL_C to the main process. These still need to be handled by `on_action` to do anything, but cannot be stopped before reaching that.
This commit is contained in:
parent
62e79fbf7a
commit
446a8d95a7
File diff suppressed because it is too large
Load Diff
|
@ -37,11 +37,10 @@ default-features = false
|
|||
features = ["wrap_help"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.15.0"
|
||||
version = "1.19.2"
|
||||
features = [
|
||||
"fs",
|
||||
"io-std",
|
||||
"parking_lot",
|
||||
"process",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
|
|
|
@ -10,7 +10,7 @@ use watchexec::{
|
|||
error::RuntimeError,
|
||||
event::{
|
||||
filekind::{FileEventKind, ModifyKind},
|
||||
Event, Tag,
|
||||
Event, Priority, Tag,
|
||||
},
|
||||
filter::{globset::GlobsetFilterer, Filterer},
|
||||
};
|
||||
|
@ -77,7 +77,7 @@ pub struct WatchexecFilterer {
|
|||
}
|
||||
|
||||
impl Filterer for WatchexecFilterer {
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
||||
let is_meta = event.tags.iter().any(|tag| {
|
||||
matches!(
|
||||
tag,
|
||||
|
@ -88,7 +88,7 @@ impl Filterer for WatchexecFilterer {
|
|||
if self.no_meta && is_meta {
|
||||
Ok(false)
|
||||
} else {
|
||||
self.inner.check_event(event)
|
||||
self.inner.check_event(event, priority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
use std::env::var;
|
||||
|
||||
use tracing_log::LogTracer;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use tracing::debug;
|
||||
use watchexec::{event::Event, Watchexec};
|
||||
use tracing_log::LogTracer;
|
||||
use watchexec::{
|
||||
event::{Event, Priority},
|
||||
Watchexec,
|
||||
};
|
||||
|
||||
mod args;
|
||||
mod config;
|
||||
|
@ -67,7 +70,7 @@ async fn main() -> Result<()> {
|
|||
let wx = Watchexec::new(init, runtime)?;
|
||||
|
||||
if !args.is_present("postpone") {
|
||||
wx.send_event(Event::default()).await?;
|
||||
wx.send_event(Event::default(), Priority::Urgent).await?;
|
||||
}
|
||||
|
||||
wx.main().await.into_diagnostic()??;
|
||||
|
|
|
@ -16,6 +16,7 @@ rust-version = "1.58.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-priority-channel = "0.1.0"
|
||||
async-recursion = "1.0.0"
|
||||
async-stream = "0.3.2"
|
||||
atomic-take = "1.0.0"
|
||||
|
@ -40,11 +41,10 @@ features = ["with-tokio"]
|
|||
version = "=5.0.0-pre.14"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.15.0"
|
||||
version = "1.19.2"
|
||||
features = [
|
||||
"fs",
|
||||
"io-std",
|
||||
"parking_lot",
|
||||
"process",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use tokio::{
|
||||
sync::{mpsc, watch},
|
||||
time::sleep,
|
||||
};
|
||||
use watchexec::{event::Event, fs};
|
||||
use watchexec::{
|
||||
event::{Event, Priority},
|
||||
fs,
|
||||
};
|
||||
|
||||
// Run with: `env RUST_LOG=debug cargo run --example fs`,
|
||||
// then touch some files within the first 15 seconds, and afterwards.
|
||||
|
@ -13,7 +17,7 @@ use watchexec::{event::Event, fs};
|
|||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (ev_s, mut ev_r) = mpsc::channel::<Event>(1024);
|
||||
let (ev_s, ev_r) = priority::bounded::<Event, Priority>(1024);
|
||||
let (er_s, mut er_r) = mpsc::channel(64);
|
||||
let (wd_s, wd_r) = watch::channel(fs::WorkingData::default());
|
||||
|
||||
|
@ -22,14 +26,14 @@ async fn main() -> Result<()> {
|
|||
wd_s.send(wkd.clone()).into_diagnostic()?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(e) = ev_r.recv().await {
|
||||
tracing::info!("event: {:?}", e);
|
||||
while let Ok((event, priority)) = ev_r.recv().await {
|
||||
tracing::info!("event ({priority:?}): {event:?}");
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(e) = er_r.recv().await {
|
||||
tracing::error!("error: {}", e);
|
||||
while let Some(error) = er_r.recv().await {
|
||||
tracing::error!("error: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::process::exit;
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use miette::Result;
|
||||
use tokio::sync::mpsc;
|
||||
use watchexec::{
|
||||
event::{Event, Tag},
|
||||
event::{Event, Priority, Tag},
|
||||
signal::{self, source::MainSignal},
|
||||
};
|
||||
|
||||
|
@ -14,22 +15,22 @@ use watchexec::{
|
|||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (ev_s, mut ev_r) = mpsc::channel::<Event>(1024);
|
||||
let (ev_s, ev_r) = priority::bounded::<Event, Priority>(1024);
|
||||
let (er_s, mut er_r) = mpsc::channel(64);
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(e) = ev_r.recv().await {
|
||||
tracing::info!("event: {:?}", e);
|
||||
while let Ok((event, priority)) = ev_r.recv().await {
|
||||
tracing::info!("event {priority:?}: {event:?}");
|
||||
|
||||
if e.tags.contains(&Tag::Signal(MainSignal::Terminate)) {
|
||||
if event.tags.contains(&Tag::Signal(MainSignal::Terminate)) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(e) = er_r.recv().await {
|
||||
tracing::error!("{}", e);
|
||||
while let Some(error) = er_r.recv().await {
|
||||
tracing::error!("error: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::sync::{
|
|||
Arc,
|
||||
};
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use clearscreen::ClearScreen;
|
||||
use futures::Future;
|
||||
use tokio::{
|
||||
|
@ -11,7 +12,12 @@ use tokio::{
|
|||
};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{command::Supervisor, error::RuntimeError, event::Event, handler::rte};
|
||||
use crate::{
|
||||
command::Supervisor,
|
||||
error::RuntimeError,
|
||||
event::{Event, Priority},
|
||||
handler::rte,
|
||||
};
|
||||
|
||||
use super::{process_holder::ProcessHolder, Outcome, PostSpawn, PreSpawn, WorkingData};
|
||||
|
||||
|
@ -23,7 +29,7 @@ pub struct OutcomeWorker {
|
|||
gen: usize,
|
||||
gencheck: Arc<AtomicUsize>,
|
||||
errors_c: mpsc::Sender<RuntimeError>,
|
||||
events_c: mpsc::Sender<Event>,
|
||||
events_c: priority::Sender<Event, Priority>,
|
||||
}
|
||||
|
||||
impl OutcomeWorker {
|
||||
|
@ -38,7 +44,7 @@ impl OutcomeWorker {
|
|||
process: ProcessHolder,
|
||||
gencheck: Arc<AtomicUsize>,
|
||||
errors_c: mpsc::Sender<RuntimeError>,
|
||||
events_c: mpsc::Sender<Event>,
|
||||
events_c: priority::Sender<Event, Priority>,
|
||||
) {
|
||||
let gen = gencheck.fetch_add(1, Ordering::SeqCst).wrapping_add(1);
|
||||
let this = Self {
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
|
@ -14,7 +15,7 @@ use tracing::{debug, trace};
|
|||
|
||||
use crate::{
|
||||
error::{CriticalError, RuntimeError},
|
||||
event::Event,
|
||||
event::{Event, Priority},
|
||||
handler::rte,
|
||||
};
|
||||
|
||||
|
@ -28,8 +29,8 @@ use super::{outcome_worker::OutcomeWorker, process_holder::ProcessHolder, Action
|
|||
pub async fn worker(
|
||||
working: watch::Receiver<WorkingData>,
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events_tx: mpsc::Sender<Event>,
|
||||
mut events: mpsc::Receiver<Event>,
|
||||
events_tx: priority::Sender<Event, Priority>,
|
||||
events: priority::Receiver<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
let mut last = Instant::now();
|
||||
let mut set = Vec::new();
|
||||
|
@ -37,6 +38,11 @@ pub async fn worker(
|
|||
let outcome_gen = OutcomeWorker::newgen();
|
||||
|
||||
loop {
|
||||
if events.is_closed() {
|
||||
trace!("events channel closed, stopping");
|
||||
break;
|
||||
}
|
||||
|
||||
let maxtime = if set.is_empty() {
|
||||
trace!("nothing in set, waiting forever for next event");
|
||||
Duration::from_secs(u64::MAX)
|
||||
|
@ -54,19 +60,27 @@ pub async fn worker(
|
|||
}
|
||||
} else {
|
||||
trace!(?maxtime, "waiting for event");
|
||||
match timeout(maxtime, events.recv()).await {
|
||||
let maybe_event = timeout(maxtime, events.recv()).await;
|
||||
if events.is_closed() {
|
||||
trace!("events channel closed during timeout, stopping");
|
||||
break;
|
||||
}
|
||||
|
||||
match maybe_event {
|
||||
Err(_timeout) => {
|
||||
trace!("timed out, cycling");
|
||||
continue;
|
||||
}
|
||||
Ok(None) => break,
|
||||
Ok(Some(event)) => {
|
||||
trace!(?event, "got event");
|
||||
Ok(Err(_empty)) => break,
|
||||
Ok(Ok((event, priority))) => {
|
||||
trace!(?event, ?priority, "got event");
|
||||
|
||||
if event.is_empty() {
|
||||
if priority == Priority::Urgent {
|
||||
trace!("urgent event, by-passing filters");
|
||||
} else if event.is_empty() {
|
||||
trace!("empty event, by-passing filters");
|
||||
} else {
|
||||
let filtered = working.borrow().filterer.check_event(&event);
|
||||
let filtered = working.borrow().filterer.check_event(&event, priority);
|
||||
match filtered {
|
||||
Err(err) => {
|
||||
trace!(%err, "filter errored on event");
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use async_priority_channel as priority;
|
||||
use command_group::AsyncCommandGroup;
|
||||
use tokio::{
|
||||
process::Command,
|
||||
|
@ -11,7 +12,7 @@ use tracing::{debug, error, trace};
|
|||
|
||||
use crate::{
|
||||
error::RuntimeError,
|
||||
event::{Event, Source, Tag},
|
||||
event::{Event, Priority, Source, Tag},
|
||||
signal::process::SubSignal,
|
||||
};
|
||||
|
||||
|
@ -39,7 +40,7 @@ impl Supervisor {
|
|||
/// Spawns the command, the supervision task, and returns a new control object.
|
||||
pub fn spawn(
|
||||
errors: Sender<RuntimeError>,
|
||||
events: Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
command: &mut Command,
|
||||
grouped: bool,
|
||||
) -> Result<Self, RuntimeError> {
|
||||
|
@ -140,7 +141,7 @@ impl Supervisor {
|
|||
};
|
||||
|
||||
debug!(?event, "creating synthetic process completion event");
|
||||
if let Err(err) = events.send(event).await {
|
||||
if let Err(err) = events.send(event, Priority::Low).await {
|
||||
error!(%err, "while sending process completion event");
|
||||
errors
|
||||
.send(RuntimeError::EventChannelSend {
|
||||
|
|
|
@ -2,7 +2,7 @@ use miette::Diagnostic;
|
|||
use thiserror::Error;
|
||||
use tokio::{sync::mpsc, task::JoinError};
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::event::{Event, Priority};
|
||||
|
||||
use super::RuntimeError;
|
||||
|
||||
|
@ -60,7 +60,7 @@ pub enum CriticalError {
|
|||
/// Error received when an event cannot be sent to the events channel.
|
||||
#[error("cannot send event to internal channel: {0}")]
|
||||
#[diagnostic(code(watchexec::critical::event_channel_send))]
|
||||
EventChannelSend(#[from] mpsc::error::SendError<Event>),
|
||||
EventChannelSend(#[from] async_priority_channel::SendError<(Event, Priority)>),
|
||||
|
||||
/// Error received when joining the main watchexec task.
|
||||
#[error("main task join: {0}")]
|
||||
|
|
|
@ -2,9 +2,12 @@ use std::path::PathBuf;
|
|||
|
||||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{event::Event, fs::Watcher, signal::process::SubSignal};
|
||||
use crate::{
|
||||
event::{Event, Priority},
|
||||
fs::Watcher,
|
||||
signal::process::SubSignal,
|
||||
};
|
||||
|
||||
/// Errors which _may_ be recoverable, transient, or only affect a part of the operation, and should
|
||||
/// be reported to the user and/or acted upon programatically, but will not outright stop watchexec.
|
||||
|
@ -65,7 +68,7 @@ pub enum RuntimeError {
|
|||
|
||||
/// The underlying error.
|
||||
#[source]
|
||||
err: mpsc::error::SendError<Event>,
|
||||
err: async_priority_channel::SendError<(Event, Priority)>,
|
||||
},
|
||||
|
||||
/// Error received when an event cannot be sent to the event channel.
|
||||
|
@ -79,7 +82,7 @@ pub enum RuntimeError {
|
|||
|
||||
/// The underlying error.
|
||||
#[source]
|
||||
err: mpsc::error::TrySendError<Event>,
|
||||
err: async_priority_channel::TrySendError<(Event, Priority)>,
|
||||
},
|
||||
|
||||
/// Error received when a [`Handler`][crate::handler::Handler] errors.
|
||||
|
|
|
@ -256,6 +256,45 @@ impl fmt::Display for Source {
|
|||
}
|
||||
}
|
||||
|
||||
/// The priority of the event in the queue.
|
||||
///
|
||||
/// In the event queue, events are inserted with a priority, such that more important events are
|
||||
/// delivered ahead of others. This is especially important when there is a large amount of events
|
||||
/// generated and relatively slow filtering, as events can become noticeably delayed, and may give
|
||||
/// the impression of stalling.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Priority {
|
||||
/// Low priority
|
||||
///
|
||||
/// Used for:
|
||||
/// - process completion events
|
||||
Low,
|
||||
|
||||
/// Normal priority
|
||||
///
|
||||
/// Used for:
|
||||
/// - filesystem events
|
||||
Normal,
|
||||
|
||||
/// High priority
|
||||
///
|
||||
/// Used for:
|
||||
/// - signals to main process, except Interrupt and Terminate
|
||||
High,
|
||||
|
||||
/// Urgent events bypass filtering entirely.
|
||||
///
|
||||
/// Used for:
|
||||
/// - Interrupt and Terminate signals to main process
|
||||
Urgent,
|
||||
}
|
||||
|
||||
impl Default for Priority {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Returns true if the event has an Internal source tag.
|
||||
pub fn is_internal(&self) -> bool {
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{error::RuntimeError, event::Event};
|
||||
use crate::{
|
||||
error::RuntimeError,
|
||||
event::{Event, Priority},
|
||||
};
|
||||
|
||||
pub mod globset;
|
||||
pub mod tagged;
|
||||
|
@ -19,17 +22,17 @@ pub trait Filterer: std::fmt::Debug + Send + Sync {
|
|||
/// the watchexec error handler. While the type signature supports any [`RuntimeError`], it's
|
||||
/// preferred that you create your own error type and return it wrapped in the
|
||||
/// [`RuntimeError::Filterer`] variant with the name of your filterer as `kind`.
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError>;
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError>;
|
||||
}
|
||||
|
||||
impl Filterer for () {
|
||||
fn check_event(&self, _event: &Event) -> Result<bool, RuntimeError> {
|
||||
fn check_event(&self, _event: &Event, _priority: Priority) -> Result<bool, RuntimeError> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Filterer> Filterer for Arc<T> {
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
Arc::as_ref(self).check_event(event)
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
||||
Arc::as_ref(self).check_event(event, priority)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
|||
use tracing::{debug, trace, trace_span};
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::event::{Event, FileType};
|
||||
use crate::event::{Event, FileType, Priority};
|
||||
use crate::filter::Filterer;
|
||||
use crate::ignore::{IgnoreFile, IgnoreFilterer};
|
||||
|
||||
|
@ -105,14 +105,14 @@ impl Filterer for GlobsetFilterer {
|
|||
/// Filter an event.
|
||||
///
|
||||
/// This implementation never errors.
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
||||
let _span = trace_span!("filterer_check").entered();
|
||||
|
||||
{
|
||||
trace!("checking internal ignore filterer");
|
||||
if !self
|
||||
.ignore_files
|
||||
.check_event(event)
|
||||
.check_event(event, priority)
|
||||
.expect("IgnoreFilterer never errors")
|
||||
{
|
||||
trace!("internal ignore filterer matched (fail)");
|
||||
|
|
|
@ -13,7 +13,7 @@ use unicase::UniCase;
|
|||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::error::TaggedFiltererError;
|
||||
use crate::event::{Event, FileType, ProcessEnd, Tag};
|
||||
use crate::event::{Event, FileType, Priority, ProcessEnd, Tag};
|
||||
use crate::filter::Filterer;
|
||||
use crate::ignore::{IgnoreFile, IgnoreFilterer};
|
||||
use crate::signal::process::SubSignal;
|
||||
|
@ -70,7 +70,8 @@ mod parse;
|
|||
/// | [Source](Matcher::Source) | `:=` (in set) |
|
||||
/// | [Process](Matcher::Process) | `:=` (in set) |
|
||||
/// | [Signal](Matcher::Signal) | `:=` (in set) |
|
||||
/// | [ProcessCompletion](Matcher::ProcessCompletion) | `*=` (glob) |
|
||||
/// | [ProcessCompletion](Matcher::ProcessCompletion) | `*=` (glob) |
|
||||
/// | [Priority](Matcher::Priority) | `:=` (in set) |
|
||||
///
|
||||
/// [Matchers][Matcher] correspond to Tags, but are not one-to-one: the `path` matcher operates on
|
||||
/// the `path` part of the `Path` tag, and the `type` matcher operates on the `file_type`, for
|
||||
|
@ -86,6 +87,7 @@ mod parse;
|
|||
/// | [Process](Matcher::Process) | `process` or `pid` | [Process](Tag::Process) |
|
||||
/// | [Signal](Matcher::Signal) | `signal` | [Signal](Tag::Signal) |
|
||||
/// | [ProcessCompletion](Matcher::ProcessCompletion) | `complete` or `exit` | [ProcessCompletion](Tag::ProcessCompletion) |
|
||||
/// | [Priority](Matcher::Priority) | `priority` | special: event [Priority] |
|
||||
///
|
||||
/// Filters are checked in order, grouped per tag and per matcher. Filter groups may be checked in
|
||||
/// any order, but the filters in the groups are checked in add order. Path glob filters are always
|
||||
|
@ -125,20 +127,60 @@ pub struct TaggedFilterer {
|
|||
}
|
||||
|
||||
impl Filterer for TaggedFilterer {
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
self.check(event).map_err(|e| e.into())
|
||||
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
||||
self.check(event, priority).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TaggedFilterer {
|
||||
fn check(&self, event: &Event) -> Result<bool, TaggedFiltererError> {
|
||||
fn check(&self, event: &Event, priority: Priority) -> Result<bool, TaggedFiltererError> {
|
||||
let _span = trace_span!("filterer_check").entered();
|
||||
trace!(?event, "checking event");
|
||||
trace!(?event, ?priority, "checking event");
|
||||
|
||||
{
|
||||
trace!("checking priority");
|
||||
if let Some(filters) = self.filters.borrow().get(&Matcher::Priority).cloned() {
|
||||
trace!(filters=%filters.len(), "found some filters for priority");
|
||||
//
|
||||
let mut pri_match = true;
|
||||
for filter in &filters {
|
||||
let _span = trace_span!("checking filter against priority", ?filter).entered();
|
||||
let applies = filter.matches(match priority {
|
||||
Priority::Low => "low",
|
||||
Priority::Normal => "normal",
|
||||
Priority::High => "high",
|
||||
Priority::Urgent => unreachable!("urgent by-passes filtering"),
|
||||
})?;
|
||||
if filter.negate {
|
||||
if applies {
|
||||
trace!(prev=%pri_match, now=%true, "negate filter passes, passing this priority");
|
||||
pri_match = true;
|
||||
break;
|
||||
} else {
|
||||
trace!(prev=%pri_match, now=%pri_match, "negate filter fails, ignoring");
|
||||
}
|
||||
} else {
|
||||
trace!(prev=%pri_match, this=%applies, now=%(pri_match&applies), "filter applies to priority");
|
||||
pri_match &= applies;
|
||||
}
|
||||
}
|
||||
|
||||
if !pri_match {
|
||||
trace!("priority fails check, failing entire event");
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
trace!("no filters for priority, skipping (pass)");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
trace!("checking internal ignore filterer");
|
||||
let igf = self.ignore_filterer.borrow();
|
||||
if !igf.check_event(event).expect("IgnoreFilterer never errors") {
|
||||
if !igf
|
||||
.check_event(event, priority)
|
||||
.expect("IgnoreFilterer never errors")
|
||||
{
|
||||
trace!("internal ignore filterer matched (fail)");
|
||||
return Ok(false);
|
||||
}
|
||||
|
@ -687,9 +729,6 @@ impl Filter {
|
|||
#[non_exhaustive]
|
||||
pub enum Matcher {
|
||||
/// The presence of a tag on an event.
|
||||
///
|
||||
/// You should be extremely careful using this, as it's possible to make it impossible to quit
|
||||
/// Watchexec by e.g. not allowing signals to go through and thus ignoring Ctrl-C.
|
||||
Tag,
|
||||
|
||||
/// A path in a filesystem event. Paths are always canonicalised.
|
||||
|
@ -732,8 +771,11 @@ pub enum Matcher {
|
|||
/// A signal sent to the main process.
|
||||
///
|
||||
/// This can be matched both on the signal number as an integer, and on the signal name as a
|
||||
/// string. On Windows, only these signal names is supported: `BREAK`, and `CTRL_C`. Matching is
|
||||
/// string. On Windows, only `BREAK` is supported; `CTRL_C` parses but won't work. Matching is
|
||||
/// on both uppercase and lowercase forms.
|
||||
///
|
||||
/// Interrupt signals (`TERM` and `INT` on Unix, `CTRL_C` on Windows) are parsed, but these are
|
||||
/// marked Urgent internally to Watchexec, and thus bypass filtering entirely.
|
||||
Signal,
|
||||
|
||||
/// The exit status of a subprocess.
|
||||
|
@ -741,6 +783,11 @@ pub enum Matcher {
|
|||
/// This is only present for events issued when the subprocess exits. The value is matched on
|
||||
/// both the exit code as an integer, and either `success` or `fail`, whichever succeeds.
|
||||
ProcessCompletion,
|
||||
|
||||
/// The [`Priority`] of the event.
|
||||
///
|
||||
/// This is never `urgent`, as urgent events bypass filtering.
|
||||
Priority,
|
||||
}
|
||||
|
||||
impl Matcher {
|
||||
|
|
|
@ -29,6 +29,7 @@ impl FromStr for Filter {
|
|||
tag_no_case("fek"),
|
||||
tag_no_case("source"),
|
||||
tag_no_case("src"),
|
||||
tag_no_case("priority"),
|
||||
tag_no_case("process"),
|
||||
tag_no_case("pid"),
|
||||
tag_no_case("signal"),
|
||||
|
@ -42,6 +43,7 @@ impl FromStr for Filter {
|
|||
"type" => Ok(Matcher::FileType),
|
||||
"kind" | "fek" => Ok(Matcher::FileEventKind),
|
||||
"source" | "src" => Ok(Matcher::Source),
|
||||
"priority" => Ok(Matcher::Priority),
|
||||
"process" | "pid" => Ok(Matcher::Process),
|
||||
"signal" | "sig" => Ok(Matcher::Signal),
|
||||
"complete" | "exit" => Ok(Matcher::ProcessCompletion),
|
||||
|
|
|
@ -8,13 +8,14 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use notify::Watcher as _;
|
||||
use tokio::sync::{mpsc, watch};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
error::{CriticalError, FsWatcherError, RuntimeError},
|
||||
event::{Event, Source, Tag},
|
||||
event::{Event, Priority, Source, Tag},
|
||||
};
|
||||
|
||||
/// What kind of filesystem watcher to use.
|
||||
|
@ -135,12 +136,13 @@ impl AsRef<Path> for WatchedPath {
|
|||
/// Direct usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_priority_channel as priority;
|
||||
/// use tokio::sync::{mpsc, watch};
|
||||
/// use watchexec::fs::{worker, WorkingData};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let (ev_s, _) = mpsc::channel(1024);
|
||||
/// let (ev_s, _) = priority::bounded(1024);
|
||||
/// let (er_s, _) = mpsc::channel(64);
|
||||
/// let (wd_s, wd_r) = watch::channel(WorkingData::default());
|
||||
///
|
||||
|
@ -155,7 +157,7 @@ impl AsRef<Path> for WatchedPath {
|
|||
pub async fn worker(
|
||||
mut working: watch::Receiver<WorkingData>,
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: mpsc::Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
debug!("launching filesystem worker");
|
||||
|
||||
|
@ -291,7 +293,7 @@ fn notify_multi_path_errors(
|
|||
fn process_event(
|
||||
nev: Result<notify::Event, notify::Error>,
|
||||
kind: Watcher,
|
||||
n_events: mpsc::Sender<Event>,
|
||||
n_events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), RuntimeError> {
|
||||
let nev = nev.map_err(|err| RuntimeError::FsWatcher {
|
||||
kind,
|
||||
|
@ -331,7 +333,7 @@ fn process_event(
|
|||
|
||||
trace!(event = ?ev, "processed notify event into watchexec event");
|
||||
n_events
|
||||
.try_send(ev)
|
||||
.try_send(ev, Priority::Normal)
|
||||
.map_err(|err| RuntimeError::EventChannelTrySend {
|
||||
ctx: "fs watcher",
|
||||
err,
|
||||
|
|
|
@ -474,7 +474,7 @@ impl DirTourist {
|
|||
|
||||
pub(crate) async fn add_last_file_to_filter(
|
||||
&mut self,
|
||||
files: &mut Vec<IgnoreFile>,
|
||||
files: &mut [IgnoreFile],
|
||||
errors: &mut Vec<Error>,
|
||||
) {
|
||||
if let Some(ig) = files.last() {
|
||||
|
|
|
@ -10,7 +10,7 @@ use tracing::{trace, trace_span};
|
|||
|
||||
use crate::{
|
||||
error::RuntimeError,
|
||||
event::{Event, FileType},
|
||||
event::{Event, FileType, Priority},
|
||||
filter::Filterer,
|
||||
};
|
||||
|
||||
|
@ -259,8 +259,8 @@ impl Filterer for IgnoreFilterer {
|
|||
/// Filter an event.
|
||||
///
|
||||
/// This implementation never errors. It returns `Ok(false)` if the event is ignored according
|
||||
/// to the ignore files, and `Ok(true)` otherwise.
|
||||
fn check_event(&self, event: &Event) -> Result<bool, RuntimeError> {
|
||||
/// to the ignore files, and `Ok(true)` otherwise. It ignores event priority.
|
||||
fn check_event(&self, event: &Event, _priority: Priority) -> Result<bool, RuntimeError> {
|
||||
let _span = trace_span!("filterer_check").entered();
|
||||
let mut pass = true;
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
//! Event source for signals / notifications sent to the main process.
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use tokio::{select, sync::mpsc};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{
|
||||
error::{CriticalError, RuntimeError},
|
||||
event::{Event, Source, Tag},
|
||||
event::{Event, Priority, Source, Tag},
|
||||
};
|
||||
|
||||
/// A notification sent to the main (watchexec) process.
|
||||
|
@ -76,11 +77,12 @@ pub enum MainSignal {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use async_priority_channel as priority;
|
||||
/// use watchexec::signal::source::worker;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let (ev_s, _) = mpsc::channel(1024);
|
||||
/// let (ev_s, _) = priority::bounded(1024);
|
||||
/// let (er_s, _) = mpsc::channel(64);
|
||||
///
|
||||
/// worker(er_s, ev_s).await?;
|
||||
|
@ -89,7 +91,7 @@ pub enum MainSignal {
|
|||
/// ```
|
||||
pub async fn worker(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: mpsc::Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
imp_worker(errors, events).await
|
||||
}
|
||||
|
@ -97,7 +99,7 @@ pub async fn worker(
|
|||
#[cfg(unix)]
|
||||
async fn imp_worker(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: mpsc::Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
|
@ -137,7 +139,7 @@ async fn imp_worker(
|
|||
#[cfg(windows)]
|
||||
async fn imp_worker(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: mpsc::Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
) -> Result<(), CriticalError> {
|
||||
use tokio::signal::windows::{ctrl_break, ctrl_c};
|
||||
|
||||
|
@ -166,10 +168,9 @@ async fn imp_worker(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how to prioritise signals.
|
||||
async fn send_event(
|
||||
errors: mpsc::Sender<RuntimeError>,
|
||||
events: mpsc::Sender<Event>,
|
||||
events: priority::Sender<Event, Priority>,
|
||||
sig: MainSignal,
|
||||
) -> Result<(), CriticalError> {
|
||||
let tags = vec![
|
||||
|
@ -187,7 +188,16 @@ async fn send_event(
|
|||
};
|
||||
|
||||
trace!(?event, "processed signal into event");
|
||||
if let Err(err) = events.send(event).await {
|
||||
if let Err(err) = events
|
||||
.send(
|
||||
event,
|
||||
match sig {
|
||||
MainSignal::Interrupt | MainSignal::Terminate => Priority::Urgent,
|
||||
_ => Priority::High,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
errors
|
||||
.send(RuntimeError::EventChannelSend {
|
||||
ctx: "signals",
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
mem::{replace, take},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use async_priority_channel as priority;
|
||||
use atomic_take::AtomicTake;
|
||||
use futures::FutureExt;
|
||||
use miette::Diagnostic;
|
||||
use once_cell::sync::OnceCell;
|
||||
use tokio::{
|
||||
spawn,
|
||||
sync::{mpsc, watch, Notify},
|
||||
task::{JoinError, JoinHandle},
|
||||
task::JoinHandle,
|
||||
try_join,
|
||||
};
|
||||
use tracing::{debug, error, trace};
|
||||
|
@ -20,7 +24,7 @@ use crate::{
|
|||
action,
|
||||
config::{InitConfig, RuntimeConfig},
|
||||
error::{CriticalError, ReconfigError, RuntimeError},
|
||||
event::Event,
|
||||
event::{Event, Priority},
|
||||
fs,
|
||||
handler::{rte, Handler},
|
||||
signal,
|
||||
|
@ -40,7 +44,7 @@ pub struct Watchexec {
|
|||
action_watch: watch::Sender<action::WorkingData>,
|
||||
fs_watch: watch::Sender<fs::WorkingData>,
|
||||
|
||||
event_input: mpsc::Sender<Event>,
|
||||
event_input: priority::Sender<Event, Priority>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Watchexec {
|
||||
|
@ -71,7 +75,7 @@ impl Watchexec {
|
|||
) -> Result<Arc<Self>, CriticalError> {
|
||||
debug!(?init, ?runtime, pid=%std::process::id(), version=%env!("CARGO_PKG_VERSION"), "initialising");
|
||||
|
||||
let (ev_s, ev_r) = mpsc::channel(init.event_channel_size);
|
||||
let (ev_s, ev_r) = priority::bounded(init.event_channel_size);
|
||||
let (ac_s, ac_r) = watch::channel(take(&mut runtime.action));
|
||||
let (fs_s, fs_r) = watch::channel(fs::WorkingData::default());
|
||||
|
||||
|
@ -94,26 +98,23 @@ impl Watchexec {
|
|||
|
||||
let eh = replace(&mut init.error_handler, Box::new(()) as _);
|
||||
|
||||
macro_rules! subtask {
|
||||
($name:ident, $task:expr) => {{
|
||||
debug!(subtask=%stringify!($name), "spawning subtask");
|
||||
spawn($task).then(|jr| async { flatten(jr) })
|
||||
}};
|
||||
}
|
||||
|
||||
let action = subtask!(
|
||||
action,
|
||||
action::worker(ac_r, er_s.clone(), ev_s.clone(), ev_r)
|
||||
let action = SubTask::spawn(
|
||||
"action",
|
||||
action::worker(ac_r, er_s.clone(), ev_s.clone(), ev_r),
|
||||
);
|
||||
let fs = subtask!(fs, fs::worker(fs_r, er_s.clone(), ev_s.clone()));
|
||||
let signal = subtask!(signal, signal::source::worker(er_s.clone(), ev_s.clone()));
|
||||
let fs = SubTask::spawn("fs", fs::worker(fs_r, er_s.clone(), ev_s.clone()));
|
||||
let signal =
|
||||
SubTask::spawn("signal", signal::source::worker(er_s.clone(), ev_s.clone()));
|
||||
|
||||
let error_hook = subtask!(error_hook, error_hook(er_r, eh));
|
||||
let error_hook = SubTask::spawn("error_hook", error_hook(er_r, eh));
|
||||
|
||||
// Use Tokio TaskSet when that lands
|
||||
try_join!(action, error_hook, fs, signal)
|
||||
.map(drop)
|
||||
.or_else(|e| {
|
||||
// Close event channel to signal worker task to stop
|
||||
ev_s.close();
|
||||
|
||||
if matches!(e, CriticalError::Exit) {
|
||||
trace!("got graceful exit request via critical error, erasing the error");
|
||||
Ok(())
|
||||
|
@ -152,8 +153,8 @@ impl Watchexec {
|
|||
/// (for example, on start).
|
||||
///
|
||||
/// Hint: use [`Event::default()`] to send an empty event (which won't be filtered).
|
||||
pub async fn send_event(&self, event: Event) -> Result<(), CriticalError> {
|
||||
self.event_input.send(event).await?;
|
||||
pub async fn send_event(&self, event: Event, priority: Priority) -> Result<(), CriticalError> {
|
||||
self.event_input.send(event, priority).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -174,13 +175,6 @@ impl Watchexec {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flatten(join_res: Result<Result<(), CriticalError>, JoinError>) -> Result<(), CriticalError> {
|
||||
join_res
|
||||
.map_err(CriticalError::MainTaskJoin)
|
||||
.and_then(|x| x)
|
||||
}
|
||||
|
||||
async fn error_hook(
|
||||
mut errors: mpsc::Receiver<RuntimeError>,
|
||||
mut handler: Box<dyn Handler<ErrorHook> + Send>,
|
||||
|
@ -276,3 +270,62 @@ impl ErrorHook {
|
|||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SubTask {
|
||||
name: &'static str,
|
||||
handle: JoinHandle<Result<(), CriticalError>>,
|
||||
}
|
||||
|
||||
impl SubTask {
|
||||
pub fn spawn(
|
||||
name: &'static str,
|
||||
task: impl Future<Output = Result<(), CriticalError>> + Send + 'static,
|
||||
) -> Self {
|
||||
debug!(subtask=%name, "spawning subtask");
|
||||
Self {
|
||||
name,
|
||||
handle: spawn(task),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SubTask {
|
||||
fn drop(&mut self) {
|
||||
debug!(subtask=%self.name, "aborting subtask");
|
||||
self.handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SubTask {
|
||||
type Target = JoinHandle<Result<(), CriticalError>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SubTask {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for SubTask {
|
||||
type Output = Result<(), CriticalError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let subtask = self.name;
|
||||
match Pin::new(&mut Pin::into_inner(self).handle).poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(join_res) => {
|
||||
debug!(%subtask, "finishing subtask");
|
||||
Poll::Ready(
|
||||
join_res
|
||||
.map_err(CriticalError::MainTaskJoin)
|
||||
.and_then(|x| x),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,7 +219,6 @@ async fn ignore_exact_filename_in_hidden_folder() {
|
|||
filterer.dir_doesnt_pass("/test/.sub/Cargo.toml");
|
||||
}
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
async fn ignore_exact_filenames_multiple() {
|
||||
let filterer = filt(&[], &["Cargo.toml", "package.json"], &[]).await;
|
||||
|
@ -401,7 +400,7 @@ async fn multipath_allow_on_any_one_pass() {
|
|||
metadata: Default::default(),
|
||||
};
|
||||
|
||||
assert!(filterer.check_event(&event).unwrap());
|
||||
assert!(filterer.check_event(&event, Priority::Normal).unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -451,17 +450,23 @@ async fn nonpath_event_passes() {
|
|||
let filterer = filt(&[], &[], &["py"]).await;
|
||||
|
||||
assert!(filterer
|
||||
.check_event(&Event {
|
||||
tags: vec![Tag::Source(Source::Internal)],
|
||||
metadata: Default::default(),
|
||||
})
|
||||
.check_event(
|
||||
&Event {
|
||||
tags: vec![Tag::Source(Source::Internal)],
|
||||
metadata: Default::default(),
|
||||
},
|
||||
Priority::Normal
|
||||
)
|
||||
.unwrap());
|
||||
|
||||
assert!(filterer
|
||||
.check_event(&Event {
|
||||
tags: vec![Tag::Source(Source::Keyboard)],
|
||||
metadata: Default::default(),
|
||||
})
|
||||
.check_event(
|
||||
&Event {
|
||||
tags: vec![Tag::Source(Source::Keyboard)],
|
||||
metadata: Default::default(),
|
||||
},
|
||||
Priority::Normal
|
||||
)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
|
|
|
@ -426,3 +426,30 @@ async fn complete_with_any_signal() {
|
|||
filterer.complete_doesnt_pass(Some(ProcessEnd::Success));
|
||||
filterer.complete_doesnt_pass(None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn priority_auto() {
|
||||
let filterer = filt(&[filter("priority=normal")]).await;
|
||||
|
||||
filterer.priority_doesnt_pass(Priority::Low);
|
||||
filterer.priority_does_pass(Priority::Normal);
|
||||
filterer.priority_doesnt_pass(Priority::High);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn priority_set() {
|
||||
let filterer = filt(&[filter("priority:=normal,high")]).await;
|
||||
|
||||
filterer.priority_doesnt_pass(Priority::Low);
|
||||
filterer.priority_does_pass(Priority::Normal);
|
||||
filterer.priority_does_pass(Priority::High);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn priority_none() {
|
||||
let filterer = filt(&[]).await;
|
||||
|
||||
filterer.priority_does_pass(Priority::Low);
|
||||
filterer.priority_does_pass(Priority::Normal);
|
||||
filterer.priority_does_pass(Priority::High);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
|
||||
use watchexec::{
|
||||
error::RuntimeError,
|
||||
event::{filekind::FileEventKind, Event, FileType, ProcessEnd, Source, Tag},
|
||||
event::{filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag},
|
||||
filter::{
|
||||
globset::GlobsetFilterer,
|
||||
tagged::{files::FilterFile, Filter, Matcher, Op, Pattern, TaggedFilterer},
|
||||
|
@ -25,6 +25,7 @@ pub mod ignore {
|
|||
pub use super::ignore_filt as filt;
|
||||
pub use super::Applies;
|
||||
pub use super::PathHarness;
|
||||
pub use watchexec::event::Priority;
|
||||
}
|
||||
|
||||
pub mod globset {
|
||||
|
@ -32,6 +33,7 @@ pub mod globset {
|
|||
pub use super::ig_file as file;
|
||||
pub use super::Applies;
|
||||
pub use super::PathHarness;
|
||||
pub use watchexec::event::Priority;
|
||||
}
|
||||
|
||||
pub mod tagged {
|
||||
|
@ -42,6 +44,7 @@ pub mod tagged {
|
|||
pub use super::PathHarness;
|
||||
pub use super::TaggedHarness;
|
||||
pub use super::{filter, glob_filter, notglob_filter};
|
||||
pub use watchexec::event::Priority;
|
||||
}
|
||||
|
||||
pub mod tagged_ff {
|
||||
|
@ -61,7 +64,7 @@ pub trait PathHarness: Filterer {
|
|||
metadata: Default::default(),
|
||||
};
|
||||
|
||||
self.check_event(&event)
|
||||
self.check_event(&event, Priority::Normal)
|
||||
}
|
||||
|
||||
fn path_pass(&self, path: &str, file_type: Option<FileType>, pass: bool) {
|
||||
|
@ -122,16 +125,35 @@ impl PathHarness for TaggedFilterer {}
|
|||
impl PathHarness for IgnoreFilterer {}
|
||||
|
||||
pub trait TaggedHarness {
|
||||
fn check_tag(&self, tag: Tag) -> std::result::Result<bool, RuntimeError>;
|
||||
fn check_tag(&self, tag: Tag, priority: Priority) -> std::result::Result<bool, RuntimeError>;
|
||||
|
||||
fn priority_pass(&self, priority: Priority, pass: bool) {
|
||||
tracing::info!(?priority, ?pass, "check");
|
||||
|
||||
assert_eq!(
|
||||
self.check_tag(Tag::Source(Source::Filesystem), priority)
|
||||
.unwrap(),
|
||||
pass,
|
||||
"{priority:?} (expected {})",
|
||||
if pass { "pass" } else { "fail" }
|
||||
);
|
||||
}
|
||||
|
||||
fn priority_does_pass(&self, priority: Priority) {
|
||||
self.priority_pass(priority, true);
|
||||
}
|
||||
|
||||
fn priority_doesnt_pass(&self, priority: Priority) {
|
||||
self.priority_pass(priority, false);
|
||||
}
|
||||
|
||||
fn tag_pass(&self, tag: Tag, pass: bool) {
|
||||
tracing::info!(?tag, ?pass, "check");
|
||||
|
||||
assert_eq!(
|
||||
self.check_tag(tag.clone()).unwrap(),
|
||||
self.check_tag(tag.clone(), Priority::Normal).unwrap(),
|
||||
pass,
|
||||
"{:?} (expected {})",
|
||||
tag,
|
||||
"{tag:?} (expected {})",
|
||||
if pass { "pass" } else { "fail" }
|
||||
);
|
||||
}
|
||||
|
@ -178,13 +200,13 @@ pub trait TaggedHarness {
|
|||
}
|
||||
|
||||
impl TaggedHarness for TaggedFilterer {
|
||||
fn check_tag(&self, tag: Tag) -> std::result::Result<bool, RuntimeError> {
|
||||
fn check_tag(&self, tag: Tag, priority: Priority) -> std::result::Result<bool, RuntimeError> {
|
||||
let event = Event {
|
||||
tags: vec![tag],
|
||||
metadata: Default::default(),
|
||||
};
|
||||
|
||||
self.check_event(&event)
|
||||
self.check_event(&event, priority)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue