Skip to content

Commit 1514cdd

Browse files
Auto merge of #143900 - GuillaumeGomez:fix-no-run, r=<try>
[rustdoc] Correctly handle `should_panic` doctest attribute and fix `--no-run` test flag on the 2024 edition try-job: x86_64-gnu-aux try-job: test-various
2 parents 4a54b26 + 4e2c43c commit 1514cdd

File tree

11 files changed

+239
-21
lines changed

11 files changed

+239
-21
lines changed

library/std/src/error.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ use crate::fmt::{self, Write};
123123
/// the `Debug` output means `Report` is an ideal starting place for formatting errors returned
124124
/// from `main`.
125125
///
126-
/// ```should_panic
126+
/// ```
127127
/// #![feature(error_reporter)]
128128
/// use std::error::Report;
129129
/// # use std::error::Error;
@@ -154,10 +154,14 @@ use crate::fmt::{self, Write};
154154
/// # Err(SuperError { source: SuperErrorSideKick })
155155
/// # }
156156
///
157-
/// fn main() -> Result<(), Report<SuperError>> {
157+
/// fn run() -> Result<(), Report<SuperError>> {
158158
/// get_super_error()?;
159159
/// Ok(())
160160
/// }
161+
///
162+
/// fn main() {
163+
/// assert!(run().is_err());
164+
/// }
161165
/// ```
162166
///
163167
/// This example produces the following output:
@@ -170,7 +174,7 @@ use crate::fmt::{self, Write};
170174
/// output format. If you want to make sure your `Report`s are pretty printed and include backtrace
171175
/// you will need to manually convert and enable those flags.
172176
///
173-
/// ```should_panic
177+
/// ```
174178
/// #![feature(error_reporter)]
175179
/// use std::error::Report;
176180
/// # use std::error::Error;
@@ -201,12 +205,16 @@ use crate::fmt::{self, Write};
201205
/// # Err(SuperError { source: SuperErrorSideKick })
202206
/// # }
203207
///
204-
/// fn main() -> Result<(), Report<SuperError>> {
208+
/// fn run() -> Result<(), Report<SuperError>> {
205209
/// get_super_error()
206210
/// .map_err(Report::from)
207211
/// .map_err(|r| r.pretty(true).show_backtrace(true))?;
208212
/// Ok(())
209213
/// }
214+
///
215+
/// fn main() {
216+
/// assert!(run().is_err());
217+
/// }
210218
/// ```
211219
///
212220
/// This example produces the following output:

src/librustdoc/doctest.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ mod rust;
77
use std::fs::File;
88
use std::hash::{Hash, Hasher};
99
use std::io::{self, Write};
10+
#[cfg(unix)]
11+
use std::os::unix::process::ExitStatusExt;
1012
use std::path::{Path, PathBuf};
1113
use std::process::{self, Command, Stdio};
1214
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -351,7 +353,7 @@ pub(crate) fn run_tests(
351353
);
352354

