Skip to content

Commit f651e5f

Browse files
bdalrhmtedinski
authored andcommitted
Add support for manually passing options to examples. (rust-lang#409)
* Add support for manually passing options to examples. * Ignore files with `.props` extension. * Use `WalkDir` instead of manually reading sub-directories.
1 parent 5f3c531 commit f651e5f

File tree

10 files changed

+137
-70
lines changed

10 files changed

+137
-70
lines changed

.github/workflows/copyright.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Get paths for files added
1616
id: git-diff
1717
run: |
18-
ignore='(.md|expected|ignore|gitignore)$'
18+
ignore='(.md|.props|expected|ignore|gitignore)$'
1919
files=$(git diff --ignore-submodules=all --name-only --diff-filter=A ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -v -E $ignore | xargs)
2020
echo "::set-output name=paths::$files"
2121

Cargo.lock

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,7 @@ name = "dashboard"
890890
version = "0.1.0"
891891
dependencies = [
892892
"pulldown-cmark 0.8.0",
893+
"walkdir",
893894
]
894895

895896
[[package]]
@@ -5595,9 +5596,9 @@ dependencies = [
55955596

55965597
[[package]]
55975598
name = "walkdir"
5598-
version = "2.3.1"
5599+
version = "2.3.2"
55995600
source = "registry+https://github.com/rust-lang/crates.io-index"
5600-
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
5601+
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
56015602
dependencies = [
56025603
"same-file",
56035604
"winapi",

src/tools/dashboard/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ edition = "2018"
88

99
[dependencies]
1010
pulldown-cmark = { version = "0.8.0", default-features = false }
11+
walkdir = "2.3.2"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// rmc-flags: --cbmc-args --unwind 4
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// rmc-codegen-fail
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// rmc-flags: --cbmc-args --unwind 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// rmc-flags: --cbmc-args --unwind 4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// rmc-flags: --cbmc-args --unwind 1

src/tools/dashboard/src/reference.rs

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@
44
//! [The Rust Reference](https://doc.rust-lang.org/nightly/reference),
55
//! run them through RMC, and display their results.
66
7-
use crate::{dashboard, litani::Litani, util};
7+
use crate::{
8+
dashboard,
9+
litani::Litani,
10+
util::{self, FailStep, TestProps},
11+
};
812
use pulldown_cmark::{Parser, Tag};
913
use std::{
10-
collections::HashMap,
14+
collections::{HashMap, HashSet},
1115
env,
12-
fmt::{Debug, Formatter, Result},
16+
fmt::{Debug, Formatter, Result, Write},
1317
fs::{self, File},
1418
hash::Hash,
1519
io::{BufRead, BufReader},
1620
path::{Path, PathBuf},
1721
process::{Command, Stdio},
1822
};
23+
use walkdir::WalkDir;
1924

2025
/// Parses the chapter/section hierarchy in the markdown file specified by
2126
/// `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)> {
151156
pairs
152157
}
153158

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
159186
}
160187

161188
/// Pre-processes the examples in `map` before running them with `compiletest`.
162189
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.
165195
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+
};
166217
let file = File::open(&from.path).unwrap();
167218
// Skip to the first line of the example code block.
168219
// Line numbers in files start with 1 but `nth(...)` starts with 0.
169220
// Subtract 1 to account for the difference.
170221
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"));
175225
}
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);
180231
}
181232
// 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);
184235
}
236+
// Prepend those properties to test/example file.
237+
prepend_props(&props);
185238
}
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+
);
219249
}
250+
// TODO: Add support for manually adding assertions (see issue #324).
220251
}
221252

222253
/// Runs `compiletest` on the `suite` and logs the results to `log_path`.
@@ -310,18 +341,12 @@ fn litani_run_tests() {
310341
let ref_dir: PathBuf = ["src", "test", "ref"].iter().collect();
311342
util::add_rmc_and_litani_to_path();
312343
let mut litani = Litani::init("RMC", &output_prefix, &output_symlink);
313-
let mut stack = vec![ref_dir];
314344
// 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);
325350
}
326351
}
327352
litani.run_build();

src/tools/dashboard/src/util.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use crate::litani::Litani;
1010
use std::{
1111
env,
12+
fmt::{self, Display, Formatter, Write},
1213
fs::File,
1314
io::{BufRead, BufReader},
1415
path::{Path, PathBuf},
@@ -30,6 +31,17 @@ pub enum FailStep {
3031
Verification,
3132
}
3233

34+
impl Display for FailStep {
35+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36+
let str = match self {
37+
FailStep::Check => "check",
38+
FailStep::Codegen => "codegen",
39+
FailStep::Verification => "verify",
40+
};
41+
f.write_str(str)
42+
}
43+
}
44+
3345
/// Data structure representing properties specific to each test.
3446
pub struct TestProps {
3547
pub path: PathBuf,
@@ -53,6 +65,29 @@ impl TestProps {
5365
}
5466
}
5567

68+
impl Display for TestProps {
69+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70+
if let Some(fail_step) = &self.fail_step {
71+
f.write_fmt(format_args!("// rmc-{}-fail\n", fail_step))?;
72+
}
73+
if !self.rustc_args.is_empty() {
74+
f.write_str("// compile-flags:")?;
75+
for arg in &self.rustc_args {
76+
f.write_fmt(format_args!(" {}", arg))?;
77+
}
78+
f.write_char('\n')?;
79+
}
80+
if !self.rmc_args.is_empty() {
81+
f.write_str("// rmc-flags:")?;
82+
for arg in &self.rmc_args {
83+
f.write_fmt(format_args!(" {}", arg))?;
84+
}
85+
f.write_char('\n')?;
86+
}
87+
Ok(())
88+
}
89+
}
90+
5691
/// Parses strings of the form `rmc-*-fail` and returns the step at which RMC is
5792
/// expected to panic.
5893
fn try_parse_fail_step(cur_fail_step: Option<FailStep>, line: &str) -> Option<FailStep> {
@@ -140,7 +175,7 @@ pub fn add_check_job(litani: &mut Litani, test_props: &TestProps) {
140175
test_props.path.to_str().unwrap(),
141176
"build",
142177
exit_status,
143-
1,
178+
5,
144179
);
145180
}
146181

@@ -166,15 +201,15 @@ pub fn add_codegen_job(litani: &mut Litani, test_props: &TestProps) {
166201
test_props.path.to_str().unwrap(),
167202
"test",
168203
exit_status,
169-
1,
204+
5,
170205
);
171206
}
172207

173208
// Does verification pass/fail as it is expected to?
174209
pub fn add_verification_job(litani: &mut Litani, test_props: &TestProps) {
175210
let exit_status = if test_props.fail_step == Some(FailStep::Verification) { 10 } else { 0 };
176211
let mut rmc = Command::new("rmc");
177-
rmc.args(&test_props.rmc_args).arg(&test_props.path);
212+
rmc.arg(&test_props.path).args(&test_props.rmc_args);
178213
if !test_props.rustc_args.is_empty() {
179214
rmc.env("RUSTFLAGS", test_props.rustc_args.join(" "));
180215
}

0 commit comments

Comments
 (0)