use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
#[cfg(feature = "rayon")]
use std::collections::HashSet;
use super::cache::DefaultCache;
use super::exploration::DefaultExplorationEngine;
use super::refinement::DefaultRefinementChecker;
use crate::testing::fdr::config::{Failure, FdrConfig, FdrVerdict, Trace};
use crate::testing::fdr::explorer::{ExplorationCore, RefinementChecker, RefinementOutcome, SeedResult};
use crate::testing::specs::csp::{Process, State};
#[cfg(feature = "testing-fmea")]
use crate::testing::fmea::generate_fmea_report;
pub struct FdrExplorer<'a, E, R>
where
E: ExplorationCore,
R: RefinementChecker,
{
process: &'a Process,
config: Arc<FdrConfig>,
explorer: E,
refinement: R,
verdict: FdrVerdict,
}
pub type DefaultFdrExplorer<'a> =
FdrExplorer<'a, DefaultExplorationEngine<'a>, DefaultRefinementChecker<'a, DefaultCache>>;
impl<'a, E, R> FdrExplorer<'a, E, R>
where
E: ExplorationCore,
R: RefinementChecker,
{
pub fn new(process: &'a Process, config: FdrConfig, explorer: E, refinement: R) -> Self {
Self {
process,
config: Arc::new(config),
explorer,
refinement,
verdict: FdrVerdict::default(),
}
}
pub fn new_with_arc(process: &'a Process, config: Arc<FdrConfig>, explorer: E, refinement: R) -> Self {
Self { process, config, explorer, refinement, verdict: FdrVerdict::default() }
}
pub fn explore(&mut self) -> FdrVerdict {
#[cfg(feature = "testing-fault")]
if self.config.fault_model.is_some() && !self.config.specs.is_empty() {
self.explore_specification_with_faults();
#[cfg(feature = "testing-fmea")]
self.generate_fmea_if_configured();
return self.verdict.clone();
}
if !self.config.specs.is_empty() {
self.check_refinement();
#[cfg(feature = "testing-fmea")]
self.generate_fmea_if_configured();
return self.verdict.clone();
}
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
let seeds: Vec<u64> = (0..self.config.seeds).map(|s| s as u64).collect();
let process = self.process;
let config = &self.config;
let results: Vec<(u64, SeedResult, HashSet<State>, usize)> = seeds
.par_iter()
.map(|&seed| {
let (result, visited, pruned) =
DefaultExplorationEngine::explore_seed_static(process, config, seed);
(seed, result, visited, pruned)
})
.collect();
for (seed, result, visited, pruned) in results {
self.update_verdict_from_result(seed, &result);
self.explorer.add_seed_result(seed, result);
self.explorer.update_visited_states(&visited);
self.explorer.add_timing_pruned(pruned);
}
}
#[cfg(not(feature = "rayon"))]
{
for seed in 0..self.config.seeds {
let result = self.explorer.explore_seed(seed as u64);
self.update_verdict_from_result(seed as u64, &result);
self.explorer.add_seed_result(seed as u64, result);
}
}
self.verdict.traces_explored = self.explorer.traces().len();
self.verdict.states_visited = self.explorer.states_visited();
self.verdict.timing_pruned = self.explorer.timing_pruned();
self.check_determinism();
self.verdict.passed = self.verdict.divergence_free && self.verdict.deadlock_free;
#[cfg(feature = "testing-fmea")]
self.generate_fmea_if_configured();
self.verdict.clone()
}
fn update_verdict_from_result(&mut self, seed: u64, result: &SeedResult) {
match result {
#[cfg(feature = "testing-fault")]
SeedResult::Divergence(_trace, hidden, faults) => {
self.verdict.divergence_free = false;
self.verdict.passed = false;
self.verdict.divergence_witness = Some((seed, hidden.clone()));
self.verdict.failing_seed = Some(seed);
if !faults.is_empty() {
self.verdict.error_recovery_failed += 1;
}
self.verdict.faults_injected.extend(faults.clone());
}
#[cfg(not(feature = "testing-fault"))]
SeedResult::Divergence(_trace, hidden) => {
self.verdict.divergence_free = false;
self.verdict.passed = false;
self.verdict.divergence_witness = Some((seed, hidden.clone()));
self.verdict.failing_seed = Some(seed);
}
#[cfg(feature = "testing-fault")]
SeedResult::Deadlock(trace, state, faults) => {
self.verdict.deadlock_free = false;
self.verdict.passed = false;
self.verdict.deadlock_witness = Some((seed, trace.clone(), *state));
self.verdict.failing_seed = Some(seed);
if !faults.is_empty() {
self.verdict.error_recovery_failed += 1;
}
self.verdict.faults_injected.extend(faults.clone());
}
#[cfg(not(feature = "testing-fault"))]
SeedResult::Deadlock(trace, state) => {
self.verdict.deadlock_free = false;
self.verdict.passed = false;
self.verdict.deadlock_witness = Some((seed, trace.clone(), *state));
self.verdict.failing_seed = Some(seed);
}
#[cfg(feature = "testing-fault")]
SeedResult::Success(_trace, _failures, faults) => {
self.verdict.seeds_completed += 1;
if !faults.is_empty() {
self.verdict.error_recovery_successful += 1;
}
self.verdict.faults_injected.extend(faults.clone());
}
#[cfg(not(feature = "testing-fault"))]
SeedResult::Success(..) => {
self.verdict.seeds_completed += 1;
}
}
}
#[cfg(feature = "testing-fault")]
fn explore_specification_with_faults(&mut self) {
if self.config.specs.is_empty() {
return;
}
let config = Arc::clone(&self.config);
for spec_process in &config.specs {
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
let seeds: Vec<u64> = (0..config.seeds).map(|s| s as u64).collect();
let results: Vec<(u64, SeedResult, HashSet<State>, usize)> = seeds
.par_iter()
.map(|&seed| {
let (result, visited, pruned) =
DefaultExplorationEngine::explore_seed_static(spec_process, &config, seed);
(seed, result, visited, pruned)
})
.collect();
for (seed, result, visited, pruned) in results {
self.update_verdict_from_result(seed, &result);
self.explorer.add_seed_result(seed, result);
self.explorer.update_visited_states(&visited);
self.explorer.add_timing_pruned(pruned);
}
}
#[cfg(not(feature = "rayon"))]
{
let results: Vec<_> = (0..config.seeds)
.map(|seed| {
let seed = seed as u64;
let (result, visited, pruned) =
DefaultExplorationEngine::explore_seed_static(spec_process, &config, seed);
(seed, result, visited, pruned)
})
.collect();
for (seed, result, visited, pruned) in results {
self.update_verdict_from_result(seed, &result);
self.explorer.add_seed_result(seed, result);
self.explorer.update_visited_states(&visited);
self.explorer.add_timing_pruned(pruned);
}
}
}
self.verdict.traces_explored = self.explorer.traces().len();
self.verdict.states_visited = self.explorer.states_visited();
self.verdict.timing_pruned = self.explorer.timing_pruned();
self.check_determinism();
self.verdict.passed = self.verdict.divergence_free && self.verdict.deadlock_free;
}
#[cfg(feature = "testing-fmea")]
fn generate_fmea_if_configured(&mut self) {
if let Some(ref fmea_config) = self.config.fmea_config {
if fmea_config.auto_generate && !self.verdict.faults_injected.is_empty() {
match generate_fmea_report(&self.verdict, self.process, Some(fmea_config.clone())) {
Ok(report) => self.verdict.fmea_report = Some(report),
Err(e) => eprintln!("Warning: FMEA generation failed: {}", e),
}
}
}
}
fn check_determinism(&mut self) {
let mut states: Vec<State> = self.process.states.iter().copied().collect();
states.sort_unstable_by_key(|state| state.0);
let mut witness = None;
'states: for state in states {
for action in self.process.enabled(state) {
if action.is_hidden() || self.process.step(state, &action.event).len() > 1 {
witness = Some((state, action.event));
break 'states;
}
}
}
self.verdict.is_deterministic = witness.is_none();
self.verdict.determinism_witness = witness;
}
fn check_refinement(&mut self) {
if self.config.specs.is_empty() {
return;
}
let specs = self.config.specs.clone();
let mut inconclusive = false;
inconclusive |= self.check_refinement_for_specs(
&specs,
|r, s| r.check_trace_refinement(s, self.process),
|v, w| {
v.trace_refines = false;
v.trace_refinement_witness = Some(w);
},
);
inconclusive |= self.check_refinement_for_specs(
&specs,
|r, s| r.check_failures_refinement(s, self.process),
|v, w| {
v.failures_refines = false;
v.failures_refinement_witness = Some(w);
},
);
inconclusive |= self.check_refinement_for_specs(
&specs,
|r, s| r.check_divergence_refinement(s, self.process),
|v, w| {
v.divergence_refines = false;
v.divergence_refinement_witness = Some(w);
},
);
self.verdict.passed = self.verdict.trace_refines
&& self.verdict.failures_refines
&& self.verdict.divergence_refines
&& !inconclusive;
}
fn check_refinement_for_specs<W, F, G>(&mut self, specs: &[Process], check: F, record_violation: G) -> bool
where
F: Fn(&mut R, &Process) -> RefinementOutcome<W>,
G: Fn(&mut FdrVerdict, W),
{
let mut inconclusive = false;
for spec in specs {
match check(&mut self.refinement, spec) {
RefinementOutcome::Holds { complete } => {
if !complete {
self.verdict.complete = false;
}
}
RefinementOutcome::Violated(witness) => {
self.verdict.passed = false;
record_violation(&mut self.verdict, witness);
if self.config.fail_fast {
return inconclusive;
}
}
RefinementOutcome::Inconclusive => {
self.verdict.complete = false;
inconclusive = true;
}
}
}
inconclusive
}
pub fn traces(&self) -> Vec<Trace> {
self.explorer.traces()
}
pub fn failures(&self) -> Vec<Failure> {
self.explorer.failures()
}
}
impl<'a> DefaultFdrExplorer<'a> {
pub fn with_defaults(process: &'a Process, config: impl Into<Arc<FdrConfig>>) -> Self {
let config = config.into();
let explorer_config = Arc::clone(&config);
let explorer = DefaultExplorationEngine::new(process, explorer_config);
let cache = Rc::new(RefCell::new(DefaultCache::new()));
let refinement_config = Arc::clone(&config);
let refinement = DefaultRefinementChecker::new(process, refinement_config, cache);
FdrExplorer::new_with_arc(process, config, explorer, refinement)
}
}