Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 6ae3524

Browse files
Split doctests into two categories: mergeable ones and standalone ones
1 parent 96051f2 commit 6ae3524

File tree

4 files changed

+99
-62
lines changed

4 files changed

+99
-62
lines changed

src/librustdoc/doctest.rs

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
206206
test_args,
207207
nocapture,
208208
opts,
209-
rustdoc_options,
209+
&rustdoc_options,
210210
&unused_extern_reports,
211211
standalone_tests,
212212
mergeable_tests,
@@ -259,39 +259,43 @@ pub(crate) fn run_tests(
259259
mut test_args: Vec<String>,
260260
nocapture: bool,
261261
opts: GlobalTestOptions,
262-
rustdoc_options: RustdocOptions,
262+
rustdoc_options: &Arc<RustdocOptions>,
263263
unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>,
264264
mut standalone_tests: Vec<test::TestDescAndFn>,
265-
mut mergeable_tests: FxHashMap<Edition, Vec<(DocTest, ScrapedDoctest)>>,
265+
mergeable_tests: FxHashMap<Edition, Vec<(DocTest, ScrapedDoctest)>>,
266266
) {
267267
test_args.insert(0, "rustdoctest".to_string());
268268
if nocapture {
269269
test_args.push("--nocapture".to_string());
270270
}
271271

272272
let mut nb_errors = 0;
273+
let target_str = rustdoc_options.target.to_string();
273274

274275
for (edition, mut doctests) in mergeable_tests {
275276
if doctests.is_empty() {
276277
continue;
277278
}
278279
doctests.sort_by(|(_, a), (_, b)| a.name.cmp(&b.name));
279-
let outdir = Arc::clone(&doctests[0].outdir);
280280

281281
let mut tests_runner = runner::DocTestRunner::new();
282282

283283
let rustdoc_test_options = IndividualTestOptions::new(
284284
&rustdoc_options,
285-
format!("merged_doctest"),
286-
PathBuf::from(r"doctest.rs"),
285+
&format!("merged_doctest_{edition}"),
286+
PathBuf::from(format!("doctest_{edition}.rs")),
287287
);
288288

289289
for (doctest, scraped_test) in &doctests {
290-
tests_runner.add_test(doctest, scraped_test);
290+
tests_runner.add_test(doctest, scraped_test, &target_str);
291291
}
292-
if let Ok(success) =
293-
tests_runner.run_tests(rustdoc_test_options, edition, &opts, &test_args, &outdir)
294-
{
292+
if let Ok(success) = tests_runner.run_tests(
293+
rustdoc_test_options,
294+
edition,
295+
&opts,
296+
&test_args,
297+
rustdoc_options,
298+
) {
295299
if !success {
296300
nb_errors += 1;
297301
}
@@ -311,7 +315,7 @@ pub(crate) fn run_tests(
311315
doctest,
312316
scraped_test,
313317
opts.clone(),
314-
rustdoc_test_options.clone(),
318+
Arc::clone(&rustdoc_options),
315319
unused_extern_reports.clone(),
316320
));
317321
}
@@ -406,7 +410,7 @@ impl DirState {
406410
// We could unify this struct the one in rustc but they have different
407411
// ownership semantics, so doing so would create wasteful allocations.
408412
#[derive(serde::Serialize, serde::Deserialize)]
409-
struct UnusedExterns {
413+
pub(crate) struct UnusedExterns {
410414
/// Lint level of the unused_crate_dependencies lint
411415
lint_level: String,
412416
/// List of unused externs by their names.
@@ -642,12 +646,11 @@ fn make_maybe_absolute_path(path: PathBuf) -> PathBuf {
642646
}
643647
struct IndividualTestOptions {
644648
outdir: DirState,
645-
test_id: String,
646649
path: PathBuf,
647650
}
648651

649652
impl IndividualTestOptions {
650-
fn new(options: &RustdocOptions, test_id: String, test_path: PathBuf) -> Self {
653+
fn new(options: &RustdocOptions, test_id: &str, test_path: PathBuf) -> Self {
651654
let outdir = if let Some(ref path) = options.persist_doctests {
652655
let mut path = path.clone();
653656
path.push(&test_id);
@@ -662,15 +665,14 @@ impl IndividualTestOptions {
662665
DirState::Temp(get_doctest_dir().expect("rustdoc needs a tempdir"))
663666
};
664667

665-
Self { outdir, test_id, path: test_path }
668+
Self { outdir, path: test_path }
666669
}
667670
}
668671

669672
/// A doctest scraped from the code, ready to be turned into a runnable test.
670-
struct ScrapedDoctest {
673+
pub(crate) struct ScrapedDoctest {
671674
filename: FileName,
672675
line: usize,
673-
logical_path: Vec<String>,
674676
langstr: LangString,
675677
text: String,
676678
name: String,
@@ -692,7 +694,7 @@ impl ScrapedDoctest {
692694
let name =
693695
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
694696

695-
Self { filename, line, logical_path, langstr, text, name }
697+
Self { filename, line, langstr, text, name }
696698
}
697699
fn edition(&self, opts: &RustdocOptions) -> Edition {
698700
self.langstr.edition.unwrap_or(opts.edition)
@@ -701,6 +703,19 @@ impl ScrapedDoctest {
701703
fn no_run(&self, opts: &RustdocOptions) -> bool {
702704
self.langstr.no_run || opts.no_run
703705
}
706+
fn path(&self) -> PathBuf {
707+
match &self.filename {
708+
FileName::Real(path) => {
709+
if let Some(local_path) = path.local_path() {
710+
local_path.to_path_buf()
711+
} else {
712+
// Somehow we got the filename from the metadata of another crate, should never happen
713+
unreachable!("doctest from a different crate");
714+
}
715+
}
716+
_ => PathBuf::from(r"doctest.rs"),
717+
}
718+
}
704719
}
705720

706721
pub(crate) trait DoctestVisitor {
@@ -757,7 +772,7 @@ impl CreateRunnableDoctests {
757772

758773
let edition = scraped_test.edition(&self.rustdoc_options);
759774
let doctest =
760-
DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition, test_id);
775+
DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition, Some(test_id));
761776
let is_standalone = scraped_test.langstr.compile_fail
762777
|| scraped_test.langstr.test_harness
763778
|| self.rustdoc_options.nocapture
@@ -784,7 +799,7 @@ impl CreateRunnableDoctests {
784799
test,
785800
scraped_test,
786801
self.opts.clone(),
787-
self.rustdoc_options.clone(),
802+
Arc::clone(&self.rustdoc_options),
788803
self.unused_extern_reports.clone(),
789804
)
790805
}
@@ -794,32 +809,20 @@ fn generate_test_desc_and_fn(
794809
test: DocTest,
795810
scraped_test: ScrapedDoctest,
796811
opts: GlobalTestOptions,
797-
rustdoc_options: IndividualTestOptions,
812+
rustdoc_options: Arc<RustdocOptions>,
798813
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
799814
) -> test::TestDescAndFn {
800815
let target_str = rustdoc_options.target.to_string();
816+
let rustdoc_test_options = IndividualTestOptions::new(
817+
&rustdoc_options,
818+
test.test_id.as_deref().unwrap_or_else(|| "<doctest>"),
819+
scraped_test.path(),
820+
);
801821

802-
let path = match &scraped_test.filename {
803-
FileName::Real(path) => {
804-
if let Some(local_path) = path.local_path() {
805-
local_path.to_path_buf()
806-
} else {
807-
// Somehow we got the filename from the metadata of another crate, should never happen
808-
unreachable!("doctest from a different crate");
809-
}
810-
}
811-
_ => PathBuf::from(r"doctest.rs"),
812-
};
813-
814-
let name = &test.name;
815-
let rustdoc_test_options =
816-
IndividualTestOptions::new(&rustdoc_options, test.test_id.clone(), path);
817-
// let rustdoc_options_clone = rustdoc_options.clone();
818-
819-
debug!("creating test {name}: {}", scraped_test.text);
822+
debug!("creating test {}: {}", scraped_test.name, scraped_test.text);
820823
test::TestDescAndFn {
821824
desc: test::TestDesc {
822-
name: test::DynTestName(name),
825+
name: test::DynTestName(scraped_test.name.clone()),
823826
ignore: match scraped_test.langstr.ignore {
824827
Ignore::All => true,
825828
Ignore::None => false,

src/librustdoc/doctest/markdown.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Doctest functionality used only for doctests in `.md` Markdown files.
22
33
use std::fs::read_to_string;
4+
use std::sync::{Arc, Mutex};
45

56
use rustc_span::FileName;
67
use tempfile::tempdir;
@@ -114,6 +115,16 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
114115

115116
let mut collector = CreateRunnableDoctests::new(options.clone(), opts);
116117
md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
117-
crate::doctest::run_tests(options.test_args, options.nocapture, collector.standalone_tests);
118+
let CreateRunnableDoctests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
119+
collector;
120+
crate::doctest::run_tests(
121+
options.test_args,
122+
options.nocapture,
123+
opts,
124+
&rustdoc_options,
125+
&Arc::new(Mutex::new(Vec::new())),
126+
standalone_tests,
127+
mergeable_tests,
128+
);
118129
Ok(())
119130
}

src/librustdoc/doctest/runner.rs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ use rustc_data_structures::fx::FxHashSet;
22
use rustc_span::edition::Edition;
33

44
use std::fmt::Write;
5-
use std::sync::{Arc, Mutex};
65

76
use crate::doctest::{
8-
run_test, DirState, DocTest, GlobalTestOptions, IndividualTestOptions, RunnableDoctest,
9-
RustdocOptions, ScrapedDoctest, TestFailure, UnusedExterns,
7+
run_test, DocTest, GlobalTestOptions, IndividualTestOptions, RunnableDoctest, RustdocOptions,
8+
ScrapedDoctest, TestFailure, UnusedExterns,
109
};
11-
use crate::html::markdown::LangString;
10+
use crate::html::markdown::{Ignore, LangString};
1211

1312
/// Convenient type to merge compatible doctests into one.
1413
pub(crate) struct DocTestRunner {
@@ -17,7 +16,6 @@ pub(crate) struct DocTestRunner {
1716
output: String,
1817
supports_color: bool,
1918
nb_tests: usize,
20-
doctests: Vec<DocTest>,
2119
}
2220

2321
impl DocTestRunner {
@@ -28,12 +26,21 @@ impl DocTestRunner {
2826
output: String::new(),
2927
supports_color: true,
3028
nb_tests: 0,
31-
doctests: Vec::with_capacity(10),
3229
}
3330
}
3431

35-
pub(crate) fn add_test(&mut self, doctest: &DocTest, scraped_test: &ScrapedDoctest) {
36-
if !doctest.ignore {
32+
pub(crate) fn add_test(
33+
&mut self,
34+
doctest: &DocTest,
35+
scraped_test: &ScrapedDoctest,
36+
target_str: &str,
37+
) {
38+
let ignore = match scraped_test.langstr.ignore {
39+
Ignore::All => true,
40+
Ignore::None => false,
41+
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
42+
};
43+
if !ignore {
3744
for line in doctest.crate_attrs.split('\n') {
3845
self.crate_attrs.insert(line.to_string());
3946
}
@@ -43,11 +50,16 @@ impl DocTestRunner {
4350
}
4451
self.ids.push_str(&format!(
4552
"{}::TEST",
46-
generate_mergeable_doctest(doctest, scraped_test, self.nb_tests, &mut self.output),
53+
generate_mergeable_doctest(
54+
doctest,
55+
scraped_test,
56+
ignore,
57+
self.nb_tests,
58+
&mut self.output
59+
),
4760
));
4861
self.supports_color &= doctest.supports_color;
4962
self.nb_tests += 1;
50-
self.doctests.push(doctest);
5163
}
5264

5365
pub(crate) fn run_tests(
@@ -56,9 +68,7 @@ impl DocTestRunner {
5668
edition: Edition,
5769
opts: &GlobalTestOptions,
5870
test_args: &[String],
59-
outdir: &Arc<DirState>,
6071
rustdoc_options: &RustdocOptions,
61-
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
6272
) -> Result<bool, ()> {
6373
let mut code = "\
6474
#![allow(unused_extern_crates)]
@@ -73,7 +83,19 @@ impl DocTestRunner {
7383
code.push('\n');
7484
}
7585

76-
DocTest::push_attrs(&mut code, opts, &mut 0);
86+
if opts.attrs.is_empty() {
87+
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
88+
// lints that are commonly triggered in doctests. The crate-level test attributes are
89+
// commonly used to make tests fail in case they trigger warnings, so having this there in
90+
// that case may cause some tests to pass when they shouldn't have.
91+
code.push_str("#![allow(unused)]\n");
92+
}
93+
94+
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
95+
for attr in &opts.attrs {
96+
code.push_str(&format!("#![{attr}]\n"));
97+
}
98+
7799
code.push_str("extern crate test;\n");
78100

79101
let test_args =
@@ -91,7 +113,6 @@ test::test_main(&[{test_args}], vec![{ids}], None);
91113
ids = self.ids,
92114
)
93115
.expect("failed to generate test code");
94-
// let out_dir = build_test_dir(outdir, true, "");
95116
let runnable_test = RunnableDoctest {
96117
full_test_code: code,
97118
full_test_line_offset: 0,
@@ -102,7 +123,8 @@ test::test_main(&[{test_args}], vec![{ids}], None);
102123
edition,
103124
no_run: false,
104125
};
105-
let ret = run_test(runnable_test, rustdoc_options, self.supports_color, unused_externs);
126+
let ret =
127+
run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {});
106128
if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) }
107129
}
108130
}
@@ -111,12 +133,13 @@ test::test_main(&[{test_args}], vec![{ids}], None);
111133
fn generate_mergeable_doctest(
112134
doctest: &DocTest,
113135
scraped_test: &ScrapedDoctest,
136+
ignore: bool,
114137
id: usize,
115138
output: &mut String,
116139
) -> String {
117140
let test_id = format!("__doctest_{id}");
118141

119-
if doctest.ignore {
142+
if ignore {
120143
// We generate nothing else.
121144
writeln!(output, "mod {test_id} {{\n").unwrap();
122145
} else {
@@ -166,8 +189,7 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
166189
}};
167190
}}",
168191
test_name = scraped_test.name,
169-
ignore = scraped_test.langstr.ignore,
170-
file = scraped_test.file,
192+
file = scraped_test.path(),
171193
line = scraped_test.line,
172194
no_run = scraped_test.langstr.no_run,
173195
should_panic = if !scraped_test.langstr.no_run && scraped_test.langstr.should_panic {
@@ -177,7 +199,7 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
177199
},
178200
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
179201
// don't give it the function to run.
180-
runner = if scraped_test.langstr.no_run || scraped_test.langstr.ignore {
202+
runner = if ignore || scraped_test.langstr.no_run {
181203
"Ok::<(), String>(())"
182204
} else {
183205
"self::main()"

src/librustdoc/doctest/tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ fn make_test(
1010
opts: &GlobalTestOptions,
1111
test_id: Option<&str>,
1212
) -> (String, usize) {
13-
let doctest = DocTest::new(test_code, crate_name, DEFAULT_EDITION);
13+
let doctest =
14+
DocTest::new(test_code, crate_name, DEFAULT_EDITION, test_id.map(|s| s.to_string()));
1415
let (code, line_offset) =
15-
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, test_id, crate_name);
16+
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
1617
(code, line_offset)
1718
}
1819

0 commit comments

Comments
 (0)