Skip to content

Commit a8a788d

Browse files
Correctly handle should_panic doctest attribute
1 parent a413f77 commit a8a788d

File tree

6 files changed

+61
-18
lines changed

6 files changed

+61
-18
lines changed

src/librustdoc/doctest.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ pub(crate) fn run_tests(
336336
scraped_test.langstr.test_harness,
337337
&opts,
338338
Some(&opts.crate_name),
339+
scraped_test.langstr.should_panic,
339340
);
340341
standalone_tests.push(generate_test_desc_and_fn(
341342
doctest,
@@ -773,9 +774,11 @@ fn run_test(
773774
match result {
774775
Err(e) => return Err(TestFailure::ExecutionError(e)),
775776
Ok(out) => {
776-
if langstr.should_panic && out.status.success() {
777+
// If the test panicked, then `should_panic` will return 0 as exit code as it is the
778+
// the expected result.
779+
if langstr.should_panic && !out.status.success() {
777780
return Err(TestFailure::UnexpectedRunPass);
778-
} else if !langstr.should_panic && !out.status.success() {
781+
} else if !out.status.success() {
779782
return Err(TestFailure::ExecutionFailure(out));
780783
}
781784
}
@@ -1059,6 +1062,7 @@ fn doctest_run_fn(
10591062
scraped_test.langstr.test_harness,
10601063
&global_opts,
10611064
Some(&global_opts.crate_name),
1065+
scraped_test.langstr.should_panic,
10621066
);
10631067
let runnable_test = RunnableDocTest {
10641068
full_test_code: wrapped.to_string(),
@@ -1083,7 +1087,7 @@ fn doctest_run_fn(
10831087
eprint!("Test compiled successfully, but it's marked `compile_fail`.");
10841088
}
10851089
TestFailure::UnexpectedRunPass => {
1086-
eprint!("Test executable succeeded, but it's marked `should_panic`.");
1090+
eprint!("Test didn't panic, but it's marked `should_panic`.");
10871091
}
10881092
TestFailure::MissingErrorCodes(codes) => {
10891093
eprint!("Some expected error codes were not found: {codes:?}");

src/librustdoc/doctest/extracted.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl ExtractedDocTests {
6060
langstr.test_harness,
6161
opts,
6262
Some(&opts.crate_name),
63+
langstr.should_panic,
6364
);
6465
self.doctests.push(ExtractedDocTest {
6566
file: filename.prefer_remapped_unconditionally().to_string(),

src/librustdoc/doctest/make.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,29 @@ impl std::string::ToString for DocTestWrapResult {
270270
}
271271
}
272272

273+
/// `catch_unwind` wrapper to ensure that a `should_panic` doctest actually panicked.
274+
///
275+
/// Returns `(wrapper_pre, wrapper_post)`.
276+
pub(crate) fn should_panic_wrapper(
277+
should_panic: bool,
278+
returns_result: bool,
279+
) -> (&'static str, String) {
280+
if !should_panic {
281+
return ("", String::new());
282+
}
283+
(
284+
"match std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {\n",
285+
format!(
286+
"
287+
}})) {{
288+
Ok(_) => std::process::exit(1),
289+
{}
290+
}}\n",
291+
if returns_result { "Err(err) => err," } else { "Err(_) => {}" },
292+
),
293+
)
294+
}
295+
273296
impl DocTestBuilder {
274297
fn invalid(
275298
global_crate_attrs: Vec<String>,
@@ -302,6 +325,7 @@ impl DocTestBuilder {
302325
dont_insert_main: bool,
303326
opts: &GlobalTestOptions,
304327
crate_name: Option<&str>,
328+
should_panic: bool,
305329
) -> (DocTestWrapResult, usize) {
306330
if self.invalid_ast {
307331
// If the AST failed to compile, no need to go generate a complete doctest, the error
@@ -384,20 +408,24 @@ impl DocTestBuilder {
384408
"_inner".into()
385409
};
386410
let inner_attr = if self.test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
411+
let (should_panic_pre, should_panic_post) =
412+
should_panic_wrapper(should_panic, returns_result);
387413
let (main_pre, main_post) = if returns_result {
388414
(
389415
format!(
390-
"fn main() {{ {inner_attr}fn {inner_fn_name}() -> core::result::Result<(), impl core::fmt::Debug> {{\n",
416+
"\
417+
fn main() {{ {inner_attr}fn {inner_fn_name}() -> core::result::Result<(), impl core::fmt::Debug> {{
418+
{should_panic_pre}",
391419
),
392-
format!("\n}} {inner_fn_name}().unwrap() }}"),
420+
format!("{should_panic_post}\n}} {inner_fn_name}().unwrap() }}"),
393421
)
394422
} else if self.test_id.is_some() {
395423
(
396-
format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
397-
format!("\n}} {inner_fn_name}() }}"),
424+
format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n{should_panic_pre}"),
425+
format!("{should_panic_post}\n}} {inner_fn_name}() }}"),
398426
)
399427
} else {
400-
("fn main() {\n".into(), "\n}".into())
428+
(format!("fn main() {{\n{should_panic_pre}"), format!("{should_panic_post}\n}}"))
401429
};
402430
// Note on newlines: We insert a line/newline *before*, and *after*
403431
// the doctest and adjust the `line_offset` accordingly.

src/librustdoc/doctest/runner.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,19 @@ mod __doctest_mod {{
129129
}}
130130
131131
#[allow(unused)]
132-
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> ExitCode {{
132+
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize, should_panic: bool) -> ExitCode {{
133133
let out = std::process::Command::new(bin)
134134
.env(self::RUN_OPTION, test_nb.to_string())
135135
.args(std::env::args().skip(1).collect::<Vec<_>>())
136136
.output()
137137
.expect(\"failed to run command\");
138138
if !out.status.success() {{
139139
if let Some(code) = out.status.code() {{
140-
eprintln!(\"Test executable failed (exit status: {{code}}).\");
140+
if should_panic {{
141+
eprintln!(\"Test didn't panic, but it's marked `should_panic`.\");
142+
}} else {{
143+
eprintln!(\"Test executable failed (exit status: {{code}}).\");
144+
}}
141145
}} else {{
142146
eprintln!(\"Test executable failed (terminated by signal).\");
143147
}}
@@ -220,34 +224,40 @@ fn generate_mergeable_doctest(
220224
output_merged_tests: &mut String,
221225
) -> String {
222226
let test_id = format!("__doctest_{id}");
227+
let should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic;
223228

224229
if ignore {
225230
// We generate nothing else.
226231
writeln!(output, "pub mod {test_id} {{}}\n").unwrap();
227232
} else {
228233
writeln!(output, "pub mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs)
229234
.unwrap();
235+
let mut returns_result = false;
230236
if doctest.has_main_fn {
231237
output.push_str(&doctest.everything_else);
232238
} else {
233-
let returns_result = if doctest.everything_else.trim_end().ends_with("(())") {
239+
let return_value = if doctest.everything_else.trim_end().ends_with("(())") {
240+
returns_result = true;
234241
"-> Result<(), impl core::fmt::Debug>"
235242
} else {
236243
""
237244
};
238245
write!(
239246
output,
240247
"\
241-
fn main() {returns_result} {{
248+
fn main() {return_value} {{
242249
{}
243250
}}",
244251
doctest.everything_else
245252
)
246253
.unwrap();
247254
}
255+
let (should_panic_pre, should_panic_post) =
256+
super::make::should_panic_wrapper(should_panic, returns_result);
248257
writeln!(
249258
output,
250-
"\npub fn __main_fn() -> impl std::process::Termination {{ main() }} \n}}\n"
259+
"\npub fn __main_fn() -> impl std::process::Termination {{{should_panic_pre}main()\
260+
{should_panic_post}}} \n}}\n"
251261
)
252262
.unwrap();
253263
}
@@ -257,7 +267,7 @@ fn main() {returns_result} {{
257267
"
258268
mod {test_id} {{
259269
pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest(
260-
{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic},
270+
{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, false,
261271
test::StaticTestFn(
262272
|| {{{runner}}},
263273
));
@@ -266,7 +276,6 @@ test::StaticTestFn(
266276
file = scraped_test.path(),
267277
line = scraped_test.line,
268278
no_run = scraped_test.langstr.no_run,
269-
should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic,
270279
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
271280
// don't give it the function to run.
272281
runner = if not_running {
@@ -275,7 +284,7 @@ test::StaticTestFn(
275284
format!(
276285
"
277286
if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{
278-
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}))
287+
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}, {should_panic}))
279288
}} else {{
280289
test::assert_test_result(doctest_bundle::{test_id}::__main_fn())
281290
}}

src/librustdoc/doctest/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn make_test(
2525
}
2626
let doctest = builder.build(None);
2727
let (wrapped, line_offset) =
28-
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
28+
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name, false);
2929
(wrapped.to_string(), line_offset)
3030
}
3131

src/librustdoc/html/markdown.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
308308
builder = builder.crate_name(krate);
309309
}
310310
let doctest = builder.build(None);
311-
let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
311+
let (wrapped, _) =
312+
doctest.generate_unique_doctest(&test, false, &opts, krate, should_panic);
312313
let test = wrapped.to_string();
313314
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
314315

0 commit comments

Comments
 (0)