use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use colored::*;
use lazy_static::lazy_static;
use regex::Regex;
use sugars::boxed;
use crate::util::t;
use crate::Message;
use crate::mc::network::DeliveryOptions;
use crate::mc::predicates;
use crate::mc::McEvent::*;
use crate::mc::{McError, McEvent, McEventId, McState, McSystem};
pub struct StrategyConfig {
pub(crate) prune: PruneFn,
pub(crate) goal: GoalFn,
pub(crate) invariant: InvariantFn,
pub(crate) collect: CollectFn,
pub(crate) execution_mode: ExecutionMode,
pub(crate) visited_states: VisitedStates,
}
impl Default for StrategyConfig {
fn default() -> Self {
Self {
prune: boxed!(predicates::default_prune),
goal: boxed!(predicates::default_goal),
invariant: boxed!(predicates::default_invariant),
collect: boxed!(predicates::default_collect),
execution_mode: ExecutionMode::Default,
visited_states: VisitedStates::Partial(HashSet::default()),
}
}
}
impl StrategyConfig {
pub fn prune(mut self, prune: PruneFn) -> Self {
self.prune = prune;
self
}
pub fn invariant(mut self, invariant: InvariantFn) -> Self {
self.invariant = invariant;
self
}
pub fn goal(mut self, goal: GoalFn) -> Self {
self.goal = goal;
self
}
pub fn collect(mut self, collect: CollectFn) -> Self {
self.collect = collect;
self
}
pub fn execution_mode(mut self, execution_mode: ExecutionMode) -> Self {
self.execution_mode = execution_mode;
self
}
pub fn visited_states(mut self, visited_states: VisitedStates) -> Self {
self.visited_states = visited_states;
self
}
}
#[derive(Clone, PartialEq)]
pub enum ExecutionMode {
Default,
Debug,
}
pub enum VisitedStates {
Full(HashSet<McState>),
Partial(HashSet<u64>),
Disabled,
}
#[allow(missing_docs)]
pub enum EventOrId {
Event(McEvent),
Id(McEventId),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct McStats {
pub statuses: HashMap<String, u32>,
pub collected_states: HashSet<McState>,
}
impl McStats {
pub(crate) fn combine(&mut self, other: McStats) {
self.collected_states.extend(other.collected_states);
for (state, cnt) in other.statuses {
let entry = self.statuses.entry(state).or_insert(0);
*entry += cnt;
}
}
}
pub type PruneFn = Box<dyn FnMut(&McState) -> Option<String>>;
pub type GoalFn = Box<dyn FnMut(&McState) -> Option<String>>;
pub type InvariantFn = Box<dyn FnMut(&McState) -> Result<(), String>>;
pub type CollectFn = Box<dyn FnMut(&McState) -> bool>;
pub type McResult = Result<McStats, McError>;
pub trait Strategy {
fn build(config: StrategyConfig) -> Self
where
Self: Sized;
fn run(&mut self, system: &mut McSystem) -> McResult;
fn search_step_impl(&mut self, system: &mut McSystem, state: McState) -> Result<(), McError>;
fn process_event(&mut self, system: &mut McSystem, event_id: McEventId) -> Result<(), McError> {
let event = system.events.get(event_id).unwrap();
match event {
MessageReceived { msg, src, dst, options } => {
match *options {
DeliveryOptions::NoFailures(..) => self.search_step(system, EventOrId::Id(event_id))?,
DeliveryOptions::PossibleFailures {
can_be_dropped,
max_dupl_count,
can_be_corrupted,
} => {
let msg = msg.clone();
let src = src.clone();
let dst = dst.clone();
self.search_step(system, EventOrId::Id(event_id))?;
if can_be_dropped {
let drop_event = MessageDropped {
msg: msg.clone(),
src: src.clone(),
dst: dst.clone(),
receive_event_id: Some(event_id),
};
self.search_step(system, EventOrId::Event(drop_event))?;
}
if can_be_corrupted {
let corruption_event = MessageCorrupted {
msg: msg.clone(),
corrupted_msg: self.corrupt_message(msg.clone()),
src: src.clone(),
dst: dst.clone(),
receive_event_id: event_id,
};
self.search_step(system, EventOrId::Event(corruption_event))?;
}
if max_dupl_count > 0 {
let duplication_event = MessageDuplicated {
msg,
src,
dst,
receive_event_id: event_id,
};
self.search_step(system, EventOrId::Event(duplication_event))?;
}
}
}
}
_ => self.search_step(system, EventOrId::Id(event_id))?,
}
Ok(())
}
fn search_step(&mut self, system: &mut McSystem, event: EventOrId) -> Result<(), McError> {
let state = system.get_state();
let mut event = match event {
EventOrId::Event(event) => event,
EventOrId::Id(event_id) => self.take_event(system, event_id),
};
match &mut event {
MessageDropped { receive_event_id, .. } => {
receive_event_id.map(|id| self.take_event(system, id));
}
MessageDuplicated { receive_event_id, .. } => {
let dupl_event = self.duplicate_event(system, *receive_event_id);
self.add_event(system, dupl_event);
}
MessageCorrupted {
receive_event_id,
corrupted_msg,
..
} => {
let original_event = self.take_event(system, *receive_event_id);
let corrupted = self.create_corrupted_receive(original_event, corrupted_msg.clone());
system.events.push_with_fixed_id(corrupted, *receive_event_id);
}
_ => {}
}
self.debug_log(&event, system.depth());
system.apply_event(event);
let new_state = system.get_state();
if !self.have_visited(&new_state) {
self.mark_visited(system.get_state());
self.search_step_impl(system, new_state)?;
}
system.set_state(state);
Ok(())
}
fn take_event(&self, system: &mut McSystem, event_id: McEventId) -> McEvent {
system.events.pop(event_id)
}
fn add_event(&self, system: &mut McSystem, event: McEvent) -> McEventId {
system.events.push(event)
}
fn corrupt_message(&self, mut msg: Message) -> Message {
lazy_static! {
static ref RE: Regex = Regex::new(r#""[^"]+""#).unwrap();
}
msg.data = RE.replace_all(&msg.data, "\"\"").to_string();
msg
}
fn create_corrupted_receive(&self, event: McEvent, corrupted_msg: Message) -> McEvent {
if let MessageReceived {
src, dst, mut options, ..
} = event
{
if let DeliveryOptions::PossibleFailures { can_be_corrupted, .. } = &mut options {
*can_be_corrupted = false;
}
MessageReceived {
msg: corrupted_msg,
src,
dst,
options,
}
} else {
panic!("Unexpected event type")
}
}
fn duplicate_event(&self, system: &mut McSystem, event_id: McEventId) -> McEvent {
let mut event = self.take_event(system, event_id);
system.events.push_with_fixed_id(event.duplicate().unwrap(), event_id);
event.disable_duplications();
event
}
fn debug_log(&self, event: &McEvent, depth: u64) {
if self.execution_mode() == &ExecutionMode::Debug {
match event {
MessageReceived { msg, src, dst, .. } => {
t!("{:>10} | {:>10} <-- {:<10} {:?}", depth, dst, src, msg);
}
TimerFired { proc, timer, .. } => {
t!(format!("{depth:>10} | {proc:>10} !-- {timer:<10} <-- timer fired").yellow());
}
TimerCancelled { proc, timer } => {
t!(format!("{depth:>10} | {proc:>10} xxx {timer:<10} <-- timer cancelled").yellow());
}
MessageDropped { msg, src, dst, .. } => {
t!(format!("{depth:>10} | {src:>10} --x {dst:<10} {msg:?} <-- message dropped").red());
}
MessageDuplicated { msg, src, dst, .. } => {
t!(format!("{depth:>10} | {src:>10} -=≡ {dst:<10} {msg:?} <-- message duplicated").blue());
}
MessageCorrupted {
msg,
corrupted_msg,
src,
dst,
..
} => {
t!(format!(
"{depth:>10} | {src:>10} -x- {dst:<10} {msg:?} ~~> {corrupted_msg:?} <-- message corrupted"
)
.blue());
}
}
}
}
fn have_visited(&mut self, state: &McState) -> bool {
match self.visited() {
VisitedStates::Full(ref states) => states.contains(state),
VisitedStates::Partial(ref hashes) => {
let mut h = DefaultHasher::default();
state.hash(&mut h);
hashes.contains(&h.finish())
}
VisitedStates::Disabled => false,
}
}
fn mark_visited(&mut self, state: McState) {
match self.visited() {
VisitedStates::Full(ref mut states) => {
states.insert(state);
}
VisitedStates::Partial(ref mut hashes) => {
let mut h = DefaultHasher::default();
state.hash(&mut h);
hashes.insert(h.finish());
}
VisitedStates::Disabled => {}
}
}
fn on_final_state_reached(&mut self, status: String) {
if let ExecutionMode::Debug = self.execution_mode() {
let counter = self.stats().statuses.entry(status).or_insert(0);
*counter += 1;
}
}
fn check_state(&mut self, state: &McState) -> Option<Result<(), McError>> {
if (self.collect())(state) {
self.stats().collected_states.insert(state.clone());
}
if let Err(err) = (self.invariant())(state) {
Some(Err(McError::new(err, state.trace.clone())))
} else if let Some(status) = (self.goal())(state) {
self.on_final_state_reached(status);
Some(Ok(()))
} else if let Some(status) = (self.prune())(state) {
self.on_final_state_reached(status);
Some(Ok(()))
} else if state.events.is_empty() {
Some(Err(McError::new(
"nothing left to do to reach the goal".to_owned(),
state.trace.clone(),
)))
} else {
None
}
}
fn set_collect(&mut self, collect: CollectFn) {
*self.collect() = collect;
}
fn execution_mode(&self) -> &ExecutionMode;
fn reset(&mut self);
fn visited(&mut self) -> &mut VisitedStates;
fn prune(&mut self) -> &mut PruneFn;
fn goal(&mut self) -> &mut GoalFn;
fn invariant(&mut self) -> &mut InvariantFn;
fn collect(&mut self) -> &mut CollectFn;
fn stats(&mut self) -> &mut McStats;
}