@@ -8,7 +8,10 @@ use crate::dashboard;
8
8
use pulldown_cmark:: { Parser , Tag } ;
9
9
use std:: {
10
10
collections:: HashMap ,
11
- env, fs,
11
+ env,
12
+ fmt:: { Debug , Formatter , Result } ,
13
+ fs:: { self , File } ,
14
+ hash:: Hash ,
12
15
io:: { BufRead , BufReader } ,
13
16
path:: { Path , PathBuf } ,
14
17
process:: { Command , Stdio } ,
@@ -19,17 +22,18 @@ use std::{
19
22
/// code to corresponding directories where the extracted rust code should
20
23
/// reside.
21
24
fn parse_hierarchy ( summary_path : & Path ) -> HashMap < PathBuf , PathBuf > {
25
+ let summary_dir = summary_path. parent ( ) . unwrap ( ) . to_path_buf ( ) ;
22
26
let start = "# The Rust Reference\n \n [Introduction](introduction.md)" ;
23
27
let summary = fs:: read_to_string ( summary_path) . unwrap ( ) ;
24
28
assert ! ( summary. starts_with( start) , "Error: The start of the summary file changed." ) ;
25
29
// Skip the title and introduction.
26
30
let n = Parser :: new ( start) . count ( ) ;
27
31
let parser = Parser :: new ( & summary) . skip ( n) ;
28
32
// Set "ref" as the root of the hierarchical path.
29
- let mut hierarchy = PathBuf :: from ( " ref") ;
33
+ let mut hierarchy: PathBuf = [ "src" , "test" , " ref"] . iter ( ) . collect ( ) ;
30
34
let mut map = HashMap :: new ( ) ;
31
35
// Introduction is a especial case, so handle it separately.
32
- map. insert ( PathBuf :: from ( "introduction.md" ) , hierarchy. join ( "Introduction" ) ) ;
36
+ map. insert ( summary_dir . join ( "introduction.md" ) , hierarchy. join ( "Introduction" ) ) ;
33
37
for event in parser {
34
38
match event {
35
39
pulldown_cmark:: Event :: End ( Tag :: Item ) => {
@@ -42,7 +46,9 @@ fn parse_hierarchy(summary_path: &Path) -> HashMap<PathBuf, PathBuf> {
42
46
// contain the title of the current chapter/section. So, we wait
43
47
// for the end of the link tag before adding the path and
44
48
// hierarchy of the current chapter/section to the map.
45
- map. insert ( path. split ( '/' ) . collect ( ) , hierarchy. clone ( ) ) ;
49
+ let mut full_path = summary_dir. clone ( ) ;
50
+ full_path. extend ( path. split ( '/' ) ) ;
51
+ map. insert ( full_path, hierarchy. clone ( ) ) ;
46
52
}
47
53
pulldown_cmark:: Event :: Text ( text) => {
48
54
// Add the current chapter/section title to the hierarchy.
@@ -54,87 +60,131 @@ fn parse_hierarchy(summary_path: &Path) -> HashMap<PathBuf, PathBuf> {
54
60
map
55
61
}
56
62
57
- /// Extracts examples from the given relative `paths` in the `book_dir` and
58
- /// saves them in `gen_dir`.
59
- fn extract_examples ( paths : Vec < & PathBuf > , book_dir : & Path , gen_dir : & Path ) {
60
- for path in paths {
61
- let mut cmd = Command :: new ( "rustdoc" ) ;
62
- cmd. args ( [
63
- "+nightly" ,
64
- "--test" ,
65
- "-Z" ,
66
- "unstable-options" ,
67
- book_dir. join ( path) . to_str ( ) . unwrap ( ) ,
68
- "--test-builder" ,
69
- & [ "src" , "tools" , "dashboard" , "print.sh" ]
70
- . iter ( )
71
- . collect :: < PathBuf > ( )
72
- . to_str ( )
73
- . unwrap ( ) ,
74
- "--persist-doctests" ,
75
- gen_dir. to_str ( ) . unwrap ( ) ,
76
- "--no-run" ,
77
- ] ) ;
78
- cmd. stdout ( Stdio :: null ( ) ) ;
79
- cmd. spawn ( ) . unwrap ( ) . wait ( ) . unwrap ( ) ;
63
+ /// The data structure represents the "full" path to examples in the Rust books.
64
+ #[ derive( PartialEq , Eq , Hash ) ]
65
+ struct Example {
66
+ /// Path to the markdown file containing the example.
67
+ path : PathBuf ,
68
+ /// Line number of the code block introducing the example.
69
+ line : usize ,
70
+ }
71
+
72
+ impl Example {
73
+ /// Creates a new [`Example`] instance representing "full" path to the
74
+ /// Rust example.
75
+ fn new ( path : PathBuf , line : usize ) -> Example {
76
+ Example { path, line }
80
77
}
81
78
}
82
79
83
- /// Copies the extracted rust code in `from_dir` to `src/test` following the
84
- /// hierarchy specified by `map`.
85
- fn organize_examples ( map : & HashMap < PathBuf , PathBuf > , book_dir : & Path , from_dir : & Path ) {
86
- // The names of the extracted examples generated by `rustdoc` have the
87
- // format `<path>_<line-num>_<test-num>` where occurrences of '/', '-', and
88
- // '.' in <path> are replaced by '_'. This transformation is not injective,
89
- // so we cannot map those names back to the original markdown file path.
90
- // Instead, we apply the same transformation on the keys of `map` in the for
91
- // loop below and lookup <path> in those modified keys.
92
- let mut modified_map = HashMap :: new ( ) ;
93
- for ( path, hierarchy) in map. iter ( ) {
94
- modified_map. insert (
95
- book_dir. join ( path) . to_str ( ) . unwrap ( ) . replace ( & [ '\\' , '/' , '-' , '.' ] [ ..] , "_" ) ,
96
- hierarchy. clone ( ) ,
97
- ) ;
80
+ impl Debug for Example {
81
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> Result {
82
+ f. write_fmt ( format_args ! ( "{}:{}" , self . path. to_str( ) . unwrap( ) , self . line) )
98
83
}
99
- for dir in from_dir. read_dir ( ) . unwrap ( ) {
100
- let dir = dir. unwrap ( ) . path ( ) ;
84
+ }
85
+
86
+ /// Extracts examples from the markdown files specified by each key in the given
87
+ /// `map` and saves them in the directory specified by the corresponding value.
88
+ /// Returns a mapping from the original location of **_each_** example to the
89
+ /// path it was extracted to.
90
+ fn extract_examples ( par_map : HashMap < PathBuf , PathBuf > ) -> HashMap < Example , PathBuf > {
91
+ let mut full_map = HashMap :: new ( ) ;
92
+ for ( par_from, par_to) in par_map {
93
+ let pairs = extract ( & par_from, & par_to) ;
94
+ for ( key, val) in pairs {
95
+ full_map. insert ( key, val) ;
96
+ }
97
+ }
98
+ full_map
99
+ }
100
+
101
+ /// Extracts examples from the markdown files specified by `par_from` and saves
102
+ /// them in the directory specified by `par_to`. Returns a mapping from the
103
+ /// original location of **_each_** example to the path it was extracted to.
104
+ fn extract ( par_from : & Path , par_to : & Path ) -> Vec < ( Example , PathBuf ) > {
105
+ let build_dir = & env:: var ( "BUILD_DIR" ) . unwrap ( ) ;
106
+ let triple = & env:: var ( "TRIPLE" ) . unwrap ( ) ;
107
+ // Create a temporary directory to save the files generated by `rustdoc`.
108
+ let gen_dir: PathBuf = [ build_dir, triple, "dashboard" , "ref" ] . iter ( ) . collect ( ) ;
109
+ fs:: create_dir_all ( & gen_dir) . unwrap ( ) ;
110
+ let mut cmd = Command :: new ( "rustdoc" ) ;
111
+ cmd. args ( [
112
+ "+nightly" ,
113
+ "--test" ,
114
+ "-Z" ,
115
+ "unstable-options" ,
116
+ par_from. to_str ( ) . unwrap ( ) ,
117
+ "--test-builder" ,
118
+ & [ "src" , "tools" , "dashboard" , "print.sh" ] . iter ( ) . collect :: < PathBuf > ( ) . to_str ( ) . unwrap ( ) ,
119
+ "--persist-doctests" ,
120
+ gen_dir. to_str ( ) . unwrap ( ) ,
121
+ "--no-run" ,
122
+ ] ) ;
123
+ cmd. stdout ( Stdio :: null ( ) ) ;
124
+ cmd. spawn ( ) . unwrap ( ) . wait ( ) . unwrap ( ) ;
125
+ // Mapping from path and line number of rust example to where it was extracted to.
126
+ let mut pairs = Vec :: new ( ) ;
127
+
128
+ for dir in gen_dir. read_dir ( ) . unwrap ( ) {
101
129
// Some directories do not contain tests because the markdown file
102
- // instructs `rustdoc` to ignore those tests.
103
- if let Some ( example) = dir. read_dir ( ) . unwrap ( ) . next ( ) {
104
- let example = example. unwrap ( ) . path ( ) ;
105
- copy ( & example, & modified_map) ;
130
+ // instructs `rustdoc` to "ignore" those tests.
131
+ let dir = dir. unwrap ( ) . path ( ) ;
132
+ if let Some ( from) = dir. read_dir ( ) . unwrap ( ) . next ( ) {
133
+ // The path to each example extracted by `rustdoc` has the form:
134
+ // <from> = `<gen_dir>/<par_from>_<line>_<test-num>/rust_out`
135
+ // where occurrences of '/', '-', and '.' in <par_from> are replaced
136
+ // by '_'. We copy the file in this path to a new path of the form:
137
+ // <to> = `<par_to>/<line>.rs`
138
+ // We omit <test-num> because all tests have the same number, 0.
139
+ let from = from. unwrap ( ) . path ( ) ;
140
+ let path_line_test = dir. file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
141
+ let splits: Vec < _ > = path_line_test. rsplitn ( 3 , '_' ) . collect ( ) ;
142
+ let line: usize = splits[ 1 ] . parse ( ) . unwrap ( ) ;
143
+ let to = par_to. join ( format ! ( "{}.rs" , line) ) ;
144
+ fs:: create_dir_all ( par_to) . unwrap ( ) ;
145
+ fs:: copy ( & from, & to) . unwrap ( ) ;
146
+ pairs. push ( ( Example :: new ( par_from. to_path_buf ( ) , line) , to) ) ;
106
147
}
107
148
}
149
+ // Delete the temporary directory.
150
+ fs:: remove_dir_all ( gen_dir) . unwrap ( ) ;
151
+ pairs
108
152
}
109
153
110
- /// Copy the file specified by `from` to the corresponding location specified by
111
- /// `map`.
112
- fn copy ( from : & Path , map : & HashMap < String , PathBuf > ) {
113
- // The path specified by `from` has the form:
114
- // `build/<triple>/dashboard/ref/<key>_<line-num>_<test-num>/rust_out`
115
- // We copy the file in this path to a new path of the form:
116
- // `src/test/<val>/<line-num>.rs
117
- // where `map[<key>] == <val>`. We omit <test-num> because all tests have
118
- // the same number, 0.
119
- // Extract `<key>_<line-num>_<test-num>`.
120
- let key_line_test = from. parent ( ) . unwrap ( ) . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
121
- // Extract <key> and <line-num> from `key_line_test` to get <val> and
122
- // construct destination path.
123
- let splits: Vec < _ > = key_line_test. rsplitn ( 3 , '_' ) . collect ( ) ;
124
- let key = splits[ 2 ] ;
125
- let line = splits[ 1 ] ;
126
- let val = & map[ key] ;
127
- let name = & format ! ( "{}.rs" , line) ;
128
- let to = Path :: new ( "src" ) . join ( "test" ) . join ( val) . join ( name) ;
129
- fs:: create_dir_all ( to. parent ( ) . unwrap ( ) ) . unwrap ( ) ;
130
- fs:: copy ( & from, & to) . unwrap ( ) ;
154
+ /// Prepends the text in `path` with the given `text`.
155
+ fn prepend_text ( path : & Path , text : & str ) {
156
+ let code = fs:: read_to_string ( & path) . unwrap ( ) ;
157
+ let code = format ! ( "{}\n {}" , text, code) ;
158
+ fs:: write ( & path, code) . unwrap ( ) ;
131
159
}
132
160
133
- /// Pre-processes the tests in the specified `paths` before running them with
134
- /// `compiletest`.
135
- fn preprocess_examples ( _paths : Vec < & PathBuf > ) {
136
- // For now, we will only pre-process the tests that cause infinite loops.
137
- // TODO: properly implement this step (see issue #324).
161
+ /// Pre-processes the examples in `map` before running them with `compiletest`.
162
+ fn preprocess_examples ( map : & HashMap < Example , PathBuf > ) {
163
+ // Copy compiler configurations specified in the original markdown code
164
+ // block.
165
+ for ( from, to) in map. iter ( ) {
166
+ let file = File :: open ( & from. path ) . unwrap ( ) ;
167
+ // Skip to the first line of the example code block.
168
+ // Line numbers in files start with 1 but `nth(...)` starts with 0.
169
+ // Subtract 1 to account for the difference.
170
+ let line = BufReader :: new ( file) . lines ( ) . nth ( from. line - 1 ) . unwrap ( ) . unwrap ( ) ;
171
+ if line. contains ( "edition2015" ) {
172
+ prepend_text ( to, "// compile-flags: --edition 2015" ) ;
173
+ } else {
174
+ prepend_text ( to, "// compile-flags: --edition 2018" ) ;
175
+ }
176
+ // Most examples with `compile_fail` configuration fail because of
177
+ // check errors.
178
+ if line. contains ( "compile_fail" ) {
179
+ prepend_text ( to, "// rmc-check-fail" ) ;
180
+ }
181
+ // RMC should catch run-time errors.
182
+ if line. contains ( "should_panic" ) {
183
+ prepend_text ( to, "// rmc-verify-fail" ) ;
184
+ }
185
+ }
186
+ // For now, we will only manually pre-process the tests that cause infinite loops.
187
+ // TODO: Add support for manually adding options and assertions (see issue #324).
138
188
let loop_tests: [ PathBuf ; 4 ] = [
139
189
[ "src" , "test" , "ref" , "Appendices" , "Glossary" , "263.rs" ] . iter ( ) . collect ( ) ,
140
190
[ "src" , "test" , "ref" , "Linkage" , "190.rs" ] . iter ( ) . collect ( ) ,
@@ -193,7 +243,6 @@ fn run_examples(suite: &str, log_path: &Path) {
193
243
] ) ;
194
244
cmd. env_clear ( ) . envs ( filtered_env) ;
195
245
cmd. stdout ( Stdio :: null ( ) ) ;
196
-
197
246
cmd. spawn ( ) . unwrap ( ) . wait ( ) . unwrap ( ) ;
198
247
}
199
248
@@ -258,20 +307,17 @@ fn display_dashboard(dashboard: dashboard::Tree) {
258
307
/// displays their results in a terminal dashboard.
259
308
pub fn display_reference_dashboard ( ) {
260
309
let summary_path: PathBuf = [ "src" , "doc" , "reference" , "src" , "SUMMARY.md" ] . iter ( ) . collect ( ) ;
261
- let ref_dir: PathBuf = [ "src" , "doc" , "reference" , "src" ] . iter ( ) . collect ( ) ;
262
310
let build_dir = & env:: var ( "BUILD_DIR" ) . unwrap ( ) ;
263
311
let triple = & env:: var ( "TRIPLE" ) . unwrap ( ) ;
264
- let gen_dir: PathBuf = [ build_dir, triple, "dashboard" , "ref" ] . iter ( ) . collect ( ) ;
265
312
let log_path: PathBuf = [ build_dir, triple, "dashboard" , "ref.log" ] . iter ( ) . collect ( ) ;
266
313
// Parse the chapter/section hierarchy from the table of contents in The
267
314
// Rust Reference.
268
315
let map = parse_hierarchy ( & summary_path) ;
269
- // Extract examples from The Rust Reference.
270
- extract_examples ( map. keys ( ) . collect ( ) , & ref_dir, & gen_dir) ;
271
- // Reorganize those examples following the The Rust Reference hierarchy.
272
- organize_examples ( & map, & ref_dir, & gen_dir) ;
316
+ // Extract examples from The Rust Reference, organize them following the
317
+ // partial hierarchy in map, and return the full hierarchy map.
318
+ let map = extract_examples ( map) ;
273
319
// Pre-process the examples before running them through `compiletest`.
274
- preprocess_examples ( map. values ( ) . collect ( ) ) ;
320
+ preprocess_examples ( & map) ;
275
321
// Run `compiletest` on the reference examples.
276
322
run_examples ( "ref" , & log_path) ;
277
323
// Parse `compiletest` log file.
0 commit comments