Correctly handle doctests with invalid AST

This commit is contained in:
Guillaume Gomez 2024-06-10 18:29:33 +02:00
parent 59a9e0986d
commit 7ec3cabe17
8 changed files with 138 additions and 9 deletions

View File

@ -635,7 +635,7 @@ fn drop(&mut self) {
cmd.current_dir(run_directory); 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 { cmd.status().map(|status| process::Output {
status, status,
stdout: Vec::new(), stdout: Vec::new(),
@ -801,10 +801,15 @@ fn add_test(&mut self, scraped_test: ScrapedDoctest) {
); );
let edition = scraped_test.edition(&self.rustdoc_options); let edition = scraped_test.edition(&self.rustdoc_options);
let doctest = let doctest = DocTest::new(
DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition, Some(test_id)); &scraped_test.text,
Some(&self.opts.crate_name),
edition,
self.can_merge_doctests,
Some(test_id),
);
let is_standalone = !self.can_merge_doctests let is_standalone = !self.can_merge_doctests
|| doctest.failed_ast || !doctest.can_be_merged
|| scraped_test.langstr.compile_fail || scraped_test.langstr.compile_fail
|| scraped_test.langstr.test_harness || scraped_test.langstr.test_harness
|| scraped_test.langstr.standalone || scraped_test.langstr.standalone

View File

@ -26,6 +26,7 @@ pub(crate) struct DocTest {
pub(crate) everything_else: String, pub(crate) everything_else: String,
pub(crate) test_id: Option<String>, pub(crate) test_id: Option<String>,
pub(crate) failed_ast: bool, pub(crate) failed_ast: bool,
pub(crate) can_be_merged: bool,
} }
impl DocTest { impl DocTest {
@ -33,6 +34,7 @@ pub(crate) fn new(
source: &str, source: &str,
crate_name: Option<&str>, crate_name: Option<&str>,
edition: Edition, edition: Edition,
can_merge_doctests: bool,
// If `test_id` is `None`, it means we're generating code for a code example "run" link. // If `test_id` is `None`, it means we're generating code for a code example "run" link.
test_id: Option<String>, test_id: Option<String>,
) -> Self { ) -> Self {
@ -49,6 +51,7 @@ pub(crate) fn new(
&crates, &crates,
edition, edition,
&mut supports_color, &mut supports_color,
can_merge_doctests,
) )
else { else {
// If the parser panicked due to a fatal error, pass the test code through unchanged. // If the parser panicked due to a fatal error, pass the test code through unchanged.
@ -62,6 +65,7 @@ pub(crate) fn new(
already_has_extern_crate: false, already_has_extern_crate: false,
test_id, test_id,
failed_ast: true, failed_ast: true,
can_be_merged: false,
}; };
}; };
Self { Self {
@ -72,7 +76,10 @@ pub(crate) fn new(
everything_else, everything_else,
already_has_extern_crate, already_has_extern_crate,
test_id, 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 @@ pub(crate) fn generate_unique_doctest(
opts: &GlobalTestOptions, opts: &GlobalTestOptions,
crate_name: Option<&str>, crate_name: Option<&str>,
) -> (String, usize) { ) -> (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 line_offset = 0;
let mut prog = String::new(); let mut prog = String::new();
let everything_else = self.everything_else.trim(); let everything_else = self.everything_else.trim();
@ -323,6 +335,7 @@ fn check_for_main_and_extern_crate(
crates: &str, crates: &str,
edition: Edition, edition: Edition,
supports_color: &mut bool, supports_color: &mut bool,
can_merge_doctests: bool,
) -> Result<(Option<Span>, bool, bool), FatalError> { ) -> Result<(Option<Span>, bool, bool), FatalError> {
let result = rustc_driver::catch_fatal_errors(|| { let result = rustc_driver::catch_fatal_errors(|| {
rustc_span::create_session_if_not_set_then(edition, |_| { 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 // No need to double-check this if the "merged doctests" feature isn't enabled (so
// before the 2024 edition). // 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 // If we found an AST error, we want to ensure it's because of an expression being
// used outside of a function. // used outside of a function.
// //
@ -525,5 +538,5 @@ enum PartitionState {
debug!("crates:\n{crates}"); debug!("crates:\n{crates}");
debug!("after:\n{after}"); debug!("after:\n{after}");
(before, after, crates) (before, after.trim().to_owned(), crates)
} }

View File

@ -11,7 +11,7 @@ fn make_test(
test_id: Option<&str>, test_id: Option<&str>,
) -> (String, usize) { ) -> (String, usize) {
let doctest = 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) = let (code, line_offset) =
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name); doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
(code, line_offset) (code, line_offset)

View File

@ -297,7 +297,7 @@ fn next(&mut self) -> Option<Self::Item> {
attrs: vec![], attrs: vec![],
args_file: PathBuf::new(), 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 (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" }; let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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