@@ -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,50 @@ 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 since the numbers
65+ // displayed by `libtest` for standalone tests are already accurate (they include both
66+ // compilation and runtime).
67+ if self . added_compilation_times > 0 {
68+ println ! ( "{self}" ) ;
69+ }
70+ }
71+ }
72+
73+ impl fmt:: Display for MergedDoctestTimes {
74+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
75+ write ! (
76+ f,
77+ "all doctests ran in {:.2}s; merged doctests compilation took {:.2}s" ,
78+ self . total_time. elapsed( ) . as_secs_f64( ) ,
79+ self . compilation_time. as_secs_f64( ) ,
80+ )
81+ }
82+ }
83+
3984/// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
4085#[ derive( Clone ) ]
4186pub ( crate ) struct GlobalTestOptions {
@@ -295,6 +340,7 @@ pub(crate) fn run_tests(
295340
296341 let mut nb_errors = 0 ;
297342 let mut ran_edition_tests = 0 ;
343+ let mut times = MergedDoctestTimes :: new ( ) ;
298344 let target_str = rustdoc_options. target . to_string ( ) ;
299345
300346 for ( MergeableTestKey { edition, global_crate_attrs_hash } , mut doctests) in mergeable_tests {
@@ -314,13 +360,15 @@ pub(crate) fn run_tests(
314360 for ( doctest, scraped_test) in & doctests {
315361 tests_runner. add_test ( doctest, scraped_test, & target_str) ;
316362 }
317- if let Ok ( success ) = tests_runner. run_merged_tests (
363+ let ( duration , ret ) = tests_runner. run_merged_tests (
318364 rustdoc_test_options,
319365 edition,
320366 & opts,
321367 & test_args,
322368 rustdoc_options,
323- ) {
369+ ) ;
370+ times. add_compilation_time ( duration) ;
371+ if let Ok ( success) = ret {
324372 ran_edition_tests += 1 ;
325373 if !success {
326374 nb_errors += 1 ;
@@ -354,11 +402,13 @@ pub(crate) fn run_tests(
354402 test:: test_main_with_exit_callback ( & test_args, standalone_tests, None , || {
355403 // We ensure temp dir destructor is called.
356404 std:: mem:: drop ( temp_dir. take ( ) ) ;
405+ times. display_times ( ) ;
357406 } ) ;
358407 }
359408 if nb_errors != 0 {
360409 // We ensure temp dir destructor is called.
361410 std:: mem:: drop ( temp_dir) ;
411+ times. display_times ( ) ;
362412 // libtest::ERROR_EXIT_CODE is not public but it's the same value.
363413 std:: process:: exit ( 101 ) ;
364414 }
@@ -496,16 +546,19 @@ impl RunnableDocTest {
496546///
497547/// This is the function that calculates the compiler command line, invokes the compiler, then
498548/// invokes the test or tests in a separate executable (if applicable).
549+ ///
550+ /// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
499551fn run_test (
500552 doctest : RunnableDocTest ,
501553 rustdoc_options : & RustdocOptions ,
502554 supports_color : bool ,
503555 report_unused_externs : impl Fn ( UnusedExterns ) ,
504- ) -> Result < ( ) , TestFailure > {
556+ ) -> ( Duration , Result < ( ) , TestFailure > ) {
505557 let langstr = & doctest. langstr ;
506558 // Make sure we emit well-formed executable names for our target.
507559 let rust_out = add_exe_suffix ( "rust_out" . to_owned ( ) , & rustdoc_options. target ) ;
508560 let output_file = doctest. test_opts . outdir . path ( ) . join ( rust_out) ;
561+ let instant = Instant :: now ( ) ;
509562
510563 // Common arguments used for compiling the doctest runner.
511564 // On merged doctests, the compiler is invoked twice: once for the test code itself,
@@ -589,7 +642,7 @@ fn run_test(
589642 if std:: fs:: write ( & input_file, & doctest. full_test_code ) . is_err ( ) {
590643 // If we cannot write this file for any reason, we leave. All combined tests will be
591644 // tested as standalone tests.
592- return Err ( TestFailure :: CompileError ) ;
645+ return ( Duration :: default ( ) , Err ( TestFailure :: CompileError ) ) ;
593646 }
594647 if !rustdoc_options. nocapture {
595648 // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -660,7 +713,7 @@ fn run_test(
660713 if std:: fs:: write ( & runner_input_file, merged_test_code) . is_err ( ) {
661714 // If we cannot write this file for any reason, we leave. All combined tests will be
662715 // tested as standalone tests.
663- return Err ( TestFailure :: CompileError ) ;
716+ return ( instant . elapsed ( ) , Err ( TestFailure :: CompileError ) ) ;
664717 }
665718 if !rustdoc_options. nocapture {
666719 // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -713,7 +766,7 @@ fn run_test(
713766 let _bomb = Bomb ( & out) ;
714767 match ( output. status . success ( ) , langstr. compile_fail ) {
715768 ( true , true ) => {
716- return Err ( TestFailure :: UnexpectedCompilePass ) ;
769+ return ( instant . elapsed ( ) , Err ( TestFailure :: UnexpectedCompilePass ) ) ;
717770 }
718771 ( true , false ) => { }
719772 ( false , true ) => {
@@ -729,17 +782,18 @@ fn run_test(
729782 . collect ( ) ;
730783
731784 if !missing_codes. is_empty ( ) {
732- return Err ( TestFailure :: MissingErrorCodes ( missing_codes) ) ;
785+ return ( instant . elapsed ( ) , Err ( TestFailure :: MissingErrorCodes ( missing_codes) ) ) ;
733786 }
734787 }
735788 }
736789 ( false , false ) => {
737- return Err ( TestFailure :: CompileError ) ;
790+ return ( instant . elapsed ( ) , Err ( TestFailure :: CompileError ) ) ;
738791 }
739792 }
740793
794+ let duration = instant. elapsed ( ) ;
741795 if doctest. no_run {
742- return Ok ( ( ) ) ;
796+ return ( duration , Ok ( ( ) ) ) ;
743797 }
744798
745799 // Run the code!
@@ -771,17 +825,17 @@ fn run_test(
771825 cmd. output ( )
772826 } ;
773827 match result {
774- Err ( e) => return Err ( TestFailure :: ExecutionError ( e) ) ,
828+ Err ( e) => return ( duration , Err ( TestFailure :: ExecutionError ( e) ) ) ,
775829 Ok ( out) => {
776830 if langstr. should_panic && out. status . success ( ) {
777- return Err ( TestFailure :: UnexpectedRunPass ) ;
831+ return ( duration , Err ( TestFailure :: UnexpectedRunPass ) ) ;
778832 } else if !langstr. should_panic && !out. status . success ( ) {
779- return Err ( TestFailure :: ExecutionFailure ( out) ) ;
833+ return ( duration , Err ( TestFailure :: ExecutionFailure ( out) ) ) ;
780834 }
781835 }
782836 }
783837
784- Ok ( ( ) )
838+ ( duration , Ok ( ( ) ) )
785839}
786840
787841/// Converts a path intended to use as a command to absolute if it is
@@ -1071,7 +1125,7 @@ fn doctest_run_fn(
10711125 no_run : scraped_test. no_run ( & rustdoc_options) ,
10721126 merged_test_code : None ,
10731127 } ;
1074- let res =
1128+ let ( _ , res) =
10751129 run_test ( runnable_test, & rustdoc_options, doctest. supports_color , report_unused_externs) ;
10761130
10771131 if let Err ( err) = res {
0 commit comments