Skip to content

Commit c550d95

Browse files
bdalrhmtedinski
authored andcommitted
Detect which reference examples should check/codegen/verify. (rust-lang#363)
* Copy options from the rust code blocks in the markdown files. * Fix typos and add comments.
1 parent ca36771 commit c550d95

File tree

3 files changed

+144
-85
lines changed

3 files changed

+144
-85
lines changed

scripts/rmc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def compile_single_rust_file(input_filename, output_filename, verbose=False, deb
142142
"-Z", f"symbol-mangling-version={mangler}",
143143
"-Z", f"symbol_table_passes={' '.join(symbol_table_passes)}",
144144
f"--cfg={RMC_CFG}", "-o", output_filename, input_filename]
145+
if "RUSTFLAGS" in os.environ:
146+
build_cmd += os.environ["RUSTFLAGS"].split(" ")
145147
build_env = os.environ
146148
if debug:
147149
add_rmc_rustc_debug_to_env(build_env)

src/tools/compiletest/src/runtest.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,12 +2394,12 @@ impl<'test> TestCx<'test> {
23942394

23952395
/// Adds rmc scripts directory to the `PATH` environment variable.
23962396
fn add_rmc_dir_to_path(&self, command: &mut Command) {
2397-
// If the PATH enviornment variable is already defined,
2397+
// If the PATH environment variable is already defined,
23982398
if let Some((key, val)) = env::vars().find(|(key, _)| key == "PATH") {
23992399
// Add the RMC scripts directory to the PATH.
24002400
command.env(key, format!("{}:{}", self.config.rmc_dir_path.to_str().unwrap(), val));
24012401
} else {
2402-
// Otherwise, insert PATH as a new enviornment variable and set its value to the RMC scripts directory.
2402+
// Otherwise, insert PATH as a new environment variable and set its value to the RMC scripts directory.
24032403
command.env(
24042404
String::from("PATH"),
24052405
String::from(self.config.rmc_dir_path.to_str().unwrap()),
@@ -2411,7 +2411,10 @@ impl<'test> TestCx<'test> {
24112411
/// error message is printed to stdout if the check result is not expected.
24122412
fn check(&self) {
24132413
let mut rustc = Command::new("rmc-rustc");
2414-
rustc.args(["-Z", "no-codegen"]).arg(&self.testpaths.file);
2414+
rustc
2415+
.args(self.props.compile_flags.clone())
2416+
.args(["-Z", "no-codegen"])
2417+
.arg(&self.testpaths.file);
24152418
self.add_rmc_dir_to_path(&mut rustc);
24162419
let proc_res = self.compose_and_run_compiler(rustc, None);
24172420
if self.props.rmc_panic_step == Some(RMCFailStep::Check) {
@@ -2431,6 +2434,7 @@ impl<'test> TestCx<'test> {
24312434
fn codegen(&self) {
24322435
let mut rustc = Command::new("rmc-rustc");
24332436
rustc
2437+
.args(self.props.compile_flags.clone())
24342438
.args(["-Z", "codegen-backend=gotoc", "--cfg=rmc", "--out-dir"])
24352439
.arg(self.output_base_dir())
24362440
.arg(&self.testpaths.file);
@@ -2461,6 +2465,13 @@ impl<'test> TestCx<'test> {
24612465
// 2. It may pass some options that do not make sense for RMC
24622466
// So we create our own command to execute RMC and pass it to self.compose_and_run_compiler(...) directly.
24632467
let mut rmc = Command::new("rmc");
2468+
// We cannot pass rustc flags directly to RMC. Instead, we add them
2469+
// to the current environment through the `RUSTFLAGS` environment
2470+
// variable. RMC recognizes the variable and adds those flags to its
2471+
// internal call to rustc.
2472+
if !self.props.compile_flags.is_empty() {
2473+
rmc.env("RUSTFLAGS", self.props.compile_flags.join(" "));
2474+
}
24642475
// Pass the test path along with RMC and CBMC flags parsed from comments at the top of the test file.
24652476
rmc.args(&self.props.rmc_flags)
24662477
.arg("--input")

src/tools/dashboard/src/reference.rs

Lines changed: 128 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use crate::dashboard;
88
use pulldown_cmark::{Parser, Tag};
99
use std::{
1010
collections::HashMap,
11-
env, fs,
11+
env,
12+
fmt::{Debug, Formatter, Result},
13+
fs::{self, File},
14+
hash::Hash,
1215
io::{BufRead, BufReader},
1316
path::{Path, PathBuf},
1417
process::{Command, Stdio},
@@ -19,17 +22,18 @@ use std::{
1922
/// code to corresponding directories where the extracted rust code should
2023
/// reside.
2124
fn parse_hierarchy(summary_path: &Path) -> HashMap<PathBuf, PathBuf> {
25+
let summary_dir = summary_path.parent().unwrap().to_path_buf();
2226
let start = "# The Rust Reference\n\n[Introduction](introduction.md)";
2327
let summary = fs::read_to_string(summary_path).unwrap();
2428
assert!(summary.starts_with(start), "Error: The start of the summary file changed.");
2529
// Skip the title and introduction.
2630
let n = Parser::new(start).count();
2731
let parser = Parser::new(&summary).skip(n);
2832
// 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();
3034
let mut map = HashMap::new();
3135
// 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"));
3337
for event in parser {
3438
match event {
3539
pulldown_cmark::Event::End(Tag::Item) => {
@@ -42,7 +46,9 @@ fn parse_hierarchy(summary_path: &Path) -> HashMap<PathBuf, PathBuf> {
4246
// contain the title of the current chapter/section. So, we wait
4347
// for the end of the link tag before adding the path and
4448
// 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());
4652
}
4753
pulldown_cmark::Event::Text(text) => {
4854
// Add the current chapter/section title to the hierarchy.
@@ -54,87 +60,131 @@ fn parse_hierarchy(summary_path: &Path) -> HashMap<PathBuf, PathBuf> {
5460
map
5561
}
5662

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 }
8077
}
8178
}
8279

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))
9883
}
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() {
101129
// 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));
106147
}
107148
}
149+
// Delete the temporary directory.
150+
fs::remove_dir_all(gen_dir).unwrap();
151+
pairs
108152
}
109153

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();
131159
}
132160

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).
138188
let loop_tests: [PathBuf; 4] = [
139189
["src", "test", "ref", "Appendices", "Glossary", "263.rs"].iter().collect(),
140190
["src", "test", "ref", "Linkage", "190.rs"].iter().collect(),
@@ -193,7 +243,6 @@ fn run_examples(suite: &str, log_path: &Path) {
193243
]);
194244
cmd.env_clear().envs(filtered_env);
195245
cmd.stdout(Stdio::null());
196-
197246
cmd.spawn().unwrap().wait().unwrap();
198247
}
199248

@@ -258,20 +307,17 @@ fn display_dashboard(dashboard: dashboard::Tree) {
258307
/// displays their results in a terminal dashboard.
259308
pub fn display_reference_dashboard() {
260309
let summary_path: PathBuf = ["src", "doc", "reference", "src", "SUMMARY.md"].iter().collect();
261-
let ref_dir: PathBuf = ["src", "doc", "reference", "src"].iter().collect();
262310
let build_dir = &env::var("BUILD_DIR").unwrap();
263311
let triple = &env::var("TRIPLE").unwrap();
264-
let gen_dir: PathBuf = [build_dir, triple, "dashboard", "ref"].iter().collect();
265312
let log_path: PathBuf = [build_dir, triple, "dashboard", "ref.log"].iter().collect();
266313
// Parse the chapter/section hierarchy from the table of contents in The
267314
// Rust Reference.
268315
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);
273319
// Pre-process the examples before running them through `compiletest`.
274-
preprocess_examples(map.values().collect());
320+
preprocess_examples(&map);
275321
// Run `compiletest` on the reference examples.
276322
run_examples("ref", &log_path);
277323
// Parse `compiletest` log file.

0 commit comments

Comments
 (0)