1- use std:: io:: IsTerminal ;
21use std:: panic:: catch_unwind;
32use std:: panic:: AssertUnwindSafe ;
43use std:: sync:: atomic:: AtomicBool ;
@@ -10,6 +9,7 @@ use std::time::Instant;
109use trident_config:: TridentConfig ;
1110use trident_fuzz_metrics:: TridentFuzzingData ;
1211
12+ use crate :: trident:: progress;
1313use crate :: trident:: Trident ;
1414
1515// Thread-local storage for panic location information.
@@ -61,13 +61,6 @@ impl ExitCodeMode {
6161 }
6262}
6363
64- /// Events sent from worker threads to the UI/controller thread in parallel fuzzing.
65- enum WorkerEvent {
66- ProgressDelta ( u64 ) ,
67- InvariantFailure ( String ) ,
68- ProgramPanicsDelta ( u64 ) ,
69- }
70-
7164/// Final process exit outcomes for a fuzzing run.
7265#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
7366enum FuzzRunExit {
@@ -114,86 +107,6 @@ fn determine_exit_outcome(input: ExitDecisionInput) -> FuzzRunExit {
114107 }
115108}
116109
117- fn colors_enabled ( ) -> bool {
118- std:: io:: stderr ( ) . is_terminal ( ) && std:: env:: var_os ( "NO_COLOR" ) . is_none ( )
119- }
120-
121- fn paint_red ( text : & str ) -> String {
122- if colors_enabled ( ) {
123- format ! ( "\x1b [31m{}\x1b [0m" , text)
124- } else {
125- text. to_string ( )
126- }
127- }
128-
129- fn paint_bold_yellow ( text : & str ) -> String {
130- if colors_enabled ( ) {
131- format ! ( "\x1b [1;33m{}\x1b [0m" , text)
132- } else {
133- text. to_string ( )
134- }
135- }
136-
137- fn paint_cyan ( text : & str ) -> String {
138- if colors_enabled ( ) {
139- format ! ( "\x1b [36m{}\x1b [0m" , text)
140- } else {
141- text. to_string ( )
142- }
143- }
144-
145- fn paint_magenta ( text : & str ) -> String {
146- if colors_enabled ( ) {
147- format ! ( "\x1b [35m{}\x1b [0m" , text)
148- } else {
149- text. to_string ( )
150- }
151- }
152-
153- fn format_invariant_line ( text : & str ) -> String {
154- const PREFIX : & str = "Assertion failed at " ;
155- const SEED_PREFIX : & str = " (seed: " ;
156-
157- if !colors_enabled ( ) {
158- return text. to_string ( ) ;
159- }
160-
161- // Expected format from handle_panic:
162- // "Assertion failed at <location>: <message> (seed: <seed>)"
163- if let Some ( location_start) = text. strip_prefix ( PREFIX ) {
164- if let Some ( seed_idx) = location_start. rfind ( SEED_PREFIX ) {
165- let before_seed = & location_start[ ..seed_idx] ;
166- let seed_with_suffix = & location_start[ seed_idx + SEED_PREFIX . len ( ) ..] ;
167- if let Some ( seed) = seed_with_suffix. strip_suffix ( ')' ) {
168- if let Some ( separator_idx) = before_seed. find ( ": " ) {
169- let location = & before_seed[ ..separator_idx] ;
170- let message = & before_seed[ separator_idx + 2 ..] ;
171- return format ! (
172- "{} {}{}: {}{}{}{}" ,
173- paint_red( "!" ) ,
174- PREFIX ,
175- paint_cyan( location) ,
176- message,
177- SEED_PREFIX ,
178- paint_magenta( seed) ,
179- ")"
180- ) ;
181- }
182- }
183- }
184- }
185-
186- // Fallback to accent-only if line doesn't match expected format.
187- format ! ( "{} {}" , paint_red( "!" ) , text)
188- }
189-
190- /// Final aggregated runtime summary produced by the UI/controller thread.
191- struct ParallelRunSummary {
192- invariant_failures : u64 ,
193- program_panics : u64 ,
194- panic_messages : Vec < String > ,
195- }
196-
197110/// Trait for executing fuzzing flows in the Trident framework
198111///
199112/// This trait defines the interface for fuzzing executors that can run
@@ -406,6 +319,9 @@ pub trait FlowExecutor: Send + 'static + Sized {
406319 let mut invariant_failed = false ; // Tracks fuzz test assertion/invariant failures
407320 let mut invariant_failure_count: u64 = 0 ;
408321 let mut panic_messages: Vec < String > = Vec :: new ( ) ;
322+ let total_flow_calls = iterations * flow_calls_per_iteration;
323+ let mut completed_flow_calls = 0u64 ;
324+ let start_time = Instant :: now ( ) ;
409325
410326 // Configure debug seed if in debug mode
411327 if is_debug_mode {
@@ -419,20 +335,11 @@ pub trait FlowExecutor: Send + 'static + Sized {
419335 let pb = if is_debug_mode {
420336 None
421337 } else {
422- let total_flow_calls = iterations * flow_calls_per_iteration;
423- let pb = indicatif:: ProgressBar :: new ( total_flow_calls) ;
424- pb. set_style (
425- indicatif:: ProgressStyle :: with_template (
426- "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({percent}%) [{eta_precise}] {msg}"
427- )
428- . unwrap ( )
429- . progress_chars ( "#>-" ) ,
430- ) ;
431- pb. set_message ( format ! (
432- "Fuzzing {} iterations with {} flow calls each..." ,
433- iterations, flow_calls_per_iteration
434- ) ) ;
435- Some ( pb)
338+ Some ( progress:: create_single_progress_bar (
339+ total_flow_calls,
340+ iterations,
341+ flow_calls_per_iteration,
342+ ) )
436343 } ;
437344
438345 // Main fuzzing loop: execute flows, catch panics, and track progress
@@ -455,7 +362,7 @@ pub trait FlowExecutor: Send + 'static + Sized {
455362
456363 // In debug mode print immediately, otherwise collect for end
457364 if is_debug_mode {
458- eprintln ! ( "{}" , format_invariant_line( & panic_msg) ) ;
365+ eprintln ! ( "{}" , progress :: format_invariant_line( & panic_msg) ) ;
459366 } else {
460367 panic_messages. push ( panic_msg) ;
461368 }
@@ -475,16 +382,19 @@ pub trait FlowExecutor: Send + 'static + Sized {
475382 // Update progress bar with live stats
476383 if let Some ( ref pb) = pb {
477384 pb. inc ( flow_calls_per_iteration) ;
385+ completed_flow_calls += flow_calls_per_iteration;
478386 let program_panics = fuzzer
479387 . trident_mut ( )
480388 . get_fuzzing_data ( )
481389 . get_program_panic_count ( ) ;
482- pb. set_message ( format ! (
483- "Iteration {}/{} | Invariant failures: {} | Program panics: {}" ,
390+ pb. set_message ( progress:: format_live_status_single (
484391 i + 1 ,
485392 iterations,
393+ completed_flow_calls,
394+ total_flow_calls,
486395 invariant_failure_count,
487- program_panics
396+ program_panics,
397+ start_time. elapsed ( ) ,
488398 ) ) ;
489399 }
490400 }
@@ -498,13 +408,13 @@ pub trait FlowExecutor: Send + 'static + Sized {
498408 if !panic_messages. is_empty ( ) {
499409 eprintln ! (
500410 "\n {}" ,
501- paint_bold_yellow( & format!(
411+ progress :: paint_bold_yellow( & format!(
502412 "--- Invariant Failures ({}) ---" ,
503413 panic_messages. len( )
504414 ) )
505415 ) ;
506416 for msg in & panic_messages {
507- eprintln ! ( "{}" , format_invariant_line( msg) ) ;
417+ eprintln ! ( "{}" , progress :: format_invariant_line( msg) ) ;
508418 }
509419 }
510420
@@ -536,56 +446,12 @@ pub trait FlowExecutor: Send + 'static + Sized {
536446 let remainder_iterations = iterations % num_threads as u64 ;
537447 let total_flow_calls = iterations * flow_calls_per_iteration;
538448 let exit_code_mode = ExitCodeMode :: from_env ( ) ;
539- let ( event_tx, event_rx) = mpsc:: channel :: < WorkerEvent > ( ) ;
540-
541- // Setup shared progress bar
542- let main_pb = indicatif:: ProgressBar :: new ( total_flow_calls) ;
543- main_pb. set_style (
544- indicatif:: ProgressStyle :: with_template (
545- "Overall: {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({percent}%) [{eta_precise}] {msg}"
546- )
547- . unwrap ( )
548- . progress_chars ( "#>-" ) ,
549- ) ;
550- main_pb. set_message ( format ! (
551- "Fuzzing with {} threads | Invariant failures: 0 | Program panics: 0" ,
552- num_threads
553- ) ) ;
449+ let ( event_tx, event_rx) = mpsc:: channel :: < progress:: WorkerEvent > ( ) ;
554450
555451 // Single UI/controller owner: consumes worker events, updates progress bar,
556452 // and builds global live counters + invariant messages.
557- let ui_handle = thread:: spawn ( move || -> ParallelRunSummary {
558- let mut invariant_failures = 0u64 ;
559- let mut program_panics = 0u64 ;
560- let mut panic_messages = Vec :: new ( ) ;
561-
562- while let Ok ( event) = event_rx. recv ( ) {
563- match event {
564- WorkerEvent :: ProgressDelta ( delta) => {
565- main_pb. inc ( delta) ;
566- }
567- WorkerEvent :: InvariantFailure ( message) => {
568- invariant_failures += 1 ;
569- panic_messages. push ( message) ;
570- }
571- WorkerEvent :: ProgramPanicsDelta ( delta) => {
572- program_panics += delta;
573- }
574- }
575-
576- main_pb. set_message ( format ! (
577- "Invariant failures: {} | Program panics: {}" ,
578- invariant_failures, program_panics
579- ) ) ;
580- }
581-
582- main_pb. finish_with_message ( "Parallel fuzzing completed!" ) ;
583- ParallelRunSummary {
584- invariant_failures,
585- program_panics,
586- panic_messages,
587- }
588- } ) ;
453+ let ui_handle =
454+ progress:: spawn_parallel_ui_controller ( event_rx, num_threads, total_flow_calls) ;
589455
590456 // Spawn worker threads
591457 let mut handles = Vec :: new ( ) ;
@@ -658,23 +524,25 @@ pub trait FlowExecutor: Send + 'static + Sized {
658524 }
659525
660526 // Get final aggregated runtime summary from the controller thread.
661- let run_summary = ui_handle. join ( ) . unwrap_or_else ( |_| ParallelRunSummary {
662- invariant_failures : 0 ,
663- program_panics : 0 ,
664- panic_messages : Vec :: new ( ) ,
665- } ) ;
527+ let run_summary = ui_handle
528+ . join ( )
529+ . unwrap_or_else ( |_| progress:: ParallelRunSummary {
530+ invariant_failures : 0 ,
531+ program_panics : 0 ,
532+ panic_messages : Vec :: new ( ) ,
533+ } ) ;
666534
667535 // Print collected invariant failure messages
668536 if !run_summary. panic_messages . is_empty ( ) {
669537 eprintln ! (
670538 "\n {}" ,
671- paint_bold_yellow( & format!(
539+ progress :: paint_bold_yellow( & format!(
672540 "--- Invariant Failures ({}) ---" ,
673541 run_summary. panic_messages. len( )
674542 ) )
675543 ) ;
676544 for msg in & run_summary. panic_messages {
677- eprintln ! ( "{}" , format_invariant_line( msg) ) ;
545+ eprintln ! ( "{}" , progress :: format_invariant_line( msg) ) ;
678546 }
679547 }
680548
@@ -750,7 +618,10 @@ pub trait FlowExecutor: Send + 'static + Sized {
750618 }
751619}
752620
753- fn send_worker_event ( event_tx : & mpsc:: Sender < WorkerEvent > , event : WorkerEvent ) -> bool {
621+ fn send_worker_event (
622+ event_tx : & mpsc:: Sender < progress:: WorkerEvent > ,
623+ event : progress:: WorkerEvent ,
624+ ) -> bool {
754625 event_tx. send ( event) . is_ok ( )
755626}
756627
@@ -762,7 +633,7 @@ fn run_thread_workload_impl<E: FlowExecutor>(
762633 thread_id : usize ,
763634 thread_iterations : u64 ,
764635 flow_calls_per_iteration : u64 ,
765- event_tx : mpsc:: Sender < WorkerEvent > ,
636+ event_tx : mpsc:: Sender < progress :: WorkerEvent > ,
766637) -> TridentFuzzingData {
767638 let mut fuzzer = E :: new ( ) ;
768639 fuzzer
@@ -789,7 +660,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
789660 {
790661 // Intentional invariant failure - count it, continue fuzzing
791662 let panic_msg = E :: handle_panic ( & panic_err, & mut fuzzer, None ) ;
792- if !send_worker_event ( & event_tx, WorkerEvent :: InvariantFailure ( panic_msg) ) {
663+ if !send_worker_event (
664+ & event_tx,
665+ progress:: WorkerEvent :: InvariantFailure ( panic_msg) ,
666+ ) {
793667 return fuzzer. trident_mut ( ) . get_fuzzing_data ( ) ;
794668 }
795669 } else {
@@ -814,7 +688,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
814688 || i == thread_iterations - 1 ; // Always update on last iteration
815689
816690 if should_update {
817- if !send_worker_event ( & event_tx, WorkerEvent :: ProgressDelta ( local_counter) ) {
691+ if !send_worker_event (
692+ & event_tx,
693+ progress:: WorkerEvent :: ProgressDelta ( local_counter) ,
694+ ) {
818695 return fuzzer. trident_mut ( ) . get_fuzzing_data ( ) ;
819696 }
820697 let thread_prog_panics = fuzzer
@@ -823,7 +700,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
823700 . get_program_panic_count ( ) ;
824701 let new_panics = thread_prog_panics. saturating_sub ( local_observed_program_panics) ;
825702 if new_panics > 0 {
826- if !send_worker_event ( & event_tx, WorkerEvent :: ProgramPanicsDelta ( new_panics) ) {
703+ if !send_worker_event (
704+ & event_tx,
705+ progress:: WorkerEvent :: ProgramPanicsDelta ( new_panics) ,
706+ ) {
827707 return fuzzer. trident_mut ( ) . get_fuzzing_data ( ) ;
828708 }
829709 local_observed_program_panics = thread_prog_panics;
@@ -834,7 +714,11 @@ fn run_thread_workload_impl<E: FlowExecutor>(
834714 }
835715
836716 // Ensure any remaining progress is reported
837- if local_counter > 0 && !send_worker_event ( & event_tx, WorkerEvent :: ProgressDelta ( local_counter) )
717+ if local_counter > 0
718+ && !send_worker_event (
719+ & event_tx,
720+ progress:: WorkerEvent :: ProgressDelta ( local_counter) ,
721+ )
838722 {
839723 return fuzzer. trident_mut ( ) . get_fuzzing_data ( ) ;
840724 }
@@ -846,7 +730,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
846730 . get_program_panic_count ( ) ;
847731 let final_new_panics = final_thread_prog_panics. saturating_sub ( local_observed_program_panics) ;
848732 if final_new_panics > 0
849- && !send_worker_event ( & event_tx, WorkerEvent :: ProgramPanicsDelta ( final_new_panics) )
733+ && !send_worker_event (
734+ & event_tx,
735+ progress:: WorkerEvent :: ProgramPanicsDelta ( final_new_panics) ,
736+ )
850737 {
851738 return fuzzer. trident_mut ( ) . get_fuzzing_data ( ) ;
852739 }
0 commit comments