Skip to content

Commit 85499eb

Browse files
camelidGuillaumeGomez
authored andcommitted
Separate doctest collection from running
1 parent 16db1a1 commit 85499eb

File tree

3 files changed

+237
-188
lines changed

3 files changed

+237
-188
lines changed

src/librustdoc/doctest.rs

Lines changed: 36 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
88
use rustc_data_structures::sync::Lrc;
99
use rustc_errors::emitter::stderr_destination;
1010
use rustc_errors::{ColorConfig, ErrorGuaranteed, FatalError};
11-
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
11+
use rustc_hir::def_id::LOCAL_CRATE;
1212
use rustc_hir::CRATE_HIR_ID;
1313
use rustc_interface::interface;
1414
use rustc_parse::new_parser_from_source_str;
@@ -19,10 +19,9 @@ use rustc_session::parse::ParseSess;
1919
use rustc_span::edition::Edition;
2020
use rustc_span::source_map::SourceMap;
2121
use rustc_span::symbol::sym;
22-
use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
22+
use rustc_span::FileName;
2323
use rustc_target::spec::{Target, TargetTriple};
2424

25-
use std::env;
2625
use std::fs::File;
2726
use std::io::{self, Write};
2827
use std::panic;
@@ -38,7 +37,8 @@ use crate::config::Options as RustdocOptions;
3837
use crate::html::markdown::{ErrorCodes, Ignore, LangString};
3938
use crate::lint::init_lints;
4039

41-
use self::rust::HirCollector;
40+
use self::markdown::MdDoctest;
41+
use self::rust::{HirCollector, RustDoctest};
4242

4343
/// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
4444
#[derive(Clone, Default)]
@@ -182,29 +182,19 @@ pub(crate) fn run(
182182
let mut collector = Collector::new(
183183
tcx.crate_name(LOCAL_CRATE).to_string(),
184184
options,
185-
false,
186185
opts,
187-
Some(compiler.sess.psess.clone_source_map()),
188-
None,
189-
enable_per_target_ignores,
190186
file_path,
191187
);
192188

193-
let mut hir_collector = HirCollector {
194-
sess: &compiler.sess,
195-
collector: &mut collector,
196-
map: tcx.hir(),
197-
codes: ErrorCodes::from(
198-
compiler.sess.opts.unstable_features.is_nightly_build(),
199-
),
189+
let hir_collector = HirCollector::new(
190+
&compiler.sess,
191+
tcx.hir(),
192+
ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
193+
enable_per_target_ignores,
200194
tcx,
201-
};
202-
hir_collector.visit_testable(
203-
"".to_string(),
204-
CRATE_DEF_ID,
205-
tcx.hir().span(CRATE_HIR_ID),
206-
|this| tcx.hir().walk_toplevel_module(this),
207195
);
196+
let tests = hir_collector.collect_crate();
197+
tests.into_iter().for_each(|t| collector.add_test(ScrapedDoctest::Rust(t)));
208198

209199
collector
210200
});
@@ -985,6 +975,12 @@ impl IndividualTestOptions {
985975
}
986976
}
987977

