|
4 | 4 | //! [The Rust Reference](https://doc.rust-lang.org/nightly/reference),
|
5 | 5 | //! run them through RMC, and display their results.
|
6 | 6 |
|
7 |
| -use crate::{dashboard, litani::Litani, util}; |
| 7 | +use crate::{ |
| 8 | + dashboard, |
| 9 | + litani::Litani, |
| 10 | + util::{self, FailStep, TestProps}, |
| 11 | +}; |
8 | 12 | use pulldown_cmark::{Parser, Tag};
|
9 | 13 | use std::{
|
10 |
| - collections::HashMap, |
| 14 | + collections::{HashMap, HashSet}, |
11 | 15 | env,
|
12 |
| - fmt::{Debug, Formatter, Result}, |
| 16 | + fmt::{Debug, Formatter, Result, Write}, |
13 | 17 | fs::{self, File},
|
14 | 18 | hash::Hash,
|
15 | 19 | io::{BufRead, BufReader},
|
16 | 20 | path::{Path, PathBuf},
|
17 | 21 | process::{Command, Stdio},
|
18 | 22 | };
|
| 23 | +use walkdir::WalkDir; |
19 | 24 |
|
20 | 25 | /// Parses the chapter/section hierarchy in the markdown file specified by
|
21 | 26 | /// `summary_path` and returns a mapping from markdown files containing rust
|
@@ -151,72 +156,98 @@ fn extract(par_from: &Path, par_to: &Path) -> Vec<(Example, PathBuf)> {
|
151 | 156 | pairs
|
152 | 157 | }
|
153 | 158 |
|
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(); |
| 159 | +/// Returns a set of paths to the config files for examples in the Rust books. |
| 160 | +fn get_config_paths() -> HashSet<PathBuf> { |
| 161 | + let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); |
| 162 | + let mut config_paths = HashSet::new(); |
| 163 | + for entry in WalkDir::new(config_dir) { |
| 164 | + let entry = entry.unwrap().into_path(); |
| 165 | + if entry.is_file() { |
| 166 | + config_paths.insert(entry); |
| 167 | + } |
| 168 | + } |
| 169 | + config_paths |
| 170 | +} |
| 171 | + |
| 172 | +/// Prepends the given `props` to the test file in `props.test`. |
| 173 | +fn prepend_props(props: &TestProps) { |
| 174 | + let code = fs::read_to_string(&props.path).unwrap(); |
| 175 | + let code = format!("{}{}", props, code); |
| 176 | + fs::write(&props.path, code).unwrap(); |
| 177 | +} |
| 178 | + |
| 179 | +/// Pretty prints the `paths` set. |
| 180 | +fn paths_to_string(paths: HashSet<PathBuf>) -> String { |
| 181 | + let mut f = String::new(); |
| 182 | + for path in paths { |
| 183 | + f.write_fmt(format_args!(" {:?}\n", path.to_str().unwrap())).unwrap(); |
| 184 | + } |
| 185 | + f |
159 | 186 | }
|
160 | 187 |
|
161 | 188 | /// Pre-processes the examples in `map` before running them with `compiletest`.
|
162 | 189 | fn preprocess_examples(map: &HashMap<Example, PathBuf>) {
|
163 |
| - // Copy compiler configurations specified in the original markdown code |
164 |
| - // block. |
| 190 | + let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); |
| 191 | + let test_dir: PathBuf = ["src", "test"].iter().collect(); |
| 192 | + let mut config_paths = get_config_paths(); |
| 193 | + // Copy compiler annotations specified in the original markdown code blocks |
| 194 | + // and custom configurations under the `config` directory. |
165 | 195 | for (from, to) in map.iter() {
|
| 196 | + // Path `to` has the following form: |
| 197 | + // `src/test/ref/<hierarchy>/<line-num>.rs` |
| 198 | + // If it has a custom props file, the path to the props file will have |
| 199 | + // the following form: |
| 200 | + // `src/tools/dashboard/configs/ref/<hierarchy>/<line-num>.props` |
| 201 | + // where <hierarchy> and <line-num> are the same for both paths. |
| 202 | + let mut props_path = config_dir.join(to.strip_prefix(&test_dir).unwrap()); |
| 203 | + props_path.set_extension("props"); |
| 204 | + let mut props = if props_path.exists() { |
| 205 | + config_paths.remove(&props_path); |
| 206 | + // Parse the properties in the file. The format follows the same |
| 207 | + // conventions for the headers in RMC regressions. |
| 208 | + let mut props = util::parse_test_header(&props_path); |
| 209 | + // `util::parse_test_header` thinks `props_path` is the path to the |
| 210 | + // test. That is not the case, `to` is the actual path to the |
| 211 | + // test/example. |
| 212 | + props.path = to.clone(); |
| 213 | + props |
| 214 | + } else { |
| 215 | + TestProps::new(to.clone(), None, Vec::new(), Vec::new()) |
| 216 | + }; |
166 | 217 | let file = File::open(&from.path).unwrap();
|
167 | 218 | // Skip to the first line of the example code block.
|
168 | 219 | // Line numbers in files start with 1 but `nth(...)` starts with 0.
|
169 | 220 | // Subtract 1 to account for the difference.
|
170 | 221 | 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"); |
| 222 | + if !line.contains("edition2015") { |
| 223 | + props.rustc_args.push(String::from("--edition")); |
| 224 | + props.rustc_args.push(String::from("2018")); |
175 | 225 | }
|
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"); |
| 226 | + // Most examples with `compile_fail` annotation fail because of check |
| 227 | + // errors. This heuristic can be overridden by manually specifying the |
| 228 | + // fail step in the corresponding config file. |
| 229 | + if props.fail_step.is_none() && line.contains("compile_fail") { |
| 230 | + props.fail_step = Some(FailStep::Check); |
180 | 231 | }
|
181 | 232 | // RMC should catch run-time errors.
|
182 |
| - if line.contains("should_panic") { |
183 |
| - prepend_text(to, "// rmc-verify-fail"); |
| 233 | + if props.fail_step.is_none() && line.contains("should_panic") { |
| 234 | + props.fail_step = Some(FailStep::Verification); |
184 | 235 | }
|
| 236 | + // Prepend those properties to test/example file. |
| 237 | + prepend_props(&props); |
185 | 238 | }
|
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). |
188 |
| - let loop_tests: [PathBuf; 4] = [ |
189 |
| - ["src", "test", "ref", "Appendices", "Glossary", "263.rs"].iter().collect(), |
190 |
| - ["src", "test", "ref", "Linkage", "190.rs"].iter().collect(), |
191 |
| - [ |
192 |
| - "src", |
193 |
| - "test", |
194 |
| - "ref", |
195 |
| - "Statements and expressions", |
196 |
| - "Expressions", |
197 |
| - "Loop expressions", |
198 |
| - "133.rs", |
199 |
| - ] |
200 |
| - .iter() |
201 |
| - .collect(), |
202 |
| - [ |
203 |
| - "src", |
204 |
| - "test", |
205 |
| - "ref", |
206 |
| - "Statements and expressions", |
207 |
| - "Expressions", |
208 |
| - "Method call expressions", |
209 |
| - "10.rs", |
210 |
| - ] |
211 |
| - .iter() |
212 |
| - .collect(), |
213 |
| - ]; |
214 |
| - |
215 |
| - for test in loop_tests { |
216 |
| - let code = fs::read_to_string(&test).unwrap(); |
217 |
| - let code = format!("// rmc-flags: --cbmc-args --unwind 1\n{}", code); |
218 |
| - fs::write(&test, code).unwrap(); |
| 239 | + if !config_paths.is_empty() { |
| 240 | + panic!( |
| 241 | + "Error: The examples corresponding to the following config files \ |
| 242 | + were not encountered in the pre-processing step:\n{}This is most \ |
| 243 | + likely because the line numbers of the config files are not in \ |
| 244 | + sync with the line numbers of the corresponding code blocks in \ |
| 245 | + the latest versions of the Rust books. Please update the line \ |
| 246 | + numbers of the config files and rerun the program.", |
| 247 | + paths_to_string(config_paths) |
| 248 | + ); |
219 | 249 | }
|
| 250 | + // TODO: Add support for manually adding assertions (see issue #324). |
220 | 251 | }
|
221 | 252 |
|
222 | 253 | /// Runs `compiletest` on the `suite` and logs the results to `log_path`.
|
@@ -310,18 +341,12 @@ fn litani_run_tests() {
|
310 | 341 | let ref_dir: PathBuf = ["src", "test", "ref"].iter().collect();
|
311 | 342 | util::add_rmc_and_litani_to_path();
|
312 | 343 | let mut litani = Litani::init("RMC", &output_prefix, &output_symlink);
|
313 |
| - let mut stack = vec![ref_dir]; |
314 | 344 | // Run all tests under the `src/test/ref` directory.
|
315 |
| - while !stack.is_empty() { |
316 |
| - let cur_dir = stack.pop().unwrap(); |
317 |
| - for child in cur_dir.read_dir().unwrap() { |
318 |
| - let child = child.unwrap().path(); |
319 |
| - if child.is_file() { |
320 |
| - let test_props = util::parse_test_header(&child); |
321 |
| - util::add_test_pipeline(&mut litani, &test_props); |
322 |
| - } else { |
323 |
| - stack.push(child); |
324 |
| - } |
| 345 | + for entry in WalkDir::new(ref_dir) { |
| 346 | + let entry = entry.unwrap().into_path(); |
| 347 | + if entry.is_file() { |
| 348 | + let test_props = util::parse_test_header(&entry); |
| 349 | + util::add_test_pipeline(&mut litani, &test_props); |
325 | 350 | }
|
326 | 351 | }
|
327 | 352 | litani.run_build();
|
|
0 commit comments