1#![cfg(all(feature = "std", feature = "testing-fuzz"))]
122
123use std::collections::hash_map::DefaultHasher;
124use std::collections::HashSet;
125use std::hash::{Hash, Hasher};
126use std::sync::{Arc, Mutex};
127
128use crate::testing::error::TestingError;
129use crate::testing::specs::csp::{Event, Process, State};
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum FuzzError {
134 Deadlock { state: State },
136 InputExhausted { state: State },
138 EventRejected { state: State, event: Event },
140}
141
142impl core::fmt::Display for FuzzError {
143 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 match self {
145 Self::Deadlock { state } => {
146 write!(f, "deadlock: no valid events in non-terminal state {}", state.0)
147 }
148 Self::InputExhausted { state } => {
149 write!(f, "input exhausted before terminal state (stopped in {})", state.0)
150 }
151 Self::EventRejected { state, event } => {
152 write!(f, "oracle rejected valid event {} in state {}", event.0, state.0)
153 }
154 }
155 }
156}
157
158impl core::error::Error for FuzzError {}
159
160impl From<FuzzError> for TestingError {
161 fn from(error: FuzzError) -> Self {
162 match error {
163 FuzzError::Deadlock { state } => TestingError::FuzzDeadlock(state.0),
164 FuzzError::InputExhausted { .. } => TestingError::FuzzInputExhausted,
165 FuzzError::EventRejected { event, .. } => TestingError::FuzzEventRejected(event.0),
166 }
167 }
168}
169
170#[derive(Debug, Clone)]
193pub struct CspOracle {
194 process: Process,
195 current_state: State,
196 visited_states: HashSet<State>,
197 visited_transitions: HashSet<(State, Event)>,
198 trace: Vec<Event>,
199}
200
201impl CspOracle {
202 pub fn new(process: Process) -> Self {
204 let initial = process.initial;
205 let mut visited_states = HashSet::new();
206 visited_states.insert(initial);
207
208 Self {
209 process,
210 current_state: initial,
211 visited_states,
212 visited_transitions: HashSet::new(),
213 trace: Vec::new(),
214 }
215 }
216
217 pub fn current_state(&self) -> State {
219 self.current_state
220 }
221
222 pub fn visited_states(&self) -> &HashSet<State> {
224 &self.visited_states
225 }
226
227 pub fn visited_transitions(&self) -> &HashSet<(State, Event)> {
229 &self.visited_transitions
230 }
231
232 pub fn trace(&self) -> &[Event] {
234 &self.trace
235 }
236
237 pub fn is_terminal(&self) -> bool {
239 self.process.is_terminal(self.current_state)
240 }
241
242 pub fn valid_events(&self) -> Vec<Event> {
251 if self.is_terminal() {
252 return Vec::new();
253 }
254
255 self.process
256 .enabled(self.current_state)
257 .iter()
258 .filter(|a| a.is_observable())
259 .map(|a| a.event)
260 .collect()
261 }
262
263 pub fn step(&mut self, event: &Event) -> bool {
271 self.step_with_target(event, 0)
272 }
273
274 pub fn step_with_target(&mut self, event: &Event, choice: u8) -> bool {
279 let mut next_states = self.process.step(self.current_state, event);
280
281 if next_states.is_empty() {
282 return false;
283 }
284
285 next_states.sort_unstable();
286
287 self.visited_transitions.insert((self.current_state, *event));
289 self.trace.push(*event);
290
291 self.current_state = next_states[(choice as usize) % next_states.len()];
292 self.visited_states.insert(self.current_state);
293
294 true
295 }
296
297 pub fn reset(&mut self) {
299 let initial = self.process.initial;
300 self.current_state = initial;
301 self.visited_states.clear();
302 self.visited_states.insert(initial);
303 self.visited_transitions.clear();
304 self.trace.clear();
305 }
306
307 pub fn track_state(&self) -> u32 {
332 let mut hasher = DefaultHasher::new();
333 self.current_state.hash(&mut hasher);
334 hasher.finish() as u32
335 }
336
337 pub fn coverage_score(&self) -> u64 {
363 ((self.visited_states.len() as u64) << 32) | (self.visited_transitions.len() as u64)
364 }
365
366 pub fn state_coverage(&self) -> f64 {
368 let total_states = self.process.states.len();
369 if total_states == 0 {
370 return 0.0;
371 }
372 (self.visited_states.len() as f64) / (total_states as f64) * 100.0
373 }
374
375 pub fn transition_coverage(&self) -> (usize, usize) {
377 let mut total_transitions = 0;
379 for state in &self.process.states {
380 for event in self.process.observable.iter().chain(&self.process.hidden) {
381 if self.process.transitions.targets(*state, event).is_some() {
382 total_transitions += 1;
383 }
384 }
385 }
386
387 (self.visited_transitions.len(), total_transitions)
388 }
389
390 pub fn crash_context(&self) -> String {
399 let (visited_trans, total_trans) = self.transition_coverage();
400 format!(
401 "AFL Crash Context:\n\
402 Current State: {:?}\n\
403 Terminal: {}\n\
404 Trace: {:?}\n\
405 State Coverage: {:.1}% ({}/{})\n\
406 Transition Coverage: {:.1}% ({}/{})\n\
407 Valid Events: {:?}",
408 self.current_state,
409 self.is_terminal(),
410 self.trace,
411 self.state_coverage(),
412 self.visited_states.len(),
413 self.process.states.len(),
414 if total_trans > 0 {
415 (visited_trans as f64 / total_trans as f64) * 100.0
416 } else {
417 0.0
418 },
419 visited_trans,
420 total_trans,
421 self.valid_events()
422 )
423 }
424
425 pub fn fuzz_from_bytes(&mut self, input: &[u8]) -> Result<(), FuzzError> {
469 self.reset();
470
471 let mut byte_idx = 0;
472 while !self.is_terminal() && byte_idx < input.len() {
473 let valid = self.valid_events();
474 if valid.is_empty() {
475 return Err(FuzzError::Deadlock { state: self.current_state });
476 }
477
478 let choice = (input[byte_idx] as usize) % valid.len();
480 let event = valid[choice];
481
482 byte_idx += 1;
483
484 let target_count = self.process.step(self.current_state, &event).len();
487 let target_choice = if target_count > 1 {
488 let Some(byte) = input.get(byte_idx) else {
489 return Err(FuzzError::InputExhausted { state: self.current_state });
490 };
491
492 byte_idx += 1;
493
494 *byte
495 } else {
496 0
497 };
498
499 if !self.step_with_target(&event, target_choice) {
500 return Err(FuzzError::EventRejected { state: self.current_state, event });
501 }
502 }
503
504 if self.is_terminal() {
505 Ok(())
506 } else {
507 Err(FuzzError::InputExhausted { state: self.current_state })
508 }
509 }
510}
511
512#[derive(Debug, Clone)]
548pub struct FuzzContext {
549 inner: Arc<Mutex<FuzzContextInner>>,
550}
551
552#[derive(Debug)]
553struct FuzzContextInner {
554 input: Vec<u8>,
555 cursor: usize,
556 oracle: CspOracle,
557}
558
559impl FuzzContext {
560 pub fn new(input: Vec<u8>, process: Process) -> Self {
562 Self {
563 inner: Arc::new(Mutex::new(FuzzContextInner {
564 input,
565 cursor: 0,
566 oracle: CspOracle::new(process),
567 })),
568 }
569 }
570
571 pub fn fuzz_from_bytes(&self) -> Result<(), TestingError> {
578 let mut guard = self.inner.lock()?;
579 let input = guard.input.clone();
580
581 guard.oracle.fuzz_from_bytes(&input)?;
582
583 Ok(())
584 }
585
586 pub fn trace(&self) -> Vec<Event> {
588 self.inner.lock().map(|g| g.oracle.trace().to_vec()).unwrap_or_default()
589 }
590
591 pub fn is_terminal(&self) -> bool {
593 self.inner.lock().map(|g| g.oracle.is_terminal()).unwrap_or(false)
594 }
595
596 pub fn valid_events(&self) -> Vec<Event> {
598 self.inner.lock().map(|g| g.oracle.valid_events()).unwrap_or_default()
599 }
600
601 pub fn current_state(&self) -> Option<State> {
603 self.inner.lock().ok().map(|g| g.oracle.current_state())
604 }
605
606 pub fn crash_context(&self) -> String {
611 self.inner
612 .lock()
613 .map(|g| g.oracle.crash_context())
614 .unwrap_or_else(|_| "Failed to acquire oracle lock".to_string())
615 }
616
617 pub fn coverage_score(&self) -> u64 {
622 self.inner.lock().map(|g| g.oracle.coverage_score()).unwrap_or(0)
623 }
624
625 pub fn track_state(&self) -> u32 {
630 self.inner.lock().map(|g| g.oracle.track_state()).unwrap_or(0)
631 }
632
633 pub fn step_event(&self, event: &Event) -> Result<bool, TestingError> {
647 let mut guard = self.inner.lock()?;
648 Ok(guard.oracle.step(event))
649 }
650
651 pub fn fuzz_u8(&self) -> Result<u8, TestingError> {
653 let mut guard = self.inner.lock()?;
654 if guard.cursor + 1 > guard.input.len() {
655 return Err(TestingError::FuzzInputExhausted);
656 }
657
658 let value = guard.input[guard.cursor];
659
660 guard.cursor += 1;
661
662 Ok(value)
663 }
664
665 pub fn fuzz_u16(&self) -> Result<u16, TestingError> {
667 let mut guard = self.inner.lock()?;
668 if guard.cursor + 2 > guard.input.len() {
669 return Err(TestingError::FuzzInputExhausted);
670 }
671
672 let bytes = [guard.input[guard.cursor], guard.input[guard.cursor + 1]];
673
674 guard.cursor += 2;
675
676 Ok(u16::from_be_bytes(bytes))
677 }
678
679 pub fn fuzz_u32(&self) -> Result<u32, TestingError> {
681 let mut guard = self.inner.lock()?;
682 if guard.cursor + 4 > guard.input.len() {
683 return Err(TestingError::FuzzInputExhausted);
684 }
685 let bytes = [
686 guard.input[guard.cursor],
687 guard.input[guard.cursor + 1],
688 guard.input[guard.cursor + 2],
689 guard.input[guard.cursor + 3],
690 ];
691
692 guard.cursor += 4;
693
694 Ok(u32::from_be_bytes(bytes))
695 }
696
697 pub fn fuzz_u64(&self) -> Result<u64, TestingError> {
699 let mut guard = self.inner.lock()?;
700 if guard.cursor + 8 > guard.input.len() {
701 return Err(TestingError::FuzzInputExhausted);
702 }
703 let bytes = [
704 guard.input[guard.cursor],
705 guard.input[guard.cursor + 1],
706 guard.input[guard.cursor + 2],
707 guard.input[guard.cursor + 3],
708 guard.input[guard.cursor + 4],
709 guard.input[guard.cursor + 5],
710 guard.input[guard.cursor + 6],
711 guard.input[guard.cursor + 7],
712 ];
713
714 guard.cursor += 8;
715
716 Ok(u64::from_be_bytes(bytes))
717 }
718
719 pub fn fuzz_bytes(&self, n: usize) -> Result<Vec<u8>, TestingError> {
721 let mut guard = self.inner.lock()?;
722 if guard.cursor + n > guard.input.len() {
723 return Err(TestingError::FuzzInputExhausted);
724 }
725
726 let bytes = guard.input[guard.cursor..guard.cursor + n].to_vec();
727
728 guard.cursor += n;
729
730 Ok(bytes)
731 }
732
733 pub fn fuzz_input(&self) -> Result<Vec<u8>, TestingError> {
735 let guard = self.inner.lock()?;
736 Ok(guard.input.clone())
737 }
738
739 pub fn fuzz_has_bytes(&self, n: usize) -> Result<bool, TestingError> {
741 let guard = self.inner.lock()?;
742 Ok(guard.cursor + n <= guard.input.len())
743 }
744
745 pub fn fuzz_remaining(&self) -> Result<usize, TestingError> {
747 let guard = self.inner.lock()?;
748 Ok(guard.input.len() - guard.cursor)
749 }
750
751 pub fn fuzz_peek_u8(&self) -> Result<u8, TestingError> {
753 let guard = self.inner.lock()?;
754 if guard.cursor + 1 > guard.input.len() {
755 return Err(TestingError::FuzzInputExhausted);
756 }
757
758 Ok(guard.input[guard.cursor])
759 }
760
761 pub fn fuzz_peek_bytes(&self, n: usize) -> Result<Vec<u8>, TestingError> {
763 let guard = self.inner.lock()?;
764 if guard.cursor + n > guard.input.len() {
765 return Err(TestingError::FuzzInputExhausted);
766 }
767
768 Ok(guard.input[guard.cursor..guard.cursor + n].to_vec())
769 }
770}
771
772mod tests {
775 use super::*;
776
777 #[allow(dead_code)]
781 fn build_simple_process(event: &'static str) -> Process {
782 Process::builder("TestProc")
783 .initial_state(State("S0"))
784 .add_observable(event)
785 .add_transition(State("S0"), event, State("S1"))
786 .add_terminal(State("S1"))
787 .build()
788 .expect("fixture process builder has a valid initial state")
789 }
790
791 #[allow(dead_code)]
793 fn build_two_step_process(e1: &'static str, e2: &'static str) -> Process {
794 Process::builder("TestProc")
795 .initial_state(State("S0"))
796 .add_observable(e1)
797 .add_observable(e2)
798 .add_transition(State("S0"), e1, State("S1"))
799 .add_transition(State("S1"), e2, State("S2"))
800 .add_terminal(State("S2"))
801 .build()
802 .expect("fixture process builder has a valid initial state")
803 }
804
805 #[allow(dead_code)]
807 fn build_three_step_process() -> Process {
808 Process::builder("TestProc")
809 .initial_state(State("S0"))
810 .add_observable("a")
811 .add_observable("b")
812 .add_observable("c")
813 .add_transition(State("S0"), "a", State("S1"))
814 .add_transition(State("S1"), "b", State("S2"))
815 .add_transition(State("S2"), "c", State("S3"))
816 .add_terminal(State("S3"))
817 .build()
818 .expect("fixture process builder has a valid initial state")
819 }
820
821 #[allow(dead_code)]
823 fn build_choice_process() -> Process {
824 Process::builder("TestProc")
825 .initial_state(State("S0"))
826 .add_observable("choice")
827 .add_transition(State("S0"), "choice", State("S1"))
828 .add_transition(State("S0"), "choice", State("S2"))
829 .add_choice(State("S0"))
830 .add_terminal(State("S1"))
831 .add_terminal(State("S2"))
832 .build()
833 .expect("fixture process builder has a valid initial state")
834 }
835
836 #[allow(dead_code)]
838 fn build_branching_process() -> Process {
839 Process::builder("TestProc")
840 .initial_state(State("S0"))
841 .add_observable("a")
842 .add_observable("b")
843 .add_observable("c")
844 .add_transition(State("S0"), "a", State("S1"))
845 .add_transition(State("S0"), "b", State("S2"))
846 .add_transition(State("S0"), "c", State("S3"))
847 .add_terminal(State("S1"))
848 .add_terminal(State("S2"))
849 .add_terminal(State("S3"))
850 .build()
851 .expect("fixture process builder has a valid initial state")
852 }
853
854 #[cfg(feature = "testing-fuzz-ijon")]
857 #[test]
858 fn oracle_ijon_feature_enabled() {
859 let proc = build_simple_process("go");
863 let mut oracle = CspOracle::new(proc);
864
865 let input = vec![0];
867 let result = oracle.fuzz_from_bytes(&input);
868 assert!(result.is_ok(), "IJON feature should not break fuzzing");
869 assert_eq!(oracle.visited_states().len(), 2);
870 assert_eq!(oracle.visited_transitions().len(), 1);
871
872 let _ = oracle.track_state();
874 let _ = oracle.coverage_score();
875 }
876
877 macro_rules! generate_oracle_core_tests {
879 ($module_name:ident) => {
880 mod $module_name {
881 #[test]
882 fn tracks_state_transitions() {
883 let mut oracle = super::CspOracle::new(super::build_two_step_process("go", "stop"));
884 assert_eq!(oracle.current_state(), super::State("S0"));
885 assert_eq!(oracle.visited_states().len(), 1);
886 assert!(!oracle.is_terminal());
887
888 let valid = oracle.valid_events();
889 assert_eq!(valid.len(), 1);
890 assert_eq!(valid[0].0, "go");
891
892 assert!(oracle.step(&super::Event("go")));
893 assert_eq!(oracle.current_state(), super::State("S1"));
894 assert_eq!(oracle.visited_states().len(), 2);
895 assert_eq!(oracle.visited_transitions().len(), 1);
896 assert_eq!(oracle.trace().len(), 1);
897
898 assert!(oracle.step(&super::Event("stop")));
899 assert_eq!(oracle.current_state(), super::State("S2"));
900 assert!(oracle.is_terminal());
901 assert_eq!(oracle.valid_events().len(), 0);
902 }
903
904 #[test]
905 fn rejects_invalid_events() {
906 let mut oracle = super::CspOracle::new(super::build_simple_process("valid"));
907 assert!(!oracle.step(&super::Event("invalid")));
908 assert_eq!(oracle.current_state(), super::State("S0"));
909 assert_eq!(oracle.visited_transitions().len(), 0);
910 }
911
912 #[test]
913 fn oracle_reset() {
914 let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
915 oracle.step(&super::Event("go"));
916 assert_eq!(oracle.current_state(), super::State("S1"));
917
918 oracle.reset();
919 assert_eq!(oracle.current_state(), super::State("S0"));
920 assert_eq!(oracle.visited_states().len(), 1);
921 assert_eq!(oracle.visited_transitions().len(), 0);
922 assert_eq!(oracle.trace().len(), 0);
923 }
924
925 #[test]
926 fn oracle_with_choice_points() {
927 let mut oracle = super::CspOracle::new(super::build_choice_process());
928 let valid = oracle.valid_events();
929 assert_eq!(valid.len(), 1);
930 assert_eq!(valid[0].0, "choice");
931
932 assert!(oracle.step(&super::Event("choice")));
933 assert_eq!(oracle.current_state(), super::State("S1"));
934 }
935 }
936 };
937 }
938
939 macro_rules! generate_coverage_tests {
941 ($module_name:ident) => {
942 mod $module_name {
943 #[test]
944 fn oracle_coverage_metrics() {
945 let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
946 let coverage = oracle.state_coverage();
947 assert!((coverage - 33.33).abs() < 0.1);
948
949 oracle.step(&super::Event("a"));
950 let coverage = oracle.state_coverage();
951 assert!((coverage - 66.66).abs() < 0.1);
952
953 oracle.step(&super::Event("b"));
954 let coverage = oracle.state_coverage();
955 assert!((coverage - 100.0).abs() < 0.1);
956
957 let (visited, _total) = oracle.transition_coverage();
958 assert_eq!(visited, 2);
959 }
960
961 #[test]
962 fn oracle_track_state_is_stable() {
963 let proc = super::build_simple_process("go");
964 let oracle1 = super::CspOracle::new(proc.clone());
965 let oracle2 = super::CspOracle::new(proc);
966 assert_eq!(oracle1.track_state(), oracle2.track_state());
967 }
968
969 #[test]
970 fn oracle_coverage_score_increases() {
971 let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
972 let score1 = oracle.coverage_score();
973
974 oracle.step(&super::Event("go"));
975 let score2 = oracle.coverage_score();
976 assert!(score2 > score1);
977 }
978 }
979 };
980 }
981
982 macro_rules! generate_fuzzing_tests {
984 ($module_name:ident) => {
985 mod $module_name {
986 #[test]
987 fn oracle_fuzz_from_bytes_reaches_terminal() {
988 let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
989 let input = vec![0];
990 assert!(oracle.fuzz_from_bytes(&input).is_ok());
991 assert_eq!(oracle.current_state(), super::State("S1"));
992 assert!(oracle.is_terminal());
993 }
994
995 #[test]
996 fn oracle_fuzz_from_bytes_multiple_transitions() {
997 let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
998 let input = vec![0, 0];
999 assert!(oracle.fuzz_from_bytes(&input).is_ok());
1000 assert_eq!(oracle.current_state(), super::State("S2"));
1001 assert_eq!(oracle.trace().len(), 2);
1002 }
1003
1004 #[test]
1005 fn oracle_fuzz_from_bytes_fails_on_insufficient_input() {
1006 let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
1007 let input = vec![0];
1008 assert!(matches!(
1009 oracle.fuzz_from_bytes(&input),
1010 Err(super::FuzzError::InputExhausted { .. })
1011 ));
1012 }
1013
1014 #[test]
1015 fn oracle_deadlock_reported_as_deadlock() {
1016 let process = super::Process::builder("TestProc")
1017 .initial_state(super::State("S0"))
1018 .add_observable("go")
1019 .add_transition(super::State("S0"), "go", super::State("Stuck"))
1020 .build()
1021 .expect("fixture process builder has a valid initial state");
1022
1023 let mut oracle = super::CspOracle::new(process);
1024 assert!(matches!(
1025 oracle.fuzz_from_bytes(&[0, 0]),
1026 Err(super::FuzzError::Deadlock { state: super::State("Stuck") })
1027 ));
1028 }
1029
1030 #[test]
1031 fn oracle_byte_mapping_deterministic_across_oracles() {
1032 let traces: Vec<Vec<super::Event>> = (0..8)
1033 .map(|_| {
1034 let mut oracle = super::CspOracle::new(super::build_branching_process());
1035 oracle.fuzz_from_bytes(&[1]).expect("branching process reaches terminal");
1036 oracle.trace().to_vec()
1037 })
1038 .collect();
1039
1040 for trace in &traces {
1041 assert_eq!(trace, &traces[0]);
1042 }
1043 }
1044
1045 #[test]
1046 fn oracle_coverage_increases_during_fuzzing() {
1047 let mut oracle = super::CspOracle::new(super::build_two_step_process("a", "b"));
1048 let initial_score = oracle.coverage_score();
1049
1050 let input = vec![0, 0];
1051 let _ = oracle.fuzz_from_bytes(&input);
1052 let final_score = oracle.coverage_score();
1053 assert!(final_score > initial_score);
1054
1055 assert_eq!(oracle.visited_states().len(), 3);
1056 assert_eq!(oracle.visited_transitions().len(), 2);
1057 }
1058
1059 #[test]
1060 fn oracle_track_state_differs_between_states() {
1061 let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
1062 let hash_s0 = oracle.track_state();
1063
1064 oracle.step(&super::Event("go"));
1065
1066 let hash_s1 = oracle.track_state();
1067 assert_ne!(hash_s0, hash_s1);
1068 }
1069
1070 #[test]
1071 fn oracle_crash_context_provides_debug_info() {
1072 let mut oracle = super::CspOracle::new(super::build_simple_process("go"));
1073 oracle.step(&super::Event("go"));
1074
1075 let context = oracle.crash_context();
1076 assert!(context.contains("Current State:"));
1077 assert!(context.contains("S1"));
1078 assert!(context.contains("Terminal: true"));
1079 assert!(context.contains("Trace:"));
1080 assert!(context.contains("Coverage:"));
1081 }
1082 }
1083 };
1084 }
1085
1086 macro_rules! generate_context_tests {
1088 ($module_name:ident) => {
1089 mod $module_name {
1090 #[test]
1091 fn fuzz_context_executes_oracle() {
1092 let proc = super::build_simple_process("go");
1093 let ctx = super::FuzzContext::new(vec![0], proc);
1094 assert!(ctx.fuzz_from_bytes().is_ok());
1095 assert!(ctx.is_terminal());
1096 assert_eq!(ctx.trace().len(), 1);
1097 }
1098
1099 #[test]
1100 fn fuzz_context_ijon_accessors() {
1101 let proc = super::build_two_step_process("a", "b");
1102 let ctx = super::FuzzContext::new(vec![0, 0], proc);
1103
1104 let initial_score = ctx.coverage_score();
1105 let initial_hash = ctx.track_state();
1106
1107 let _ = ctx.fuzz_from_bytes();
1108
1109 let final_score = ctx.coverage_score();
1110 let final_hash = ctx.track_state();
1111 assert!(final_score > initial_score);
1112 assert_ne!(initial_hash, final_hash);
1113 }
1114
1115 #[test]
1116 fn fuzz_context_crash_context() {
1117 let proc = super::build_simple_process("go");
1118 let ctx = super::FuzzContext::new(vec![0], proc);
1119 let _ = ctx.fuzz_from_bytes();
1120
1121 let context = ctx.crash_context();
1122 assert!(context.contains("AFL Crash Context"));
1123 assert!(context.contains("Current State:"));
1124 assert!(context.contains("S1"));
1125 assert!(context.contains("Coverage:"));
1126 }
1127
1128 #[test]
1129 fn fuzz_context_thread_safe_clone() {
1130 let proc = super::build_simple_process("go");
1131 let ctx1 = super::FuzzContext::new(vec![0], proc);
1132 let ctx2 = ctx1.clone();
1133
1134 let _ = ctx1.fuzz_from_bytes();
1135 assert_eq!(ctx1.current_state(), ctx2.current_state());
1136 }
1137 }
1138 };
1139 }
1140
1141 macro_rules! generate_advanced_tests {
1143 ($module_name:ident) => {
1144 mod $module_name {
1145 #[test]
1146 fn oracle_input_modulo_selection() {
1147 let proc = super::build_branching_process();
1148 let oracle_ref = super::CspOracle::new(proc.clone());
1149 let valid = oracle_ref.valid_events();
1150 assert_eq!(valid.len(), 3);
1151
1152 let state_for_choice: Vec<super::State> = (0..3)
1153 .map(|choice| {
1154 let mut oracle = super::CspOracle::new(proc.clone());
1155 oracle.fuzz_from_bytes(&[choice as u8]).unwrap();
1156 oracle.current_state()
1157 })
1158 .collect();
1159
1160 let test_cases = vec![
1161 (0, 0, "byte=0: 0 % 3 = 0 -> first event"),
1162 (1, 1, "byte=1: 1 % 3 = 1 -> second event"),
1163 (2, 2, "byte=2: 2 % 3 = 2 -> third event"),
1164 (3, 0, "byte=3: 3 % 3 = 0 -> wraps to first"),
1165 (255, 0, "byte=255: 255 % 3 = 0 -> wraps to first"),
1166 ];
1167
1168 for (input_byte, expected_choice, desc) in test_cases {
1169 let mut oracle = super::CspOracle::new(proc.clone());
1170 assert!(oracle.fuzz_from_bytes(&[input_byte]).is_ok());
1171 assert_eq!(oracle.current_state(), state_for_choice[expected_choice], "{}", desc);
1172 }
1173 }
1174
1175 #[test]
1176 fn oracle_choice_point_fuzzing() {
1177 let proc = super::build_choice_process();
1178 let mut oracle = super::CspOracle::new(proc);
1179 assert!(oracle.fuzz_from_bytes(&[0, 0]).is_ok());
1180 assert_eq!(oracle.current_state(), super::State("S1"));
1181 }
1182
1183 #[test]
1184 fn oracle_target_byte_reaches_second_nondeterministic_target() {
1185 let proc = super::build_choice_process();
1186 let mut oracle = super::CspOracle::new(proc);
1187 assert!(oracle.fuzz_from_bytes(&[0, 1]).is_ok());
1188 assert_eq!(oracle.current_state(), super::State("S2"));
1189 }
1190
1191 #[test]
1192 fn oracle_reset_between_fuzz_runs() {
1193 let proc = super::build_simple_process("go");
1194 let mut oracle = super::CspOracle::new(proc);
1195 assert!(oracle.fuzz_from_bytes(&[0]).is_ok());
1196 assert_eq!(oracle.current_state(), super::State("S1"));
1197 assert_eq!(oracle.trace().len(), 1);
1198 assert!(oracle.fuzz_from_bytes(&[0]).is_ok());
1199 assert_eq!(oracle.current_state(), super::State("S1"));
1200 assert_eq!(oracle.trace().len(), 1);
1201 }
1202
1203 #[test]
1204 fn oracle_exhaustive_state_exploration() {
1205 let proc = super::build_three_step_process();
1206 let mut oracle = super::CspOracle::new(proc);
1207
1208 let input = vec![0, 0, 0];
1209 assert!(oracle.fuzz_from_bytes(&input).is_ok());
1210 assert_eq!(oracle.visited_states().len(), 4);
1211 assert_eq!(oracle.visited_transitions().len(), 3);
1212 assert_eq!(oracle.state_coverage(), 100.0);
1213 }
1214
1215 #[test]
1216 fn fuzz_context_concurrent_access() {
1217 let proc = super::build_simple_process("go");
1218 let ctx = std::sync::Arc::new(super::FuzzContext::new(vec![0], proc));
1219
1220 let handles: Vec<_> = (0..4)
1221 .map(|_| {
1222 let ctx_clone = std::sync::Arc::clone(&ctx);
1223 std::thread::spawn(move || {
1224 let _ = ctx_clone.coverage_score();
1225 let _ = ctx_clone.track_state();
1226 let _ = ctx_clone.is_terminal();
1227 let _ = ctx_clone.current_state();
1228 })
1229 })
1230 .collect();
1231
1232 for handle in handles {
1233 handle.join().expect("Thread should not panic");
1234 }
1235 }
1236 }
1237 };
1238 }
1239
1240 generate_oracle_core_tests!(oracle_core);
1242 generate_coverage_tests!(coverage);
1243 generate_fuzzing_tests!(fuzzing);
1244 generate_context_tests!(context);
1245 generate_advanced_tests!(advanced);
1246}