@@ -15,6 +15,7 @@ mod check_panics;
1515mod docs;
1616mod fs_ops;
1717mod qualification;
18+ mod update_panic_registry;
1819mod wasm_ops;
1920mod wast_tests;
2021
@@ -84,6 +85,16 @@ enum Command {
8485 #[ command( subcommand) ]
8586 command : DocsCommands ,
8687 } ,
88+ /// Update the panic registry CSV file
89+ UpdatePanicRegistry {
90+ /// Output CSV file path (relative to workspace root)
91+ #[ arg( long, default_value = "docs/source/development/panic_registry.csv" ) ]
92+ output : String ,
93+
94+ /// Whether to print verbose output
95+ #[ arg( short, long) ]
96+ verbose : bool ,
97+ } ,
8798}
8899
89100#[ derive( Subcommand , Debug ) ]
@@ -147,7 +158,47 @@ pub struct BuildOpts {
147158
148159#[ derive( Args , Debug ) ]
149160pub struct CoverageOpts {
150- // Add options for coverage if needed
161+ /// Mode of operation: single (run llvm-cov), individual (per-crate coverage), or combined (merge with grcov)
162+ #[ clap( long, value_enum, default_value = "single" ) ]
163+ mode : CoverageMode ,
164+
165+ /// Format for coverage output
166+ #[ clap( long, value_enum, default_value = "lcov" ) ]
167+ format : CoverageFormat ,
168+
169+ /// Crates to include, if not specified all will be included
170+ #[ clap( long, use_value_delimiter = true , value_delimiter = ',' ) ]
171+ crates : Vec < String > ,
172+
173+ /// Exclude specific crates from coverage
174+ #[ clap( long, use_value_delimiter = true , value_delimiter = ',' ) ]
175+ exclude : Vec < String > ,
176+
177+ /// Directory to store coverage artifacts
178+ #[ clap( long, default_value = "target/coverage" ) ]
179+ output_dir : PathBuf ,
180+ }
181+
182+ #[ derive( ValueEnum , Clone , Debug , PartialEq ) ]
183+ enum CoverageMode {
184+ /// Run coverage for all crates combined (default)
185+ Single ,
186+ /// Run coverage for each crate individually
187+ Individual ,
188+ /// Combine previously generated coverage reports with grcov
189+ Combined ,
190+ }
191+
192+ #[ derive( ValueEnum , Clone , Debug , PartialEq ) ]
193+ enum CoverageFormat {
194+ /// LCOV format (default)
195+ Lcov ,
196+ /// HTML report
197+ Html ,
198+ /// Cobertura XML format
199+ Cobertura ,
200+ /// All formats
201+ All ,
151202}
152203
153204#[ derive( Args , Debug ) ]
@@ -375,6 +426,10 @@ fn main() -> Result<()> {
375426 DocsCommands :: SwitcherJson { local } => docs:: generate_switcher_json ( local) ,
376427 DocsCommands :: Serve => docs:: serve_docs ( ) ,
377428 } ,
429+ Command :: UpdatePanicRegistry { output, verbose } => {
430+ update_panic_registry:: run ( & sh, & output, verbose) ?;
431+ Ok ( ( ) )
432+ }
378433 }
379434}
380435
@@ -445,45 +500,228 @@ fn run_build(sh: &Shell, opts: BuildOpts) -> Result<()> {
445500 Ok ( ( ) )
446501}
447502
448- fn run_coverage ( sh : & Shell , _opts : CoverageOpts ) -> Result < ( ) > {
449- let lcov_path = PathBuf :: from ( "coverage.lcov" ) ;
450- let html_output_dir = PathBuf :: from ( "target/llvm-cov/html" ) ;
503+ fn run_coverage ( sh : & Shell , opts : CoverageOpts ) -> Result < ( ) > {
504+ // Create output directory if it doesn't exist
505+ fs_ops:: mkdirp ( & opts. output_dir ) ?;
506+
507+ match opts. mode {
508+ CoverageMode :: Single => run_single_coverage ( sh, & opts) ,
509+ CoverageMode :: Individual => run_individual_coverage ( sh, & opts) ,
510+ CoverageMode :: Combined => run_combined_coverage ( sh, & opts) ,
511+ }
512+ }
513+
514+ fn run_single_coverage ( sh : & Shell , opts : & CoverageOpts ) -> Result < ( ) > {
515+ let lcov_path = opts. output_dir . join ( "coverage.lcov" ) ;
516+ let html_output_dir = opts. output_dir . join ( "html" ) ;
451517 let summary_rst_path = PathBuf :: from ( "docs/source/_generated_coverage_summary.rst" ) ;
452518
453519 // 1. Generate LCOV data
454- println ! ( "Generating LCOV coverage data..." ) ;
455- sh. cmd ( "cargo" )
520+ println ! ( "Generating LCOV coverage data for all crates..." ) ;
521+ let mut cmd = sh. cmd ( "cargo" ) ;
522+ cmd = cmd
456523 . arg ( "llvm-cov" )
457524 . arg ( "test" ) // Run tests to generate coverage
458525 . arg ( "--all-features" )
459- . arg ( "--lcov" )
460- . arg ( "--output-path" )
461- . arg ( & lcov_path)
462- . run ( ) ?;
463- println ! ( "LCOV data generated at {}" , lcov_path. display( ) ) ;
526+ . arg ( "--workspace" ) ;
464527
528+ // Add exclusions if specified
529+ for excl in & opts. exclude {
530+ cmd = cmd. arg ( "--exclude" ) . arg ( excl) ;
531+ }
532+
533+ cmd = cmd. arg ( "--lcov" ) . arg ( "--output-path" ) . arg ( & lcov_path) ;
534+
535+ // Run the command but don't fail if it returns an error
536+ match cmd. run ( ) {
537+ Ok ( _) => println ! ( "LCOV data generated at {}" , lcov_path. display( ) ) ,
538+ Err ( e) => {
539+ println ! ( "Warning: Failed to generate complete LCOV data: {}" , e) ;
540+ println ! ( "Continuing with partial coverage data if available" ) ;
541+ }
542+ }
543+
544+ // Only proceed if the LCOV file was generated
465545 if !lcov_path. exists ( ) {
466546 anyhow:: bail!( "LCOV file was not generated: {}" , lcov_path. display( ) ) ;
467547 }
468548
469- // 2. Generate HTML report from LCOV data
549+ // 2. Generate HTML report from LCOV data if requested
550+ if opts. format == CoverageFormat :: Html || opts. format == CoverageFormat :: All {
551+ generate_html_report ( sh, & lcov_path, & html_output_dir) ?;
552+ }
553+
554+ // 3. Generate summary for documentation
555+ generate_coverage_summary ( & lcov_path, & summary_rst_path) ?;
556+
557+ Ok ( ( ) )
558+ }
559+
560+ fn run_individual_coverage ( sh : & Shell , opts : & CoverageOpts ) -> Result < ( ) > {
561+ println ! ( "Running individual coverage for each crate..." ) ;
562+
563+ // Get list of crates in workspace
564+ let mut crates = if opts. crates . is_empty ( ) {
565+ // Get all crates in workspace
566+ let output = sh
567+ . cmd ( "cargo" )
568+ . arg ( "metadata" )
569+ . arg ( "--format-version=1" )
570+ . read ( ) ?;
571+ let metadata: serde_json:: Value = serde_json:: from_str ( & output) ?;
572+
573+ metadata[ "packages" ]
574+ . as_array ( )
575+ . map ( |packages| {
576+ packages
577+ . iter ( )
578+ . filter_map ( |pkg| pkg[ "name" ] . as_str ( ) . map ( String :: from) )
579+ . collect :: < Vec < _ > > ( )
580+ } )
581+ . unwrap_or_default ( )
582+ } else {
583+ opts. crates . clone ( )
584+ } ;
585+
586+ // Filter out excluded crates
587+ crates. retain ( |c| !opts. exclude . contains ( c) ) ;
588+
589+ // Create directory for individual reports
590+ let individual_dir = opts. output_dir . join ( "individual" ) ;
591+ fs_ops:: mkdirp ( & individual_dir) ?;
592+
593+ // Run coverage for each crate
594+ for crate_name in & crates {
595+ println ! ( "Generating coverage for crate: {}" , crate_name) ;
596+ let crate_lcov_path = individual_dir. join ( format ! ( "{}.lcov" , crate_name) ) ;
597+
598+ // Run coverage for this crate
599+ let result = sh
600+ . cmd ( "cargo" )
601+ . arg ( "llvm-cov" )
602+ . arg ( "test" )
603+ . arg ( "--all-features" )
604+ . arg ( "--package" )
605+ . arg ( crate_name)
606+ . arg ( "--lcov" )
607+ . arg ( "--output-path" )
608+ . arg ( & crate_lcov_path)
609+ . run ( ) ;
610+
611+ match result {
612+ Ok ( _) => println ! ( " ✓ Coverage generated for {}" , crate_name) ,
613+ Err ( e) => println ! ( " ✗ Failed to generate coverage for {}: {}" , crate_name, e) ,
614+ }
615+ }
616+
617+ println ! (
618+ "Individual coverage reports generated in {}" ,
619+ individual_dir. display( )
620+ ) ;
621+ Ok ( ( ) )
622+ }
623+
624+ fn run_combined_coverage ( sh : & Shell , opts : & CoverageOpts ) -> Result < ( ) > {
625+ println ! ( "Combining coverage reports with grcov..." ) ;
626+
627+ // Check if grcov is installed
628+ if sh. cmd ( "which" ) . arg ( "grcov" ) . read ( ) . is_err ( ) {
629+ return Err ( anyhow:: anyhow!(
630+ "grcov is not installed. Install with 'cargo install grcov'"
631+ ) ) ;
632+ }
633+
634+ // Find all LCOV files
635+ let individual_dir = opts. output_dir . join ( "individual" ) ;
636+ if !individual_dir. exists ( ) {
637+ return Err ( anyhow:: anyhow!(
638+ "No individual coverage reports found in {}" ,
639+ individual_dir. display( )
640+ ) ) ;
641+ }
642+
643+ // Output paths
644+ let combined_lcov = opts. output_dir . join ( "combined.lcov" ) ;
645+ let html_output_dir = opts. output_dir . join ( "html" ) ;
646+
647+ // Run grcov to combine reports
648+ println ! ( "Merging LCOV files with grcov..." ) ;
649+
650+ // Use grcov to process the individual directory with all LCOV files
651+ let mut cmd = sh. cmd ( "grcov" ) ;
652+ cmd = cmd. arg ( & individual_dir) ;
653+
654+ // Add proper output format
655+ if opts. format == CoverageFormat :: Lcov || opts. format == CoverageFormat :: All {
656+ cmd = cmd. arg ( "-t" ) . arg ( "lcov" ) . arg ( "-o" ) . arg ( & combined_lcov) ;
657+ }
658+
659+ if opts. format == CoverageFormat :: Html || opts. format == CoverageFormat :: All {
660+ fs_ops:: mkdirp ( & html_output_dir) ?;
661+ cmd = cmd. arg ( "-t" ) . arg ( "html" ) . arg ( "--branch" ) ;
662+
663+ // Add exclusion pattern for assertions and derives
664+ cmd = cmd
665+ . arg ( "--excl-br-line" )
666+ . arg ( "^\\ s*((debug_)?assert(_eq|_ne)?!|#\\ [derive\\ ()" ) ;
667+
668+ cmd = cmd. arg ( "--ignore-not-existing" ) ;
669+ cmd = cmd. arg ( "-o" ) . arg ( & html_output_dir) ;
670+ }
671+
672+ if opts. format == CoverageFormat :: Cobertura || opts. format == CoverageFormat :: All {
673+ let cobertura_path = opts. output_dir . join ( "cobertura.xml" ) ;
674+ cmd = cmd. arg ( "-t" ) . arg ( "cobertura" ) . arg ( "-o" ) . arg ( cobertura_path) ;
675+ }
676+
677+ // Run grcov (don't fail if it returns an error)
678+ match cmd. run ( ) {
679+ Ok ( _) => println ! ( "Combined coverage reports successfully" ) ,
680+ Err ( e) => println ! ( "Warning: grcov had issues combining reports: {}" , e) ,
681+ }
682+
683+ // Generate summary for documentation if LCOV was generated
684+ if combined_lcov. exists ( )
685+ && ( opts. format == CoverageFormat :: Lcov || opts. format == CoverageFormat :: All )
686+ {
687+ let summary_rst_path = PathBuf :: from ( "docs/source/_generated_coverage_summary.rst" ) ;
688+ generate_coverage_summary ( & combined_lcov, & summary_rst_path) ?;
689+ }
690+
691+ println ! (
692+ "Combined coverage report generated in {}" ,
693+ opts. output_dir. display( )
694+ ) ;
695+ Ok ( ( ) )
696+ }
697+
698+ fn generate_html_report ( sh : & Shell , lcov_path : & PathBuf , html_output_dir : & PathBuf ) -> Result < ( ) > {
470699 println ! ( "Generating HTML coverage report from LCOV data..." ) ;
471700 // Ensure the target directory exists
472- fs_ops:: mkdirp ( html_output_dir. parent ( ) . unwrap ( ) ) ?; // Create target/llvm-cov if needed
473- sh. cmd ( "cargo" )
701+ fs_ops:: mkdirp ( html_output_dir) ?;
702+
703+ let result = sh
704+ . cmd ( "cargo" )
474705 . arg ( "llvm-cov" )
475706 . arg ( "report" ) // Use report subcommand
476707 . arg ( "--lcov" )
477- . arg ( & lcov_path) // Input LCOV
708+ . arg ( lcov_path) // Input LCOV
478709 . arg ( "--html" )
479710 . arg ( "--output-dir" ) // Specify output directory
480- . arg ( & html_output_dir)
481- . run ( ) ?; // removed .arg("test") - we are reporting, not testing again
482- println ! ( "HTML report generated in {}" , html_output_dir. display( ) ) ;
711+ . arg ( html_output_dir)
712+ . run ( ) ;
713+
714+ match result {
715+ Ok ( _) => println ! ( "HTML report generated in {}" , html_output_dir. display( ) ) ,
716+ Err ( e) => println ! ( "Warning: Failed to generate HTML report: {}" , e) ,
717+ }
718+
719+ Ok ( ( ) )
720+ }
483721
484- // 3. Parse LCOV data for summary
722+ fn generate_coverage_summary ( lcov_path : & PathBuf , summary_rst_path : & PathBuf ) -> Result < ( ) > {
485723 println ! ( "Parsing LCOV data for summary..." ) ;
486- let file = File :: open ( & lcov_path)
724+ let file = File :: open ( lcov_path)
487725 . with_context ( || format ! ( "Failed to open LCOV file: {}" , lcov_path. display( ) ) ) ?;
488726 let reader = BufReader :: new ( file) ;
489727
@@ -516,7 +754,7 @@ fn run_coverage(sh: &Shell, _opts: CoverageOpts) -> Result<()> {
516754 covered_lines, total_lines, percentage
517755 ) ;
518756
519- // 4. Generate RST summary file
757+ // Generate RST summary file
520758 println ! (
521759 "Generating RST summary file: {}" ,
522760 summary_rst_path. display( )
@@ -540,7 +778,7 @@ fn run_coverage(sh: &Shell, _opts: CoverageOpts) -> Result<()> {
540778 )
541779 } ) ?;
542780 }
543- fs:: write ( & summary_rst_path, rst_content) . with_context ( || {
781+ fs:: write ( summary_rst_path, rst_content) . with_context ( || {
544782 format ! (
545783 "Failed to write RST summary file: {}" ,
546784 summary_rst_path. display( )
0 commit comments