353355
for (doctest, scraped_test) in &doctests {
354-
tests_runner.add_test(doctest, scraped_test, &target_str);
356+
tests_runner.add_test(doctest, scraped_test, &target_str, rustdoc_options);
355357
}
356358
let (duration, ret) = tests_runner.run_merged_tests(
357359
rustdoc_test_options,
@@ -801,6 +803,22 @@ fn run_test(
801803
let duration = instant.elapsed();
802804
if doctest.no_run {
803805
return (duration, Ok(()));
806+
} else if doctest.langstr.should_panic
807+
// Equivalent of:
808+
//
809+
// ```
810+
// (cfg!(target_family = "wasm") || cfg!(target_os = "zkvm"))
811+
// && !cfg!(target_os = "emscripten")
812+
// ```
813+
&& let TargetTuple::TargetTuple(ref s) = rustdoc_options.target
814+
&& let mut iter = s.split('-')
815+
&& let Some(arch) = iter.next()
816+
&& iter.next().is_some()
817+
&& let os = iter.next()
818+
&& (arch.starts_with("wasm") || os == Some("zkvm")) && os != Some("emscripten")
819+
{
820+
// We cannot correctly handle `should_panic` in some wasm targets so we exit early.
821+
return (duration, Ok(()));
804822
}
805823

806824
// Run the code!
@@ -831,13 +849,40 @@ fn run_test(
831849
} else {
832850
cmd.output()
833851
};
852+
853+
// FIXME: Make `test::get_result_from_exit_code` public and use this code instead of this.
854+
//
855+
// On Zircon (the Fuchsia kernel), an abort from userspace calls the
856+
// LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which
857+
// raises a kernel exception. If a userspace process does not
858+
// otherwise arrange exception handling, the kernel kills the process
859+
// with this return code.
860+
#[cfg(target_os = "fuchsia")]
861+
const ZX_TASK_RETCODE_EXCEPTION_KILL: i32 = -1028;
862+
// On Windows we use __fastfail to abort, which is documented to use this
863+
// exception code.
864+
#[cfg(windows)]
865+
const STATUS_FAIL_FAST_EXCEPTION: i32 = 0xC0000409u32 as i32;
866+
#[cfg(unix)]
867+
const SIGABRT: std::ffi::c_int = 6;
834868
match result {
835869
Err(e) => return (duration, Err(TestFailure::ExecutionError(e))),
836870
Ok(out) => {
837-
if langstr.should_panic && out.status.success() {
838-
return (duration, Err(TestFailure::UnexpectedRunPass));
839-
} else if !langstr.should_panic && !out.status.success() {
871+
if !langstr.should_panic && !out.status.success() {
840872
return (duration, Err(TestFailure::ExecutionFailure(out)));
873+
} else if langstr.should_panic {
874+
match out.status.code() {
875+
Some(test::ERROR_EXIT_CODE) => {}
876+
#[cfg(windows)]
877+
Some(STATUS_FAIL_FAST_EXCEPTION) => {}
878+
#[cfg(unix)]
879+
None if out.status.signal() == Some(SIGABRT) => {}
880+
// Upon an abort, Fuchsia returns the status code
881+
// `ZX_TASK_RETCODE_EXCEPTION_KILL`.
882+
#[cfg(target_os = "fuchsia")]
883+
Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => {}
884+
_ => return (duration, Err(TestFailure::UnexpectedRunPass)),
885+
}
841886
}
842887
}
843888
}
@@ -1144,7 +1189,7 @@ fn doctest_run_fn(
11441189
eprint!("Test compiled successfully, but it's marked `compile_fail`.");
11451190
}
11461191
TestFailure::UnexpectedRunPass => {
1147-
eprint!("Test executable succeeded, but it's marked `should_panic`.");
1192+
eprint!("Test didn't panic, but it's marked `should_panic`.");
11481193
}
11491194
TestFailure::MissingErrorCodes(codes) => {
11501195
eprint!("Some expected error codes were not found: {codes:?}");

src/librustdoc/doctest/runner.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ impl DocTestRunner {
3939
doctest: &DocTestBuilder,
4040
scraped_test: &ScrapedDocTest,
4141
target_str: &str,
42+
opts: &RustdocOptions,
4243
) {
4344
let ignore = match scraped_test.langstr.ignore {
4445
Ignore::All => true,
@@ -62,6 +63,7 @@ impl DocTestRunner {
6263
self.nb_tests,
6364
&mut self.output,
6465
&mut self.output_merged_tests,
66+
opts,
6567
),
6668
));
6769
self.supports_color &= doctest.supports_color;
@@ -134,13 +136,41 @@ mod __doctest_mod {{
134136
}}
135137
136138
#[allow(unused)]
137-
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> ExitCode {{
139+
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize, should_panic: bool) -> ExitCode {{
138140
let out = std::process::Command::new(bin)
139141
.env(self::RUN_OPTION, test_nb.to_string())
140142
.args(std::env::args().skip(1).collect::<Vec<_>>())
141143
.output()
142144
.expect(\"failed to run command\");
143-
if !out.status.success() {{
145+
if should_panic {{
146+
// FIXME: Make `test::get_result_from_exit_code` public and use this code instead of this.
147+
//
148+
// On Zircon (the Fuchsia kernel), an abort from userspace calls the
149+
// LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which
150+
// raises a kernel exception. If a userspace process does not
151+
// otherwise arrange exception handling, the kernel kills the process
152+
// with this return code.
153+
#[cfg(target_os = \"fuchsia\")]
154+
const ZX_TASK_RETCODE_EXCEPTION_KILL: i32 = -1028;
155+
// On Windows we use __fastfail to abort, which is documented to use this
156+
// exception code.
157+
#[cfg(windows)]
158+
const STATUS_FAIL_FAST_EXCEPTION: i32 = 0xC0000409u32 as i32;
159+
#[cfg(unix)]
160+
const SIGABRT: std::ffi::c_int = 6;
161+
162+
match out.status.code() {{
163+
Some(test::ERROR_EXIT_CODE) => ExitCode::SUCCESS,
164+
#[cfg(windows)]
165+
Some(STATUS_FAIL_FAST_EXCEPTION) => ExitCode::SUCCESS,
166+
#[cfg(unix)]
167+
None if out.status.signal() == Some(libc::SIGABRT) => ExitCode::SUCCESS,
168+
// Upon an abort, Fuchsia returns the status code ZX_TASK_RETCODE_EXCEPTION_KILL.
169+
#[cfg(target_os = \"fuchsia\")]
170+
Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => ExitCode::SUCCESS,
171+
_ => ExitCode::FAILURE,
172+
}}
173+
}} else if !out.status.success() {{
144174
if let Some(code) = out.status.code() {{
145175
eprintln!(\"Test executable failed (exit status: {{code}}).\");
146176
}} else {{
@@ -223,6 +253,7 @@ fn generate_mergeable_doctest(
223253
id: usize,
224254
output: &mut String,
225255
output_merged_tests: &mut String,
256+
opts: &RustdocOptions,
226257
) -> String {
227258
let test_id = format!("__doctest_{id}");
228259

@@ -256,31 +287,33 @@ fn main() {returns_result} {{
256287
)
257288
.unwrap();
258289
}
259-
let not_running = ignore || scraped_test.langstr.no_run;
290+
let should_panic = scraped_test.langstr.should_panic;
291+
let not_running = ignore || scraped_test.no_run(opts);
260292
writeln!(
261293
output_merged_tests,
262294
"
263295
mod {test_id} {{
264296
pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest(
265-
{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic},
297+
{test_name:?}, {ignore} || ({should_panic} && crate::__doctest_mod::SHOULD_PANIC_DISABLED), {file:?}, {line}, {no_run}, false,
266298
test::StaticTestFn(
267299
|| {{{runner}}},
268300
));
269301
}}",
270302
test_name = scraped_test.name,
271303
file = scraped_test.path(),
272304
line = scraped_test.line,
273-
no_run = scraped_test.langstr.no_run,
274-
should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic,
305+
no_run = scraped_test.no_run(opts),
275306
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
276307
// don't give it the function to run.
277308
runner = if not_running {
278309
"test::assert_test_result(Ok::<(), String>(()))".to_string()
279310
} else {
280311
format!(
281312
"
282-
if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{
283-
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}))
313+
if {should_panic} && crate::__doctest_mod::SHOULD_PANIC_DISABLED {{
314+
test::assert_test_result(Ok::<(), String>(()))
315+
}} else if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{
316+
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}, {should_panic}))
284317
}} else {{
285318
test::assert_test_result(doctest_bundle::{test_id}::__main_fn())
286319
}}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Ensure that `should_panic` doctests only succeed if the test actually panicked.
2+
// Regression test for <https://github.com/rust-lang/rust/issues/143009>.
3+
4+
//@ ignore-cross-compile
5+
6+
use run_make_support::rustdoc;
7+
8+
fn check_output(edition: &str, panic_abort: bool) {
9+
let mut rustdoc_cmd = rustdoc();
10+
rustdoc_cmd.input("test.rs").arg("--test").edition(edition);
11+
if panic_abort {
12+
rustdoc_cmd.args(["-C", "panic=abort"]);
13+
}
14+
let output = rustdoc_cmd.run_fail().stdout_utf8();
15+
let should_contain = &[
16+
"test test.rs - bad_exit_code (line 1) ... FAILED",
17+
"test test.rs - did_not_panic (line 6) ... FAILED",
18+
"test test.rs - did_panic (line 11) ... ok",
19+
"---- test.rs - bad_exit_code (line 1) stdout ----
20+
Test executable failed (exit status: 1).",
21+
"---- test.rs - did_not_panic (line 6) stdout ----
22+
Test didn't panic, but it's marked `should_panic`.",
23+
"test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out;",
24+
];
25+
for text in should_contain {
26+
assert!(
27+
output.contains(text),
28+
"output (edition: {edition}) doesn't contain {:?}\nfull output: {output}",
29+
text
30+
);
31+
}
32+
}
33+
34+
fn main() {
35+
check_output("2015", false);
36+
37+
// Same check with the merged doctest feature (enabled with the 2024 edition).
38+
check_output("2024", false);
39+
40+
// Checking that `-C panic=abort` is working too.
41+
check_output("2015", true);
42+
check_output("2024", true);
43+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// ```
2+
/// std::process::exit(1);
3+
/// ```
4+
fn bad_exit_code() {}
5+
6+
/// ```should_panic
7+
/// std::process::exit(1);
8+
/// ```
9+
fn did_not_panic() {}
10+
11+
/// ```should_panic
12+
/// panic!("yeay");
13+
/// ```
14+
fn did_panic() {}

tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ test $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) ... FAILED
55
failures:
66

77
---- $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) stdout ----
8-
Test executable succeeded, but it's marked `should_panic`.
8+
Test didn't panic, but it's marked `should_panic`.
99

1010
failures:
1111
$DIR/failed-doctest-should-panic-2021.rs - Foo (line 10)

tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11

22
running 1 test
3-
test $DIR/failed-doctest-should-panic.rs - Foo (line 12) - should panic ... FAILED
3+
test $DIR/failed-doctest-should-panic.rs - Foo (line 12) ... FAILED
44

55
failures:
66

77
---- $DIR/failed-doctest-should-panic.rs - Foo (line 12) stdout ----
8-
note: test did not panic as expected at $DIR/failed-doctest-should-panic.rs:12:0
8+
Test didn't panic, but it's marked `should_panic`.
9+
910

1011
failures:
1112
$DIR/failed-doctest-should-panic.rs - Foo (line 12)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
running 7 tests
3+
test $DIR/no-run.rs - f (line 14) - compile ... ok
4+
test $DIR/no-run.rs - f (line 17) - compile ... ok
5+
test $DIR/no-run.rs - f (line 20) ... ignored
6+
test $DIR/no-run.rs - f (line 23) - compile ... ok
7+
test $DIR/no-run.rs - f (line 29) - compile fail ... ok
8+
test $DIR/no-run.rs - f (line 34) - compile ... ok
9+
test $DIR/no-run.rs - f (line 38) - compile ... ok
10+
11+
test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME
12+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
running 5 tests
3+
test $DIR/no-run.rs - f (line 14) - compile ... ok
4+
test $DIR/no-run.rs - f (line 17) - compile ... ok
5+
test $DIR/no-run.rs - f (line 23) - compile ... ok
6+
test $DIR/no-run.rs - f (line 34) - compile ... ok
7+
test $DIR/no-run.rs - f (line 38) - compile ... ok
8+
9+
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
10+
11+
12+
running 2 tests
13+
test $DIR/no-run.rs - f (line 20) ... ignored
14+
test $DIR/no-run.rs - f (line 29) - compile fail ... ok
15+
16+
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME
17+
18+
all doctests ran in $TIME; merged doctests compilation took $TIME

0 commit comments

Comments
 (0)