use cordis_core::Context;
use std::sync::Arc;
#[tokio::test]
async fn typed_update_control_stays_scoped_to_each_fiber() {
use cordis_core::event::around;
use cordis_core::{Plugin, PreparedChange, PreparedPlugin};
use std::borrow::Cow;
use std::convert::Infallible;
use std::sync::atomic::{AtomicU32, Ordering};
struct Watcher {
tag: &'static str,
hits: Arc<AtomicU32>,
veto: bool,
}
impl Plugin for Watcher {
type Config = String;
type Input = ();
type PrepareError = Infallible;
type ApplyError = Infallible;
fn prepare(&self, _config: String) -> Result<(), Infallible> {
Ok(())
}
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed(self.tag)
}
async fn apply(&self, ctx: Context, _prepared: &()) -> Result<(), Infallible> {
let hits = self.hits.clone();
let veto = self.veto;
ctx.on_update::<Watcher, _>(
around::<Watcher, _>(
move |_ctx, args, next: cordis_core::lifecycle::UpdateNext<Watcher>| {
let hits = hits.clone();
Box::pin(async move {
if veto {
return Ok(());
}
hits.fetch_add(1, Ordering::SeqCst);
next.call(args).await?; Ok::<(), cordis_core::event::InvocationFailure>(())
})
},
),
Default::default(),
)
.unwrap();
Ok(())
}
}
let root = Context::new();
let hits_parent = Arc::new(AtomicU32::new(0));
let parent_hits = hits_parent.clone();
root.on_update::<Watcher, _>(
around::<Watcher, _>(
move |_ctx, args, next: cordis_core::lifecycle::UpdateNext<Watcher>| {
let parent_hits = parent_hits.clone();
Box::pin(async move {
parent_hits.fetch_add(1, Ordering::SeqCst);
next.call(args).await
})
},
),
Default::default(),
)
.unwrap();
let fiber_handle_a = root
.spawn(PreparedPlugin::from_input(
Watcher {
tag: "watcher-a",
hits: Arc::new(AtomicU32::new(0)),
veto: true,
},
(),
))
.await
.unwrap();
let fiber_handle_b = root
.spawn(PreparedPlugin::from_input(
Watcher {
tag: "watcher-b",
hits: Arc::new(AtomicU32::new(0)),
veto: false,
},
(),
))
.await
.unwrap();
fiber_handle_a
.update(PreparedChange::from_input::<Watcher>(()))
.await
.unwrap();
assert_eq!(fiber_handle_a.state(), cordis_core::FiberState::Active);
assert_eq!(hits_parent.load(Ordering::SeqCst), 1, "ancestor saw A");
fiber_handle_b
.update(PreparedChange::from_input::<Watcher>(()))
.await
.unwrap();
assert_eq!(fiber_handle_b.state(), cordis_core::FiberState::Active);
assert_eq!(
hits_parent.load(Ordering::SeqCst),
2,
"ancestor saw B too; A's veto never reached it"
);
}
#[tokio::test]
async fn restart_inherits_the_fibers_spawn_scope() {
use cordis_core::event::around;
use cordis_core::{Plugin, PreparedChange, PreparedPlugin};
use std::borrow::Cow;
use std::convert::Infallible;
use std::sync::atomic::{AtomicU32, Ordering};
struct VetoingWatcher {
hits: Arc<AtomicU32>,
}
impl Plugin for VetoingWatcher {
type Config = String;
type Input = ();
type PrepareError = Infallible;
type ApplyError = Infallible;
fn prepare(&self, _config: String) -> Result<(), Infallible> {
Ok(())
}
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed("vetoing-watcher")
}
async fn apply(&self, ctx: Context, _prepared: &()) -> Result<(), Infallible> {
let hits = self.hits.clone();
ctx.on_update::<VetoingWatcher, _>(
around::<VetoingWatcher, _>(move |_ctx, _args, _next| {
let hits = hits.clone();
Box::pin(async move {
hits.fetch_add(1, Ordering::SeqCst);
Ok::<(), Infallible>(())
})
}),
Default::default(),
)
.unwrap();
Ok(())
}
}
let root = Context::new();
let hits = Arc::new(AtomicU32::new(0));
let fiber_handle = root
.spawn(PreparedPlugin::from_input(
VetoingWatcher { hits: hits.clone() },
(),
))
.await
.unwrap();
fiber_handle
.update(PreparedChange::from_input::<VetoingWatcher>(()))
.await
.unwrap();
assert_eq!(hits.load(Ordering::SeqCst), 1);
fiber_handle.restart().await.unwrap();
assert_eq!(fiber_handle.state(), cordis_core::FiberState::Active);
fiber_handle
.update(PreparedChange::from_input::<VetoingWatcher>(()))
.await
.unwrap();
assert_eq!(
hits.load(Ordering::SeqCst),
2,
"the re-registered layer vetoed again"
);
}