use std::collections::HashSet;
#[cfg(feature = "testing-timing")]
use core::time::Duration;
#[cfg(feature = "testing-timing")]
use std::collections::HashMap;
use super::config::{Failure, FdrConfig, Trace};
use crate::testing::specs::csp::{Event, Process, State};
#[cfg(feature = "testing-fault")]
use crate::testing::fdr::config::InjectedFaultRecord;
pub trait ExplorationCore {
fn process(&self) -> &Process;
fn config(&self) -> &FdrConfig;
fn explore_seed(&mut self, seed: u64) -> SeedResult;
fn traces(&self) -> Vec<Trace>;
fn failures(&self) -> Vec<Failure>;
fn states_visited(&self) -> usize;
fn seeds_completed(&self) -> u32;
fn add_timing_pruned(&mut self, _count: usize) {}
fn add_seed_result(&mut self, seed: u64, result: SeedResult);
fn update_visited_states(&mut self, visited: &HashSet<State>);
fn timing_pruned(&self) -> usize {
0
}
fn compute_refusals(&self, process: &Process, state: State) -> HashSet<Event> {
use std::collections::HashSet;
let enabled_events: HashSet<Event> = process
.enabled(state)
.iter()
.filter_map(|action| {
if !process.hidden.contains(&action.event) {
Some(action.event)
} else {
None
}
})
.collect();
process
.observable
.iter()
.filter(|&event| !enabled_events.contains(event))
.cloned()
.collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RefinementOutcome<W> {
Holds {
complete: bool,
},
Violated(W),
Inconclusive,
}
pub trait RefinementChecker {
const MAX_TRACES: usize = 5000;
const MAX_QUEUE_SIZE: usize = 10000;
const MAX_VISITED: usize = 20000;
fn check_trace_refinement(&mut self, spec: &Process, impl_process: &Process) -> RefinementOutcome<Trace>;
fn check_failures_refinement(&mut self, spec: &Process, impl_process: &Process) -> RefinementOutcome<Failure>;
fn check_divergence_refinement(&mut self, spec: &Process, impl_process: &Process) -> RefinementOutcome<Trace>;
fn compute_traces(&mut self, process: &Process, max_depth: usize) -> (HashSet<Trace>, bool);
fn compute_failures(&mut self, process: &Process, max_depth: usize) -> (Vec<Failure>, bool);
fn compute_divergences(&mut self, process: &Process, max_depth: usize) -> (HashSet<Trace>, bool);
fn compute_refusals(&self, process: &Process, state: State) -> HashSet<Event>;
}
pub trait MemoizationCache {
fn get_cached_traces(&self, structure: u64) -> Option<(Vec<Trace>, bool)>;
fn cache_traces(&mut self, structure: u64, traces: Vec<Trace>, complete: bool);
fn get_cached_failures(&self, structure: u64) -> Option<(Vec<Failure>, bool)>;
fn cache_failures(&mut self, structure: u64, failures: Vec<Failure>, complete: bool);
fn get_cached_divergences(&self, structure: u64) -> Option<(Vec<Trace>, bool)>;
fn cache_divergences(&mut self, structure: u64, divergences: Vec<Trace>, complete: bool);
}
#[derive(Debug, Clone)]
pub enum SeedResult {
#[cfg(feature = "testing-fault")]
Success(Trace, Vec<Failure>, Vec<InjectedFaultRecord>),
#[cfg(not(feature = "testing-fault"))]
Success(Trace, Vec<Failure>),
#[cfg(feature = "testing-fault")]
Divergence(Trace, Vec<Event>, Vec<InjectedFaultRecord>),
#[cfg(not(feature = "testing-fault"))]
Divergence(Trace, Vec<Event>),
#[cfg(feature = "testing-fault")]
Deadlock(Trace, State, Vec<InjectedFaultRecord>),
#[cfg(not(feature = "testing-fault"))]
Deadlock(Trace, State),
}
#[derive(Debug, Clone)]
pub struct ExplorationState {
pub process_state: State,
pub trace: Trace,
pub internal_run: usize,
pub state_path: Vec<State>,
pub hidden_events: Vec<Event>,
#[cfg(feature = "testing-timing")]
pub elapsed_time: Duration,
#[cfg(feature = "testing-timing")]
pub event_times: Vec<(Event, Duration)>,
#[cfg(feature = "testing-timing")]
pub clock_values: HashMap<String, Duration>,
}
impl ExplorationState {
pub fn initial(process_state: State) -> Self {
Self {
process_state,
trace: Vec::new(),
internal_run: 0,
state_path: vec![process_state],
hidden_events: Vec::new(),
#[cfg(feature = "testing-timing")]
elapsed_time: Duration::ZERO,
#[cfg(feature = "testing-timing")]
event_times: Vec::new(),
#[cfg(feature = "testing-timing")]
clock_values: HashMap::new(),
}
}
pub fn branch(&self) -> Self {
self.clone()
}
pub fn record_observable(&mut self, event: Event, next_state: State) {
self.trace.push(event);
self.process_state = next_state;
self.state_path.push(next_state);
self.internal_run = 0; #[cfg(feature = "testing-timing")]
{
}
}
pub fn record_hidden(&mut self, event: Event, next_state: State) {
self.hidden_events.push(event);
self.process_state = next_state;
self.state_path.push(next_state);
self.internal_run += 1;
#[cfg(feature = "testing-timing")]
{
}
}
#[cfg(feature = "testing-timing")]
pub fn update_timing(&mut self, event: &Event, wcet: Duration) {
self.elapsed_time += wcet;
self.event_times.push((*event, self.elapsed_time));
}
#[cfg(feature = "testing-timing")]
pub fn update_clocks(&mut self, elapsed: Duration) {
for clock_value in self.clock_values.values_mut() {
*clock_value = clock_value.saturating_add(elapsed);
}
}
#[cfg(feature = "testing-timing")]
pub fn reset_clocks(&mut self, clock_names: &[String]) {
for name in clock_names {
self.clock_values.insert(name.clone(), Duration::ZERO);
}
}
}
#[derive(Debug, Clone)]
pub struct SeededRng {
state: u64,
}
impl SeededRng {
pub fn new(seed: u64) -> Self {
Self { state: seed.wrapping_add(1) }
}
pub fn get_next(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(crate::constants::LCG_MULTIPLIER)
.wrapping_add(crate::constants::LCG_INCREMENT);
self.state
}
pub fn choose<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
if items.is_empty() {
None
} else {
let index = (self.get_next() as usize) % items.len();
Some(&items[index])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod seeded_rng {
use super::*;
#[test]
fn deterministic_sequences() {
let mut rng1 = SeededRng::new(42);
let mut rng2 = SeededRng::new(42);
for _ in 0..10 {
assert_eq!(rng1.get_next(), rng2.get_next());
}
}
#[test]
fn different_seeds_produce_different_sequences() {
let mut rng1 = SeededRng::new(42);
let mut rng2 = SeededRng::new(43);
let same_count = (0..10).filter(|_| rng1.get_next() == rng2.get_next()).count();
assert!(same_count < 10, "All values should not be identical");
}
#[test]
fn choose_from_items() {
let mut rng = SeededRng::new(42);
let items = vec![1, 2, 3, 4, 5];
assert!(rng.choose(&items).is_some());
assert!(rng.choose(&Vec::<i32>::new()).is_none());
}
}
mod exploration_state {
use super::*;
use crate::testing::specs::csp::{Event, State};
#[test]
fn initial_state() {
let state = State("Start");
let exp_state = ExplorationState::initial(state);
assert_eq!(exp_state.process_state, state);
assert!(exp_state.trace.is_empty());
assert_eq!(exp_state.internal_run, 0);
assert_eq!(exp_state.state_path.len(), 1);
assert!(exp_state.hidden_events.is_empty());
}
#[test]
fn record_observable_event() {
let (state1, state2) = (State("S1"), State("S2"));
let event = Event("evt");
let mut exp_state = ExplorationState::initial(state1);
exp_state.record_observable(event, state2);
assert_eq!(exp_state.process_state, state2);
assert_eq!(exp_state.trace, vec![event]);
assert_eq!(exp_state.internal_run, 0); assert_eq!(exp_state.state_path.len(), 2);
}
#[test]
fn record_hidden_event() {
let (state1, state2) = (State("S1"), State("S2"));
let event = Event("tau");
let mut exp_state = ExplorationState::initial(state1);
exp_state.record_hidden(event, state2);
assert_eq!(exp_state.process_state, state2);
assert!(exp_state.trace.is_empty()); assert_eq!(exp_state.internal_run, 1); assert_eq!(exp_state.hidden_events, vec![event]);
assert_eq!(exp_state.state_path.len(), 2);
}
#[test]
fn divergence_detection_via_internal_run_counter() {
let state = State("S1");
let tau = Event("tau");
let mut exp_state = ExplorationState::initial(state);
for _ in 0..35 {
exp_state.record_hidden(tau, state);
}
assert!(exp_state.internal_run > 32);
assert_eq!(exp_state.hidden_events.len(), 35);
assert!(exp_state.trace.is_empty()); }
#[cfg(feature = "testing-timing")]
#[test]
fn update_timing() {
let state = State("S1");
let event = Event("evt");
let mut exp_state = ExplorationState::initial(state);
exp_state.record_observable(event, state);
exp_state.update_timing(&event, Duration::from_millis(10));
assert_eq!(exp_state.elapsed_time, Duration::from_millis(10));
assert_eq!(exp_state.event_times.len(), 1);
assert_eq!(exp_state.event_times[0].0, event);
assert_eq!(exp_state.event_times[0].1, Duration::from_millis(10));
let event2 = Event("evt2");
exp_state.record_observable(event2, state);
exp_state.update_timing(&event2, Duration::from_millis(5));
assert_eq!(exp_state.elapsed_time, Duration::from_millis(15));
assert_eq!(exp_state.event_times.len(), 2);
assert_eq!(exp_state.event_times[1].0, event2);
assert_eq!(exp_state.event_times[1].1, Duration::from_millis(15));
}
}
}