#[cfg(feature = "testing-fault")]
use std::borrow::Cow;
use std::collections::{HashSet, VecDeque};
use std::sync::Arc;
#[cfg(feature = "testing-timing")]
use core::time::Duration;
use crate::testing::fdr::config::{Failure, FdrConfig, RefusalSet, Trace};
use crate::testing::fdr::explorer::{ExplorationCore, ExplorationState, SeedResult, SeededRng};
use crate::testing::specs::csp::{Action, Event, Process, State};
#[cfg(feature = "testing-fault")]
use crate::testing::fdr::config::{FaultInjection, FaultModel, InjectedFaultRecord};
#[cfg(feature = "testing-timing")]
use crate::testing::fdr::subsys::timing::check_event_wcet_violation;
#[cfg(feature = "testing-timing")]
use crate::testing::fdr::subsys::timing::check_timing_violations;
#[cfg(feature = "testing-timing")]
use crate::testing::timing::{TimingConstraint, TimingConstraints};
pub struct DefaultExplorationEngine<'a> {
process: &'a Process,
config: Arc<FdrConfig>,
seed_results: Vec<(u64, SeedResult)>,
visited_states: HashSet<State>,
}
impl<'a> DefaultExplorationEngine<'a> {
pub fn new(process: &'a Process, config: Arc<FdrConfig>) -> Self {
Self { process, config, seed_results: Vec::new(), visited_states: HashSet::new() }
}
pub fn seed_results(&self) -> &[(u64, SeedResult)] {
&self.seed_results
}
pub fn seed_results_mut(&mut self) -> &mut Vec<(u64, SeedResult)> {
&mut self.seed_results
}
pub fn visited_states(&self) -> &HashSet<State> {
&self.visited_states
}
pub fn visited_states_mut(&mut self) -> &mut HashSet<State> {
&mut self.visited_states
}
fn explore_seed_internal(&mut self, seed: u64) -> SeedResult {
Self::explore_seed_core(self.process, &self.config, seed, |state| {
self.visited_states.insert(state);
})
}
#[cfg(feature = "rayon")]
pub fn explore_seed_static(process: &Process, config: &FdrConfig, seed: u64) -> (SeedResult, HashSet<State>) {
let mut visited_states = HashSet::new();
let result = Self::explore_seed_core(process, config, seed, |state| {
visited_states.insert(state);
});
(result, visited_states)
}
fn explore_seed_core<F>(process: &Process, config: &FdrConfig, seed: u64, mut track_visited: F) -> SeedResult
where
F: FnMut(State),
{
let mut rng = SeededRng::new(seed);
let mut queue = VecDeque::new();
let initial = ExplorationState::initial(process.initial);
queue.push_back(initial);
let mut longest_trace = Vec::new();
let mut failures = Vec::new();
#[cfg(feature = "testing-fault")]
let mut injected_faults = Vec::new();
while let Some(state) = queue.pop_front() {
track_visited(state.process_state);
if state.trace.len() >= config.max_depth {
if state.trace.len() > longest_trace.len() {
longest_trace = state.trace.clone();
}
let refusals = Self::compute_refusals_helper(process, state.process_state);
failures.push((state.trace.clone(), refusals));
continue;
}
if state.internal_run > config.max_internal_run {
return SeedResult::Divergence(state.trace.clone(), state.hidden_events.clone());
}
if process.is_terminal(state.process_state) {
if state.trace.len() > longest_trace.len() {
longest_trace = state.trace.clone();
}
let refusals = process.observable.iter().cloned().collect();
failures.push((state.trace.clone(), refusals));
continue;
}
let actions = process.enabled(state.process_state);
if actions.is_empty() {
return SeedResult::Deadlock(state.trace.clone(), state.process_state);
}
if state.internal_run == 0 {
let refusals = Self::compute_refusals_helper(process, state.process_state);
failures.push((state.trace.clone(), refusals));
}
let action = Self::select_action_helper(&mut rng, process, state.process_state, &actions);
#[cfg(feature = "testing-fault")]
if let Some(ref fault_model) = config.fault_model {
if let Some(fault_record) =
Self::check_fault_injection(fault_model, process, state.process_state, &action.event, &mut rng)
{
injected_faults.push(fault_record);
}
}
Self::execute_transition_helper(process, &state, action, &mut queue);
}
#[cfg(feature = "testing-fault")]
{
SeedResult::Success(longest_trace, failures, injected_faults)
}
#[cfg(not(feature = "testing-fault"))]
{
SeedResult::Success(longest_trace, failures)
}
}
}
impl<'a> ExplorationCore for DefaultExplorationEngine<'a> {
fn process(&self) -> &Process {
self.process
}
fn config(&self) -> &FdrConfig {
&self.config
}
fn explore_seed(&mut self, seed: u64) -> SeedResult {
self.explore_seed_internal(seed)
}
fn traces(&self) -> Vec<Trace> {
self.seed_results
.iter()
.filter_map(|(_, result)| match result {
#[cfg(feature = "testing-fault")]
SeedResult::Success(trace, _failures, _faults) => Some(trace.clone()),
#[cfg(not(feature = "testing-fault"))]
SeedResult::Success(trace, _failures) => Some(trace.clone()),
_ => None,
})
.collect()
}
fn failures(&self) -> Vec<Failure> {
let mut failures = Vec::new();
for (_, result) in &self.seed_results {
#[cfg(feature = "testing-fault")]
if let SeedResult::Success(_trace, seed_failures, _faults) = result {
failures.extend(seed_failures.clone());
}
#[cfg(not(feature = "testing-fault"))]
if let SeedResult::Success(_trace, seed_failures) = result {
failures.extend(seed_failures.clone());
}
}
failures
}
fn states_visited(&self) -> usize {
self.visited_states.len()
}
fn seeds_completed(&self) -> u32 {
self.seed_results.len() as u32
}
fn add_seed_result(&mut self, seed: u64, result: SeedResult) {
self.seed_results.push((seed, result));
}
fn update_visited_states(&mut self, visited: &HashSet<State>) {
self.visited_states.extend(visited.iter().cloned());
}
fn compute_refusals(&self, process: &Process, state: State) -> HashSet<Event> {
Self::compute_refusals_helper(process, state)
}
}
impl<'a> DefaultExplorationEngine<'a> {
fn compute_refusals_helper(process: &Process, state: State) -> RefusalSet {
let enabled_events: HashSet<_> = 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()
}
fn select_action_helper<'b>(
rng: &mut SeededRng,
process: &'b Process,
process_state: State,
actions: &'b [Action],
) -> &'b Action {
if process.choice.contains(&process_state) {
rng.choose(actions).unwrap_or(&actions[0])
} else if actions.len() == 1 {
&actions[0]
} else {
rng.choose(actions).unwrap_or(&actions[0])
}
}
#[cfg(feature = "testing-fault")]
fn check_fault_injection(
fault_model: &FaultModel,
process: &Process,
state: State,
event: &Event,
rng: &mut SeededRng,
) -> Option<InjectedFaultRecord> {
let state_key_str = format!("{}.{}", process.name, state.0);
let lookup_key = (Cow::Borrowed(state_key_str.as_str()), Cow::Borrowed(event.0));
fault_model
.injection_points
.get(&lookup_key)
.and_then(|injection: &FaultInjection| {
let rng_value = (rng.get_next() % 10001) as u16;
if rng_value < injection.probability_bps.get() {
let error = (injection.error_factory)();
Some(InjectedFaultRecord {
csp_state: state_key_str.clone(),
event_label: event.0.to_string(),
error_message: error.to_string(),
probability_bps: injection.probability_bps.get(),
})
} else {
None
}
})
}
fn execute_transition_helper(
process: &Process,
state: &ExplorationState,
action: &Action,
queue: &mut VecDeque<ExplorationState>,
) {
#[cfg(feature = "testing-timing")]
{
use crate::testing::fdr::subsys::timing::check_timed_transition_guard;
if let Some(ref timed_transitions) = process.timed_transitions {
if let Some(transitions) = timed_transitions.get(&(state.process_state, action.event)) {
let valid_transitions: Vec<_> = transitions
.iter()
.filter(|tt| check_timed_transition_guard(tt, &state.clock_values))
.collect();
if valid_transitions.is_empty() {
return;
}
for timed_trans in valid_transitions {
let mut next_exploration = state.branch();
let next_state = timed_trans.to;
next_exploration.reset_clocks(&timed_trans.reset_clocks);
if process.hidden.contains(&action.event) {
next_exploration.record_hidden(action.event, next_state);
} else {
next_exploration.record_observable(action.event, next_state);
if let Some(ref constraints) = process.timing_constraints {
let wcet = Self::lookup_wcet(&action.event, constraints);
if check_event_wcet_violation(&action.event, wcet, constraints) {
continue;
}
next_exploration.update_timing(&action.event, wcet);
next_exploration.update_clocks(wcet);
if Self::check_timing_violations(&next_exploration, constraints) {
continue;
}
} else {
}
}
queue.push_back(next_exploration);
}
return;
}
}
}
let next_states = process.step(state.process_state, &action.event);
for next_state in next_states {
let mut next_exploration = state.branch();
if process.hidden.contains(&action.event) {
next_exploration.record_hidden(action.event, next_state);
} else {
next_exploration.record_observable(action.event, next_state);
#[cfg(feature = "testing-timing")]
{
if let Some(ref constraints) = process.timing_constraints {
let wcet = Self::lookup_wcet(&action.event, constraints);
if check_event_wcet_violation(&action.event, wcet, constraints) {
continue;
}
next_exploration.update_timing(&action.event, wcet);
next_exploration.update_clocks(wcet);
if Self::check_timing_violations(&next_exploration, constraints) {
continue;
}
}
}
}
queue.push_back(next_exploration);
}
}
#[cfg(feature = "testing-timing")]
fn lookup_wcet(event: &Event, constraints: &TimingConstraints) -> Duration {
if let Some(constraint) = constraints.get(event) {
match constraint {
TimingConstraint::Wcet(wcet_config) => wcet_config.duration,
_ => Duration::ZERO, }
} else {
Duration::ZERO }
}
#[cfg(feature = "testing-timing")]
fn check_timing_violations(state: &ExplorationState, constraints: &TimingConstraints) -> bool {
check_timing_violations(&state.trace, state.elapsed_time, &state.event_times, constraints)
}
}