From 7ec3cabe1776b1a3d0340b24a5a6901797836430 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 10 Jun 2024 18:29:33 +0200 Subject: [PATCH] Correctly handle doctests with invalid AST --- src/librustdoc/doctest.rs | 13 ++++--- src/librustdoc/doctest/make.rs | 19 ++++++++-- src/librustdoc/doctest/tests.rs | 2 +- src/librustdoc/html/markdown.rs | 2 +- tests/rustdoc-ui/doctest/wrong-ast-2024.rs | 20 +++++++++++ .../rustdoc-ui/doctest/wrong-ast-2024.stdout | 35 ++++++++++++++++++ tests/rustdoc-ui/doctest/wrong-ast.rs | 20 +++++++++++ tests/rustdoc-ui/doctest/wrong-ast.stdout | 36 +++++++++++++++++++ 8 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/wrong-ast-2024.rs create mode 100644 tests/rustdoc-ui/doctest/wrong-ast-2024.stdout create mode 100644 tests/rustdoc-ui/doctest/wrong-ast.rs create mode 100644 tests/rustdoc-ui/doctest/wrong-ast.stdout diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 5586a23595d..07bdccff278 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -635,7 +635,7 @@ fn run_test( cmd.current_dir(run_directory); } - let result = if rustdoc_options.nocapture { + let result = if is_multiple_tests || rustdoc_options.nocapture { cmd.status().map(|status| process::Output { status, stdout: Vec::new(), @@ -801,10 +801,15 @@ impl CreateRunnableDoctests { ); let edition = scraped_test.edition(&self.rustdoc_options); - let doctest = - DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition, Some(test_id)); + let doctest = DocTest::new( + &scraped_test.text, + Some(&self.opts.crate_name), + edition, + self.can_merge_doctests, + Some(test_id), + ); let is_standalone = !self.can_merge_doctests - || doctest.failed_ast + || !doctest.can_be_merged || scraped_test.langstr.compile_fail || scraped_test.langstr.test_harness || scraped_test.langstr.standalone diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 492a9a6e38d..c95dace1a81 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -26,6 +26,7 @@ pub(crate) struct DocTest { pub(crate) everything_else: String, pub(crate) test_id: Option, pub(crate) failed_ast: bool, + pub(crate) can_be_merged: bool, } impl DocTest { @@ -33,6 +34,7 @@ impl DocTest { source: &str, crate_name: Option<&str>, edition: Edition, + can_merge_doctests: bool, // If `test_id` is `None`, it means we're generating code for a code example "run" link. test_id: Option, ) -> Self { @@ -49,6 +51,7 @@ impl DocTest { &crates, edition, &mut supports_color, + can_merge_doctests, ) else { // If the parser panicked due to a fatal error, pass the test code through unchanged. @@ -62,6 +65,7 @@ impl DocTest { already_has_extern_crate: false, test_id, failed_ast: true, + can_be_merged: false, }; }; Self { @@ -72,7 +76,10 @@ impl DocTest { everything_else, already_has_extern_crate, test_id, - failed_ast, + failed_ast: false, + // If the AST returned an error, we don't want this doctest to be merged with the + // others. + can_be_merged: !failed_ast, } } @@ -85,6 +92,11 @@ impl DocTest { opts: &GlobalTestOptions, crate_name: Option<&str>, ) -> (String, usize) { + if self.failed_ast { + // If the AST failed to compile, no need to go generate a complete doctest, the error + // will be better this way. + return (test_code.to_string(), 0); + } let mut line_offset = 0; let mut prog = String::new(); let everything_else = self.everything_else.trim(); @@ -323,6 +335,7 @@ fn check_for_main_and_extern_crate( crates: &str, edition: Edition, supports_color: &mut bool, + can_merge_doctests: bool, ) -> Result<(Option, bool, bool), FatalError> { let result = rustc_driver::catch_fatal_errors(|| { rustc_span::create_session_if_not_set_then(edition, |_| { @@ -340,7 +353,7 @@ fn check_for_main_and_extern_crate( ); // No need to double-check this if the "merged doctests" feature isn't enabled (so // before the 2024 edition). - if edition >= Edition::Edition2024 && parsing_result != ParsingResult::Ok { + if can_merge_doctests && parsing_result != ParsingResult::Ok { // If we found an AST error, we want to ensure it's because of an expression being // used outside of a function. // @@ -525,5 +538,5 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) { debug!("crates:\n{crates}"); debug!("after:\n{after}"); - (before, after, crates) + (before, after.trim().to_owned(), crates) } diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs index 533fc3a56ed..982bfae5883 100644 --- a/src/librustdoc/doctest/tests.rs +++ b/src/librustdoc/doctest/tests.rs @@ -11,7 +11,7 @@ fn make_test( test_id: Option<&str>, ) -> (String, usize) { let doctest = - DocTest::new(test_code, crate_name, DEFAULT_EDITION, test_id.map(|s| s.to_string())); + DocTest::new(test_code, crate_name, DEFAULT_EDITION, false, test_id.map(|s| s.to_string())); let (code, line_offset) = doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name); (code, line_offset) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index da6aa47fff2..879e44c1712 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -297,7 +297,7 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { attrs: vec![], args_file: PathBuf::new(), }; - let doctest = doctest::DocTest::new(&test, krate, edition, None); + let doctest = doctest::DocTest::new(&test, krate, edition, false, None); let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate); let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" }; diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.rs b/tests/rustdoc-ui/doctest/wrong-ast-2024.rs new file mode 100644 index 00000000000..b0a700477b2 --- /dev/null +++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.rs @@ -0,0 +1,20 @@ +//@ compile-flags:--test --test-args=--test-threads=1 -Zunstable-options --edition 2024 +//@ normalize-stdout-test: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ normalize-stdout-test "wrong-ast.rs:\d+:\d+" -> "wrong-ast.rs:$$LINE:$$COL" +//@ failure-status: 101 + +/// ``` +/// /* plop +/// ``` +pub fn one() {} + +/// ``` +/// } mod __doctest_1 { fn main() { +/// ``` +pub fn two() {} + +/// ```should_panic +/// panic!() +/// ``` +pub fn three() {} diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout new file mode 100644 index 00000000000..1dea2719399 --- /dev/null +++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout @@ -0,0 +1,35 @@ + +running 2 tests +test $DIR/wrong-ast-2024.rs - one (line 7) ... FAILED +test $DIR/wrong-ast-2024.rs - two (line 12) ... FAILED + +failures: + +---- $DIR/wrong-ast-2024.rs - one (line 7) stdout ---- +error[E0758]: unterminated block comment + --> $DIR/wrong-ast-2024.rs:8:1 + | +LL | /* plop + | ^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0758`. +Couldn't compile the test. +---- $DIR/wrong-ast-2024.rs - two (line 12) stdout ---- +error: unexpected closing delimiter: `}` + --> $DIR/wrong-ast-2024.rs:13:1 + | +LL | } mod __doctest_1 { fn main() { + | ^ unexpected closing delimiter + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/wrong-ast-2024.rs - one (line 7) + $DIR/wrong-ast-2024.rs - two (line 12) + +test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/wrong-ast.rs b/tests/rustdoc-ui/doctest/wrong-ast.rs new file mode 100644 index 00000000000..b3fbf630c32 --- /dev/null +++ b/tests/rustdoc-ui/doctest/wrong-ast.rs @@ -0,0 +1,20 @@ +//@ compile-flags:--test --test-args=--test-threads=1 +//@ normalize-stdout-test: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ normalize-stdout-test "wrong-ast.rs:\d+:\d+" -> "wrong-ast.rs:$$LINE:$$COL" +//@ failure-status: 101 + +/// ``` +/// /* plop +/// ``` +pub fn one() {} + +/// ``` +/// } mod __doctest_1 { fn main() { +/// ``` +pub fn two() {} + +/// ```should_panic +/// panic!() +/// ``` +pub fn three() {} diff --git a/tests/rustdoc-ui/doctest/wrong-ast.stdout b/tests/rustdoc-ui/doctest/wrong-ast.stdout new file mode 100644 index 00000000000..b50999d17d7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/wrong-ast.stdout @@ -0,0 +1,36 @@ + +running 3 tests +test $DIR/wrong-ast.rs - one (line 7) ... FAILED +test $DIR/wrong-ast.rs - three (line 17) ... ok +test $DIR/wrong-ast.rs - two (line 12) ... FAILED + +failures: + +---- $DIR/wrong-ast.rs - one (line 7) stdout ---- +error[E0758]: unterminated block comment + --> $DIR/wrong-ast.rs:$LINE:$COL + | +LL | /* plop + | ^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0758`. +Couldn't compile the test. +---- $DIR/wrong-ast.rs - two (line 12) stdout ---- +error: unexpected closing delimiter: `}` + --> $DIR/wrong-ast.rs:$LINE:$COL + | +LL | } mod __doctest_1 { fn main() { + | ^ unexpected closing delimiter + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/wrong-ast.rs - one (line 7) + $DIR/wrong-ast.rs - two (line 12) + +test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +