#![cfg(all(feature = "std", feature = "testing-fuzz"))]
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex};
use crate::testing::error::TestingError;
use crate::testing::specs::csp::{Event, Process, State};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FuzzError {
Deadlock { state: State },
InputExhausted { state: State },
EventRejected { state: State, event: Event },
}
impl core::fmt::Display for FuzzError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Deadlock { state } => {
write!(f, "deadlock: no valid events in non-terminal state {}", state.0)
}
Self::InputExhausted { state } => {
write!(f, "input exhausted before terminal state (stopped in {})", state.0)
}
Self::EventRejected { state, event } => {
write!(f, "oracle rejected valid event {} in state {}", event.0, state.0)
}
}
}
}
impl core::error::Error for FuzzError {}
impl From<FuzzError> for TestingError {
fn from(error: FuzzError) -> Self {
match error {
FuzzError::Deadlock { state } => TestingError::FuzzDeadlock(state.0),
FuzzError::InputExhausted { .. } => TestingError::FuzzInputExhausted,
FuzzError::EventRejected { event, .. } => TestingError::FuzzEventRejected(event.0),
}
}
}
#[derive(Debug, Clone)]
pub struct CspOracle {
process: Process,
current_state: State,
visited_states: HashSet<State>,
visited_transitions: HashSet<(State, Event)>,
trace: Vec<Event>,
}
impl CspOracle {
pub fn new(process: Process) -> Self {
let initial = process.initial;
let mut visited_states = HashSet::new();
visited_states.insert(initial);
Self {
process,
current_state: initial,
visited_states,
visited_transitions: HashSet::new(),
trace: Vec::new(),
}
}
pub fn current_state(&self) -> State {
self.current_state
}
pub fn visited_states(&self) -> &HashSet<State> {
&self.visited_states
}
pub fn visited_transitions(&self) -> &HashSet<(State, Event)> {
&self.visited_transitions
}
pub fn trace(&self) -> &[Event] {
&self.trace
}
pub fn is_terminal(&self) -> bool {
self.process.is_terminal(self.current_state)
}
pub fn valid_events(&self) -> Vec<Event> {
if self.is_terminal() {
return Vec::new();
}
self.process
.enabled(self.current_state)
.iter()
.filter(|a| a.is_observable())
.map(|a| a.event)
.collect()
}
pub fn step(&mut self, event: &Event) -> bool {
self.step_with_target(event, 0)
}
pub fn step_with_target(&mut self, event: &Event, choice: u8) -> bool {
let mut next_states = self.process.step(self.current_state, event);
if next_states.is_empty() {
return false;
}
next_states.sort_unstable();
self.visited_transitions.insert((self.current_state, *event));
self.trace.push(*event);
self.current_state = next_states[(choice as usize) % next_states.len()];
self.visited_states.insert(self.current_state);
true
}
pub fn reset(&mut self) {
let initial = self.process.initial;
self.current_state = initial;
self.visited_states.clear();
self.visited_states.insert(initial);
self.visited_transitions.clear();
self.trace.clear();
}
pub fn track_state(&self) -> u32 {
let mut hasher = DefaultHasher::new();
self.current_state.hash(&mut hasher);
hasher.finish() as u32
}
pub fn coverage_score(&self) -> u64 {
((self.visited_states.len() as u64) << 32) | (self.visited_transitions.len() as u64)
}
pub fn state_coverage(&self) -> f64 {
let total_states = self.process.states.len();
if total_states == 0 {
return 0.0;
}
(self.visited_states.len() as f64) / (total_states as f64) * 100.0
}
pub fn transition_coverage(&self) -> (usize, usize) {
let mut total_transitions = 0;
for state in &self.process.states {
for event in self.process.observable.iter().chain(&self.process.hidden) {
if self.process.transitions.targets(*state, event).is_some() {
total_transitions += 1;
}
}
}
(self.visited_transitions.len(), total_transitions)
}
pub fn crash_context(&self) -> String {
let (visited_trans, total_trans) = self.transition_coverage();
format!(
"AFL Crash Context:\n\
Current State: {:?}\n\
Terminal: {}\n\
Trace: {:?}\n\
State Coverage: {:.1}% ({}/{})\n\
Transition Coverage: {:.1}% ({}/{})\n\
Valid Events: {:?}",
self.current_state,
self.is_terminal(),
self.trace,
self.state_coverage(),
self.visited_states.len(),
self.process.states.len(),
if total_trans > 0 {
(visited_trans as f64 / total_trans as f64) * 100.0
} else {
0.0
},
visited_trans,
total_trans,
self.valid_events()
)
}
pub fn fuzz_from_bytes(&mut self, input: &[u8]) -> Result<(), FuzzError> {
self.reset();
let mut byte_idx = 0;
while !self.is_terminal() && byte_idx < input.len() {
let valid = self.valid_events();
if valid.is_empty() {
return Err(FuzzError::Deadlock { state: self.current_state });
}
let choice = (input[byte_idx] as usize) % valid.len();
let event = valid[choice];
byte_idx += 1;
let target_count = self.process.step(self.current_state, &event).len();
let target_choice = if target_count > 1 {
let Some(byte) = input.get(byte_idx) else {
return Err(FuzzError::InputExhausted { state: self.current_state });
};
byte_idx += 1;
*byte
} else {
0
};
if !self.step_with_target(&event, target_choice) {
return Err(FuzzError::EventRejected { state: self.current_state, event });
}
}
if self.is_terminal() {
Ok(())
} else {
Err(FuzzError::InputExhausted { state: self.current_state })
}
}
}
#[derive(Debug, Clone)]
pub struct FuzzContext {
inner: Arc<Mutex<FuzzContextInner>>,
}
#[derive(Debug)]
struct FuzzContextInner {
input: Vec<u8>,
cursor: usize,
oracle: CspOracle,
}
impl FuzzContext {
pub fn new(input: Vec<u8>, process: Process) -> Self {
Self {
inner: Arc::new(Mutex::new(FuzzContextInner {
input,
cursor: 0,
oracle: CspOracle::new(process),
})),
}
}
pub fn fuzz_from_bytes(&self) -> Result<(), TestingError> {
let mut guard = self.inner.lock()?;
let input = guard.input.clone();
guard.oracle.fuzz_from_bytes(&input)?;
Ok(())
}
pub fn trace(&self) -> Vec<Event> {
self.inner.lock().map(|g| g.oracle.trace().to_vec()).unwrap_or_default()
}
pub fn is_terminal(&self) -> bool {
self.inner.lock().map(|g| g.oracle.is_terminal()).unwrap_or(false)
}
pub fn valid_events(&self) -> Vec<Event> {
self.inner.lock().map(|g| g.oracle.valid_events()).unwrap_or_default()
}
pub fn current_state(&self) -> Option<State> {
self.inner.lock().ok().map(|g| g.oracle.current_state())
}
pub fn crash_context(&self) -> String {
self.inner
.lock()
.map(|g| g.oracle.crash_context())
.unwrap_or_else(|_| "Failed to acquire oracle lock".to_string())
}
pub fn coverage_score(&self) -> u64 {
self.inner.lock().map(|g| g.oracle.coverage_score()).unwrap_or(0)
}
pub fn track_state(&self) -> u32 {
self.inner.lock().map(|g| g.oracle.track_state()).unwrap_or(0)
}
pub fn step_event(&self, event: &Event) -> Result<bool, TestingError> {
let mut guard = self.inner.lock()?;
Ok(guard.oracle.step(event))
}
pub fn fuzz_u8(&self) -> Result<u8, TestingError> {
let mut guard = self.inner.lock()?;
if guard.cursor + 1 > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
let value = guard.input[guard.cursor];
guard.cursor += 1;
Ok(value)
}
pub fn fuzz_u16(&self) -> Result<u16, TestingError> {
let mut guard = self.inner.lock()?;
if guard.cursor + 2 > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
let bytes = [guard.input[guard.cursor], guard.input[guard.cursor + 1]];
guard.cursor += 2;
Ok(u16::from_be_bytes(bytes))
}
pub fn fuzz_u32(&self) -> Result<u32, TestingError> {
let mut guard = self.inner.lock()?;
if guard.cursor + 4 > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
let bytes = [
guard.input[guard.cursor],
guard.input[guard.cursor + 1],
guard.input[guard.cursor + 2],
guard.input[guard.cursor + 3],
];
guard.cursor += 4;
Ok(u32::from_be_bytes(bytes))
}
pub fn fuzz_u64(&self) -> Result<u64, TestingError> {
let mut guard = self.inner.lock()?;
if guard.cursor + 8 > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
let bytes = [
guard.input[guard.cursor],
guard.input[guard.cursor + 1],
guard.input[guard.cursor + 2],
guard.input[guard.cursor + 3],
guard.input[guard.cursor + 4],
guard.input[guard.cursor + 5],
guard.input[guard.cursor + 6],
guard.input[guard.cursor + 7],
];
guard.cursor += 8;
Ok(u64::from_be_bytes(bytes))
}
pub fn fuzz_bytes(&self, n: usize) -> Result<Vec<u8>, TestingError> {
let mut guard = self.inner.lock()?;
if guard.cursor + n > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
let bytes = guard.input[guard.cursor..guard.cursor + n].to_vec();
guard.cursor += n;
Ok(bytes)
}
pub fn fuzz_input(&self) -> Result<Vec<u8>, TestingError> {
let guard = self.inner.lock()?;
Ok(guard.input.clone())
}
pub fn fuzz_has_bytes(&self, n: usize) -> Result<bool, TestingError> {
let guard = self.inner.lock()?;
Ok(guard.cursor + n <= guard.input.len())
}
pub fn fuzz_remaining(&self) -> Result<usize, TestingError> {
let guard = self.inner.lock()?;
Ok(guard.input.len() - guard.cursor)
}
pub fn fuzz_peek_u8(&self) -> Result<u8, TestingError> {
let guard = self.inner.lock()?;
if guard.cursor + 1 > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
Ok(guard.input[guard.cursor])
}
pub fn fuzz_peek_bytes(&self, n: usize) -> Result<Vec<u8>, TestingError> {
let guard = self.inner.lock()?;
if guard.cursor + n > guard.input.len() {
return Err(TestingError::FuzzInputExhausted);
}
Ok(guard.input[guard.cursor..guard.cursor + n].to_vec())
}
}
mod tests {
use super::*;
#[allow(dead_code)]
fn build_simple_process(event: &'static str) -> Process {
Process::builder("TestProc")
.initial_state(State("S0"))
.add_observable(event)
.add_transition(State("S0"), event, State("S1"))
.add_terminal(State("S1"))
.build()
.expect("fixture process builder has a valid initial state")
}
#[allow(dead_code)]
fn build_two_step_process(e1: &'static str, e2: &'static str) -> Process {
Process::builder("TestProc")
.initial_state(State("S0"))
.add_observable(e1)
.add_observable(e2)
.add_transition(State("S0"), e1, State("S1"))
.add_transition(State("S1"), e2, State("S2"))
.add_terminal(State("S2"))
.build()
.expect("fixture process builder has a valid initial state")
}
#[allow(dead_code)]
fn build_three_step_process() -> Process {
Process::builder("TestProc")
.initial_state(State("S0"))
.add_observable("a")
.add_observable("b")
.add_observable("c")
.add_transition(State("S0"), "a", State("S1"))
.add_transition(State("S1"), "b", State("S2"))
.add_transition(State("S2"), "c", State("S3"))
.add_terminal(State("S3"))
.build()
.expect("fixture process builder has a valid initial state")
}
#[allow(dead_code)]
fn build_choice_process() -> Process {
Process::builder("TestProc")
.initial_state(State("S0"))
.add_observable("choice")
.add_transition(State("S0"), "choice", State("S1"))
.add_transition(State("S0"), "choice", State("S2"))
.add_choice(State("S0"))
.add_terminal(State("S1"))
.add_terminal(State("S2"))
.build()
.expect("fixture process builder has a valid initial state")
}
#[allow(dead_code)]
fn build_branching_process() -> Process {
Process::builder("TestProc")
.initial_state(State("S0"))
.add_observable("a")
.add_observable("b")
.add_observable("c")
.add_transition(State("S0"), "a", State("S1"))
.add_transition(State("S0"), "b", State("S2"))
.add_transition(State("S0"), "c", State("S3"))
.add_terminal(State("S1"))
.add_terminal(State("S2"))
.add_terminal(State("S3"))
.build()
.expect("fixture process builder has a valid initial state")
}
#[cfg(feature = "testing-fuzz-ijon")]
#[test]
fn oracle_ijon_feature_enabled() {
let proc = build_simple_process("go");
let mut oracle = CspOracle::new(proc);
let input = vec![0];
let result = oracle.fuzz_from_bytes(&input);
assert!(result.is_ok(), "IJON feature should not break fuzzing");
assert_eq!(oracle.visited_states().len(), 2);
assert_eq!(oracle.visited_transitions().len(), 1);
let _ = oracle.track_state();
let _ = oracle.coverage_score();
}
macro_rules! generate_oracle_core_tests {
($module_name:ident) => {
mod $module_name {
#[test]
fn tracks_state_transitions() {
let mut oracle = super::CspOracle::new(super::build_two_step_process("go", "stop"));
assert_eq!(oracle.current_state(), super::State("S0"));
assert_eq!(oracle.visited_states().len(), 1);
assert!(!oracle.is_terminal());
let valid = oracle.valid_events();
assert_eq!(valid.len(), 1);
assert_eq!(valid[0].0, "go");
assert!(oracle.step(&super::Event("go")));
assert_eq!(oracle.current_state(), super::State("S1"));
assert_eq!(oracle.visited_states().len(), 2);
assert_eq!(oracle.visited_transitions().len(), 1);
assert_eq!(oracle.trace().len(), 1);
assert!(oracle.step(&super::Event("stop")));
assert_eq!(oracle.current_state(), super::State("S2"));
assert!(oracle.is_terminal());
assert_eq!(oracle.valid_events().len(), 0);
}
#[test]
fn rejects_invalid_events() {
let mut oracle = super::CspOracle::new(super::build_simple_process("valid"));
assert!(!oracle.step(&super::Event("invalid")));
assert_eq!(oracle.current_state(), super::State("S0"));
assert_eq!(oracle.visited_transitions().len(), 0);
}
#[test]
fn oracle_reset() {
let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
oracle.step(&super::Event("go"));
assert_eq!(oracle.current_state(), super::State("S1"));
oracle.reset();
assert_eq!(oracle.current_state(), super::State("S0"));
assert_eq!(oracle.visited_states().len(), 1);
assert_eq!(oracle.visited_transitions().len(), 0);
assert_eq!(oracle.trace().len(), 0);
}
#[test]
fn oracle_with_choice_points() {
let mut oracle = super::CspOracle::new(super::build_choice_process());
let valid = oracle.valid_events();
assert_eq!(valid.len(), 1);
assert_eq!(valid[0].0, "choice");
assert!(oracle.step(&super::Event("choice")));
assert_eq!(oracle.current_state(), super::State("S1"));
}
}
};
}
macro_rules! generate_coverage_tests {
($module_name:ident) => {
mod $module_name {
#[test]
fn oracle_coverage_metrics() {
let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
let coverage = oracle.state_coverage();
assert!((coverage - 33.33).abs() < 0.1);
oracle.step(&super::Event("a"));
let coverage = oracle.state_coverage();
assert!((coverage - 66.66).abs() < 0.1);
oracle.step(&super::Event("b"));
let coverage = oracle.state_coverage();
assert!((coverage - 100.0).abs() < 0.1);
let (visited, _total) = oracle.transition_coverage();
assert_eq!(visited, 2);
}
#[test]
fn oracle_track_state_is_stable() {
let proc = super::build_simple_process("go");
let oracle1 = super::CspOracle::new(proc.clone());
let oracle2 = super::CspOracle::new(proc);
assert_eq!(oracle1.track_state(), oracle2.track_state());
}
#[test]
fn oracle_coverage_score_increases() {
let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
let score1 = oracle.coverage_score();
oracle.step(&super::Event("go"));
let score2 = oracle.coverage_score();
assert!(score2 > score1);
}
}
};
}
macro_rules! generate_fuzzing_tests {
($module_name:ident) => {
mod $module_name {
#[test]
fn oracle_fuzz_from_bytes_reaches_terminal() {
let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
let input = vec![0];
assert!(oracle.fuzz_from_bytes(&input).is_ok());
assert_eq!(oracle.current_state(), super::State("S1"));
assert!(oracle.is_terminal());
}
#[test]
fn oracle_fuzz_from_bytes_multiple_transitions() {
let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
let input = vec![0, 0];
assert!(oracle.fuzz_from_bytes(&input).is_ok());
assert_eq!(oracle.current_state(), super::State("S2"));
assert_eq!(oracle.trace().len(), 2);
}
#[test]
fn oracle_fuzz_from_bytes_fails_on_insufficient_input() {
let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
let input = vec![0];
assert!(matches!(
oracle.fuzz_from_bytes(&input),
Err(super::FuzzError::InputExhausted { .. })
));
}
#[test]
fn oracle_deadlock_reported_as_deadlock() {
let process = super::Process::builder("TestProc")
.initial_state(super::State("S0"))
.add_observable("go")
.add_transition(super::State("S0"), "go", super::State("Stuck"))
.build()
.expect("fixture process builder has a valid initial state");
let mut oracle = super::CspOracle::new(process);
assert!(matches!(
oracle.fuzz_from_bytes(&[0, 0]),
Err(super::FuzzError::Deadlock { state: super::State("Stuck") })
));
}
#[test]
fn oracle_byte_mapping_deterministic_across_oracles() {
let traces: Vec<Vec<super::Event>> = (0..8)
.map(|_| {
let mut oracle = super::CspOracle::new(super::build_branching_process());
oracle.fuzz_from_bytes(&[1]).expect("branching process reaches terminal");
oracle.trace().to_vec()
})
.collect();
for trace in &traces {
assert_eq!(trace, &traces[0]);
}
}
#[test]
fn oracle_coverage_increases_during_fuzzing() {
let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
let initial_score = oracle.coverage_score();
let input = vec![0, 0];
let _ = oracle.fuzz_from_bytes(&input);
let final_score = oracle.coverage_score();
assert!(final_score > initial_score);
assert_eq!(oracle.visited_states().len(), 3);
assert_eq!(oracle.visited_transitions().len(), 2);
}
#[test]
fn oracle_track_state_differs_between_states() {
let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
let hash_s0 = oracle.track_state();
oracle.step(&super::Event("go"));
let hash_s1 = oracle.track_state();
assert_ne!(hash_s0, hash_s1);
}
#[test]
fn oracle_crash_context_provides_debug_info() {
let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
oracle.step(&super::Event("go"));
let context = oracle.crash_context();
assert!(context.contains("Current State:"));
assert!(context.contains("S1"));
assert!(context.contains("Terminal: true"));
assert!(context.contains("Trace:"));
assert!(context.contains("Coverage:"));
}
}
};
}
macro_rules! generate_context_tests {
($module_name:ident) => {
mod $module_name {
#[test]
fn fuzz_context_executes_oracle() {
let proc = super::build_simple_process("go");
let ctx = super::FuzzContext::new(vec![0], proc);
assert!(ctx.fuzz_from_bytes().is_ok());
assert!(ctx.is_terminal());
assert_eq!(ctx.trace().len(), 1);
}
#[test]
fn fuzz_context_ijon_accessors() {
let proc = super::build_two_step_process("a", "b");
let ctx = super::FuzzContext::new(vec![0, 0], proc);
let initial_score = ctx.coverage_score();
let initial_hash = ctx.track_state();
let _ = ctx.fuzz_from_bytes();
let final_score = ctx.coverage_score();
let final_hash = ctx.track_state();
assert!(final_score > initial_score);
assert_ne!(initial_hash, final_hash);
}
#[test]
fn fuzz_context_crash_context() {
let proc = super::build_simple_process("go");
let ctx = super::FuzzContext::new(vec![0], proc);
let _ = ctx.fuzz_from_bytes();
let context = ctx.crash_context();
assert!(context.contains("AFL Crash Context"));
assert!(context.contains("Current State:"));
assert!(context.contains("S1"));
assert!(context.contains("Coverage:"));
}
#[test]
fn fuzz_context_thread_safe_clone() {
let proc = super::build_simple_process("go");
let ctx1 = super::FuzzContext::new(vec![0], proc);
let ctx2 = ctx1.clone();
let _ = ctx1.fuzz_from_bytes();
assert_eq!(ctx1.current_state(), ctx2.current_state());
}
}
};
}
macro_rules! generate_advanced_tests {
($module_name:ident) => {
mod $module_name {
#[test]
fn oracle_input_modulo_selection() {
let proc = super::build_branching_process();
let oracle_ref = super::CspOracle::new(proc.clone());
let valid = oracle_ref.valid_events();
assert_eq!(valid.len(), 3);
let state_for_choice: Vec<super::State> = (0..3)
.map(|choice| {
let mut oracle = super::CspOracle::new(proc.clone());
oracle.fuzz_from_bytes(&[choice as u8]).unwrap();
oracle.current_state()
})
.collect();
let test_cases = vec![
(0, 0, "byte=0: 0 % 3 = 0 -> first event"),
(1, 1, "byte=1: 1 % 3 = 1 -> second event"),
(2, 2, "byte=2: 2 % 3 = 2 -> third event"),
(3, 0, "byte=3: 3 % 3 = 0 -> wraps to first"),
(255, 0, "byte=255: 255 % 3 = 0 -> wraps to first"),
];
for (input_byte, expected_choice, desc) in test_cases {
let mut oracle = super::CspOracle::new(proc.clone());
assert!(oracle.fuzz_from_bytes(&[input_byte]).is_ok());
assert_eq!(oracle.current_state(), state_for_choice[expected_choice], "{}", desc);
}
}
#[test]
fn oracle_choice_point_fuzzing() {
let proc = super::build_choice_process();
let mut oracle = super::CspOracle::new(proc);
assert!(oracle.fuzz_from_bytes(&[0, 0]).is_ok());
assert_eq!(oracle.current_state(), super::State("S1"));
}
#[test]
fn oracle_target_byte_reaches_second_nondeterministic_target() {
let proc = super::build_choice_process();
let mut oracle = super::CspOracle::new(proc);
assert!(oracle.fuzz_from_bytes(&[0, 1]).is_ok());
assert_eq!(oracle.current_state(), super::State("S2"));
}
#[test]
fn oracle_reset_between_fuzz_runs() {
let proc = super::build_simple_process("go");
let mut oracle = super::CspOracle::new(proc);
assert!(oracle.fuzz_from_bytes(&[0]).is_ok());
assert_eq!(oracle.current_state(), super::State("S1"));
assert_eq!(oracle.trace().len(), 1);
assert!(oracle.fuzz_from_bytes(&[0]).is_ok());
assert_eq!(oracle.current_state(), super::State("S1"));
assert_eq!(oracle.trace().len(), 1);
}
#[test]
fn oracle_exhaustive_state_exploration() {
let proc = super::build_three_step_process();
let mut oracle = super::CspOracle::new(proc);
let input = vec![0, 0, 0];
assert!(oracle.fuzz_from_bytes(&input).is_ok());
assert_eq!(oracle.visited_states().len(), 4);
assert_eq!(oracle.visited_transitions().len(), 3);
assert_eq!(oracle.state_coverage(), 100.0);
}
#[test]
fn fuzz_context_concurrent_access() {
let proc = super::build_simple_process("go");
let ctx = std::sync::Arc::new(super::FuzzContext::new(vec![0], proc));
let handles: Vec<_> = (0..4)
.map(|_| {
let ctx_clone = std::sync::Arc::clone(&ctx);
std::thread::spawn(move || {
let _ = ctx_clone.coverage_score();
let _ = ctx_clone.track_state();
let _ = ctx_clone.is_terminal();
let _ = ctx_clone.current_state();
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should not panic");
}
}
}
};
}
generate_oracle_core_tests!(oracle_core);
generate_coverage_tests!(coverage);
generate_fuzzing_tests!(fuzzing);
generate_context_tests!(context);
generate_advanced_tests!(advanced);
}