978+
/// A doctest scraped from the code, ready to be turned into a runnable test.
979+
enum ScrapedDoctest {
980+
Rust(RustDoctest),
981+
Markdown(MdDoctest),
982+
}
983+
988984
pub(crate) trait DoctestVisitor {
989985
fn visit_test(&mut self, test: String, config: LangString, line: usize);
990986
fn get_line(&self) -> usize {
@@ -996,36 +992,9 @@ pub(crate) trait DoctestVisitor {
996992
pub(crate) struct Collector {
997993
pub(crate) tests: Vec<test::TestDescAndFn>,
998994

999-
// The name of the test displayed to the user, separated by `::`.
1000-
//
1001-
// In tests from Rust source, this is the path to the item
1002-
// e.g., `["std", "vec", "Vec", "push"]`.
1003-
//
1004-
// In tests from a markdown file, this is the titles of all headers (h1~h6)
1005-
// of the sections that contain the code block, e.g., if the markdown file is
1006-
// written as:
1007-
//
1008-
// ``````markdown
1009-
// # Title
1010-
//
1011-
// ## Subtitle
1012-
//
1013-
// ```rust
1014-
// assert!(true);
1015-
// ```
1016-
// ``````
1017-
//
1018-
// the `names` vector of that test will be `["Title", "Subtitle"]`.
1019-
names: Vec<String>,
1020-
1021995
rustdoc_options: RustdocOptions,
1022-
use_headers: bool,
1023-
enable_per_target_ignores: bool,
1024996
crate_name: String,
1025997
opts: GlobalTestOptions,
1026-
position: Span,
1027-
source_map: Option<Lrc<SourceMap>>,
1028-
filename: Option<PathBuf>,
1029998
visited_tests: FxHashMap<(String, usize), usize>,
1030999
unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
10311000
compiling_test_count: AtomicUsize,
@@ -1036,74 +1005,48 @@ impl Collector {
10361005
pub(crate) fn new(
10371006
crate_name: String,
10381007
rustdoc_options: RustdocOptions,
1039-
use_headers: bool,
10401008
opts: GlobalTestOptions,
1041-
source_map: Option<Lrc<SourceMap>>,
1042-
filename: Option<PathBuf>,
1043-
enable_per_target_ignores: bool,
10441009
arg_file: PathBuf,
10451010
) -> Collector {
10461011
Collector {
10471012
tests: Vec::new(),
1048-
names: Vec::new(),
10491013
rustdoc_options,
1050-
use_headers,
1051-
enable_per_target_ignores,
10521014
crate_name,
10531015
opts,
1054-
position: DUMMY_SP,
1055-
source_map,
1056-
filename,
10571016
visited_tests: FxHashMap::default(),
10581017
unused_extern_reports: Default::default(),
10591018
compiling_test_count: AtomicUsize::new(0),
10601019
arg_file,
10611020
}
10621021
}
10631022

1064-
fn generate_name(&self, line: usize, filename: &FileName) -> String {
1065-
let mut item_path = self.names.join("::");
1023+
fn generate_name(&self, filename: &FileName, line: usize, logical_path: &[String]) -> String {
1024+
let mut item_path = logical_path.join("::");
10661025
item_path.retain(|c| c != ' ');
10671026
if !item_path.is_empty() {
10681027
item_path.push(' ');
10691028
}
10701029
format!("{} - {item_path}(line {line})", filename.prefer_local())
10711030
}
10721031

1073-
pub(crate) fn set_position(&mut self, position: Span) {
1074-
self.position = position;
1075-
}
1076-
1077-
fn get_filename(&self) -> FileName {
1078-
if let Some(ref source_map) = self.source_map {
1079-
let filename = source_map.span_to_filename(self.position);
1080-
if let FileName::Real(ref filename) = filename
1081-
&& let Ok(cur_dir) = env::current_dir()
1082-
&& let Some(local_path) = filename.local_path()
1083-
&& let Ok(path) = local_path.strip_prefix(&cur_dir)
1084-
{
1085-
return path.to_owned().into();
1032+
fn add_test(&mut self, test: ScrapedDoctest) {
1033+
let (filename, line, logical_path, langstr, text) = match test {
1034+
ScrapedDoctest::Rust(RustDoctest { filename, line, logical_path, langstr, text }) => {
1035+
(filename, line, logical_path, langstr, text)
10861036
}
1087-
filename
1088-
} else if let Some(ref filename) = self.filename {
1089-
filename.clone().into()
1090-
} else {
1091-
FileName::Custom("input".to_owned())
1092-
}
1093-
}
1094-
}
1037+
ScrapedDoctest::Markdown(MdDoctest { filename, line, logical_path, langstr, text }) => {
1038+
(filename, line, logical_path, langstr, text)
1039+
}
1040+
};
10951041

1096-
impl DoctestVisitor for Collector {
1097-
fn visit_test(&mut self, test: String, config: LangString, line: usize) {
1098-
let filename = self.get_filename();
1099-
let name = self.generate_name(line, &filename);
1042+
let name = self.generate_name(&filename, line, &logical_path);
11001043
let crate_name = self.crate_name.clone();
11011044
let opts = self.opts.clone();
1102-
let edition = config.edition.unwrap_or(self.rustdoc_options.edition);
1045+
let edition = langstr.edition.unwrap_or(self.rustdoc_options.edition);
11031046
let target_str = self.rustdoc_options.target.to_string();
11041047
let unused_externs = self.unused_extern_reports.clone();
1105-
let no_run = config.no_run || self.rustdoc_options.no_run;
1106-
if !config.compile_fail {
1048+
let no_run = langstr.no_run || self.rustdoc_options.no_run;
1049+
if !langstr.compile_fail {
11071050
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
11081051
}
11091052

@@ -1140,11 +1083,11 @@ impl DoctestVisitor for Collector {
11401083
let rustdoc_test_options =
11411084
IndividualTestOptions::new(&self.rustdoc_options, &self.arg_file, test_id);
11421085

1143-
debug!("creating test {name}: {test}");
1086+
debug!("creating test {name}: {text}");
11441087
self.tests.push(test::TestDescAndFn {
11451088
desc: test::TestDesc {
11461089
name: test::DynTestName(name),
1147-
ignore: match config.ignore {
1090+
ignore: match langstr.ignore {
11481091
Ignore::All => true,
11491092
Ignore::None => false,
11501093
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
@@ -1157,7 +1100,7 @@ impl DoctestVisitor for Collector {
11571100
end_col: 0,
11581101
// compiler failures are test failures
11591102
should_panic: test::ShouldPanic::No,
1160-
compile_fail: config.compile_fail,
1103+
compile_fail: langstr.compile_fail,
11611104
no_run,
11621105
test_type: test::TestType::DocTest,
11631106
},
@@ -1166,11 +1109,11 @@ impl DoctestVisitor for Collector {
11661109
unused_externs.lock().unwrap().push(uext);
11671110
};
11681111
let res = run_test(
1169-
&test,
1112+
&text,
11701113
&crate_name,
11711114
line,
11721115
rustdoc_test_options,
1173-
config,
1116+
langstr,
11741117
no_run,
11751118
&opts,
11761119
edition,
@@ -1233,59 +1176,6 @@ impl DoctestVisitor for Collector {
12331176
})),
12341177
});
12351178
}
1236-
1237-
fn get_line(&self) -> usize {
1238-
if let Some(ref source_map) = self.source_map {
1239-
let line = self.position.lo().to_usize();
1240-
let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
1241-
if line > 0 { line - 1 } else { line }
1242-
} else {
1243-
0
1244-
}
1245-
}
1246-
1247-
fn visit_header(&mut self, name: &str, level: u32) {
1248-
if self.use_headers {
1249-
// We use these headings as test names, so it's good if
1250-
// they're valid identifiers.
1251-
let name = name
1252-
.chars()
1253-
.enumerate()
1254-
.map(|(i, c)| {
1255-
if (i == 0 && rustc_lexer::is_id_start(c))
1256-
|| (i != 0 && rustc_lexer::is_id_continue(c))
1257-
{
1258-
c
1259-
} else {
1260-
'_'
1261-
}
1262-
})
1263-
.collect::<String>();
1264-
1265-
// Here we try to efficiently assemble the header titles into the
1266-
// test name in the form of `h1::h2::h3::h4::h5::h6`.
1267-
//
1268-
// Suppose that originally `self.names` contains `[h1, h2, h3]`...
1269-
let level = level as usize;
1270-
if level <= self.names.len() {
1271-
// ... Consider `level == 2`. All headers in the lower levels
1272-
// are irrelevant in this new level. So we should reset
1273-
// `self.names` to contain headers until <h2>, and replace that
1274-
// slot with the new name: `[h1, name]`.
1275-
self.names.truncate(level);
1276-
self.names[level - 1] = name;
1277-
} else {
1278-
// ... On the other hand, consider `level == 5`. This means we
1279-
// need to extend `self.names` to contain five headers. We fill
1280-
// in the missing level (<h4>) with `_`. Thus `self.names` will
1281-
// become `[h1, h2, h3, "_", name]`.
1282-
if level - 1 > self.names.len() {
1283-
self.names.resize(level - 1, "_".to_owned());
1284-
}
1285-
self.names.push(name);
1286-
}
1287-
}
1288-
}
12891179
}
12901180

12911181
#[cfg(test)] // used in tests

0 commit comments

Comments
 (0)