@@ -11,7 +11,8 @@ use std::path::{Path, PathBuf};
1111use std:: process:: { self , Command , Stdio } ;
1212use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
1313use std:: sync:: { Arc , Mutex } ;
14- use std:: { panic, str} ;
14+ use std:: time:: { Duration , Instant } ;
15+ use std:: { fmt, panic, str} ;
1516
1617pub ( crate ) use make:: { BuildDocTestBuilder , DocTestBuilder } ;
1718pub ( crate ) use markdown:: test as test_markdown;
@@ -36,6 +37,48 @@ use crate::config::{Options as RustdocOptions, OutputFormat};
3637use crate :: html:: markdown:: { ErrorCodes , Ignore , LangString , MdRelLine } ;
3738use crate :: lint:: init_lints;
3839
40+ /// Type used to display times (compilation and total) information for merged doctests.
41+ struct MergedDoctestTimes {
42+ total_time : Instant ,
43+ /// Total time spent compiling all merged doctests.
44+ compilation_time : Duration ,
45+ /// This field is used to keep track of how many merged doctests we (tried to) compile.
46+ added_compilation_times : usize ,
47+ }
48+
49+ impl MergedDoctestTimes {
50+ fn new ( ) -> Self {
51+ Self {
52+ total_time : Instant :: now ( ) ,
53+ compilation_time : Duration :: default ( ) ,
54+ added_compilation_times : 0 ,
55+ }
56+ }
57+
58+ fn add_compilation_time ( & mut self , duration : Duration ) {
59+ self . compilation_time += duration;
60+ self . added_compilation_times += 1 ;
61+ }
62+
63+ fn display_times ( & self ) {
64+ // If no merged doctest was compiled, then there is nothing to display.
65+ if self . added_compilation_times > 0 {
66+ println ! ( "{self}" ) ;
67+ }
68+ }
69+ }
70+
71+ impl fmt:: Display for MergedDoctestTimes {
72+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
73+ write ! (
74+ f,
75+ "all doctests ran in {:.2}s; merged doctests compilation took {:.2}s" ,
76+ self . total_time. elapsed( ) . as_secs_f64( ) ,
77+ self . compilation_time. as_secs_f64( ) ,
78+ )
79+ }
80+ }
81+
3982/// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
4083#[ derive( Clone ) ]
4184pub ( crate ) struct GlobalTestOptions {
@@ -295,6 +338,7 @@ pub(crate) fn run_tests(
295338
296339 let mut nb_errors = 0 ;
297340 let mut ran_edition_tests = 0 ;
341+ let mut times = MergedDoctestTimes :: new ( ) ;
298342 let target_str = rustdoc_options. target . to_string ( ) ;
299343
300344 for ( MergeableTestKey { edition, global_crate_attrs_hash } , mut doctests) in mergeable_tests {
@@ -314,13 +358,15 @@ pub(crate) fn run_tests(
314358 for ( doctest, scraped_test) in & doctests {
315359 tests_runner. add_test ( doctest, scraped_test, & target_str) ;
316360 }
317- if let Ok ( success ) = tests_runner. run_merged_tests (
361+ let ( duration , ret ) = tests_runner. run_merged_tests (
318362 rustdoc_test_options,
319363 edition,
320364 & opts,
321365 & test_args,
322366 rustdoc_options,
323- ) {
367+ ) ;
368+ times. add_compilation_time ( duration) ;
369+ if let Ok ( success) = ret {
324370 ran_edition_tests += 1 ;
325371 if !success {
326372 nb_errors += 1 ;
@@ -354,11 +400,13 @@ pub(crate) fn run_tests(
354400 test:: test_main_with_exit_callback ( & test_args, standalone_tests, None , || {
355401 // We ensure temp dir destructor is called.
356402 std:: mem:: drop ( temp_dir. take ( ) ) ;
403+ times. display_times ( ) ;
357404 } ) ;
358405 }
359406 if nb_errors != 0 {
360407 // We ensure temp dir destructor is called.
361408 std:: mem:: drop ( temp_dir) ;
409+ times. display_times ( ) ;
362410 // libtest::ERROR_EXIT_CODE is not public but it's the same value.
363411 std:: process:: exit ( 101 ) ;
364412 }
@@ -496,16 +544,19 @@ impl RunnableDocTest {
496544///
497545/// This is the function that calculates the compiler command line, invokes the compiler, then
498546/// invokes the test or tests in a separate executable (if applicable).
547+ ///
548+ /// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
499549fn run_test (
500550 doctest : RunnableDocTest ,
501551 rustdoc_options : & RustdocOptions ,
502552 supports_color : bool ,
503553 report_unused_externs : impl Fn ( UnusedExterns ) ,
504- ) -> Result < ( ) , TestFailure > {
554+ ) -> ( Duration , Result < ( ) , TestFailure > ) {
505555 let langstr = & doctest. langstr ;
506556 // Make sure we emit well-formed executable names for our target.
507557 let rust_out = add_exe_suffix ( "rust_out" . to_owned ( ) , & rustdoc_options. target ) ;
508558 let output_file = doctest. test_opts . outdir . path ( ) . join ( rust_out) ;
559+ let instant = Instant :: now ( ) ;
509560
510561 // Common arguments used for compiling the doctest runner.
511562 // On merged doctests, the compiler is invoked twice: once for the test code itself,
@@ -589,7 +640,7 @@ fn run_test(
589640 if std:: fs:: write ( & input_file, & doctest. full_test_code ) . is_err ( ) {
590641 // If we cannot write this file for any reason, we leave. All combined tests will be
591642 // tested as standalone tests.
592- return Err ( TestFailure :: CompileError ) ;
643+ return ( Duration :: default ( ) , Err ( TestFailure :: CompileError ) ) ;
593644 }
594645 if !rustdoc_options. nocapture {
595646 // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -660,7 +711,7 @@ fn run_test(
660711 if std:: fs:: write ( & runner_input_file, merged_test_code) . is_err ( ) {
661712 // If we cannot write this file for any reason, we leave. All combined tests will be
662713 // tested as standalone tests.
663- return Err ( TestFailure :: CompileError ) ;
714+ return ( instant . elapsed ( ) , Err ( TestFailure :: CompileError ) ) ;
664715 }
665716 if !rustdoc_options. nocapture {
666717 // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -713,7 +764,7 @@ fn run_test(
713764 let _bomb = Bomb ( & out) ;
714765 match ( output. status . success ( ) , langstr. compile_fail ) {
715766 ( true , true ) => {
716- return Err ( TestFailure :: UnexpectedCompilePass ) ;
767+ return ( instant . elapsed ( ) , Err ( TestFailure :: UnexpectedCompilePass ) ) ;
717768 }
718769 ( true , false ) => { }
719770 ( false , true ) => {
@@ -729,17 +780,18 @@ fn run_test(
729780 . collect ( ) ;
730781
731782 if !missing_codes. is_empty ( ) {
732- return Err ( TestFailure :: MissingErrorCodes ( missing_codes) ) ;
783+ return ( instant . elapsed ( ) , Err ( TestFailure :: MissingErrorCodes ( missing_codes) ) ) ;
733784 }
734785 }
735786 }
736787 ( false , false ) => {
737- return Err ( TestFailure :: CompileError ) ;
788+ return ( instant . elapsed ( ) , Err ( TestFailure :: CompileError ) ) ;
738789 }
739790 }
740791
792+ let duration = instant. elapsed ( ) ;
741793 if doctest. no_run {
742- return Ok ( ( ) ) ;
794+ return ( duration , Ok ( ( ) ) ) ;
743795 }
744796
745797 // Run the code!
@@ -771,17 +823,17 @@ fn run_test(
771823 cmd. output ( )
772824 } ;
773825 match result {
774- Err ( e) => return Err ( TestFailure :: ExecutionError ( e) ) ,
826+ Err ( e) => return ( duration , Err ( TestFailure :: ExecutionError ( e) ) ) ,
775827 Ok ( out) => {
776828 if langstr. should_panic && out. status . success ( ) {
777- return Err ( TestFailure :: UnexpectedRunPass ) ;
829+ return ( duration , Err ( TestFailure :: UnexpectedRunPass ) ) ;
778830 } else if !langstr. should_panic && !out. status . success ( ) {
779- return Err ( TestFailure :: ExecutionFailure ( out) ) ;
831+ return ( duration , Err ( TestFailure :: ExecutionFailure ( out) ) ) ;
780832 }
781833 }
782834 }
783835
784- Ok ( ( ) )
836+ ( duration , Ok ( ( ) ) )
785837}
786838
787839/// Converts a path intended to use as a command to absolute if it is
@@ -1071,7 +1123,7 @@ fn doctest_run_fn(
10711123 no_run : scraped_test. no_run ( & rustdoc_options) ,
10721124 merged_test_code : None ,
10731125 } ;
1074- let res =
1126+ let ( _ , res) =
10751127 run_test ( runnable_test, & rustdoc_options, doctest. supports_color , report_unused_externs) ;
10761128
10771129 if let Err ( err) = res {
0 commit comments