Skip to content

Commit bd3fe05

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

File tree

8 files changed

+240
-166
lines changed

8 files changed

+240
-166
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: 119 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ pub(crate) fn run_tests(
407407
// We failed to compile all compatible tests as one so we push them into the
408408
// `standalone_tests` doctests.
409409
debug!("Failed to compile compatible doctests for edition {} all at once", edition);
410-
for (pos, (doctest, scraped_test)) in doctests.into_iter().enumerate() {
410+
for (doctest, scraped_test) in doctests {
411411
doctest.generate_unique_doctest(
412412
&scraped_test.text,
413413
scraped_test.langstr.test_harness,
@@ -420,7 +420,6 @@ pub(crate) fn run_tests(
420420
opts.clone(),
421421
Arc::clone(rustdoc_options),
422422
unused_extern_reports.clone(),
423-
pos,
424423
));
425424
}
426425
}
@@ -550,44 +549,33 @@ pub(crate) struct RunnableDocTest {
550549
}
551550

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

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

592580
let mut runner_compiler =
593581
wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary);
@@ -596,13 +584,10 @@ fn compile_merged_doctest_and_caller_binary(
596584
runner_compiler.env("RUSTC_BOOTSTRAP", "1");
597585
runner_compiler.args(compiler_args);
598586
runner_compiler.args(["--crate-type=bin", "-o"]).arg(output_file);
599-
let mut extern_path = if let Some(id) = id {
600-
std::ffi::OsString::from(format!("--extern=doctest_bundle_id_{id}="))
587+
let base_name = if is_compile_fail {
588+
format!("rust_out")
601589
} else {
602-
std::ffi::OsString::from(format!(
603-
"--extern=doctest_bundle_{edition}=",
604-
edition = doctest.edition
605-
))
590+
format!("doctest_bundle_{edition}", edition = doctest.edition)
606591
};
607592

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

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

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

657664
/// Execute a `RunnableDoctest`.
@@ -665,7 +672,6 @@ fn run_test(
665672
rustdoc_options: &RustdocOptions,
666673
supports_color: bool,
667674
report_unused_externs: impl Fn(UnusedExterns),
668-
doctest_id: usize,
669675
) -> (Duration, Result<(), RustdocResult>) {
670676
let langstr = &doctest.langstr;
671677
// Make sure we emit well-formed executable names for our target.
@@ -696,11 +702,6 @@ fn run_test(
696702
compiler_args.extend_from_slice(&["-Z".to_owned(), "unstable-options".to_owned()]);
697703
}
698704

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

747748
compiler.args(&compiler_args);
748749

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

785793
debug!("compiler invocation for doctest: {compiler:?}");
@@ -795,21 +803,25 @@ fn run_test(
795803
compiler_args,
796804
merged_test_code,
797805
instant,
798-
None,
806+
false,
799807
) {
800808
Ok(out) => out,
801809
Err(err) => return err,
802810
}
803-
} else if !langstr.compile_fail && langstr.should_panic {
804-
match compile_merged_doctest_and_caller_binary(
805-
child,
806-
&doctest,
807-
rustdoc_options,
808-
rustc_binary,
809-
&output_file,
810-
compiler_args,
811-
&format!(
812-
"\
811+
} else {
812+
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
813+
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
814+
815+
if !langstr.compile_fail && langstr.should_panic {
816+
match compile_merged_doctest_and_caller_binary(
817+
child,
818+
&doctest,
819+
rustdoc_options,
820+
rustc_binary,
821+
&output_file,
822+
compiler_args,
823+
&format!(
824+
"\
813825
#![feature(test)]
814826
extern crate test;
815827
@@ -819,21 +831,20 @@ fn main() -> ExitCode {{
819831
if test::cannot_handle_should_panic() {{
820832
ExitCode::SUCCESS
821833
}} else {{
822-
extern crate doctest_bundle_id_{doctest_id} as doctest_bundle;
834+
extern crate rust_out as doctest_bundle;
823835
doctest_bundle::main().report()
824836
}}
825-
}}"
826-
),
827-
instant,
828-
Some(doctest_id),
829-
) {
830-
Ok(out) => out,
831-
Err(err) => return err,
837+
}}",
838+
),
839+
instant,
840+
true,
841+
) {
842+
Ok(out) => out,
843+
Err(err) => return err,
844+
}
845+
} else {
846+
child.wait_with_output().expect("Failed to read stdout")
832847
}
833-
} else {
834-
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
835-
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
836-
child.wait_with_output().expect("Failed to read stdout")
837848
};
838849

839850
struct Bomb<'a>(&'a str);
@@ -1142,7 +1153,6 @@ impl CreateRunnableDocTests {
11421153
self.opts.clone(),
11431154
Arc::clone(&self.rustdoc_options),
11441155
self.unused_extern_reports.clone(),
1145-
self.standalone_tests.len(),
11461156
)
11471157
}
11481158
}
@@ -1153,7 +1163,6 @@ fn generate_test_desc_and_fn(
11531163
opts: GlobalTestOptions,
11541164
rustdoc_options: Arc<RustdocOptions>,
11551165
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
1156-
doctest_id: usize,
11571166
) -> test::TestDescAndFn {
11581167
let target_str = rustdoc_options.target.to_string();
11591168
let rustdoc_test_options =
@@ -1188,7 +1197,6 @@ fn generate_test_desc_and_fn(
11881197
scraped_test,
11891198
rustdoc_options,
11901199
unused_externs,
1191-
doctest_id,
11921200
)
11931201
})),
11941202
}
@@ -1201,7 +1209,6 @@ fn doctest_run_fn(
12011209
scraped_test: ScrapedDocTest,
12021210
rustdoc_options: Arc<RustdocOptions>,
12031211
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
1204-
doctest_id: usize,
12051212
) -> Result<(), String> {
12061213
#[cfg(not(bootstrap))]
12071214
if scraped_test.langstr.should_panic && test::cannot_handle_should_panic() {
@@ -1227,13 +1234,8 @@ fn doctest_run_fn(
12271234
no_run: scraped_test.no_run(&rustdoc_options),
12281235
merged_test_code: None,
12291236
};
1230-
let (_, res) = run_test(
1231-
runnable_test,
1232-
&rustdoc_options,
1233-
doctest.supports_color,
1234-
report_unused_externs,
1235-
doctest_id,
1236-
);
1237+
let (_, res) =
1238+
run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs);
12371239

12381240
if let Err(err) = res {
12391241
eprint!("{err}");

0 commit comments

Comments
 (0)