Skip to content

Commit 5864247

Browse files
Give more information into extracted doctest information
1 parent 40daf23 commit 5864247

File tree

6 files changed

+135
-50
lines changed

6 files changed

+135
-50
lines changed

src/librustdoc/doctest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,14 +1053,14 @@ fn doctest_run_fn(
10531053
let report_unused_externs = |uext| {
10541054
unused_externs.lock().unwrap().push(uext);
10551055
};
1056-
let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
1056+
let (wrapper, full_test_line_offset) = doctest.generate_unique_doctest(
10571057
&scraped_test.text,
10581058
scraped_test.langstr.test_harness,
10591059
&global_opts,
10601060
Some(&global_opts.crate_name),
10611061
);
10621062
let runnable_test = RunnableDocTest {
1063-
full_test_code,
1063+
full_test_code: wrapper.to_string(),
10641064
full_test_line_offset,
10651065
test_opts,
10661066
global_opts,

src/librustdoc/doctest/extracted.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use serde::Serialize;
77

8+
use super::make::DocTestWrapResult;
89
use super::{BuildDocTestBuilder, ScrapedDocTest};
910
use crate::config::Options as RustdocOptions;
1011
use crate::html::markdown;
@@ -14,7 +15,7 @@ use crate::html::markdown;
1415
/// This integer is incremented with every breaking change to the API,
1516
/// and is returned along with the JSON blob into the `format_version` root field.
1617
/// Consuming code should assert that this value matches the format version(s) that it supports.
17-
const FORMAT_VERSION: u32 = 1;
18+
const FORMAT_VERSION: u32 = 2;
1819

1920
#[derive(Serialize)]
2021
pub(crate) struct ExtractedDocTests {
@@ -44,8 +45,7 @@ impl ExtractedDocTests {
4445
.edition(edition)
4546
.lang_str(&langstr)
4647
.build(None);
47-
48-
let (full_test_code, size) = doctest.generate_unique_doctest(
48+
let (wrapper, _size) = doctest.generate_unique_doctest(
4949
&text,
5050
langstr.test_harness,
5151
opts,
@@ -55,21 +55,46 @@ impl ExtractedDocTests {
5555
file: filename.prefer_remapped_unconditionaly().to_string(),
5656
line,
5757
doctest_attributes: langstr.into(),
58-
doctest_code: if size != 0 { Some(full_test_code) } else { None },
58+
doctest_code: match wrapper {
59+
DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
60+
crate_level: crate_level_code,
61+
code,
62+
wrapper: wrapper.map(
63+
|super::make::WrapperInfo { before, after, returns_result, .. }| {
64+
WrapperInfo { before, after, returns_result }
65+
},
66+
),
67+
}),
68+
DocTestWrapResult::SyntaxError { .. } => None,
69+
},
5970
original_code: text,
6071
name,
6172
});
6273
}
6374
}
6475

76+
#[derive(Serialize)]
77+
pub(crate) struct WrapperInfo {
78+
before: String,
79+
after: String,
80+
returns_result: bool,
81+
}
82+
83+
#[derive(Serialize)]
84+
pub(crate) struct DocTest {
85+
crate_level: String,
86+
code: String,
87+
wrapper: Option<WrapperInfo>,
88+
}
89+
6590
#[derive(Serialize)]
6691
pub(crate) struct ExtractedDocTest {
6792
file: String,
6893
line: usize,
6994
doctest_attributes: LangString,
7095
original_code: String,
7196
/// `None` if the code syntax is invalid.
72-
doctest_code: Option<String>,
97+
doctest_code: Option<DocTest>,
7398
name: String,
7499
}
75100

src/librustdoc/doctest/make.rs

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,73 @@ pub(crate) struct DocTestBuilder {
196196
pub(crate) can_be_merged: bool,
197197
}
198198

199+
/// Contains needed information for doctest to be correctly generated with expected "wrapping".
200+
pub(crate) struct WrapperInfo {
201+
pub(crate) before: String,
202+
pub(crate) after: String,
203+
pub(crate) returns_result: bool,
204+
insert_indent_space: bool,
205+
}
206+
207+
impl WrapperInfo {
208+
fn len(&self) -> usize {
209+
self.before.len() + self.after.len()
210+
}
211+
}
212+
213+
/// Contains a doctest information. Can be converted into code with the `to_string()` method.
214+
pub(crate) enum DocTestWrapResult {
215+
Valid {
216+
crate_level_code: String,
217+
wrapper: Option<WrapperInfo>,
218+
code: String,
219+
},
220+
/// Contains the original source code.
221+
SyntaxError(String),
222+
}
223+
224+
impl std::string::ToString for DocTestWrapResult {
225+
fn to_string(&self) -> String {
226+
match self {
227+
Self::SyntaxError(s) => s.clone(),
228+
Self::Valid { crate_level_code, wrapper, code } => {
229+
let mut prog_len = code.len() + crate_level_code.len();
230+
if let Some(wrapper) = wrapper {
231+
prog_len += wrapper.len();
232+
if wrapper.insert_indent_space {
233+
prog_len += code.lines().count() * 4;
234+
}
235+
}
236+
let mut prog = String::with_capacity(prog_len);
237+
238+
prog.push_str(crate_level_code);
239+
if let Some(wrapper) = wrapper {
240+
prog.push_str(&wrapper.before);
241+
242+
// add extra 4 spaces for each line to offset the code block
243+
if wrapper.insert_indent_space {
244+
write!(
245+
prog,
246+
"{}",
247+
fmt::from_fn(|f| code
248+
.lines()
249+
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
250+
.joined("\n", f))
251+
)
252+
.unwrap();
253+
} else {
254+
prog.push_str(code);
255+
}
256+
prog.push_str(&wrapper.after);
257+
} else {
258+
prog.push_str(code);
259+
}
260+
prog
261+
}
262+
}
263+
}
264+
}
265+
199266
impl DocTestBuilder {
200267
fn invalid(
201268
global_crate_attrs: Vec<String>,
@@ -228,50 +295,49 @@ impl DocTestBuilder {
228295
dont_insert_main: bool,
229296
opts: &GlobalTestOptions,
230297
crate_name: Option<&str>,
231-
) -> (String, usize) {
298+
) -> (DocTestWrapResult, usize) {
232299
if self.invalid_ast {
233300
// If the AST failed to compile, no need to go generate a complete doctest, the error
234301
// will be better this way.
235302
debug!("invalid AST:\n{test_code}");
236-
return (test_code.to_string(), 0);
303+
return (DocTestWrapResult::SyntaxError(test_code.to_string()), 0);
237304
}
238305
let mut line_offset = 0;
239-
let mut prog = String::new();
240-
let everything_else = self.everything_else.trim();
241-
306+
let mut crate_level_code = String::new();
307+
let code = self.everything_else.trim();
242308
if self.global_crate_attrs.is_empty() {
243309
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
244310
// lints that are commonly triggered in doctests. The crate-level test attributes are
245311
// commonly used to make tests fail in case they trigger warnings, so having this there in
246312
// that case may cause some tests to pass when they shouldn't have.
247-
prog.push_str("#![allow(unused)]\n");
313+
crate_level_code.push_str("#![allow(unused)]\n");
248314
line_offset += 1;
249315
}
250316

251317
// Next, any attributes that came from #![doc(test(attr(...)))].
252318
for attr in &self.global_crate_attrs {
253-
prog.push_str(&format!("#![{attr}]\n"));
319+
crate_level_code.push_str(&format!("#![{attr}]\n"));
254320
line_offset += 1;
255321
}
256322

257323
// Now push any outer attributes from the example, assuming they
258324
// are intended to be crate attributes.
259325
if !self.crate_attrs.is_empty() {
260-
prog.push_str(&self.crate_attrs);
326+
crate_level_code.push_str(&self.crate_attrs);
261327
if !self.crate_attrs.ends_with('\n') {
262-
prog.push('\n');
328+
crate_level_code.push('\n');
263329
}
264330
}
265331
if !self.maybe_crate_attrs.is_empty() {
266-
prog.push_str(&self.maybe_crate_attrs);
332+
crate_level_code.push_str(&self.maybe_crate_attrs);
267333
if !self.maybe_crate_attrs.ends_with('\n') {
268-
prog.push('\n');
334+
crate_level_code.push('\n');
269335
}
270336
}
271337
if !self.crates.is_empty() {
272-
prog.push_str(&self.crates);
338+
crate_level_code.push_str(&self.crates);
273339
if !self.crates.ends_with('\n') {
274-
prog.push('\n');
340+
crate_level_code.push('\n');
275341
}
276342
}
277343

@@ -289,17 +355,20 @@ impl DocTestBuilder {
289355
{
290356
// rustdoc implicitly inserts an `extern crate` item for the own crate
291357
// which may be unused, so we need to allow the lint.
292-
prog.push_str("#[allow(unused_extern_crates)]\n");
358+
crate_level_code.push_str("#[allow(unused_extern_crates)]\n");
293359

294-
prog.push_str(&format!("extern crate r#{crate_name};\n"));
360+
crate_level_code.push_str(&format!("extern crate r#{crate_name};\n"));
295361
line_offset += 1;
296362
}
297363

298364
// FIXME: This code cannot yet handle no_std test cases yet
299-
if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
300-
prog.push_str(everything_else);
365+
let wrapper = if dont_insert_main
366+
|| self.has_main_fn
367+
|| crate_level_code.contains("![no_std]")
368+
{
369+
None
301370
} else {
302-
let returns_result = everything_else.ends_with("(())");
371+
let returns_result = code.ends_with("(())");
303372
// Give each doctest main function a unique name.
304373
// This is for example needed for the tooling around `-C instrument-coverage`.
305374
let inner_fn_name = if let Some(ref test_id) = self.test_id {
@@ -333,28 +402,18 @@ impl DocTestBuilder {
333402
// /// ``` <- end of the inner main
334403
line_offset += 1;
335404

336-
prog.push_str(&main_pre);
337-
338-
// add extra 4 spaces for each line to offset the code block
339-
if opts.insert_indent_space {
340-
write!(
341-
prog,
342-
"{}",
343-
fmt::from_fn(|f| everything_else
344-
.lines()
345-
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
346-
.joined("\n", f))
347-
)
348-
.unwrap();
349-
} else {
350-
prog.push_str(everything_else);
351-
};
352-
prog.push_str(&main_post);
353-
}
354-
355-
debug!("final doctest:\n{prog}");
405+
Some(WrapperInfo {
406+
before: main_pre,
407+
after: main_post,
408+
returns_result,
409+
insert_indent_space: opts.insert_indent_space,
410+
})
411+
};
356412

357-
(prog, line_offset)
413+
(
414+
DocTestWrapResult::Valid { code: code.to_string(), wrapper, crate_level_code },
415+
line_offset,
416+
)
358417
}
359418
}
360419

src/librustdoc/doctest/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ fn make_test(
1919
builder = builder.test_id(test_id.to_string());
2020
}
2121
let doctest = builder.build(None);
22-
let (code, line_offset) =
22+
let (wrapper, line_offset) =
2323
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
24-
(code, line_offset)
24+
(wrapper.to_string(), line_offset)
2525
}
2626

2727
/// Default [`GlobalTestOptions`] for these unit tests.

src/librustdoc/html/markdown.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
307307
builder = builder.crate_name(krate);
308308
}
309309
let doctest = builder.build(None);
310-
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
310+
let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
311+
let test = wrapped.to_string();
311312
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
312313

313314
let test_escaped = small_url_encode(test);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"format_version":1,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":"#![allow(unused)]\nfn main() {\nlet x = 12;\nlet y = 14;\n}","name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":null,"name":"$DIR/extract-doctests.rs - (line 13)"}]}
1+
{"format_version":2,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":{"crate_level":"#![allow(unused)]\n","code":"let x = 12;\nlet y = 14;","wrapper":{"before":"fn main() {\n","after":"\n}","returns_result":false}},"name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":null,"name":"$DIR/extract-doctests.rs - (line 13)"}]}

0 commit comments

Comments
 (0)