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, SeedResult};
use crate::testing::specs::csp::Process;
#[cfg(feature = "testing-fmea")]
use crate::testing::fmea::generate_fmea_report;
#[cfg(feature = "rayon")]
use crate::testing::specs::csp::State;
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>)> = seeds
.par_iter()
.map(|&seed| {
let (result, visited) = DefaultExplorationEngine::explore_seed_static(process, config, seed);
(seed, result, visited)
})
.collect();
for (seed, result, visited) in results {
self.update_verdict_from_result(seed, &result);
self.explorer.add_seed_result(seed, result);
self.explorer.update_visited_states(&visited);
}
}
#[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.check_determinism();
self.verdict.passed = self.verdict.divergence_free
&& self.verdict.deadlock_free
&& (self.verdict.is_deterministic || self.verdict.determinism_witness.is_none());
#[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 {
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);
}
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;
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 spec_process = &self.config.specs[0];
let config = &self.config;
#[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>)> = seeds
.par_iter()
.map(|&seed| {
let (result, visited) = DefaultExplorationEngine::explore_seed_static(spec_process, config, seed);
(seed, result, visited)
})
.collect();
for (seed, result, visited) in results {
self.update_verdict_from_result(seed, &result);
self.explorer.add_seed_result(seed, result);
self.explorer.update_visited_states(&visited);
}
}
#[cfg(not(feature = "rayon"))]
{
for seed in 0..config.seeds {
let (result, visited) =
DefaultExplorationEngine::explore_seed_static(spec_process, config, seed as u64);
self.update_verdict_from_result(seed as u64, &result);
self.explorer.add_seed_result(seed as u64, result);
self.explorer.update_visited_states(&visited);
}
}
self.verdict.traces_explored = self.explorer.traces().len();
self.verdict.states_visited = self.explorer.states_visited();
self.check_determinism();
self.verdict.passed = self.verdict.divergence_free
&& self.verdict.deadlock_free
&& (self.verdict.is_deterministic || self.verdict.determinism_witness.is_none());
}
#[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 traces = self.explorer.traces();
if traces.len() > 1 {
let first_trace = &traces[0];
for trace in &traces[1..] {
if trace != first_trace {
self.verdict.is_deterministic = false;
break;
}
}
}
}
fn check_refinement(&mut self) {
if self.config.specs.is_empty() {
return;
}
let specs = self.config.specs.clone();
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 = w;
},
);
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 = w;
},
);
if self.verdict.trace_refines && self.verdict.divergence_refines {
self.verdict.passed = true;
}
}
fn check_refinement_for_specs<W, F, G>(&mut self, specs: &[Process], check: F, update_witness: G)
where
F: Fn(&mut R, &Process) -> (bool, Option<W>),
G: Fn(&mut FdrVerdict, Option<W>),
{
for spec in specs {
let (passed, witness) = check(&mut self.refinement, spec);
if !passed {
self.verdict.passed = false;
update_witness(&mut self.verdict, witness);
if self.config.fail_fast {
return;
}
}
}
}
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 = DefaultExplorationEngine::new(process, Arc::clone(&config));
let cache = Rc::new(RefCell::new(DefaultCache::new()));
let refinement = DefaultRefinementChecker::new(process, Arc::clone(&config), cache);
FdrExplorer::new_with_arc(process, config, explorer, refinement)
}
}