mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-19 11:07:32 +00:00
Give more information into extracted doctest information
This commit is contained in:
parent
40daf23eeb
commit
5864247b10
@ -1053,14 +1053,14 @@ fn doctest_run_fn(
|
|||||||
let report_unused_externs = |uext| {
|
let report_unused_externs = |uext| {
|
||||||
unused_externs.lock().unwrap().push(uext);
|
unused_externs.lock().unwrap().push(uext);
|
||||||
};
|
};
|
||||||
let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
|
let (wrapper, full_test_line_offset) = doctest.generate_unique_doctest(
|
||||||
&scraped_test.text,
|
&scraped_test.text,
|
||||||
scraped_test.langstr.test_harness,
|
scraped_test.langstr.test_harness,
|
||||||
&global_opts,
|
&global_opts,
|
||||||
Some(&global_opts.crate_name),
|
Some(&global_opts.crate_name),
|
||||||
);
|
);
|
||||||
let runnable_test = RunnableDocTest {
|
let runnable_test = RunnableDocTest {
|
||||||
full_test_code,
|
full_test_code: wrapper.to_string(),
|
||||||
full_test_line_offset,
|
full_test_line_offset,
|
||||||
test_opts,
|
test_opts,
|
||||||
global_opts,
|
global_opts,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::make::DocTestWrapResult;
|
||||||
use super::{BuildDocTestBuilder, ScrapedDocTest};
|
use super::{BuildDocTestBuilder, ScrapedDocTest};
|
||||||
use crate::config::Options as RustdocOptions;
|
use crate::config::Options as RustdocOptions;
|
||||||
use crate::html::markdown;
|
use crate::html::markdown;
|
||||||
@ -14,7 +15,7 @@ use crate::html::markdown;
|
|||||||
/// This integer is incremented with every breaking change to the API,
|
/// This integer is incremented with every breaking change to the API,
|
||||||
/// and is returned along with the JSON blob into the `format_version` root field.
|
/// and is returned along with the JSON blob into the `format_version` root field.
|
||||||
/// Consuming code should assert that this value matches the format version(s) that it supports.
|
/// Consuming code should assert that this value matches the format version(s) that it supports.
|
||||||
const FORMAT_VERSION: u32 = 1;
|
const FORMAT_VERSION: u32 = 2;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub(crate) struct ExtractedDocTests {
|
pub(crate) struct ExtractedDocTests {
|
||||||
@ -44,8 +45,7 @@ impl ExtractedDocTests {
|
|||||||
.edition(edition)
|
.edition(edition)
|
||||||
.lang_str(&langstr)
|
.lang_str(&langstr)
|
||||||
.build(None);
|
.build(None);
|
||||||
|
let (wrapper, _size) = doctest.generate_unique_doctest(
|
||||||
let (full_test_code, size) = doctest.generate_unique_doctest(
|
|
||||||
&text,
|
&text,
|
||||||
langstr.test_harness,
|
langstr.test_harness,
|
||||||
opts,
|
opts,
|
||||||
@ -55,13 +55,38 @@ impl ExtractedDocTests {
|
|||||||
file: filename.prefer_remapped_unconditionaly().to_string(),
|
file: filename.prefer_remapped_unconditionaly().to_string(),
|
||||||
line,
|
line,
|
||||||
doctest_attributes: langstr.into(),
|
doctest_attributes: langstr.into(),
|
||||||
doctest_code: if size != 0 { Some(full_test_code) } else { None },
|
doctest_code: match wrapper {
|
||||||
|
DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
|
||||||
|
crate_level: crate_level_code,
|
||||||
|
code,
|
||||||
|
wrapper: wrapper.map(
|
||||||
|
|super::make::WrapperInfo { before, after, returns_result, .. }| {
|
||||||
|
WrapperInfo { before, after, returns_result }
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
DocTestWrapResult::SyntaxError { .. } => None,
|
||||||
|
},
|
||||||
original_code: text,
|
original_code: text,
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct WrapperInfo {
|
||||||
|
before: String,
|
||||||
|
after: String,
|
||||||
|
returns_result: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct DocTest {
|
||||||
|
crate_level: String,
|
||||||
|
code: String,
|
||||||
|
wrapper: Option<WrapperInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub(crate) struct ExtractedDocTest {
|
pub(crate) struct ExtractedDocTest {
|
||||||
file: String,
|
file: String,
|
||||||
@ -69,7 +94,7 @@ pub(crate) struct ExtractedDocTest {
|
|||||||
doctest_attributes: LangString,
|
doctest_attributes: LangString,
|
||||||
original_code: String,
|
original_code: String,
|
||||||
/// `None` if the code syntax is invalid.
|
/// `None` if the code syntax is invalid.
|
||||||
doctest_code: Option<String>,
|
doctest_code: Option<DocTest>,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +196,73 @@ pub(crate) struct DocTestBuilder {
|
|||||||
pub(crate) can_be_merged: bool,
|
pub(crate) can_be_merged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains needed information for doctest to be correctly generated with expected "wrapping".
|
||||||
|
pub(crate) struct WrapperInfo {
|
||||||
|
pub(crate) before: String,
|
||||||
|
pub(crate) after: String,
|
||||||
|
pub(crate) returns_result: bool,
|
||||||
|
insert_indent_space: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapperInfo {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.before.len() + self.after.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains a doctest information. Can be converted into code with the `to_string()` method.
|
||||||
|
pub(crate) enum DocTestWrapResult {
|
||||||
|
Valid {
|
||||||
|
crate_level_code: String,
|
||||||
|
wrapper: Option<WrapperInfo>,
|
||||||
|
code: String,
|
||||||
|
},
|
||||||
|
/// Contains the original source code.
|
||||||
|
SyntaxError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::string::ToString for DocTestWrapResult {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::SyntaxError(s) => s.clone(),
|
||||||
|
Self::Valid { crate_level_code, wrapper, code } => {
|
||||||
|
let mut prog_len = code.len() + crate_level_code.len();
|
||||||
|
if let Some(wrapper) = wrapper {
|
||||||
|
prog_len += wrapper.len();
|
||||||
|
if wrapper.insert_indent_space {
|
||||||
|
prog_len += code.lines().count() * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut prog = String::with_capacity(prog_len);
|
||||||
|
|
||||||
|
prog.push_str(crate_level_code);
|
||||||
|
if let Some(wrapper) = wrapper {
|
||||||
|
prog.push_str(&wrapper.before);
|
||||||
|
|
||||||
|
// add extra 4 spaces for each line to offset the code block
|
||||||
|
if wrapper.insert_indent_space {
|
||||||
|
write!(
|
||||||
|
prog,
|
||||||
|
"{}",
|
||||||
|
fmt::from_fn(|f| code
|
||||||
|
.lines()
|
||||||
|
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
|
||||||
|
.joined("\n", f))
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
prog.push_str(code);
|
||||||
|
}
|
||||||
|
prog.push_str(&wrapper.after);
|
||||||
|
} else {
|
||||||
|
prog.push_str(code);
|
||||||
|
}
|
||||||
|
prog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DocTestBuilder {
|
impl DocTestBuilder {
|
||||||
fn invalid(
|
fn invalid(
|
||||||
global_crate_attrs: Vec<String>,
|
global_crate_attrs: Vec<String>,
|
||||||
@ -228,50 +295,49 @@ impl DocTestBuilder {
|
|||||||
dont_insert_main: bool,
|
dont_insert_main: bool,
|
||||||
opts: &GlobalTestOptions,
|
opts: &GlobalTestOptions,
|
||||||
crate_name: Option<&str>,
|
crate_name: Option<&str>,
|
||||||
) -> (String, usize) {
|
) -> (DocTestWrapResult, usize) {
|
||||||
if self.invalid_ast {
|
if self.invalid_ast {
|
||||||
// If the AST failed to compile, no need to go generate a complete doctest, the error
|
// If the AST failed to compile, no need to go generate a complete doctest, the error
|
||||||
// will be better this way.
|
// will be better this way.
|
||||||
debug!("invalid AST:\n{test_code}");
|
debug!("invalid AST:\n{test_code}");
|
||||||
return (test_code.to_string(), 0);
|
return (DocTestWrapResult::SyntaxError(test_code.to_string()), 0);
|
||||||
}
|
}
|
||||||
let mut line_offset = 0;
|
let mut line_offset = 0;
|
||||||
let mut prog = String::new();
|
let mut crate_level_code = String::new();
|
||||||
let everything_else = self.everything_else.trim();
|
let code = self.everything_else.trim();
|
||||||
|
|
||||||
if self.global_crate_attrs.is_empty() {
|
if self.global_crate_attrs.is_empty() {
|
||||||
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
|
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
|
||||||
// lints that are commonly triggered in doctests. The crate-level test attributes are
|
// lints that are commonly triggered in doctests. The crate-level test attributes are
|
||||||
// commonly used to make tests fail in case they trigger warnings, so having this there in
|
// commonly used to make tests fail in case they trigger warnings, so having this there in
|
||||||
// that case may cause some tests to pass when they shouldn't have.
|
// that case may cause some tests to pass when they shouldn't have.
|
||||||
prog.push_str("#![allow(unused)]\n");
|
crate_level_code.push_str("#![allow(unused)]\n");
|
||||||
line_offset += 1;
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, any attributes that came from #![doc(test(attr(...)))].
|
// Next, any attributes that came from #![doc(test(attr(...)))].
|
||||||
for attr in &self.global_crate_attrs {
|
for attr in &self.global_crate_attrs {
|
||||||
prog.push_str(&format!("#![{attr}]\n"));
|
crate_level_code.push_str(&format!("#![{attr}]\n"));
|
||||||
line_offset += 1;
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now push any outer attributes from the example, assuming they
|
// Now push any outer attributes from the example, assuming they
|
||||||
// are intended to be crate attributes.
|
// are intended to be crate attributes.
|
||||||
if !self.crate_attrs.is_empty() {
|
if !self.crate_attrs.is_empty() {
|
||||||
prog.push_str(&self.crate_attrs);
|
crate_level_code.push_str(&self.crate_attrs);
|
||||||
if !self.crate_attrs.ends_with('\n') {
|
if !self.crate_attrs.ends_with('\n') {
|
||||||
prog.push('\n');
|
crate_level_code.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.maybe_crate_attrs.is_empty() {
|
if !self.maybe_crate_attrs.is_empty() {
|
||||||
prog.push_str(&self.maybe_crate_attrs);
|
crate_level_code.push_str(&self.maybe_crate_attrs);
|
||||||
if !self.maybe_crate_attrs.ends_with('\n') {
|
if !self.maybe_crate_attrs.ends_with('\n') {
|
||||||
prog.push('\n');
|
crate_level_code.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.crates.is_empty() {
|
if !self.crates.is_empty() {
|
||||||
prog.push_str(&self.crates);
|
crate_level_code.push_str(&self.crates);
|
||||||
if !self.crates.ends_with('\n') {
|
if !self.crates.ends_with('\n') {
|
||||||
prog.push('\n');
|
crate_level_code.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,17 +355,20 @@ impl DocTestBuilder {
|
|||||||
{
|
{
|
||||||
// rustdoc implicitly inserts an `extern crate` item for the own crate
|
// rustdoc implicitly inserts an `extern crate` item for the own crate
|
||||||
// which may be unused, so we need to allow the lint.
|
// which may be unused, so we need to allow the lint.
|
||||||
prog.push_str("#[allow(unused_extern_crates)]\n");
|
crate_level_code.push_str("#[allow(unused_extern_crates)]\n");
|
||||||
|
|
||||||
prog.push_str(&format!("extern crate r#{crate_name};\n"));
|
crate_level_code.push_str(&format!("extern crate r#{crate_name};\n"));
|
||||||
line_offset += 1;
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This code cannot yet handle no_std test cases yet
|
// FIXME: This code cannot yet handle no_std test cases yet
|
||||||
if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
|
let wrapper = if dont_insert_main
|
||||||
prog.push_str(everything_else);
|
|| self.has_main_fn
|
||||||
|
|| crate_level_code.contains("![no_std]")
|
||||||
|
{
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
let returns_result = everything_else.ends_with("(())");
|
let returns_result = code.ends_with("(())");
|
||||||
// Give each doctest main function a unique name.
|
// Give each doctest main function a unique name.
|
||||||
// This is for example needed for the tooling around `-C instrument-coverage`.
|
// This is for example needed for the tooling around `-C instrument-coverage`.
|
||||||
let inner_fn_name = if let Some(ref test_id) = self.test_id {
|
let inner_fn_name = if let Some(ref test_id) = self.test_id {
|
||||||
@ -333,28 +402,18 @@ impl DocTestBuilder {
|
|||||||
// /// ``` <- end of the inner main
|
// /// ``` <- end of the inner main
|
||||||
line_offset += 1;
|
line_offset += 1;
|
||||||
|
|
||||||
prog.push_str(&main_pre);
|
Some(WrapperInfo {
|
||||||
|
before: main_pre,
|
||||||
|
after: main_post,
|
||||||
|
returns_result,
|
||||||
|
insert_indent_space: opts.insert_indent_space,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
// add extra 4 spaces for each line to offset the code block
|
(
|
||||||
if opts.insert_indent_space {
|
DocTestWrapResult::Valid { code: code.to_string(), wrapper, crate_level_code },
|
||||||
write!(
|
line_offset,
|
||||||
prog,
|
)
|
||||||
"{}",
|
|
||||||
fmt::from_fn(|f| everything_else
|
|
||||||
.lines()
|
|
||||||
.map(|line| fmt::from_fn(move |f| write!(f, " {line}")))
|
|
||||||
.joined("\n", f))
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
} else {
|
|
||||||
prog.push_str(everything_else);
|
|
||||||
};
|
|
||||||
prog.push_str(&main_post);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("final doctest:\n{prog}");
|
|
||||||
|
|
||||||
(prog, line_offset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ fn make_test(
|
|||||||
builder = builder.test_id(test_id.to_string());
|
builder = builder.test_id(test_id.to_string());
|
||||||
}
|
}
|
||||||
let doctest = builder.build(None);
|
let doctest = builder.build(None);
|
||||||
let (code, line_offset) =
|
let (wrapper, line_offset) =
|
||||||
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
|
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
|
||||||
(code, line_offset)
|
(wrapper.to_string(), line_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default [`GlobalTestOptions`] for these unit tests.
|
/// Default [`GlobalTestOptions`] for these unit tests.
|
||||||
|
@ -307,7 +307,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
|
|||||||
builder = builder.crate_name(krate);
|
builder = builder.crate_name(krate);
|
||||||
}
|
}
|
||||||
let doctest = builder.build(None);
|
let doctest = builder.build(None);
|
||||||
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
|
let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
|
||||||
|
let test = wrapped.to_string();
|
||||||
let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" };
|
let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" };
|
||||||
|
|
||||||
let test_escaped = small_url_encode(test);
|
let test_escaped = small_url_encode(test);
|
||||||
|
@ -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)"}]}
|
{"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)"}]}
|
Loading…
Reference in New Issue
Block a user