Skip to content

Commit f457600

Browse files
Display total time and compilation time of merged doctests
1 parent 20aa182 commit f457600

File tree

2 files changed

+75
-18
lines changed

2 files changed

+75
-18
lines changed

src/librustdoc/doctest.rs

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use std::path::{Path, PathBuf};
1111
use std::process::{self, Command, Stdio};
1212
use std::sync::atomic::{AtomicUsize, Ordering};
1313
use std::sync::{Arc, Mutex};
14-
use std::{panic, str};
14+
use std::time::{Duration, Instant};
15+
use std::{fmt, panic, str};
1516

1617
pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
1718
pub(crate) use markdown::test as test_markdown;
@@ -36,6 +37,48 @@ use crate::config::{Options as RustdocOptions, OutputFormat};
3637
use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine};
3738
use 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)]
4184
pub(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.
499549
fn 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 {

src/librustdoc/doctest/runner.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt::Write;
2+
use std::time::Duration;
23

34
use rustc_data_structures::fx::FxIndexSet;
45
use rustc_span::edition::Edition;
@@ -67,14 +68,18 @@ impl DocTestRunner {
6768
self.nb_tests += 1;
6869
}
6970

71+
/// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
72+
///
73+
/// If compilation failed, it will return `Err`, otherwise it will return `Ok` containing if
74+
/// the test ran successfully.
7075
pub(crate) fn run_merged_tests(
7176
&mut self,
7277
test_options: IndividualTestOptions,
7378
edition: Edition,
7479
opts: &GlobalTestOptions,
7580
test_args: &[String],
7681
rustdoc_options: &RustdocOptions,
77-
) -> Result<bool, ()> {
82+
) -> (Duration, Result<bool, ()>) {
7883
let mut code = "\
7984
#![allow(unused_extern_crates)]
8085
#![allow(internal_features)]
@@ -204,9 +209,9 @@ std::process::Termination::report(test::test_main(test_args, tests, None))
204209
no_run: false,
205210
merged_test_code: Some(code),
206211
};
207-
let ret =
212+
let (duration, ret) =
208213
run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {});
209-
if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) }
214+
(duration, if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) })
210215
}
211216
}
212217

0 commit comments

Comments
 (0)