use std::{error::Error as StdError, fmt};
use super::observer::Observer;
use crate::model::{FailureReason, State, StateView, World};
pub fn explore<'a>(
setup: &'a dyn Fn(&mut World<'a>),
observer: &mut impl Observer,
) -> Result<(), FailedState<'a>> {
let root = StateView::new(setup).state();
super::optimal::run(root, observer)
}
pub struct FailedState<'a> {
reason: FailureReason,
view: StateView<'a>,
}
impl<'a> FailedState<'a> {
pub(super) fn new(reason: FailureReason, view: StateView<'a>) -> Self {
Self { reason, view }
}
fn from_state(state: State<'a>) -> Self {
let (reason, view) = state.into_failure();
Self::new(reason, view)
}
pub fn reason(&self) -> &FailureReason {
&self.reason
}
pub fn play(&self) -> Self {
Self::from_state(self.view.state())
}
}
impl fmt::Debug for FailedState<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FailedState")
.field("reason", &self.reason)
.field("trace", &self.view.trace())
.finish()
}
}
impl fmt::Display for FailedState<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reason)
}
}
impl StdError for FailedState<'_> {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.reason.source()
}
}
#[cfg(test)]
mod tests {
use super::explore;
use crate::model::World;
fn racy(world: &mut World) {
let x = world.atomic("x", 0u32);
let writer = x.clone();
let reader = x.clone();
world.spawn("writer", async move {
writer.store(1).await;
Ok(())
});
world.spawn("reader", async move {
match reader.load().await {
1 => Ok(()),
_ => Err("unexpected value".into()),
}
});
}
#[test]
fn play_reproduces_failure() {
let failed = explore(&racy, &mut ()).unwrap_err();
let again = failed.play();
assert_eq!(failed.to_string(), again.to_string());
}
fn two_writers(world: &mut World) {
let x = world.atomic("x", 0u32);
let first = x.clone();
let second = x.clone();
world.spawn("writer-1", async move {
first.store(1).await;
Ok(())
});
world.spawn("writer-2", async move {
second.store(2).await;
Ok(())
});
}
#[test]
fn explores_clean_program() {
assert!(explore(&two_writers, &mut ()).is_ok());
}
fn never_finishes(world: &mut World) {
world.spawn("stuck", async {
std::future::pending::<()>().await;
Ok(())
});
}
#[test]
fn deadlock_is_detected() {
let failed = explore(&never_finishes, &mut ()).unwrap_err();
assert_eq!(failed.to_string(), "deadlock");
failed.play();
}
fn starved_consumer(world: &mut World) {
let (tx, rx) = world.channel::<i32>("ch");
world.spawn("producer", async move {
tx.send(1).await;
Ok(())
});
world.spawn("consumer", async move {
rx.recv().await;
rx.recv().await; Ok(())
});
}
#[test]
fn channel_starvation_is_deadlock() {
let failed = explore(&starved_consumer, &mut ()).unwrap_err();
assert_eq!(failed.to_string(), "deadlock");
}
}