Skip to content

Commit 0f51bb2

Browse files
Split should_panic doctests like merged doctests so we can use libtest API directly
1 parent fc90106 commit 0f51bb2

File tree

8 files changed

+242
-168
lines changed

8 files changed

+242
-168
lines changed

library/test/src/test_result.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ pub(crate) fn get_result_from_exit_code(
158158
result
159159
}
160160

161+
#[derive(Debug)]
161162
pub enum RustdocResult {
162163
/// The test failed to compile.
163164
CompileError,

src/librustdoc/doctest.rs

Lines changed: 121 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ pub(crate) fn run_tests(
406406
// We failed to compile all compatible tests as one so we push them into the
407407
// `standalone_tests` doctests.
408408
debug!("Failed to compile compatible doctests for edition {} all at once", edition);
409-
for (pos, (doctest, scraped_test)) in doctests.into_iter().enumerate() {
409+
for (doctest, scraped_test) in doctests {
410410
doctest.generate_unique_doctest(
411411
&scraped_test.text,
412412
scraped_test.langstr.test_harness,
@@ -419,7 +419,6 @@ pub(crate) fn run_tests(
419419
opts.clone(),
420420
Arc::clone(rustdoc_options),
421421
unused_extern_reports.clone(),
422-
pos,
423422
));
424423
}
425424
}
@@ -549,44 +548,33 @@ pub(crate) struct RunnableDocTest {
549548
}
550549

551550
impl RunnableDocTest {
552-
fn path_for_merged_doctest_bundle(&self, id: Option<usize>) -> PathBuf {
553-
let name = if let Some(id) = id {
554-
format!("doctest_bundle_id_{id}.rs")
555-
} else {
556-
format!("doctest_bundle_{}.rs", self.edition)
557-
};
558-
self.test_opts.outdir.path().join(name)
551+
fn path_for_merged_doctest_bundle(&self) -> PathBuf {
552+
self.test_opts.outdir.path().join(format!("doctest_bundle_{}.rs", self.edition))
559553
}
560-
fn path_for_merged_doctest_runner(&self, id: Option<usize>) -> PathBuf {
561-
let name = if let Some(id) = id {
562-
format!("doctest_runner_id_{id}.rs")
563-
} else {
564-
format!("doctest_runner_{}.rs", self.edition)
565-
};
566-
self.test_opts.outdir.path().join(name)
554+
fn path_for_merged_doctest_runner(&self) -> PathBuf {
555+
self.test_opts.outdir.path().join(format!("doctest_runner_{}.rs", self.edition))
567556
}
568557
fn is_multiple_tests(&self) -> bool {
569558
self.merged_test_code.is_some()
570559
}
571560
}
572561

573562
fn compile_merged_doctest_and_caller_binary(
574-
mut child: process::Child,
563+
child: process::Child,
575564
doctest: &RunnableDocTest,
576565
rustdoc_options: &RustdocOptions,
577566
rustc_binary: &Path,
578567
output_file: &Path,
579568
compiler_args: Vec<String>,
580569
test_code: &str,
581570
instant: Instant,
582-
id: Option<usize>,
571+
is_compile_fail: bool,
583572
) -> Result<process::Output, (Duration, Result<(), RustdocResult>)> {
584573
// compile-fail tests never get merged, so this should always pass
585-
let status = child.wait().expect("Failed to wait");
586-
587-
// the actual test runner is a separate component, built with nightly-only features;
588-
// build it now
589-
let runner_input_file = doctest.path_for_merged_doctest_runner(id);
574+
let output = child.wait_with_output().expect("Failed to wait");
575+
if is_compile_fail && !output.status.success() {
576+
return Ok(output);
577+
}
590578

591579
let mut runner_compiler =
592580
wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary);
@@ -595,13 +583,10 @@ fn compile_merged_doctest_and_caller_binary(
595583
runner_compiler.env("RUSTC_BOOTSTRAP", "1");
596584
runner_compiler.args(compiler_args);
597585
runner_compiler.args(["--crate-type=bin", "-o"]).arg(output_file);
598-
let mut extern_path = if let Some(id) = id {
599-
std::ffi::OsString::from(format!("--extern=doctest_bundle_id_{id}="))
586+
let base_name = if is_compile_fail {
587+
format!("rust_out")
600588
} else {
601-
std::ffi::OsString::from(format!(
602-
"--extern=doctest_bundle_{edition}=",
603-
edition = doctest.edition
604-
))
589+
format!("doctest_bundle_{edition}", edition = doctest.edition)
605590
};
606591

607592
// Deduplicate passed -L directory paths, since usually all dependencies will be in the
@@ -621,36 +606,58 @@ fn compile_merged_doctest_and_caller_binary(
621606
}
622607
}
623608
}
624-
let filename = if let Some(id) = id {
625-
format!("libdoctest_bundle_id_{id}.rlib")
626-
} else {
627-
format!("libdoctest_bundle_{edition}.rlib", edition = doctest.edition)
628-
};
629-
let output_bundle_file = doctest.test_opts.outdir.path().join(filename);
609+
let output_bundle_file = doctest.test_opts.outdir.path().join(format!("lib{base_name}.rlib"));
610+
let mut extern_path = std::ffi::OsString::from(format!("--extern={base_name}="));
630611
extern_path.push(&output_bundle_file);
631-
runner_compiler.arg(extern_path);
632-
runner_compiler.arg(&runner_input_file);
633-
if std::fs::write(&runner_input_file, test_code).is_err() {
634-
// If we cannot write this file for any reason, we leave. All combined tests will be
635-
// tested as standalone tests.
636-
return Err((instant.elapsed(), Err(RustdocResult::CompileError)));
637-
}
638-
if !rustdoc_options.no_capture {
639-
// If `no_capture` is disabled, then we don't display rustc's output when compiling
640-
// the merged doctests.
641-
runner_compiler.stderr(Stdio::null());
612+
runner_compiler.arg(&extern_path);
613+
614+
if is_compile_fail {
615+
add_rustdoc_env_vars(&mut runner_compiler, doctest);
616+
runner_compiler.stderr(Stdio::piped());
617+
runner_compiler.stdin(Stdio::piped());
618+
runner_compiler.arg("-");
619+
} else {
620+
// The actual test runner is a separate component, built with nightly-only features;
621+
// build it now
622+
let runner_input_file = doctest.path_for_merged_doctest_runner();
623+
runner_compiler.arg(&runner_input_file);
624+
if std::fs::write(&runner_input_file, test_code).is_err() {
625+
// If we cannot write this file for any reason, we leave. All combined tests will be
626+
// tested as standalone tests.
627+
return Err((instant.elapsed(), Err(RustdocResult::CompileError)));
628+
}
629+
if !rustdoc_options.no_capture {
630+
// If `no_capture` is disabled, then we don't display rustc's output when compiling
631+
// the merged doctests.
632+
runner_compiler.stderr(Stdio::null());
633+
runner_compiler.arg("--error-format=short");
634+
}
642635
}
643-
runner_compiler.arg("--error-format=short");
644636
debug!("compiler invocation for doctest runner: {runner_compiler:?}");
645637

646-
let status = if !status.success() {
647-
status
638+
let output = if !output.status.success() {
639+
output
648640
} else {
649641
let mut child_runner = runner_compiler.spawn().expect("Failed to spawn rustc process");
650-
child_runner.wait().expect("Failed to wait")
642+
if is_compile_fail {
643+
let stdin = child_runner.stdin.as_mut().expect("Failed to open stdin");
644+
stdin.write_all(test_code.as_bytes()).expect("could write out test sources");
645+
}
646+
child_runner.wait_with_output().expect("Failed to wait")
651647
};
648+
if is_compile_fail {
649+
Ok(output)
650+
} else {
651+
Ok(process::Output { status: output.status, stdout: Vec::new(), stderr: Vec::new() })
652+
}
653+
}
652654

653-
Ok(process::Output { status, stdout: Vec::new(), stderr: Vec::new() })
655+
fn add_rustdoc_env_vars(compiler: &mut Command, doctest: &RunnableDocTest) {
656+
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path);
657+
compiler.env(
658+
"UNSTABLE_RUSTDOC_TEST_LINE",
659+
format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize),
660+
);
654661
}
655662

656663
/// Execute a `RunnableDoctest`.
@@ -664,7 +671,6 @@ fn run_test(
664671
rustdoc_options: &RustdocOptions,
665672
supports_color: bool,
666673
report_unused_externs: impl Fn(UnusedExterns),
667-
doctest_id: usize,
668674
) -> (Duration, Result<(), RustdocResult>) {
669675
let langstr = &doctest.langstr;
670676
// Make sure we emit well-formed executable names for our target.
@@ -695,11 +701,6 @@ fn run_test(
695701
compiler_args.extend_from_slice(&["-Z".to_owned(), "unstable-options".to_owned()]);
696702
}
697703

698-
if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() {
699-
// FIXME: why does this code check if it *shouldn't* persist doctests
700-
// -- shouldn't it be the negation?
701-
compiler_args.push("--emit=metadata".to_owned());
702-
}
703704
compiler_args.extend_from_slice(&[
704705
"--target".to_owned(),
705706
match &rustdoc_options.target {
@@ -745,40 +746,47 @@ fn run_test(
745746

746747
compiler.args(&compiler_args);
747748

748-
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path);
749-
compiler.env(
750-
"UNSTABLE_RUSTDOC_TEST_LINE",
751-
format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize),
752-
);
749+
let is_should_panic = !langstr.compile_fail && langstr.should_panic;
753750
// If this is a merged doctest, we need to write it into a file instead of using stdin
754751
// because if the size of the merged doctests is too big, it'll simply break stdin.
755-
if doctest.is_multiple_tests() || (!langstr.compile_fail && langstr.should_panic) {
756-
// It makes the compilation failure much faster if it is for a combined doctest.
757-
compiler.arg("--error-format=short");
758-
let input_file = doctest.path_for_merged_doctest_bundle(
759-
if !langstr.compile_fail && langstr.should_panic { Some(doctest_id) } else { None },
760-
);
761-
if std::fs::write(&input_file, &doctest.full_test_code).is_err() {
762-
// If we cannot write this file for any reason, we leave. All combined tests will be
763-
// tested as standalone tests.
764-
return (Duration::default(), Err(RustdocResult::CompileError));
765-
}
766-
if !rustdoc_options.no_capture {
767-
// If `no_capture` is disabled, then we don't display rustc's output when compiling
768-
// the merged doctests.
769-
compiler.stderr(Stdio::null());
770-
}
752+
if doctest.is_multiple_tests() || is_should_panic {
771753
// bundled tests are an rlib, loaded by a separate runner executable
772-
compiler
773-
.arg("--crate-type=lib")
774-
.arg("--out-dir")
775-
.arg(doctest.test_opts.outdir.path())
776-
.arg(input_file);
754+
compiler.arg("--crate-type=lib").arg("--out-dir").arg(doctest.test_opts.outdir.path());
755+
756+
if !is_should_panic {
757+
compiler.arg("--error-format=short");
758+
if !rustdoc_options.no_capture {
759+
// If `no_capture` is disabled, then we don't display rustc's output when compiling
760+
// the merged doctests.
761+
compiler.stderr(Stdio::null());
762+
compiler.stdout(Stdio::null());
763+
}
764+
// It makes the compilation failure much faster if it is for a combined doctest.
765+
let input_file = doctest.path_for_merged_doctest_bundle();
766+
if std::fs::write(&input_file, &doctest.full_test_code).is_err() {
767+
// If we cannot write this file for any reason, we leave. All combined tests will be
768+
// tested as standalone tests.
769+
return (Duration::default(), Err(RustdocResult::CompileError));
770+
}
771+
compiler.arg(input_file);
772+
} else {
773+
compiler.stdin(Stdio::piped());
774+
compiler.stderr(Stdio::piped());
775+
add_rustdoc_env_vars(&mut compiler, &doctest);
776+
compiler.arg("-");
777+
}
777778
} else {
779+
add_rustdoc_env_vars(&mut compiler, &doctest);
778780
compiler.arg("--crate-type=bin").arg("-o").arg(&output_file);
779781
compiler.arg("-");
780782
compiler.stdin(Stdio::piped());
781783
compiler.stderr(Stdio::piped());
784+
785+
if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() {
786+
// FIXME: why does this code check if it *shouldn't* persist doctests
787+
// -- shouldn't it be the negation?
788+
compiler_args.push("--emit=metadata".to_owned());
789+
}
782790
}
783791

784792
debug!("compiler invocation for doctest: {compiler:?}");
@@ -794,45 +802,48 @@ fn run_test(
794802
compiler_args,
795803
merged_test_code,
796804
instant,
797-
None,
805+
false,
798806
) {
799807
Ok(out) => out,
800808
Err(err) => return err,
801809
}
802-
} else if !langstr.compile_fail && langstr.should_panic {
803-
match compile_merged_doctest_and_caller_binary(
804-
child,
805-
&doctest,
806-
rustdoc_options,
807-
rustc_binary,
808-
&output_file,
809-
compiler_args,
810-
&format!(
811-
"\
810+
} else {
811+
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
812+
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
813+
814+
if !langstr.compile_fail && langstr.should_panic {
815+
match compile_merged_doctest_and_caller_binary(
816+
child,
817+
&doctest,
818+
rustdoc_options,
819+
rustc_binary,
820+
&output_file,
821+
compiler_args,
822+
&format!(
823+
"\
812824
#![feature(test)]
813825
extern crate test;
814826
815-
use std::process::{{ExitCode, Termination}};
827+
use std::process::{{ExitCode, Termination, exit}};
816828
817829
fn main() -> ExitCode {{
818830
if test::cannot_handle_should_panic() {{
819-
ExitCode::SUCCESS
831+
exit(test::ERROR_EXIT_CODE);
820832
}} else {{
821-
extern crate doctest_bundle_id_{doctest_id} as doctest_bundle;
833+
extern crate rust_out as doctest_bundle;
822834
doctest_bundle::main().report()
823835
}}
824-
}}"
825-
),
826-
instant,
827-
Some(doctest_id),
828-
) {
829-
Ok(out) => out,
830-
Err(err) => return err,
836+
}}",
837+
),
838+
instant,
839+
true,
840+
) {
841+
Ok(out) => out,
842+
Err(err) => return err,
843+
}
844+
} else {
845+
child.wait_with_output().expect("Failed to read stdout")
831846
}
832-
} else {
833-
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
834-
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
835-
child.wait_with_output().expect("Failed to read stdout")
836847
};
837848

838849
struct Bomb<'a>(&'a str);
@@ -1138,7 +1149,6 @@ impl CreateRunnableDocTests {
11381149
self.opts.clone(),
11391150
Arc::clone(&self.rustdoc_options),
11401151
self.unused_extern_reports.clone(),
1141-
self.standalone_tests.len(),
11421152
)
11431153
}
11441154
}
@@ -1149,7 +1159,6 @@ fn generate_test_desc_and_fn(
11491159
opts: GlobalTestOptions,
11501160
rustdoc_options: Arc<RustdocOptions>,
11511161
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
1152-
doctest_id: usize,
11531162
) -> test::TestDescAndFn {
11541163
let target_str = rustdoc_options.target.to_string();
11551164
let rustdoc_test_options =
@@ -1184,7 +1193,6 @@ fn generate_test_desc_and_fn(
11841193
scraped_test,
11851194
rustdoc_options,
11861195
unused_externs,
1187-
doctest_id,
11881196
)
11891197
})),
11901198
}
@@ -1197,7 +1205,6 @@ fn doctest_run_fn(
11971205
scraped_test: ScrapedDocTest,
11981206
rustdoc_options: Arc<RustdocOptions>,
11991207
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
1200-
doctest_id: usize,
12011208
) -> Result<(), String> {
12021209
#[cfg(not(bootstrap))]
12031210
if scraped_test.langstr.should_panic && test::cannot_handle_should_panic() {
@@ -1223,13 +1230,8 @@ fn doctest_run_fn(
12231230
no_run: scraped_test.no_run(&rustdoc_options),
12241231
merged_test_code: None,
12251232
};
1226-
let (_, res) = run_test(
1227-
runnable_test,
1228-
&rustdoc_options,
1229-
doctest.supports_color,
1230-
report_unused_externs,
1231-
doctest_id,
1232-
);
1233+
let (_, res) =
1234+
run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs);
12331235

12341236
if let Err(err) = res {
12351237
eprint!("{err}");

0 commit comments

Comments
 (0)