@@ -6,8 +6,8 @@ use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJs
6
6
use crate :: common:: { Codegen , CodegenUnits , DebugInfo , Debugger , Rustdoc } ;
7
7
use crate :: common:: { CompareMode , FailMode , PassMode } ;
8
8
use 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 } ;
11
11
use crate :: compute_diff:: { write_diff, write_filtered_diff} ;
12
12
use crate :: errors:: { self , Error , ErrorKind } ;
13
13
use crate :: header:: TestProps ;
@@ -253,6 +253,7 @@ impl<'test> TestCx<'test> {
253
253
MirOpt => self . run_mir_opt_test ( ) ,
254
254
Assembly => self . run_assembly_test ( ) ,
255
255
JsDocTest => self . run_js_doc_test ( ) ,
256
+ RunCoverage => self . run_coverage_test ( ) ,
256
257
}
257
258
}
258
259
@@ -465,6 +466,184 @@ impl<'test> TestCx<'test> {
465
466
}
466
467
}
467
468
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
+
468
647
fn run_pretty_test ( & self ) {
469
648
if self . props . pp_exact . is_some ( ) {
470
649
logv ( self . config , "testing for exact pretty-printing" . to_owned ( ) ) ;
@@ -1822,6 +2001,7 @@ impl<'test> TestCx<'test> {
1822
2001
|| self . is_vxworks_pure_static ( )
1823
2002
|| self . config . target . contains ( "bpf" )
1824
2003
|| !self . config . target_cfg ( ) . dynamic_linking
2004
+ || self . config . mode == RunCoverage
1825
2005
{
1826
2006
// We primarily compile all auxiliary libraries as dynamic libraries
1827
2007
// to avoid code size bloat and large binaries as much as possible
@@ -1832,6 +2012,10 @@ impl<'test> TestCx<'test> {
1832
2012
// dynamic libraries so we just go back to building a normal library. Note,
1833
2013
// however, that for MUSL if the library is built with `force_host` then
1834
2014
// 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.
1835
2019
( false , Some ( "lib" ) )
1836
2020
} else {
1837
2021
( true , Some ( "dylib" ) )
@@ -2009,6 +2193,10 @@ impl<'test> TestCx<'test> {
2009
2193
}
2010
2194
}
2011
2195
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
+ }
2012
2200
_ => {
2013
2201
rustc. arg ( "-O" ) ;
2014
2202
}
@@ -2075,6 +2263,9 @@ impl<'test> TestCx<'test> {
2075
2263
2076
2264
rustc. arg ( dir_opt) ;
2077
2265
}
2266
+ RunCoverage => {
2267
+ rustc. arg ( "-Cinstrument-coverage" ) ;
2268
+ }
2078
2269
RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson | RunMake
2079
2270
| CodegenUnits | JsDocTest | Assembly => {
2080
2271
// do not use JSON output
0 commit comments