Skip to content

Commit ca9028a

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

File tree

1 file changed

+173
-62
lines changed

1 file changed

+173
-62
lines changed

src/librustdoc/doctest.rs

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

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

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

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

0 commit comments

Comments
 (0)