rustdoc/doctest/
markdown.rs1use std::fs::read_to_string;
4use std::sync::{Arc, Mutex};
5
6use rustc_session::config::Input;
7use rustc_span::{DUMMY_SP, FileName};
8use tempfile::tempdir;
9
10use super::{
11 CreateRunnableDocTests, DocTestVisitor, GlobalTestOptions, ScrapedDocTest, generate_args_file,
12};
13use crate::config::Options;
14use crate::html::markdown::{ErrorCodes, LangString, MdRelLine, find_testable_code};
15
16struct MdCollector {
17 tests: Vec<ScrapedDocTest>,
18 cur_path: Vec<String>,
19 filename: FileName,
20}
21
22impl DocTestVisitor for MdCollector {
23 fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
24 let filename = self.filename.clone();
25 let line = 1 + rel_line.offset();
27 self.tests.push(ScrapedDocTest::new(
28 filename,
29 line,
30 self.cur_path.clone(),
31 config,
32 test,
33 DUMMY_SP,
34 ));
35 }
36
37 fn visit_header(&mut self, name: &str, level: u32) {
38 let name = name
41 .chars()
42 .enumerate()
43 .map(|(i, c)| {
44 if (i == 0 && rustc_lexer::is_id_start(c))
45 || (i != 0 && rustc_lexer::is_id_continue(c))
46 {
47 c
48 } else {
49 '_'
50 }
51 })
52 .collect::<String>();
53
54 let level = level as usize;
59 if level <= self.cur_path.len() {
60 self.cur_path.truncate(level);
65 self.cur_path[level - 1] = name;
66 } else {
67 if level - 1 > self.cur_path.len() {
72 self.cur_path.resize(level - 1, "_".to_owned());
73 }
74 self.cur_path.push(name);
75 }
76 }
77}
78
79pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
81 let input_str = match input {
82 Input::File(path) => {
83 read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?
84 }
85 Input::Str { name: _, input } => input.clone(),
86 };
87
88 let crate_name = input.filestem().to_string();
90 let temp_dir =
91 tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
92 let args_file = temp_dir.path().join("rustdoc-cfgs");
93 generate_args_file(&args_file, &options)?;
94
95 let opts = GlobalTestOptions {
96 crate_name,
97 no_crate_inject: true,
98 insert_indent_space: false,
99 attrs: vec![],
100 args_file,
101 };
102
103 let mut md_collector = MdCollector {
104 tests: vec![],
105 cur_path: vec![],
106 filename: input
107 .opt_path()
108 .map(ToOwned::to_owned)
109 .map(FileName::from)
110 .unwrap_or(FileName::Custom("input".to_owned())),
111 };
112 let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
113
114 find_testable_code(&input_str, &mut md_collector, codes, None);
115
116 let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
117 md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
118 let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
119 collector;
120 crate::doctest::run_tests(
121 opts,
122 &rustdoc_options,
123 &Arc::new(Mutex::new(Vec::new())),
124 standalone_tests,
125 mergeable_tests,
126 None,
127 );
128 Ok(())
129}