Skip to content

Commit 5b0e218

Browse files
Give more information into extracted doctest information
1 parent d423c81 commit 5b0e218

File tree

6 files changed

+132
-48
lines changed

6 files changed

+132
-48
lines changed

src/librustdoc/doctest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,14 +1044,14 @@ fn doctest_run_fn(
10441044
let report_unused_externs = |uext| {
10451045
unused_externs.lock().unwrap().push(uext);
10461046
};
1047-
let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
1047+
let (wrapper, full_test_line_offset) = doctest.generate_unique_doctest(
10481048
&scraped_test.text,
10491049
scraped_test.langstr.test_harness,
10501050
&global_opts,
10511051
Some(&global_opts.crate_name),
10521052
);
10531053
let runnable_test = RunnableDocTest {
1054-
full_test_code,
1054+
full_test_code: wrapper.to_string(),
10551055
full_test_line_offset,
10561056
test_opts,
10571057
global_opts,

src/librustdoc/doctest/extracted.rs

Lines changed: 30 additions & 4 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 {
@@ -42,7 +43,7 @@ impl ExtractedDocTests {
4243
.edition(edition)
4344
.lang_str(&langstr)
4445
.build(None);
45-
let (full_test_code, size) = doctest.generate_unique_doctest(
46+
let (wrapper, _size) = doctest.generate_unique_doctest(
4647
&text,
4748
langstr.test_harness,
4849
opts,
@@ -52,21 +53,46 @@ impl ExtractedDocTests {
5253
file: filename.prefer_remapped_unconditionaly().to_string(),
5354
line,
5455
doctest_attributes: langstr.into(),
55-
doctest_code: if size != 0 { Some(full_test_code) } else { None },
56+
doctest_code: match wrapper {
57+
DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
58+
crate_level: crate_level_code,
59+
code,
60+
wrapper: wrapper.map(
61+
|super::make::WrapperInfo { before, after, returns_result, .. }| {
62+
WrapperInfo { before, after, returns_result }
63+
},
64+
),
65+
}),
66+
DocTestWrapResult::SyntaxError { .. } => None,
67+
},
5668
original_code: text,
5769
name,
5870
});
5971
}
6072
}
6173

74+
#[derive(Serialize)]
75+
pub(crate) struct WrapperInfo {
76+
before: String,
77+
after: String,
78+
returns_result: bool,
79+
}
80+
81+
#[derive(Serialize)]
82+
pub(crate) struct DocTest {
83+
crate_level: String,
84+
code: String,
85+
wrapper: Option<WrapperInfo>,
86+
}
87+
6288
#[derive(Serialize)]
6389
pub(crate) struct ExtractedDocTest {
6490
file: String,
6591
line: usize,
6692
doctest_attributes: LangString,
6793
original_code: String,
6894
/// `None` if the code syntax is invalid.
69-
doctest_code: Option<String>,
95+
doctest_code: Option<DocTest>,
7096
name: String,
7197
}
7298

src/librustdoc/doctest/make.rs

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,73 @@ pub(crate) struct DocTestBuilder {
184184
pub(crate) can_be_merged: bool,
185185
}
186186

187+
/// Contains needed information for doctest to be correctly generated with expected "wrapping".
188+
pub(crate) struct WrapperInfo {
189+
pub(crate) before: String,
190+
pub(crate) after: String,
191+
pub(crate) returns_result: bool,
192+
insert_indent_space: bool,
193+
}
194+
195+
impl WrapperInfo {
196+
fn len(&self) -> usize {
197+
self.before.len() + self.after.len()
198+
}
199+
}
200+
201+
/// Contains a doctest information. Can be converted into code with the `to_string()` method.
202+
pub(crate) enum DocTestWrapResult {
203+
Valid {
204+
crate_level_code: String,
205+
wrapper: Option<WrapperInfo>,
206+
code: String,
207+
},
208+
/// Contains the original source code.
209+
SyntaxError(String),
210+
}
211+
212+
impl std::string::ToString for DocTestWrapResult {
213+
fn to_string(&self) -> String {
214+
match self {
215+
Self::SyntaxError(s) => s.clone(),
216+
Self::Valid { crate_level_code, wrapper, code } => {
217+
let mut prog_len = code.len() + crate_level_code.len();
218+
if let Some(wrapper) = wrapper {
219+
prog_len += wrapper.len();
220+
if wrapper.insert_indent_space {
221+
prog_len += code.lines().count() * 4;
222+
}
223+
}
224+
let mut prog = String::with_capacity(prog_len);
225+
226+
prog.push_str(crate_level_code);
227+
if let Some(wrapper) = wrapper {
228+
prog.push_str(&wrapper.before);
229+
230+
// add extra 4 spaces for each line to offset the code block
231+
if wrapper.insert_indent_space {
232+
write!(
233+
prog,
234+
"{}",
235+
fmt::from_fn(|f| code
236+
.lines()
237+
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
238+
.joined("\n", f))
239+
)
240+
.unwrap();
241+
} else {
242+
prog.push_str(code);
243+
}
244+
prog.push_str(&wrapper.after);
245+
} else {
246+
prog.push_str(code);
247+
}
248+
prog
249+
}
250+
}
251+
}
252+
}
253+
187254
impl DocTestBuilder {
188255
fn invalid(
189256
crate_attrs: String,
@@ -214,49 +281,49 @@ impl DocTestBuilder {
214281
dont_insert_main: bool,
215282
opts: &GlobalTestOptions,
216283
crate_name: Option<&str>,
217-
) -> (String, usize) {
284+
) -> (DocTestWrapResult, usize) {
218285
if self.invalid_ast {
219286
// If the AST failed to compile, no need to go generate a complete doctest, the error
220287
// will be better this way.
221288
debug!("invalid AST:\n{test_code}");
222-
return (test_code.to_string(), 0);
289+
return (DocTestWrapResult::SyntaxError(test_code.to_string()), 0);
223290
}
224291
let mut line_offset = 0;
225-
let mut prog = String::new();
226-
let everything_else = self.everything_else.trim();
292+
let mut crate_level_code = String::new();
293+
let code = self.everything_else.trim().to_string();
227294
if opts.attrs.is_empty() {
228295
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
229296
// lints that are commonly triggered in doctests. The crate-level test attributes are
230297
// commonly used to make tests fail in case they trigger warnings, so having this there in
231298
// that case may cause some tests to pass when they shouldn't have.
232-
prog.push_str("#![allow(unused)]\n");
299+
crate_level_code.push_str("#![allow(unused)]\n");
233300
line_offset += 1;
234301
}
235302

236303
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
237304
for attr in &opts.attrs {
238-
prog.push_str(&format!("#![{attr}]\n"));
305+
crate_level_code.push_str(&format!("#![{attr}]\n"));
239306
line_offset += 1;
240307
}
241308

242309
// Now push any outer attributes from the example, assuming they
243310
// are intended to be crate attributes.
244311
if !self.crate_attrs.is_empty() {
245-
prog.push_str(&self.crate_attrs);
312+
crate_level_code.push_str(&self.crate_attrs);
246313
if !self.crate_attrs.ends_with('\n') {
247-
prog.push('\n');
314+
crate_level_code.push('\n');
248315
}
249316
}
250317
if !self.maybe_crate_attrs.is_empty() {
251-
prog.push_str(&self.maybe_crate_attrs);
318+
crate_level_code.push_str(&self.maybe_crate_attrs);
252319
if !self.maybe_crate_attrs.ends_with('\n') {
253-
prog.push('\n');
320+
crate_level_code.push('\n');
254321
}
255322
}
256323
if !self.crates.is_empty() {
257-
prog.push_str(&self.crates);
324+
crate_level_code.push_str(&self.crates);
258325
if !self.crates.ends_with('\n') {
259-
prog.push('\n');
326+
crate_level_code.push('\n');
260327
}
261328
}
262329

@@ -274,17 +341,20 @@ impl DocTestBuilder {
274341
{
275342
// rustdoc implicitly inserts an `extern crate` item for the own crate
276343
// which may be unused, so we need to allow the lint.
277-
prog.push_str("#[allow(unused_extern_crates)]\n");
344+
crate_level_code.push_str("#[allow(unused_extern_crates)]\n");
278345

279-
prog.push_str(&format!("extern crate r#{crate_name};\n"));
346+
crate_level_code.push_str(&format!("extern crate r#{crate_name};\n"));
280347
line_offset += 1;
281348
}
282349

283350
// FIXME: This code cannot yet handle no_std test cases yet
284-
if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
285-
prog.push_str(everything_else);
351+
let wrapper = if dont_insert_main
352+
|| self.has_main_fn
353+
|| crate_level_code.contains("![no_std]")
354+
{
355+
None
286356
} else {
287-
let returns_result = everything_else.ends_with("(())");
357+
let returns_result = code.ends_with("(())");
288358
// Give each doctest main function a unique name.
289359
// This is for example needed for the tooling around `-C instrument-coverage`.
290360
let inner_fn_name = if let Some(ref test_id) = self.test_id {
@@ -318,28 +388,15 @@ impl DocTestBuilder {
318388
// /// ``` <- end of the inner main
319389
line_offset += 1;
320390

321-
prog.push_str(&main_pre);
322-
323-
// add extra 4 spaces for each line to offset the code block
324-
if opts.insert_indent_space {
325-
write!(
326-
prog,
327-
"{}",
328-
fmt::from_fn(|f| everything_else
329-
.lines()
330-
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
331-
.joined("\n", f))
332-
)
333-
.unwrap();
334-
} else {
335-
prog.push_str(everything_else);
336-
};
337-
prog.push_str(&main_post);
338-
}
339-
340-
debug!("final doctest:\n{prog}");
391+
Some(WrapperInfo {
392+
before: main_pre,
393+
after: main_post,
394+
returns_result,
395+
insert_indent_space: opts.insert_indent_space,
396+
})
397+
};
341398

342-
(prog, line_offset)
399+
(DocTestWrapResult::Valid { code, wrapper, crate_level_code }, line_offset)
343400
}
344401
}
345402

src/librustdoc/doctest/tests.rs

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

2525
/// 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
@@ -308,7 +308,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
308308
builder = builder.crate_name(krate);
309309
}
310310
let doctest = builder.build(None);
311-
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
311+
let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
312+
let test = wrapped.to_string();
312313
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
313314

314315
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)