@@ -9,7 +9,7 @@ use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc};
9
9
use crate :: common:: { CompareMode , FailMode , PassMode } ;
10
10
use crate :: common:: { Config , TestPaths } ;
11
11
use crate :: common:: { CoverageMap , CoverageRun , Pretty , RunPassValgrind } ;
12
- use crate :: common:: { UI_COVERAGE , UI_COVERAGE_MAP , UI_RUN_STDERR , UI_RUN_STDOUT } ;
12
+ use crate :: common:: { UI_RUN_STDERR , UI_RUN_STDOUT } ;
13
13
use crate :: compute_diff:: { write_diff, write_filtered_diff} ;
14
14
use crate :: errors:: { self , Error , ErrorKind } ;
15
15
use crate :: header:: TestProps ;
@@ -41,6 +41,7 @@ use tracing::*;
41
41
use crate :: extract_gdb_version;
42
42
use crate :: is_android_gdb_target;
43
43
44
+ mod coverage;
44
45
mod debugger;
45
46
use debugger:: DebuggerCommands ;
46
47
@@ -53,6 +54,7 @@ macro_rules! static_regex {
53
54
RE . get_or_init( || :: regex:: Regex :: new( $re) . unwrap( ) )
54
55
} } ;
55
56
}
57
+ use static_regex;
56
58
57
59
const FAKE_SRC_BASE : & str = "fake-test-src-base" ;
58
60
@@ -267,8 +269,8 @@ impl<'test> TestCx<'test> {
267
269
MirOpt => self . run_mir_opt_test ( ) ,
268
270
Assembly => self . run_assembly_test ( ) ,
269
271
JsDocTest => self . run_js_doc_test ( ) ,
270
- CoverageMap => self . run_coverage_map_test ( ) ,
271
- CoverageRun => self . run_coverage_run_test ( ) ,
272
+ CoverageMap => self . run_coverage_map_test ( ) , // see self::coverage
273
+ CoverageRun => self . run_coverage_run_test ( ) , // see self::coverage
272
274
Crashes => self . run_crash_test ( ) ,
273
275
}
274
276
}
@@ -504,224 +506,6 @@ impl<'test> TestCx<'test> {
504
506
}
505
507
}
506
508
507
- fn run_coverage_map_test ( & self ) {
508
- let Some ( coverage_dump_path) = & self . config . coverage_dump_path else {
509
- self . fatal ( "missing --coverage-dump" ) ;
510
- } ;
511
-
512
- let ( proc_res, llvm_ir_path) = self . compile_test_and_save_ir ( ) ;
513
- if !proc_res. status . success ( ) {
514
- self . fatal_proc_rec ( "compilation failed!" , & proc_res) ;
515
- }
516
- drop ( proc_res) ;
517
-
518
- let mut dump_command = Command :: new ( coverage_dump_path) ;
519
- dump_command. arg ( llvm_ir_path) ;
520
- let proc_res = self . run_command_to_procres ( & mut dump_command) ;
521
- if !proc_res. status . success ( ) {
522
- self . fatal_proc_rec ( "coverage-dump failed!" , & proc_res) ;
523
- }
524
-
525
- let kind = UI_COVERAGE_MAP ;
526
-
527
- let expected_coverage_dump = self . load_expected_output ( kind) ;
528
- let actual_coverage_dump = self . normalize_output ( & proc_res. stdout , & [ ] ) ;
529
-
530
- let coverage_dump_errors =
531
- self . compare_output ( kind, & actual_coverage_dump, & expected_coverage_dump) ;
532
-
533
- if coverage_dump_errors > 0 {
534
- self . fatal_proc_rec (
535
- & format ! ( "{coverage_dump_errors} errors occurred comparing coverage output." ) ,
536
- & proc_res,
537
- ) ;
538
- }
539
- }
540
-
541
- fn run_coverage_run_test ( & self ) {
542
- let should_run = self . run_if_enabled ( ) ;
543
- let proc_res = self . compile_test ( should_run, Emit :: None ) ;
544
-
545
- if !proc_res. status . success ( ) {
546
- self . fatal_proc_rec ( "compilation failed!" , & proc_res) ;
547
- }
548
- drop ( proc_res) ;
549
-
550
- if let WillExecute :: Disabled = should_run {
551
- return ;
552
- }
553
-
554
- let profraw_path = self . output_base_dir ( ) . join ( "default.profraw" ) ;
555
- let profdata_path = self . output_base_dir ( ) . join ( "default.profdata" ) ;
556
-
557
- // Delete any existing profraw/profdata files to rule out unintended
558
- // interference between repeated test runs.
559
- if profraw_path. exists ( ) {
560
- std:: fs:: remove_file ( & profraw_path) . unwrap ( ) ;
561
- }
562
- if profdata_path. exists ( ) {
563
- std:: fs:: remove_file ( & profdata_path) . unwrap ( ) ;
564
- }
565
-
566
- let proc_res = self . exec_compiled_test_general (
567
- & [ ( "LLVM_PROFILE_FILE" , & profraw_path. to_str ( ) . unwrap ( ) ) ] ,
568
- false ,
569
- ) ;
570
- if self . props . failure_status . is_some ( ) {
571
- self . check_correct_failure_status ( & proc_res) ;
572
- } else if !proc_res. status . success ( ) {
573
- self . fatal_proc_rec ( "test run failed!" , & proc_res) ;
574
- }
575
- drop ( proc_res) ;
576
-
577
- let mut profraw_paths = vec ! [ profraw_path] ;
578
- let mut bin_paths = vec ! [ self . make_exe_name( ) ] ;
579
-
580
- if self . config . suite == "coverage-run-rustdoc" {
581
- self . run_doctests_for_coverage ( & mut profraw_paths, & mut bin_paths) ;
582
- }
583
-
584
- // Run `llvm-profdata merge` to index the raw coverage output.
585
- let proc_res = self . run_llvm_tool ( "llvm-profdata" , |cmd| {
586
- cmd. args ( [ "merge" , "--sparse" , "--output" ] ) ;
587
- cmd. arg ( & profdata_path) ;
588
- cmd. args ( & profraw_paths) ;
589
- } ) ;
590
- if !proc_res. status . success ( ) {
591
- self . fatal_proc_rec ( "llvm-profdata merge failed!" , & proc_res) ;
592
- }
593
- drop ( proc_res) ;
594
-
595
- // Run `llvm-cov show` to produce a coverage report in text format.
596
- let proc_res = self . run_llvm_tool ( "llvm-cov" , |cmd| {
597
- cmd. args ( [ "show" , "--format=text" , "--show-line-counts-or-regions" ] ) ;
598
-
599
- cmd. arg ( "--Xdemangler" ) ;
600
- cmd. arg ( self . config . rust_demangler_path . as_ref ( ) . unwrap ( ) ) ;
601
-
602
- cmd. arg ( "--instr-profile" ) ;
603
- cmd. arg ( & profdata_path) ;
604
-
605
- for bin in & bin_paths {
606
- cmd. arg ( "--object" ) ;
607
- cmd. arg ( bin) ;
608
- }
609
-
610
- cmd. args ( & self . props . llvm_cov_flags ) ;
611
- } ) ;
612
- if !proc_res. status . success ( ) {
613
- self . fatal_proc_rec ( "llvm-cov show failed!" , & proc_res) ;
614
- }
615
-
616
- let kind = UI_COVERAGE ;
617
-
618
- let expected_coverage = self . load_expected_output ( kind) ;
619
- let normalized_actual_coverage =
620
- self . normalize_coverage_output ( & proc_res. stdout ) . unwrap_or_else ( |err| {
621
- self . fatal_proc_rec ( & err, & proc_res) ;
622
- } ) ;
623
-
624
- let coverage_errors =
625
- self . compare_output ( kind, & normalized_actual_coverage, & expected_coverage) ;
626
-
627
- if coverage_errors > 0 {
628
- self . fatal_proc_rec (
629
- & format ! ( "{} errors occurred comparing coverage output." , coverage_errors) ,
630
- & proc_res,
631
- ) ;
632
- }
633
- }
634
-
635
- /// Run any doctests embedded in this test file, and add any resulting
636
- /// `.profraw` files and doctest executables to the given vectors.
637
- fn run_doctests_for_coverage (
638
- & self ,
639
- profraw_paths : & mut Vec < PathBuf > ,
640
- bin_paths : & mut Vec < PathBuf > ,
641
- ) {
642
- // Put .profraw files and doctest executables in dedicated directories,
643
- // to make it easier to glob them all later.
644
- let profraws_dir = self . output_base_dir ( ) . join ( "doc_profraws" ) ;
645
- let bins_dir = self . output_base_dir ( ) . join ( "doc_bins" ) ;
646
-
647
- // Remove existing directories to prevent cross-run interference.
648
- if profraws_dir. try_exists ( ) . unwrap ( ) {
649
- std:: fs:: remove_dir_all ( & profraws_dir) . unwrap ( ) ;
650
- }
651
- if bins_dir. try_exists ( ) . unwrap ( ) {
652
- std:: fs:: remove_dir_all ( & bins_dir) . unwrap ( ) ;
653
- }
654
-
655
- let mut rustdoc_cmd =
656
- Command :: new ( self . config . rustdoc_path . as_ref ( ) . expect ( "--rustdoc-path not passed" ) ) ;
657
-
658
- // In general there will be multiple doctest binaries running, so we
659
- // tell the profiler runtime to write their coverage data into separate
660
- // profraw files.
661
- rustdoc_cmd. env ( "LLVM_PROFILE_FILE" , profraws_dir. join ( "%p-%m.profraw" ) ) ;
662
-
663
- rustdoc_cmd. args ( [ "--test" , "-Cinstrument-coverage" ] ) ;
664
-
665
- // Without this, the doctests complain about not being able to find
666
- // their enclosing file's crate for some reason.
667
- rustdoc_cmd. args ( [ "--crate-name" , "workaround_for_79771" ] ) ;
668
-
669
- // Persist the doctest binaries so that `llvm-cov show` can read their
670
- // embedded coverage mappings later.
671
- rustdoc_cmd. arg ( "-Zunstable-options" ) ;
672
- rustdoc_cmd. arg ( "--persist-doctests" ) ;
673
- rustdoc_cmd. arg ( & bins_dir) ;
674
-
675
- rustdoc_cmd. arg ( "-L" ) ;
676
- rustdoc_cmd. arg ( self . aux_output_dir_name ( ) ) ;
677
-
678
- rustdoc_cmd. arg ( & self . testpaths . file ) ;
679
-
680
- let proc_res = self . compose_and_run_compiler ( rustdoc_cmd, None ) ;
681
- if !proc_res. status . success ( ) {
682
- self . fatal_proc_rec ( "rustdoc --test failed!" , & proc_res)
683
- }
684
-
685
- fn glob_iter ( path : impl AsRef < Path > ) -> impl Iterator < Item = PathBuf > {
686
- let path_str = path. as_ref ( ) . to_str ( ) . unwrap ( ) ;
687
- let iter = glob ( path_str) . unwrap ( ) ;
688
- iter. map ( Result :: unwrap)
689
- }
690
-
691
- // Find all profraw files in the profraw directory.
692
- for p in glob_iter ( profraws_dir. join ( "*.profraw" ) ) {
693
- profraw_paths. push ( p) ;
694
- }
695
- // Find all executables in the `--persist-doctests` directory, while
696
- // avoiding other file types (e.g. `.pdb` on Windows). This doesn't
697
- // need to be perfect, as long as it can handle the files actually
698
- // produced by `rustdoc --test`.
699
- for p in glob_iter ( bins_dir. join ( "**/*" ) ) {
700
- let is_bin = p. is_file ( )
701
- && match p. extension ( ) {
702
- None => true ,
703
- Some ( ext) => ext == OsStr :: new ( "exe" ) ,
704
- } ;
705
- if is_bin {
706
- bin_paths. push ( p) ;
707
- }
708
- }
709
- }
710
-
711
- fn run_llvm_tool ( & self , name : & str , configure_cmd_fn : impl FnOnce ( & mut Command ) ) -> ProcRes {
712
- let tool_path = self
713
- . config
714
- . llvm_bin_dir
715
- . as_ref ( )
716
- . expect ( "this test expects the LLVM bin dir to be available" )
717
- . join ( name) ;
718
-
719
- let mut cmd = Command :: new ( tool_path) ;
720
- configure_cmd_fn ( & mut cmd) ;
721
-
722
- self . run_command_to_procres ( & mut cmd)
723
- }
724
-
725
509
fn run_command_to_procres ( & self , cmd : & mut Command ) -> ProcRes {
726
510
let output = cmd. output ( ) . unwrap_or_else ( |e| panic ! ( "failed to exec `{cmd:?}`: {e:?}" ) ) ;
727
511
@@ -737,143 +521,6 @@ impl<'test> TestCx<'test> {
737
521
proc_res
738
522
}
739
523
740
- fn normalize_coverage_output ( & self , coverage : & str ) -> Result < String , String > {
741
- let normalized = self . normalize_output ( coverage, & [ ] ) ;
742
- let normalized = Self :: anonymize_coverage_line_numbers ( & normalized) ;
743
-
744
- let mut lines = normalized. lines ( ) . collect :: < Vec < _ > > ( ) ;
745
-
746
- Self :: sort_coverage_file_sections ( & mut lines) ?;
747
- Self :: sort_coverage_subviews ( & mut lines) ?;
748
-
749
- let joined_lines = lines. iter ( ) . flat_map ( |line| [ line, "\n " ] ) . collect :: < String > ( ) ;
750
- Ok ( joined_lines)
751
- }
752
-
753
- /// Replace line numbers in coverage reports with the placeholder `LL`,
754
- /// so that the tests are less sensitive to lines being added/removed.
755
- fn anonymize_coverage_line_numbers ( coverage : & str ) -> String {
756
- // The coverage reporter prints line numbers at the start of a line.
757
- // They are truncated or left-padded to occupy exactly 5 columns.
758
- // (`LineNumberColumnWidth` in `SourceCoverageViewText.cpp`.)
759
- // A pipe character `|` appears immediately after the final digit.
760
- //
761
- // Line numbers that appear inside expansion/instantiation subviews
762
- // have an additional prefix of ` |` for each nesting level.
763
- //
764
- // Branch views also include the relevant line number, so we want to
765
- // redact those too. (These line numbers don't have padding.)
766
- //
767
- // Note: The pattern `(?m:^)` matches the start of a line.
768
-
769
- // ` 1|` => ` LL|`
770
- // ` 10|` => ` LL|`
771
- // ` 100|` => ` LL|`
772
- // ` | 1000|` => ` | LL|`
773
- // ` | | 1000|` => ` | | LL|`
774
- let coverage = static_regex ! ( r"(?m:^)(?<prefix>(?: \|)*) *[0-9]+\|" )
775
- . replace_all ( & coverage, "${prefix} LL|" ) ;
776
-
777
- // ` | Branch (1:` => ` | Branch (LL:`
778
- // ` | | Branch (10:` => ` | | Branch (LL:`
779
- let coverage = static_regex ! ( r"(?m:^)(?<prefix>(?: \|)+ Branch \()[0-9]+:" )
780
- . replace_all ( & coverage, "${prefix}LL:" ) ;
781
-
782
- // ` |---> MC/DC Decision Region (1:30) to (2:` => ` |---> MC/DC Decision Region (LL:30) to (LL:`
783
- let coverage =
784
- static_regex ! ( r"(?m:^)(?<prefix>(?: \|)+---> MC/DC Decision Region \()[0-9]+:(?<middle>[0-9]+\) to \()[0-9]+:" )
785
- . replace_all ( & coverage, "${prefix}LL:${middle}LL:" ) ;
786
-
787
- // ` | Condition C1 --> (1:` => ` | Condition C1 --> (LL:`
788
- let coverage =
789
- static_regex ! ( r"(?m:^)(?<prefix>(?: \|)+ Condition C[0-9]+ --> \()[0-9]+:" )
790
- . replace_all ( & coverage, "${prefix}LL:" ) ;
791
-
792
- coverage. into_owned ( )
793
- }
794
-
795
- /// Coverage reports can describe multiple source files, separated by
796
- /// blank lines. The order of these files is unpredictable (since it
797
- /// depends on implementation details), so we need to sort the file
798
- /// sections into a consistent order before comparing against a snapshot.
799
- fn sort_coverage_file_sections ( coverage_lines : & mut Vec < & str > ) -> Result < ( ) , String > {
800
- // Group the lines into file sections, separated by blank lines.
801
- let mut sections = coverage_lines. split ( |line| line. is_empty ( ) ) . collect :: < Vec < _ > > ( ) ;
802
-
803
- // The last section should be empty, representing an extra trailing blank line.
804
- if !sections. last ( ) . is_some_and ( |last| last. is_empty ( ) ) {
805
- return Err ( "coverage report should end with an extra blank line" . to_owned ( ) ) ;
806
- }
807
-
808
- // Sort the file sections (not including the final empty "section").
809
- let except_last = sections. len ( ) - 1 ;
810
- ( & mut sections[ ..except_last] ) . sort ( ) ;
811
-
812
- // Join the file sections back into a flat list of lines, with
813
- // sections separated by blank lines.
814
- let joined = sections. join ( & [ "" ] as & [ _ ] ) ;
815
- assert_eq ! ( joined. len( ) , coverage_lines. len( ) ) ;
816
- * coverage_lines = joined;
817
-
818
- Ok ( ( ) )
819
- }
820
-
821
- fn sort_coverage_subviews ( coverage_lines : & mut Vec < & str > ) -> Result < ( ) , String > {
822
- let mut output_lines = Vec :: new ( ) ;
823
-
824
- // We accumulate a list of zero or more "subviews", where each
825
- // subview is a list of one or more lines.
826
- let mut subviews: Vec < Vec < & str > > = Vec :: new ( ) ;
827
-
828
- fn flush < ' a > ( subviews : & mut Vec < Vec < & ' a str > > , output_lines : & mut Vec < & ' a str > ) {
829
- if subviews. is_empty ( ) {
830
- return ;
831
- }
832
-
833
- // Take and clear the list of accumulated subviews.
834
- let mut subviews = std:: mem:: take ( subviews) ;
835
-
836
- // The last "subview" should be just a boundary line on its own,
837
- // so exclude it when sorting the other subviews.
838
- let except_last = subviews. len ( ) - 1 ;
839
- ( & mut subviews[ ..except_last] ) . sort ( ) ;
840
-
841
- for view in subviews {
842
- for line in view {
843
- output_lines. push ( line) ;
844
- }
845
- }
846
- }
847
-
848
- for ( line, line_num) in coverage_lines. iter ( ) . zip ( 1 ..) {
849
- if line. starts_with ( " ------------------" ) {
850
- // This is a subview boundary line, so start a new subview.
851
- subviews. push ( vec ! [ line] ) ;
852
- } else if line. starts_with ( " |" ) {
853
- // Add this line to the current subview.
854
- subviews
855
- . last_mut ( )
856
- . ok_or ( format ! (
857
- "unexpected subview line outside of a subview on line {line_num}"
858
- ) ) ?
859
- . push ( line) ;
860
- } else {
861
- // This line is not part of a subview, so sort and print any
862
- // accumulated subviews, and then print the line as-is.
863
- flush ( & mut subviews, & mut output_lines) ;
864
- output_lines. push ( line) ;
865
- }
866
- }
867
-
868
- flush ( & mut subviews, & mut output_lines) ;
869
- assert ! ( subviews. is_empty( ) ) ;
870
-
871
- assert_eq ! ( output_lines. len( ) , coverage_lines. len( ) ) ;
872
- * coverage_lines = output_lines;
873
-
874
- Ok ( ( ) )
875
- }
876
-
877
524
fn run_pretty_test ( & self ) {
878
525
if self . props . pp_exact . is_some ( ) {
879
526
logv ( self . config , "testing for exact pretty-printing" . to_owned ( ) ) ;
0 commit comments