Skip to content

Commit b304cd0

Browse files
committed
Run doctests via out-of-process rustc
1 parent 56ab485 commit b304cd0

File tree

3 files changed

+82
-151
lines changed

3 files changed

+82
-151
lines changed

src/librustdoc/config.rs

+12
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,18 @@ pub struct Options {
3939
pub error_format: ErrorOutputType,
4040
/// Library search paths to hand to the compiler.
4141
pub libs: Vec<SearchPath>,
42+
/// Library search paths strings to hand to the compiler.
43+
pub lib_strs: Vec<String>,
4244
/// The list of external crates to link against.
4345
pub externs: Externs,
46+
/// The list of external crates strings to link against.
47+
pub extern_strs: Vec<String>,
4448
/// List of `cfg` flags to hand to the compiler. Always includes `rustdoc`.
4549
pub cfgs: Vec<String>,
4650
/// Codegen options to hand to the compiler.
4751
pub codegen_options: CodegenOptions,
52+
/// Codegen options strings to hand to the compiler.
53+
pub codegen_options_strs: Vec<String>,
4854
/// Debugging (`-Z`) options to pass to the compiler.
4955
pub debugging_options: DebuggingOptions,
5056
/// The target used to compile the crate against.
@@ -461,6 +467,9 @@ impl Options {
461467
let generate_search_filter = !matches.opt_present("disable-per-crate-search");
462468
let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from);
463469
let generate_redirect_pages = matches.opt_present("generate-redirect-pages");
470+
let codegen_options_strs = matches.opt_strs("C");
471+
let lib_strs = matches.opt_strs("L");
472+
let extern_strs = matches.opt_strs("extern");
464473

465474
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
466475

@@ -470,9 +479,12 @@ impl Options {
470479
proc_macro_crate,
471480
error_format,
472481
libs,
482+
lib_strs,
473483
externs,
484+
extern_strs,
474485
cfgs,
475486
codegen_options,
487+
codegen_options_strs,
476488
debugging_options,
477489
target,
478490
edition,

src/librustdoc/markdown.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,8 @@ pub fn test(mut options: Options, diag: &errors::Handler) -> i32 {
142142
let mut opts = TestOptions::default();
143143
opts.no_crate_inject = true;
144144
opts.display_warnings = options.display_warnings;
145-
let mut collector = Collector::new(options.input.display().to_string(), options.cfgs,
146-
options.libs, options.codegen_options, options.externs,
147-
true, opts, options.maybe_sysroot, None,
148-
Some(options.input),
149-
options.linker, options.edition, options.persist_doctests);
145+
let mut collector = Collector::new(options.input.display().to_string(), options.clone(),
146+
true, opts, None, Some(options.input));
150147
collector.set_position(DUMMY_SP);
151148
let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
152149

src/librustdoc/test.rs

+68-146
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,19 @@ use rustc_data_structures::sync::Lrc;
22
use rustc_interface::interface;
33
use rustc::hir;
44
use rustc::hir::intravisit;
5-
use rustc::hir::def_id::LOCAL_CRATE;
65
use rustc::session::{self, config, DiagnosticOutput};
7-
use rustc::session::config::{OutputType, OutputTypes, Externs, CodegenOptions};
8-
use rustc::session::search_paths::SearchPath;
96
use rustc::util::common::ErrorReported;
107
use syntax::ast;
118
use syntax::with_globals;
129
use syntax::source_map::SourceMap;
1310
use syntax::edition::Edition;
1411
use syntax::feature_gate::UnstableFeatures;
1512
use std::env;
16-
use std::io::prelude::*;
17-
use std::io;
18-
use std::panic::{self, AssertUnwindSafe};
13+
use std::io::{self, Write};
14+
use std::panic;
1915
use std::path::PathBuf;
20-
use std::process::{self, Command};
16+
use std::process::{self, Command, Stdio};
2117
use std::str;
22-
use std::sync::{Arc, Mutex};
2318
use syntax::symbol::sym;
2419
use syntax_pos::{BytePos, DUMMY_SP, Pos, Span, FileName};
2520
use tempfile::Builder as TempFileBuilder;
@@ -89,18 +84,11 @@ pub fn run(options: Options) -> i32 {
8984
opts.display_warnings |= options.display_warnings;
9085
let mut collector = Collector::new(
9186
compiler.crate_name()?.peek().to_string(),
92-
options.cfgs,
93-
options.libs,
94-
options.codegen_options,
95-
options.externs,
87+
options,
9688
false,
9789
opts,
98-
options.maybe_sysroot,
9990
Some(compiler.source_map().clone()),
10091
None,
101-
options.linker,
102-
options.edition,
103-
options.persist_doctests,
10492
);
10593

10694
let mut global_ctxt = compiler.global_ctxt()?.take();
@@ -189,20 +177,14 @@ fn run_test(
189177
cratename: &str,
190178
filename: &FileName,
191179
line: usize,
192-
cfgs: Vec<String>,
193-
libs: Vec<SearchPath>,
194-
cg: CodegenOptions,
195-
externs: Externs,
180+
options: Options,
196181
should_panic: bool,
197182
no_run: bool,
198183
as_test_harness: bool,
199184
compile_fail: bool,
200185
mut error_codes: Vec<String>,
201186
opts: &TestOptions,
202-
maybe_sysroot: Option<PathBuf>,
203-
linker: Option<PathBuf>,
204187
edition: Edition,
205-
persist_doctests: Option<PathBuf>,
206188
) -> Result<(), TestFailure> {
207189
let (test, line_offset) = match panic::catch_unwind(|| {
208190
make_test(test, Some(cratename), as_test_harness, opts, edition)
@@ -223,61 +205,6 @@ fn run_test(
223205
_ => PathBuf::from(r"doctest.rs"),
224206
};
225207

226-
let input = config::Input::Str {
227-
name: FileName::DocTest(path, line as isize - line_offset as isize),
228-
input: test,
229-
};
230-
let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
231-
232-
let sessopts = config::Options {
233-
maybe_sysroot,
234-
search_paths: libs,
235-
crate_types: vec![config::CrateType::Executable],
236-
output_types: outputs,
237-
externs,
238-
cg: config::CodegenOptions {
239-
linker,
240-
..cg
241-
},
242-
test: as_test_harness,
243-
unstable_features: UnstableFeatures::from_environment(),
244-
debugging_opts: config::DebuggingOptions {
245-
..config::basic_debugging_options()
246-
},
247-
edition,
248-
..config::Options::default()
249-
};
250-
251-
// Shuffle around a few input and output handles here. We're going to pass
252-
// an explicit handle into rustc to collect output messages, but we also
253-
// want to catch the error message that rustc prints when it fails.
254-
//
255-
// We take our thread-local stderr (likely set by the test runner) and replace
256-
// it with a sink that is also passed to rustc itself. When this function
257-
// returns the output of the sink is copied onto the output of our own thread.
258-
//
259-
// The basic idea is to not use a default Handler for rustc, and then also
260-
// not print things by default to the actual stderr.
261-
struct Sink(Arc<Mutex<Vec<u8>>>);
262-
impl Write for Sink {
263-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
264-
Write::write(&mut *self.0.lock().unwrap(), data)
265-
}
266-
fn flush(&mut self) -> io::Result<()> { Ok(()) }
267-
}
268-
struct Bomb(Arc<Mutex<Vec<u8>>>, Option<Box<dyn Write+Send>>);
269-
impl Drop for Bomb {
270-
fn drop(&mut self) {
271-
let mut old = self.1.take().unwrap();
272-
let _ = old.write_all(&self.0.lock().unwrap());
273-
io::set_panic(Some(old));
274-
}
275-
}
276-
let data = Arc::new(Mutex::new(Vec::new()));
277-
278-
let old = io::set_panic(Some(box Sink(data.clone())));
279-
let _bomb = Bomb(data.clone(), Some(old.unwrap_or(box io::stdout())));
280-
281208
enum DirState {
282209
Temp(tempfile::TempDir),
283210
Perm(PathBuf),
@@ -292,7 +219,7 @@ fn run_test(
292219
}
293220
}
294221

295-
let outdir = if let Some(mut path) = persist_doctests {
222+
let outdir = if let Some(mut path) = options.persist_doctests {
296223
path.push(format!("{}_{}",
297224
filename
298225
.to_string()
@@ -314,49 +241,73 @@ fn run_test(
314241
};
315242
let output_file = outdir.path().join("rust_out");
316243

317-
let config = interface::Config {
318-
opts: sessopts,
319-
crate_cfg: config::parse_cfgspecs(cfgs),
320-
input,
321-
input_path: None,
322-
output_file: Some(output_file.clone()),
323-
output_dir: None,
324-
file_loader: None,
325-
diagnostic_output: DiagnosticOutput::Raw(box Sink(data.clone())),
326-
stderr: Some(data.clone()),
327-
crate_name: None,
328-
lint_caps: Default::default(),
329-
};
244+
let mut compiler = Command::new(std::env::current_exe().unwrap().with_file_name("rustc"));
245+
compiler.arg("--crate-type").arg("bin");
246+
for cfg in &options.cfgs {
247+
compiler.arg("--cfg").arg(&cfg);
248+
}
249+
if let Some(sysroot) = options.maybe_sysroot {
250+
compiler.arg("--sysroot").arg(sysroot);
251+
}
252+
compiler.arg("--edition").arg(&edition.to_string());
253+
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
254+
compiler.env("UNSTABLE_RUSTDOC_TEST_LINE",
255+
format!("{}", line as isize - line_offset as isize));
256+
compiler.arg("-o").arg(&output_file);
257+
if as_test_harness {
258+
compiler.arg("--test");
259+
}
260+
for lib_str in &options.lib_strs {
261+
compiler.arg("-L").arg(&lib_str);
262+
}
263+
for extern_str in &options.extern_strs {
264+
compiler.arg("--extern").arg(&extern_str);
265+
}
266+
for codegen_options_str in &options.codegen_options_strs {
267+
compiler.arg("-C").arg(&codegen_options_str);
268+
}
269+
if let Some(linker) = options.linker {
270+
compiler.arg(&format!("-C linker={:?}", linker));
271+
}
272+
if no_run {
273+
compiler.arg("--emit=metadata");
274+
}
330275

331-
let compile_result = panic::catch_unwind(AssertUnwindSafe(|| {
332-
interface::run_compiler(config, |compiler| {
333-
if no_run {
334-
compiler.global_ctxt().and_then(|global_ctxt| global_ctxt.take().enter(|tcx| {
335-
tcx.analysis(LOCAL_CRATE)
336-
})).ok();
337-
} else {
338-
compiler.compile().ok();
339-
};
340-
compiler.session().compile_status()
341-
})
342-
})).map_err(|_| ()).and_then(|s| s.map_err(|_| ()));
276+
compiler.arg("-");
277+
compiler.stdin(Stdio::piped());
278+
compiler.stderr(Stdio::piped());
279+
280+
let mut child = compiler.spawn().expect("Failed to spawn rustc process");
281+
{
282+
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
283+
stdin.write_all(test.as_bytes()).expect("could write out test sources");
284+
}
285+
let output = child.wait_with_output().expect("Failed to read stdout");
343286

344-
match (compile_result, compile_fail) {
345-
(Ok(()), true) => {
287+
struct Bomb<'a>(&'a str);
288+
impl Drop for Bomb<'_> {
289+
fn drop(&mut self) {
290+
eprint!("{}",self.0);
291+
}
292+
}
293+
294+
let out = str::from_utf8(&output.stderr).unwrap();
295+
let _bomb = Bomb(&out);
296+
match (output.status.success(), compile_fail) {
297+
(true, true) => {
346298
return Err(TestFailure::UnexpectedCompilePass);
347299
}
348-
(Ok(()), false) => {}
349-
(Err(_), true) => {
300+
(true, false) => {}
301+
(false, true) => {
350302
if !error_codes.is_empty() {
351-
let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
352303
error_codes.retain(|err| !out.contains(err));
353304

354305
if !error_codes.is_empty() {
355306
return Err(TestFailure::MissingErrorCodes(error_codes));
356307
}
357308
}
358309
}
359-
(Err(_), false) => {
310+
(false, false) => {
360311
return Err(TestFailure::CompileError);
361312
}
362313
}
@@ -652,45 +603,28 @@ pub struct Collector {
652603
// the `names` vector of that test will be `["Title", "Subtitle"]`.
653604
names: Vec<String>,
654605

655-
cfgs: Vec<String>,
656-
libs: Vec<SearchPath>,
657-
cg: CodegenOptions,
658-
externs: Externs,
606+
options: Options,
659607
use_headers: bool,
660608
cratename: String,
661609
opts: TestOptions,
662-
maybe_sysroot: Option<PathBuf>,
663610
position: Span,
664611
source_map: Option<Lrc<SourceMap>>,
665612
filename: Option<PathBuf>,
666-
linker: Option<PathBuf>,
667-
edition: Edition,
668-
persist_doctests: Option<PathBuf>,
669613
}
670614

671615
impl Collector {
672-
pub fn new(cratename: String, cfgs: Vec<String>, libs: Vec<SearchPath>, cg: CodegenOptions,
673-
externs: Externs, use_headers: bool, opts: TestOptions,
674-
maybe_sysroot: Option<PathBuf>, source_map: Option<Lrc<SourceMap>>,
675-
filename: Option<PathBuf>, linker: Option<PathBuf>, edition: Edition,
676-
persist_doctests: Option<PathBuf>) -> Collector {
616+
pub fn new(cratename: String, options: Options, use_headers: bool, opts: TestOptions,
617+
source_map: Option<Lrc<SourceMap>>, filename: Option<PathBuf>,) -> Collector {
677618
Collector {
678619
tests: Vec::new(),
679620
names: Vec::new(),
680-
cfgs,
681-
libs,
682-
cg,
683-
externs,
621+
options,
684622
use_headers,
685623
cratename,
686624
opts,
687-
maybe_sysroot,
688625
position: DUMMY_SP,
689626
source_map,
690627
filename,
691-
linker,
692-
edition,
693-
persist_doctests,
694628
}
695629
}
696630

@@ -725,16 +659,10 @@ impl Tester for Collector {
725659
fn add_test(&mut self, test: String, config: LangString, line: usize) {
726660
let filename = self.get_filename();
727661
let name = self.generate_name(line, &filename);
728-
let cfgs = self.cfgs.clone();
729-
let libs = self.libs.clone();
730-
let cg = self.cg.clone();
731-
let externs = self.externs.clone();
732662
let cratename = self.cratename.to_string();
733663
let opts = self.opts.clone();
734-
let maybe_sysroot = self.maybe_sysroot.clone();
735-
let linker = self.linker.clone();
736-
let edition = config.edition.unwrap_or(self.edition);
737-
let persist_doctests = self.persist_doctests.clone();
664+
let edition = config.edition.unwrap_or(self.options.edition.clone());
665+
let options = self.options.clone();
738666

739667
debug!("creating test {}: {}", name, test);
740668
self.tests.push(testing::TestDescAndFn {
@@ -751,20 +679,14 @@ impl Tester for Collector {
751679
&cratename,
752680
&filename,
753681
line,
754-
cfgs,
755-
libs,
756-
cg,
757-
externs,
682+
options,
758683
config.should_panic,
759684
config.no_run,
760685
config.test_harness,
761686
config.compile_fail,
762687
config.error_codes,
763688
&opts,
764-
maybe_sysroot,
765-
linker,
766689
edition,
767-
persist_doctests
768690
);
769691

770692
if let Err(err) = res {

0 commit comments

Comments
 (0)