@@ -6,8 +6,8 @@ use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJs
66use crate :: common:: { Codegen , CodegenUnits , DebugInfo , Debugger , Rustdoc } ;
77use crate :: common:: { CompareMode , FailMode , PassMode } ;
88use crate :: common:: { Config , TestPaths } ;
9- use crate :: common:: { Pretty , RunPassValgrind } ;
10- use crate :: common:: { UI_RUN_STDERR , UI_RUN_STDOUT } ;
9+ use crate :: common:: { Pretty , RunCoverage , RunPassValgrind } ;
10+ use crate :: common:: { UI_COVERAGE , UI_RUN_STDERR , UI_RUN_STDOUT } ;
1111use crate :: compute_diff:: { write_diff, write_filtered_diff} ;
1212use crate :: errors:: { self , Error , ErrorKind } ;
1313use crate :: header:: TestProps ;
@@ -253,6 +253,7 @@ impl<'test> TestCx<'test> {
253253 MirOpt => self . run_mir_opt_test ( ) ,
254254 Assembly => self . run_assembly_test ( ) ,
255255 JsDocTest => self . run_js_doc_test ( ) ,
256+ RunCoverage => self . run_coverage_test ( ) ,
256257 }
257258 }
258259
@@ -465,6 +466,184 @@ impl<'test> TestCx<'test> {
465466 }
466467 }
467468
469+ fn run_coverage_test ( & self ) {
470+ let should_run = self . run_if_enabled ( ) ;
471+ let proc_res = self . compile_test ( should_run, Emit :: None ) ;
472+
473+ if !proc_res. status . success ( ) {
474+ self . fatal_proc_rec ( "compilation failed!" , & proc_res) ;
475+ }
476+ drop ( proc_res) ;
477+
478+ if let WillExecute :: Disabled = should_run {
479+ return ;
480+ }
481+
482+ let profraw_path = self . output_base_dir ( ) . join ( "default.profraw" ) ;
483+ let profdata_path = self . output_base_dir ( ) . join ( "default.profdata" ) ;
484+
485+ // Delete any existing profraw/profdata files to rule out unintended
486+ // interference between repeated test runs.
487+ if profraw_path. exists ( ) {
488+ std:: fs:: remove_file ( & profraw_path) . unwrap ( ) ;
489+ }
490+ if profdata_path. exists ( ) {
491+ std:: fs:: remove_file ( & profdata_path) . unwrap ( ) ;
492+ }
493+
494+ let proc_res = self . exec_compiled_test_general (
495+ & [ ( "LLVM_PROFILE_FILE" , & profraw_path. to_str ( ) . unwrap ( ) ) ] ,
496+ false ,
497+ ) ;
498+ if self . props . failure_status . is_some ( ) {
499+ self . check_correct_failure_status ( & proc_res) ;
500+ } else if !proc_res. status . success ( ) {
501+ self . fatal_proc_rec ( "test run failed!" , & proc_res) ;
502+ }
503+ drop ( proc_res) ;
504+
505+ // Run `llvm-profdata merge` to index the raw coverage output.
506+ let proc_res = self . run_llvm_tool ( "llvm-profdata" , |cmd| {
507+ cmd. args ( [ "merge" , "--sparse" , "--output" ] ) ;
508+ cmd. arg ( & profdata_path) ;
509+ cmd. arg ( & profraw_path) ;
510+ } ) ;
511+ if !proc_res. status . success ( ) {
512+ self . fatal_proc_rec ( "llvm-profdata merge failed!" , & proc_res) ;
513+ }
514+ drop ( proc_res) ;
515+
516+ // Run `llvm-cov show` to produce a coverage report in text format.
517+ let proc_res = self . run_llvm_tool ( "llvm-cov" , |cmd| {
518+ cmd. args ( [ "show" , "--format=text" , "--show-line-counts-or-regions" ] ) ;
519+
520+ cmd. arg ( "--Xdemangler" ) ;
521+ cmd. arg ( self . config . rust_demangler_path . as_ref ( ) . unwrap ( ) ) ;
522+
523+ cmd. arg ( "--instr-profile" ) ;
524+ cmd. arg ( & profdata_path) ;
525+
526+ cmd. arg ( "--object" ) ;
527+ cmd. arg ( & self . make_exe_name ( ) ) ;
528+ } ) ;
529+ if !proc_res. status . success ( ) {
530+ self . fatal_proc_rec ( "llvm-cov show failed!" , & proc_res) ;
531+ }
532+
533+ let kind = UI_COVERAGE ;
534+
535+ let expected_coverage = self . load_expected_output ( kind) ;
536+ let normalized_actual_coverage =
537+ self . normalize_coverage_output ( & proc_res. stdout ) . unwrap_or_else ( |err| {
538+ self . fatal_proc_rec ( & err, & proc_res) ;
539+ } ) ;
540+
541+ let coverage_errors = self . compare_output (
542+ kind,
543+ & normalized_actual_coverage,
544+ & expected_coverage,
545+ self . props . compare_output_lines_by_subset ,
546+ ) ;
547+
548+ if coverage_errors > 0 {
549+ self . fatal_proc_rec (
550+ & format ! ( "{} errors occurred comparing coverage output." , coverage_errors) ,
551+ & proc_res,
552+ ) ;
553+ }
554+ }
555+
556+ fn run_llvm_tool ( & self , name : & str , configure_cmd_fn : impl FnOnce ( & mut Command ) ) -> ProcRes {
557+ let tool_path = self
558+ . config
559+ . llvm_bin_dir
560+ . as_ref ( )
561+ . expect ( "this test expects the LLVM bin dir to be available" )
562+ . join ( name) ;
563+
564+ let mut cmd = Command :: new ( tool_path) ;
565+ configure_cmd_fn ( & mut cmd) ;
566+
567+ let output = cmd. output ( ) . unwrap_or_else ( |_| panic ! ( "failed to exec `{cmd:?}`" ) ) ;
568+
569+ let proc_res = ProcRes {
570+ status : output. status ,
571+ stdout : String :: from_utf8 ( output. stdout ) . unwrap ( ) ,
572+ stderr : String :: from_utf8 ( output. stderr ) . unwrap ( ) ,
573+ cmdline : format ! ( "{cmd:?}" ) ,
574+ } ;
575+ self . dump_output ( & proc_res. stdout , & proc_res. stderr ) ;
576+
577+ proc_res
578+ }
579+
580+ fn normalize_coverage_output ( & self , coverage : & str ) -> Result < String , String > {
581+ let normalized = self . normalize_output ( coverage, & [ ] ) ;
582+
583+ let mut lines = normalized. lines ( ) . collect :: < Vec < _ > > ( ) ;
584+
585+ Self :: sort_coverage_subviews ( & mut lines) ?;
586+
587+ let joined_lines = lines. iter ( ) . flat_map ( |line| [ line, "\n " ] ) . collect :: < String > ( ) ;
588+ Ok ( joined_lines)
589+ }
590+
591+ fn sort_coverage_subviews ( coverage_lines : & mut Vec < & str > ) -> Result < ( ) , String > {
592+ let mut output_lines = Vec :: new ( ) ;
593+
594+ // We accumulate a list of zero or more "subviews", where each
595+ // subview is a list of one or more lines.
596+ let mut subviews: Vec < Vec < & str > > = Vec :: new ( ) ;
597+
598+ fn flush < ' a > ( subviews : & mut Vec < Vec < & ' a str > > , output_lines : & mut Vec < & ' a str > ) {
599+ if subviews. is_empty ( ) {
600+ return ;
601+ }
602+
603+ // Take and clear the list of accumulated subviews.
604+ let mut subviews = std:: mem:: take ( subviews) ;
605+
606+ // The last "subview" should be just a boundary line on its own,
607+ // so exclude it when sorting the other subviews.
608+ let except_last = subviews. len ( ) - 1 ;
609+ ( & mut subviews[ ..except_last] ) . sort ( ) ;
610+
611+ for view in subviews {
612+ for line in view {
613+ output_lines. push ( line) ;
614+ }
615+ }
616+ }
617+
618+ for ( line, line_num) in coverage_lines. iter ( ) . zip ( 1 ..) {
619+ if line. starts_with ( " ------------------" ) {
620+ // This is a subview boundary line, so start a new subview.
621+ subviews. push ( vec ! [ line] ) ;
622+ } else if line. starts_with ( " |" ) {
623+ // Add this line to the current subview.
624+ subviews
625+ . last_mut ( )
626+ . ok_or ( format ! (
627+ "unexpected subview line outside of a subview on line {line_num}"
628+ ) ) ?
629+ . push ( line) ;
630+ } else {
631+ // This line is not part of a subview, so sort and print any
632+ // accumulated subviews, and then print the line as-is.
633+ flush ( & mut subviews, & mut output_lines) ;
634+ output_lines. push ( line) ;
635+ }
636+ }
637+
638+ flush ( & mut subviews, & mut output_lines) ;
639+ assert ! ( subviews. is_empty( ) ) ;
640+
641+ assert_eq ! ( output_lines. len( ) , coverage_lines. len( ) ) ;
642+ * coverage_lines = output_lines;
643+
644+ Ok ( ( ) )
645+ }
646+
468647 fn run_pretty_test ( & self ) {
469648 if self . props . pp_exact . is_some ( ) {
470649 logv ( self . config , "testing for exact pretty-printing" . to_owned ( ) ) ;
@@ -1822,6 +2001,7 @@ impl<'test> TestCx<'test> {
18222001 || self . is_vxworks_pure_static ( )
18232002 || self . config . target . contains ( "bpf" )
18242003 || !self . config . target_cfg ( ) . dynamic_linking
2004+ || self . config . mode == RunCoverage
18252005 {
18262006 // We primarily compile all auxiliary libraries as dynamic libraries
18272007 // to avoid code size bloat and large binaries as much as possible
@@ -1832,6 +2012,10 @@ impl<'test> TestCx<'test> {
18322012 // dynamic libraries so we just go back to building a normal library. Note,
18332013 // however, that for MUSL if the library is built with `force_host` then
18342014 // it's ok to be a dylib as the host should always support dylibs.
2015+ //
2016+ // Coverage tests want static linking by default so that coverage
2017+ // mappings in auxiliary libraries can be merged into the final
2018+ // executable.
18352019 ( false , Some ( "lib" ) )
18362020 } else {
18372021 ( true , Some ( "dylib" ) )
@@ -2009,6 +2193,10 @@ impl<'test> TestCx<'test> {
20092193 }
20102194 }
20112195 DebugInfo => { /* debuginfo tests must be unoptimized */ }
2196+ RunCoverage => {
2197+ // Coverage reports are affected by optimization level, and
2198+ // the current snapshots assume no optimization by default.
2199+ }
20122200 _ => {
20132201 rustc. arg ( "-O" ) ;
20142202 }
@@ -2075,6 +2263,9 @@ impl<'test> TestCx<'test> {
20752263
20762264 rustc. arg ( dir_opt) ;
20772265 }
2266+ RunCoverage => {
2267+ rustc. arg ( "-Cinstrument-coverage" ) ;
2268+ }
20782269 RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson | RunMake
20792270 | CodegenUnits | JsDocTest | Assembly => {
20802271 // do not use JSON output
0 commit comments