Skip to content

Commit ed3fb4b

Browse files
Run doctests by batches instead of running them all at once when there are too many
1 parent 4c766eb commit ed3fb4b

File tree

1 file changed

+173
-62
lines changed

1 file changed

+173
-62
lines changed

src/librustdoc/doctest.rs

+173-62
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,166 @@ pub(crate) trait Tester {
12671267
fn register_header(&mut self, _name: &str, _level: u32) {}
12681268
}
12691269

1270+
/// If there are too many doctests than can be compiled at once, we need to limit the size
1271+
/// of the generated test to prevent everything to break down.
1272+
///
1273+
/// We add all doctests one by one through [`DocTestRunner::add_test`] and when it reaches
1274+
/// `TEST_BATCH_SIZE` size or is dropped, it runs all stored doctests at once.
1275+
struct DocTestRunner<'a> {
1276+
nb_errors: &'a mut usize,
1277+
ran_edition_tests: &'a mut usize,
1278+
standalone: &'a mut Vec<TestDescAndFn>,
1279+
crate_attrs: FxHashSet<String>,
1280+
edition: Edition,
1281+
ids: String,
1282+
output: String,
1283+
supports_color: bool,
1284+
rustdoc_test_options: &'a Arc<IndividualTestOptions>,
1285+
outdir: &'a Arc<DirState>,
1286+
nb_tests: usize,
1287+
doctests: Vec<DocTest>,
1288+
opts: &'a GlobalTestOptions,
1289+
test_args: &'a [String],
1290+
unused_externs: &'a Arc<Mutex<Vec<UnusedExterns>>>,
1291+
}
1292+
1293+
impl<'a> DocTestRunner<'a> {
1294+
const TEST_BATCH_SIZE: usize = 250;
1295+
1296+
fn new(
1297+
nb_errors: &'a mut usize,
1298+
ran_edition_tests: &'a mut usize,
1299+
standalone: &'a mut Vec<TestDescAndFn>,
1300+
edition: Edition,
1301+
rustdoc_test_options: &'a Arc<IndividualTestOptions>,
1302+
outdir: &'a Arc<DirState>,
1303+
opts: &'a GlobalTestOptions,
1304+
test_args: &'a [String],
1305+
unused_externs: &'a Arc<Mutex<Vec<UnusedExterns>>>,
1306+
) -> Self {
1307+
Self {
1308+
nb_errors,
1309+
ran_edition_tests,
1310+
standalone,
1311+
edition,
1312+
crate_attrs: FxHashSet::default(),
1313+
ids: String::new(),
1314+
output: String::new(),
1315+
supports_color: true,
1316+
rustdoc_test_options,
1317+
outdir,
1318+
nb_tests: 0,
1319+
doctests: Vec::with_capacity(Self::TEST_BATCH_SIZE),
1320+
opts,
1321+
test_args,
1322+
unused_externs,
1323+
}
1324+
}
1325+
1326+
fn add_test(&mut self, doctest: DocTest) {
1327+
for line in doctest.crate_attrs.split('\n') {
1328+
self.crate_attrs.insert(line.to_string());
1329+
}
1330+
if !self.ids.is_empty() {
1331+
self.ids.push(',');
1332+
}
1333+
self.ids.push_str(&format!(
1334+
"{}::TEST",
1335+
doctest.generate_test_desc(self.nb_tests, &mut self.output)
1336+
));
1337+
self.supports_color &= doctest.supports_color;
1338+
self.nb_tests += 1;
1339+
self.doctests.push(doctest);
1340+
1341+
if self.nb_tests >= Self::TEST_BATCH_SIZE {
1342+
self.run_tests();
1343+
}
1344+
}
1345+
1346+
fn run_tests(&mut self) {
1347+
if self.nb_tests == 0 {
1348+
return;
1349+
}
1350+
let mut code = "\
1351+
#![allow(unused_extern_crates)]
1352+
#![allow(internal_features)]
1353+
#![feature(test)]
1354+
#![feature(rustc_attrs)]
1355+
#![feature(coverage_attribute)]\n"
1356+
.to_string();
1357+
1358+
for crate_attr in &self.crate_attrs {
1359+
code.push_str(crate_attr);
1360+
code.push('\n');
1361+
}
1362+
1363+
DocTest::push_attrs(&mut code, &self.opts, &mut 0);
1364+
code.push_str("extern crate test;\n");
1365+
1366+
let test_args =
1367+
self.test_args.iter().map(|arg| format!("{arg:?}.to_string(),")).collect::<String>();
1368+
write!(
1369+
code,
1370+
"\
1371+
{output}
1372+
#[rustc_main]
1373+
#[coverage(off)]
1374+
fn main() {{
1375+
test::test_main(&[{test_args}], vec![{ids}], None);
1376+
}}",
1377+
output = self.output,
1378+
ids = self.ids,
1379+
)
1380+
.expect("failed to generate test code");
1381+
let ret = run_test(
1382+
code,
1383+
self.supports_color,
1384+
None,
1385+
Arc::clone(self.rustdoc_test_options),
1386+
true,
1387+
Arc::clone(self.outdir),
1388+
LangString::empty_for_test(),
1389+
self.edition,
1390+
|_: UnusedExterns| {},
1391+
false,
1392+
);
1393+
if let Err(TestFailure::CompileError) = ret {
1394+
// We failed to compile all compatible tests as one so we push them into the
1395+
// "standalone" doctests.
1396+
debug!(
1397+
"Failed to compile compatible doctests for edition {} all at once",
1398+
self.edition
1399+
);
1400+
for doctest in self.doctests.drain(..) {
1401+
self.standalone.push(doctest.generate_test_desc_and_fn(
1402+
&self.opts,
1403+
self.edition,
1404+
Arc::clone(self.unused_externs),
1405+
));
1406+
}
1407+
} else {
1408+
*self.ran_edition_tests += 1;
1409+
if ret.is_err() {
1410+
*self.nb_errors += 1;
1411+
}
1412+
}
1413+
1414+
// We reset values.
1415+
self.supports_color = true;
1416+
self.ids.clear();
1417+
self.output.clear();
1418+
self.crate_attrs.clear();
1419+
self.nb_tests = 0;
1420+
self.doctests.clear();
1421+
}
1422+
}
1423+
1424+
impl<'a> Drop for DocTestRunner<'a> {
1425+
fn drop(&mut self) {
1426+
self.run_tests();
1427+
}
1428+
}
1429+
12701430
#[derive(Default)]
12711431
pub(crate) struct DocTestKinds {
12721432
/// Tests that cannot be run together with the rest (`compile_fail` and `test_harness`).
@@ -1317,73 +1477,24 @@ impl DocTestKinds {
13171477

13181478
for (edition, mut doctests) in others {
13191479
doctests.sort_by(|a, b| a.name.cmp(&b.name));
1320-
let mut ids = String::new();
1321-
let mut output = "\
1322-
#![allow(unused_extern_crates)]
1323-
#![allow(internal_features)]
1324-
#![feature(test)]
1325-
#![feature(rustc_attrs)]
1326-
#![feature(coverage_attribute)]\n"
1327-
.to_string();
1328-
1329-
for doctest in &doctests {
1330-
output.push_str(&doctest.crate_attrs);
1331-
}
1332-
1333-
DocTest::push_attrs(&mut output, &opts, &mut 0);
1334-
output.push_str("extern crate test;\n");
1335-
13361480
let rustdoc_test_options = Arc::clone(&doctests[0].rustdoc_test_options);
13371481
let outdir = Arc::clone(&doctests[0].outdir);
13381482

1339-
let mut supports_color = true;
1340-
for (pos, doctest) in doctests.iter().enumerate() {
1341-
if !ids.is_empty() {
1342-
ids.push(',');
1343-
}
1344-
ids.push_str(&format!("{}::TEST", doctest.generate_test_desc(pos, &mut output)));
1345-
supports_color &= doctest.supports_color;
1346-
}
1347-
let test_args =
1348-
test_args.iter().map(|arg| format!("{arg:?}.to_string(),")).collect::<String>();
1349-
write!(
1350-
output,
1351-
"\
1352-
#[rustc_main]
1353-
#[coverage(off)]
1354-
fn main() {{
1355-
test::test_main(&[{test_args}], vec![{ids}], None);
1356-
}}",
1357-
)
1358-
.unwrap();
1359-
let ret = run_test(
1360-
output,
1361-
supports_color,
1362-
None,
1363-
rustdoc_test_options,
1364-
true,
1365-
outdir,
1366-
LangString::empty_for_test(),
1483+
// When `DocTestRunner` is dropped, it'll run all pending doctests it didn't already
1484+
// run, so no need to worry about it.
1485+
let mut tests_runner = DocTestRunner::new(
1486+
&mut nb_errors,
1487+
&mut ran_edition_tests,
1488+
&mut standalone,
13671489
edition,
1368-
|_: UnusedExterns| {},
1369-
false,
1490+
&rustdoc_test_options,
1491+
&outdir,
1492+
&opts,
1493+
&test_args,
1494+
unused_externs,
13701495
);
1371-
if let Err(TestFailure::CompileError) = ret {
1372-
// We failed to compile all compatible tests as one so we push them into the
1373-
// "standalone" doctests.
1374-
debug!("Failed to compile compatible doctests for edition {edition} all at once");
1375-
for doctest in doctests {
1376-
standalone.push(doctest.generate_test_desc_and_fn(
1377-
&opts,
1378-
edition,
1379-
Arc::clone(unused_externs),
1380-
));
1381-
}
1382-
} else {
1383-
ran_edition_tests += 1;
1384-
if ret.is_err() {
1385-
nb_errors += 1;
1386-
}
1496+
for doctest in doctests {
1497+
tests_runner.add_test(doctest);
13871498
}
13881499
}
13891500

0 commit comments

Comments
 (0)