Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 60600a6

Browse files
committedSep 19, 2024·
Break up compiletest runtest.rs into smaller helper modules
Previously compiletest's `runtest.rs` was a massive 4700 lines file that made reading and navigation very awkward. This commit intentionally does not neatly reorganize where all the methods on `TestCx` goes, that is intended for a follow-up PR.
1 parent b7b9453 commit 60600a6

File tree

16 files changed

+2266
-2151
lines changed

16 files changed

+2266
-2151
lines changed
 

‎src/tools/compiletest/src/runtest.rs

Lines changed: 212 additions & 2149 deletions
Large diffs are not rendered by default.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use super::TestCx;
2+
3+
impl TestCx<'_> {
4+
pub(super) fn run_assembly_test(&self) {
5+
if self.config.llvm_filecheck.is_none() {
6+
self.fatal("missing --llvm-filecheck");
7+
}
8+
9+
let (proc_res, output_path) = self.compile_test_and_save_assembly();
10+
if !proc_res.status.success() {
11+
self.fatal_proc_rec("compilation failed!", &proc_res);
12+
}
13+
14+
let proc_res = self.verify_with_filecheck(&output_path);
15+
if !proc_res.status.success() {
16+
self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res);
17+
}
18+
}
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use super::{PassMode, TestCx};
2+
3+
impl TestCx<'_> {
4+
pub(super) fn run_codegen_test(&self) {
5+
if self.config.llvm_filecheck.is_none() {
6+
self.fatal("missing --llvm-filecheck");
7+
}
8+
9+
let (proc_res, output_path) = self.compile_test_and_save_ir();
10+
if !proc_res.status.success() {
11+
self.fatal_proc_rec("compilation failed!", &proc_res);
12+
}
13+
14+
if let Some(PassMode::Build) = self.pass_mode() {
15+
return;
16+
}
17+
let proc_res = self.verify_with_filecheck(&output_path);
18+
if !proc_res.status.success() {
19+
self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res);
20+
}
21+
}
22+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use std::collections::HashSet;
2+
3+
use super::{Emit, TestCx, WillExecute};
4+
use crate::errors;
5+
use crate::util::static_regex;
6+
7+
impl TestCx<'_> {
8+
pub(super) fn run_codegen_units_test(&self) {
9+
assert!(self.revision.is_none(), "revisions not relevant here");
10+
11+
let proc_res = self.compile_test(WillExecute::No, Emit::None);
12+
13+
if !proc_res.status.success() {
14+
self.fatal_proc_rec("compilation failed!", &proc_res);
15+
}
16+
17+
self.check_no_compiler_crash(&proc_res, self.props.should_ice);
18+
19+
const PREFIX: &str = "MONO_ITEM ";
20+
const CGU_MARKER: &str = "@@";
21+
22+
// Some MonoItems can contain {closure@/path/to/checkout/tests/codgen-units/test.rs}
23+
// To prevent the current dir from leaking, we just replace the entire path to the test
24+
// file with TEST_PATH.
25+
let actual: Vec<MonoItem> = proc_res
26+
.stdout
27+
.lines()
28+
.filter(|line| line.starts_with(PREFIX))
29+
.map(|line| {
30+
line.replace(&self.testpaths.file.display().to_string(), "TEST_PATH").to_string()
31+
})
32+
.map(|line| str_to_mono_item(&line, true))
33+
.collect();
34+
35+
let expected: Vec<MonoItem> = errors::load_errors(&self.testpaths.file, None)
36+
.iter()
37+
.map(|e| str_to_mono_item(&e.msg[..], false))
38+
.collect();
39+
40+
let mut missing = Vec::new();
41+
let mut wrong_cgus = Vec::new();
42+
43+
for expected_item in &expected {
44+
let actual_item_with_same_name = actual.iter().find(|ti| ti.name == expected_item.name);
45+
46+
if let Some(actual_item) = actual_item_with_same_name {
47+
if !expected_item.codegen_units.is_empty() &&
48+
// Also check for codegen units
49+
expected_item.codegen_units != actual_item.codegen_units
50+
{
51+
wrong_cgus.push((expected_item.clone(), actual_item.clone()));
52+
}
53+
} else {
54+
missing.push(expected_item.string.clone());
55+
}
56+
}
57+
58+
let unexpected: Vec<_> = actual
59+
.iter()
60+
.filter(|acgu| !expected.iter().any(|ecgu| acgu.name == ecgu.name))
61+
.map(|acgu| acgu.string.clone())
62+
.collect();
63+
64+
if !missing.is_empty() {
65+
missing.sort();
66+
67+
println!("\nThese items should have been contained but were not:\n");
68+
69+
for item in &missing {
70+
println!("{}", item);
71+
}
72+
73+
println!("\n");
74+
}
75+
76+
if !unexpected.is_empty() {
77+
let sorted = {
78+
let mut sorted = unexpected.clone();
79+
sorted.sort();
80+
sorted
81+
};
82+
83+
println!("\nThese items were contained but should not have been:\n");
84+
85+
for item in sorted {
86+
println!("{}", item);
87+
}
88+
89+
println!("\n");
90+
}
91+
92+
if !wrong_cgus.is_empty() {
93+
wrong_cgus.sort_by_key(|pair| pair.0.name.clone());
94+
println!("\nThe following items were assigned to wrong codegen units:\n");
95+
96+
for &(ref expected_item, ref actual_item) in &wrong_cgus {
97+
println!("{}", expected_item.name);
98+
println!(" expected: {}", codegen_units_to_str(&expected_item.codegen_units));
99+
println!(" actual: {}", codegen_units_to_str(&actual_item.codegen_units));
100+
println!();
101+
}
102+
}
103+
104+
if !(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty()) {
105+
panic!();
106+
}
107+
108+
#[derive(Clone, Eq, PartialEq)]
109+
struct MonoItem {
110+
name: String,
111+
codegen_units: HashSet<String>,
112+
string: String,
113+
}
114+
115+
// [MONO_ITEM] name [@@ (cgu)+]
116+
fn str_to_mono_item(s: &str, cgu_has_crate_disambiguator: bool) -> MonoItem {
117+
let s = if s.starts_with(PREFIX) { (&s[PREFIX.len()..]).trim() } else { s.trim() };
118+
119+
let full_string = format!("{}{}", PREFIX, s);
120+
121+
let parts: Vec<&str> =
122+
s.split(CGU_MARKER).map(str::trim).filter(|s| !s.is_empty()).collect();
123+
124+
let name = parts[0].trim();
125+
126+
let cgus = if parts.len() > 1 {
127+
let cgus_str = parts[1];
128+
129+
cgus_str
130+
.split(' ')
131+
.map(str::trim)
132+
.filter(|s| !s.is_empty())
133+
.map(|s| {
134+
if cgu_has_crate_disambiguator {
135+
remove_crate_disambiguators_from_set_of_cgu_names(s)
136+
} else {
137+
s.to_string()
138+
}
139+
})
140+
.collect()
141+
} else {
142+
HashSet::new()
143+
};
144+
145+
MonoItem { name: name.to_owned(), codegen_units: cgus, string: full_string }
146+
}
147+
148+
fn codegen_units_to_str(cgus: &HashSet<String>) -> String {
149+
let mut cgus: Vec<_> = cgus.iter().collect();
150+
cgus.sort();
151+
152+
let mut string = String::new();
153+
for cgu in cgus {
154+
string.push_str(&cgu[..]);
155+
string.push(' ');
156+
}
157+
158+
string
159+
}
160+
161+
// Given a cgu-name-prefix of the form <crate-name>.<crate-disambiguator> or
162+
// the form <crate-name1>.<crate-disambiguator1>-in-<crate-name2>.<crate-disambiguator2>,
163+
// remove all crate-disambiguators.
164+
fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String {
165+
let Some(captures) =
166+
static_regex!(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?")
167+
.captures(cgu)
168+
else {
169+
panic!("invalid cgu name encountered: {cgu}");
170+
};
171+
172+
let mut new_name = cgu.to_owned();
173+
174+
if let Some(d2) = captures.name("d2") {
175+
new_name.replace_range(d2.start()..d2.end(), "");
176+
}
177+
178+
let d1 = captures.name("d1").unwrap();
179+
new_name.replace_range(d1.start()..d1.end(), "");
180+
181+
new_name
182+
}
183+
184+
// The name of merged CGUs is constructed as the names of the original
185+
// CGUs joined with "--". This function splits such composite CGU names
186+
// and handles each component individually.
187+
fn remove_crate_disambiguators_from_set_of_cgu_names(cgus: &str) -> String {
188+
cgus.split("--").map(remove_crate_disambiguator_from_cgu).collect::<Vec<_>>().join("--")
189+
}
190+
}
191+
}

‎src/tools/compiletest/src/runtest/coverage.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl<'test> TestCx<'test> {
1818
.unwrap_or_else(|| self.fatal("missing --coverage-dump"))
1919
}
2020

21-
pub(crate) fn run_coverage_map_test(&self) {
21+
pub(super) fn run_coverage_map_test(&self) {
2222
let coverage_dump_path = self.coverage_dump_path();
2323

2424
let (proc_res, llvm_ir_path) = self.compile_test_and_save_ir();
@@ -50,7 +50,7 @@ impl<'test> TestCx<'test> {
5050
}
5151
}
5252

53-
pub(crate) fn run_coverage_run_test(&self) {
53+
pub(super) fn run_coverage_run_test(&self) {
5454
let should_run = self.run_if_enabled();
5555
let proc_res = self.compile_test(should_run, Emit::None);
5656

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use super::{TestCx, WillExecute};
2+
3+
impl TestCx<'_> {
4+
pub(super) fn run_crash_test(&self) {
5+
let pm = self.pass_mode();
6+
let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm));
7+
8+
if std::env::var("COMPILETEST_VERBOSE_CRASHES").is_ok() {
9+
eprintln!("{}", proc_res.status);
10+
eprintln!("{}", proc_res.stdout);
11+
eprintln!("{}", proc_res.stderr);
12+
eprintln!("{}", proc_res.cmdline);
13+
}
14+
15+
// if a test does not crash, consider it an error
16+
if proc_res.status.success() || matches!(proc_res.status.code(), Some(1 | 0)) {
17+
self.fatal(&format!(
18+
"crashtest no longer crashes/triggers ICE, horray! Please give it a meaningful name, \
19+
add a doc-comment to the start of the test explaining why it exists and \
20+
move it to tests/ui or wherever you see fit. Adding 'Fixes #<issueNr>' to your PR description \
21+
ensures that the corresponding ticket is auto-closed upon merge."
22+
));
23+
}
24+
}
25+
}

‎src/tools/compiletest/src/runtest/debuginfo.rs

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use super::{TestCx, WillExecute};
2+
use crate::errors;
3+
4+
// FIXME(jieyouxu): `run_rpass_test` got hoisted out of this because apparently valgrind falls back
5+
// to `run_rpass_test` if valgrind isn't available, which is questionable, but keeping it for
6+
// refactoring changes to preserve current behavior.
7+
8+
impl TestCx<'_> {
9+
pub(super) fn run_incremental_test(&self) {
10+
// Basic plan for a test incremental/foo/bar.rs:
11+
// - load list of revisions rpass1, cfail2, rpass3
12+
// - each should begin with `cpass`, `rpass`, `cfail`, or `rfail`
13+
// - if `cpass`, expect compilation to succeed, don't execute
14+
// - if `rpass`, expect compilation and execution to succeed
15+
// - if `cfail`, expect compilation to fail
16+
// - if `rfail`, expect compilation to succeed and execution to fail
17+
// - create a directory build/foo/bar.incremental
18+
// - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass1
19+
// - because name of revision starts with "rpass", expect success
20+
// - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C cfail2
21+
// - because name of revision starts with "cfail", expect an error
22+
// - load expected errors as usual, but filter for those that end in `[rfail2]`
23+
// - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass3
24+
// - because name of revision starts with "rpass", expect success
25+
// - execute build/foo/bar.exe and save output
26+
//
27+
// FIXME -- use non-incremental mode as an oracle? That doesn't apply
28+
// to #[rustc_dirty] and clean tests I guess
29+
30+
let revision = self.revision.expect("incremental tests require a list of revisions");
31+
32+
// Incremental workproduct directory should have already been created.
33+
let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
34+
assert!(incremental_dir.exists(), "init_incremental_test failed to create incremental dir");
35+
36+
if self.config.verbose {
37+
print!("revision={:?} props={:#?}", revision, self.props);
38+
}
39+
40+
if revision.starts_with("cpass") {
41+
if self.props.should_ice {
42+
self.fatal("can only use should-ice in cfail tests");
43+
}
44+
self.run_cpass_test();
45+
} else if revision.starts_with("rpass") {
46+
if self.props.should_ice {
47+
self.fatal("can only use should-ice in cfail tests");
48+
}
49+
self.run_rpass_test();
50+
} else if revision.starts_with("rfail") {
51+
if self.props.should_ice {
52+
self.fatal("can only use should-ice in cfail tests");
53+
}
54+
self.run_rfail_test();
55+
} else if revision.starts_with("cfail") {
56+
self.run_cfail_test();
57+
} else {
58+
self.fatal("revision name must begin with cpass, rpass, rfail, or cfail");
59+
}
60+
}
61+
62+
fn run_cpass_test(&self) {
63+
let emit_metadata = self.should_emit_metadata(self.pass_mode());
64+
let proc_res = self.compile_test(WillExecute::No, emit_metadata);
65+
66+
if !proc_res.status.success() {
67+
self.fatal_proc_rec("compilation failed!", &proc_res);
68+
}
69+
70+
// FIXME(#41968): Move this check to tidy?
71+
if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() {
72+
self.fatal("compile-pass tests with expected warnings should be moved to ui/");
73+
}
74+
}
75+
76+
fn run_cfail_test(&self) {
77+
let pm = self.pass_mode();
78+
let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm));
79+
self.check_if_test_should_compile(&proc_res, pm);
80+
self.check_no_compiler_crash(&proc_res, self.props.should_ice);
81+
82+
let output_to_check = self.get_output(&proc_res);
83+
let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
84+
if !expected_errors.is_empty() {
85+
if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty()
86+
{
87+
self.fatal("both error pattern and expected errors specified");
88+
}
89+
self.check_expected_errors(expected_errors, &proc_res);
90+
} else {
91+
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
92+
}
93+
if self.props.should_ice {
94+
match proc_res.status.code() {
95+
Some(101) => (),
96+
_ => self.fatal("expected ICE"),
97+
}
98+
}
99+
100+
self.check_forbid_output(&output_to_check, &proc_res);
101+
}
102+
103+
fn run_rfail_test(&self) {
104+
let pm = self.pass_mode();
105+
let should_run = self.run_if_enabled();
106+
let proc_res = self.compile_test(should_run, self.should_emit_metadata(pm));
107+
108+
if !proc_res.status.success() {
109+
self.fatal_proc_rec("compilation failed!", &proc_res);
110+
}
111+
112+
if let WillExecute::Disabled = should_run {
113+
return;
114+
}
115+
116+
let proc_res = self.exec_compiled_test();
117+
118+
// The value our Makefile configures valgrind to return on failure
119+
const VALGRIND_ERR: i32 = 100;
120+
if proc_res.status.code() == Some(VALGRIND_ERR) {
121+
self.fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res);
122+
}
123+
124+
let output_to_check = self.get_output(&proc_res);
125+
self.check_correct_failure_status(&proc_res);
126+
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
127+
}
128+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::process::Command;
2+
3+
use super::TestCx;
4+
5+
impl TestCx<'_> {
6+
pub(super) fn run_js_doc_test(&self) {
7+
if let Some(nodejs) = &self.config.nodejs {
8+
let out_dir = self.output_base_dir();
9+
10+
self.document(&out_dir, &self.testpaths);
11+
12+
let root = self.config.find_rust_src_root().unwrap();
13+
let file_stem =
14+
self.testpaths.file.file_stem().and_then(|f| f.to_str()).expect("no file stem");
15+
let res = self.run_command_to_procres(
16+
Command::new(&nodejs)
17+
.arg(root.join("src/tools/rustdoc-js/tester.js"))
18+
.arg("--doc-folder")
19+
.arg(out_dir)
20+
.arg("--crate-name")
21+
.arg(file_stem.replace("-", "_"))
22+
.arg("--test-file")
23+
.arg(self.testpaths.file.with_extension("js")),
24+
);
25+
if !res.status.success() {
26+
self.fatal_proc_rec("rustdoc-js test failed!", &res);
27+
}
28+
} else {
29+
self.fatal("no nodeJS");
30+
}
31+
}
32+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use std::fs;
2+
use std::path::{Path, PathBuf};
3+
4+
use glob::glob;
5+
use miropt_test_tools::{files_for_miropt_test, MiroptTest, MiroptTestFile};
6+
use tracing::debug;
7+
8+
use super::{Emit, TestCx, WillExecute};
9+
use crate::compute_diff::write_diff;
10+
11+
impl TestCx<'_> {
12+
pub(super) fn run_mir_opt_test(&self) {
13+
let pm = self.pass_mode();
14+
let should_run = self.should_run(pm);
15+
16+
let mut test_info = files_for_miropt_test(
17+
&self.testpaths.file,
18+
self.config.get_pointer_width(),
19+
self.config.target_cfg().panic.for_miropt_test_tools(),
20+
);
21+
22+
let passes = std::mem::take(&mut test_info.passes);
23+
24+
let proc_res = self.compile_test_with_passes(should_run, Emit::Mir, passes);
25+
if !proc_res.status.success() {
26+
self.fatal_proc_rec("compilation failed!", &proc_res);
27+
}
28+
self.check_mir_dump(test_info);
29+
30+
if let WillExecute::Yes = should_run {
31+
let proc_res = self.exec_compiled_test();
32+
33+
if !proc_res.status.success() {
34+
self.fatal_proc_rec("test run failed!", &proc_res);
35+
}
36+
}
37+
}
38+
39+
fn check_mir_dump(&self, test_info: MiroptTest) {
40+
let test_dir = self.testpaths.file.parent().unwrap();
41+
let test_crate =
42+
self.testpaths.file.file_stem().unwrap().to_str().unwrap().replace('-', "_");
43+
44+
let MiroptTest { run_filecheck, suffix, files, passes: _ } = test_info;
45+
46+
if self.config.bless {
47+
for e in
48+
glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, suffix)).unwrap()
49+
{
50+
fs::remove_file(e.unwrap()).unwrap();
51+
}
52+
for e in
53+
glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, suffix)).unwrap()
54+
{
55+
fs::remove_file(e.unwrap()).unwrap();
56+
}
57+
}
58+
59+
for MiroptTestFile { from_file, to_file, expected_file } in files {
60+
let dumped_string = if let Some(after) = to_file {
61+
self.diff_mir_files(from_file.into(), after.into())
62+
} else {
63+
let mut output_file = PathBuf::new();
64+
output_file.push(self.get_mir_dump_dir());
65+
output_file.push(&from_file);
66+
debug!(
67+
"comparing the contents of: {} with {}",
68+
output_file.display(),
69+
expected_file.display()
70+
);
71+
if !output_file.exists() {
72+
panic!(
73+
"Output file `{}` from test does not exist, available files are in `{}`",
74+
output_file.display(),
75+
output_file.parent().unwrap().display()
76+
);
77+
}
78+
self.check_mir_test_timestamp(&from_file, &output_file);
79+
let dumped_string = fs::read_to_string(&output_file).unwrap();
80+
self.normalize_output(&dumped_string, &[])
81+
};
82+
83+
if self.config.bless {
84+
let _ = fs::remove_file(&expected_file);
85+
fs::write(expected_file, dumped_string.as_bytes()).unwrap();
86+
} else {
87+
if !expected_file.exists() {
88+
panic!("Output file `{}` from test does not exist", expected_file.display());
89+
}
90+
let expected_string = fs::read_to_string(&expected_file).unwrap();
91+
if dumped_string != expected_string {
92+
print!("{}", write_diff(&expected_string, &dumped_string, 3));
93+
panic!(
94+
"Actual MIR output differs from expected MIR output {}",
95+
expected_file.display()
96+
);
97+
}
98+
}
99+
}
100+
101+
if run_filecheck {
102+
let output_path = self.output_base_name().with_extension("mir");
103+
let proc_res = self.verify_with_filecheck(&output_path);
104+
if !proc_res.status.success() {
105+
self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res);
106+
}
107+
}
108+
}
109+
110+
fn diff_mir_files(&self, before: PathBuf, after: PathBuf) -> String {
111+
let to_full_path = |path: PathBuf| {
112+
let full = self.get_mir_dump_dir().join(&path);
113+
if !full.exists() {
114+
panic!(
115+
"the mir dump file for {} does not exist (requested in {})",
116+
path.display(),
117+
self.testpaths.file.display(),
118+
);
119+
}
120+
full
121+
};
122+
let before = to_full_path(before);
123+
let after = to_full_path(after);
124+
debug!("comparing the contents of: {} with {}", before.display(), after.display());
125+
let before = fs::read_to_string(before).unwrap();
126+
let after = fs::read_to_string(after).unwrap();
127+
let before = self.normalize_output(&before, &[]);
128+
let after = self.normalize_output(&after, &[]);
129+
let mut dumped_string = String::new();
130+
for result in diff::lines(&before, &after) {
131+
use std::fmt::Write;
132+
match result {
133+
diff::Result::Left(s) => writeln!(dumped_string, "- {}", s).unwrap(),
134+
diff::Result::Right(s) => writeln!(dumped_string, "+ {}", s).unwrap(),
135+
diff::Result::Both(s, _) => writeln!(dumped_string, " {}", s).unwrap(),
136+
}
137+
}
138+
dumped_string
139+
}
140+
141+
fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Path) {
142+
let t = |file| fs::metadata(file).unwrap().modified().unwrap();
143+
let source_file = &self.testpaths.file;
144+
let output_time = t(output_file);
145+
let source_time = t(source_file);
146+
if source_time > output_time {
147+
debug!("source file time: {:?} output file time: {:?}", source_time, output_time);
148+
panic!(
149+
"test source file `{}` is newer than potentially stale output file `{}`.",
150+
source_file.display(),
151+
test_name
152+
);
153+
}
154+
}
155+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::fs;
2+
3+
use super::{ProcRes, ReadFrom, TestCx};
4+
use crate::util::logv;
5+
6+
impl TestCx<'_> {
7+
pub(super) fn run_pretty_test(&self) {
8+
if self.props.pp_exact.is_some() {
9+
logv(self.config, "testing for exact pretty-printing".to_owned());
10+
} else {
11+
logv(self.config, "testing for converging pretty-printing".to_owned());
12+
}
13+
14+
let rounds = match self.props.pp_exact {
15+
Some(_) => 1,
16+
None => 2,
17+
};
18+
19+
let src = fs::read_to_string(&self.testpaths.file).unwrap();
20+
let mut srcs = vec![src];
21+
22+
let mut round = 0;
23+
while round < rounds {
24+
logv(
25+
self.config,
26+
format!("pretty-printing round {} revision {:?}", round, self.revision),
27+
);
28+
let read_from =
29+
if round == 0 { ReadFrom::Path } else { ReadFrom::Stdin(srcs[round].to_owned()) };
30+
31+
let proc_res = self.print_source(read_from, &self.props.pretty_mode);
32+
if !proc_res.status.success() {
33+
self.fatal_proc_rec(
34+
&format!(
35+
"pretty-printing failed in round {} revision {:?}",
36+
round, self.revision
37+
),
38+
&proc_res,
39+
);
40+
}
41+
42+
let ProcRes { stdout, .. } = proc_res;
43+
srcs.push(stdout);
44+
round += 1;
45+
}
46+
47+
let mut expected = match self.props.pp_exact {
48+
Some(ref file) => {
49+
let filepath = self.testpaths.file.parent().unwrap().join(file);
50+
fs::read_to_string(&filepath).unwrap()
51+
}
52+
None => srcs[srcs.len() - 2].clone(),
53+
};
54+
let mut actual = srcs[srcs.len() - 1].clone();
55+
56+
if self.props.pp_exact.is_some() {
57+
// Now we have to care about line endings
58+
let cr = "\r".to_owned();
59+
actual = actual.replace(&cr, "");
60+
expected = expected.replace(&cr, "");
61+
}
62+
63+
if !self.config.bless {
64+
self.compare_source(&expected, &actual);
65+
} else if expected != actual {
66+
let filepath_buf;
67+
let filepath = match &self.props.pp_exact {
68+
Some(file) => {
69+
filepath_buf = self.testpaths.file.parent().unwrap().join(file);
70+
&filepath_buf
71+
}
72+
None => &self.testpaths.file,
73+
};
74+
fs::write(filepath, &actual).unwrap();
75+
}
76+
77+
// If we're only making sure that the output matches then just stop here
78+
if self.props.pretty_compare_only {
79+
return;
80+
}
81+
82+
// Finally, let's make sure it actually appears to remain valid code
83+
let proc_res = self.typecheck_source(actual);
84+
if !proc_res.status.success() {
85+
self.fatal_proc_rec("pretty-printed source does not typecheck", &proc_res);
86+
}
87+
88+
if !self.props.pretty_expanded {
89+
return;
90+
}
91+
92+
// additionally, run `-Zunpretty=expanded` and try to build it.
93+
let proc_res = self.print_source(ReadFrom::Path, "expanded");
94+
if !proc_res.status.success() {
95+
self.fatal_proc_rec("pretty-printing (expanded) failed", &proc_res);
96+
}
97+
98+
let ProcRes { stdout: expanded_src, .. } = proc_res;
99+
let proc_res = self.typecheck_source(expanded_src);
100+
if !proc_res.status.success() {
101+
self.fatal_proc_rec("pretty-printed source (expanded) does not typecheck", &proc_res);
102+
}
103+
}
104+
}

‎src/tools/compiletest/src/runtest/run_make.rs

Lines changed: 518 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::process::Command;
2+
3+
use super::{remove_and_create_dir_all, TestCx};
4+
5+
impl TestCx<'_> {
6+
pub(super) fn run_rustdoc_test(&self) {
7+
assert!(self.revision.is_none(), "revisions not relevant here");
8+
9+
let out_dir = self.output_base_dir();
10+
remove_and_create_dir_all(&out_dir);
11+
12+
let proc_res = self.document(&out_dir, &self.testpaths);
13+
if !proc_res.status.success() {
14+
self.fatal_proc_rec("rustdoc failed!", &proc_res);
15+
}
16+
17+
if self.props.check_test_line_numbers_match {
18+
self.check_rustdoc_test_option(proc_res);
19+
} else {
20+
let root = self.config.find_rust_src_root().unwrap();
21+
let mut cmd = Command::new(&self.config.python);
22+
cmd.arg(root.join("src/etc/htmldocck.py")).arg(&out_dir).arg(&self.testpaths.file);
23+
if self.config.bless {
24+
cmd.arg("--bless");
25+
}
26+
let res = self.run_command_to_procres(&mut cmd);
27+
if !res.status.success() {
28+
self.fatal_proc_rec_with_ctx("htmldocck failed!", &res, |mut this| {
29+
this.compare_to_default_rustdoc(&out_dir)
30+
});
31+
}
32+
}
33+
}
34+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::process::Command;
2+
3+
use super::{remove_and_create_dir_all, TestCx};
4+
5+
impl TestCx<'_> {
6+
pub(super) fn run_rustdoc_json_test(&self) {
7+
//FIXME: Add bless option.
8+
9+
assert!(self.revision.is_none(), "revisions not relevant here");
10+
11+
let out_dir = self.output_base_dir();
12+
remove_and_create_dir_all(&out_dir);
13+
14+
let proc_res = self.document(&out_dir, &self.testpaths);
15+
if !proc_res.status.success() {
16+
self.fatal_proc_rec("rustdoc failed!", &proc_res);
17+
}
18+
19+
let root = self.config.find_rust_src_root().unwrap();
20+
let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap());
21+
json_out.set_extension("json");
22+
let res = self.run_command_to_procres(
23+
Command::new(self.config.jsondocck_path.as_ref().unwrap())
24+
.arg("--doc-dir")
25+
.arg(root.join(&out_dir))
26+
.arg("--template")
27+
.arg(&self.testpaths.file),
28+
);
29+
30+
if !res.status.success() {
31+
self.fatal_proc_rec_with_ctx("jsondocck failed!", &res, |_| {
32+
println!("Rustdoc Output:");
33+
proc_res.print_info();
34+
})
35+
}
36+
37+
let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap());
38+
json_out.set_extension("json");
39+
40+
let res = self.run_command_to_procres(
41+
Command::new(self.config.jsondoclint_path.as_ref().unwrap()).arg(&json_out),
42+
);
43+
44+
if !res.status.success() {
45+
self.fatal_proc_rec("jsondoclint failed!", &res);
46+
}
47+
}
48+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use std::collections::HashSet;
2+
use std::fs::OpenOptions;
3+
use std::io::Write;
4+
5+
use rustfix::{apply_suggestions, get_suggestions_from_json, Filter};
6+
use tracing::debug;
7+
8+
use super::{
9+
AllowUnused, Emit, ErrorKind, FailMode, LinkToAux, PassMode, TargetLocation, TestCx,
10+
TestOutput, Truncated, WillExecute, UI_FIXED,
11+
};
12+
use crate::{errors, json};
13+
14+
impl TestCx<'_> {
15+
pub(super) fn run_ui_test(&self) {
16+
if let Some(FailMode::Build) = self.props.fail_mode {
17+
// Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck).
18+
let pm = Some(PassMode::Check);
19+
let proc_res =
20+
self.compile_test_general(WillExecute::No, Emit::Metadata, pm, Vec::new());
21+
self.check_if_test_should_compile(&proc_res, pm);
22+
}
23+
24+
let pm = self.pass_mode();
25+
let should_run = self.should_run(pm);
26+
let emit_metadata = self.should_emit_metadata(pm);
27+
let proc_res = self.compile_test(should_run, emit_metadata);
28+
self.check_if_test_should_compile(&proc_res, pm);
29+
if matches!(proc_res.truncated, Truncated::Yes)
30+
&& !self.props.dont_check_compiler_stdout
31+
&& !self.props.dont_check_compiler_stderr
32+
{
33+
self.fatal_proc_rec(
34+
"compiler output got truncated, cannot compare with reference file",
35+
&proc_res,
36+
);
37+
}
38+
39+
// if the user specified a format in the ui test
40+
// print the output to the stderr file, otherwise extract
41+
// the rendered error messages from json and print them
42+
let explicit = self.props.compile_flags.iter().any(|s| s.contains("--error-format"));
43+
44+
let expected_fixed = self.load_expected_output(UI_FIXED);
45+
46+
self.check_and_prune_duplicate_outputs(&proc_res, &[], &[]);
47+
48+
let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit);
49+
let rustfix_input = json::rustfix_diagnostics_only(&proc_res.stderr);
50+
51+
if self.config.compare_mode.is_some() {
52+
// don't test rustfix with nll right now
53+
} else if self.config.rustfix_coverage {
54+
// Find out which tests have `MachineApplicable` suggestions but are missing
55+
// `run-rustfix` or `run-rustfix-only-machine-applicable` headers.
56+
//
57+
// This will return an empty `Vec` in case the executed test file has a
58+
// `compile-flags: --error-format=xxxx` header with a value other than `json`.
59+
let suggestions = get_suggestions_from_json(
60+
&rustfix_input,
61+
&HashSet::new(),
62+
Filter::MachineApplicableOnly,
63+
)
64+
.unwrap_or_default();
65+
if !suggestions.is_empty()
66+
&& !self.props.run_rustfix
67+
&& !self.props.rustfix_only_machine_applicable
68+
{
69+
let mut coverage_file_path = self.config.build_base.clone();
70+
coverage_file_path.push("rustfix_missing_coverage.txt");
71+
debug!("coverage_file_path: {}", coverage_file_path.display());
72+
73+
let mut file = OpenOptions::new()
74+
.create(true)
75+
.append(true)
76+
.open(coverage_file_path.as_path())
77+
.expect("could not create or open file");
78+
79+
if let Err(e) = writeln!(file, "{}", self.testpaths.file.display()) {
80+
panic!("couldn't write to {}: {e:?}", coverage_file_path.display());
81+
}
82+
}
83+
} else if self.props.run_rustfix {
84+
// Apply suggestions from rustc to the code itself
85+
let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file).unwrap();
86+
let suggestions = get_suggestions_from_json(
87+
&rustfix_input,
88+
&HashSet::new(),
89+
if self.props.rustfix_only_machine_applicable {
90+
Filter::MachineApplicableOnly
91+
} else {
92+
Filter::Everything
93+
},
94+
)
95+
.unwrap();
96+
let fixed_code = apply_suggestions(&unfixed_code, &suggestions).unwrap_or_else(|e| {
97+
panic!(
98+
"failed to apply suggestions for {:?} with rustfix: {}",
99+
self.testpaths.file, e
100+
)
101+
});
102+
103+
errors += self.compare_output("fixed", &fixed_code, &expected_fixed);
104+
} else if !expected_fixed.is_empty() {
105+
panic!(
106+
"the `//@ run-rustfix` directive wasn't found but a `*.fixed` \
107+
file was found"
108+
);
109+
}
110+
111+
if errors > 0 {
112+
println!("To update references, rerun the tests and pass the `--bless` flag");
113+
let relative_path_to_file =
114+
self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
115+
println!(
116+
"To only update this specific test, also pass `--test-args {}`",
117+
relative_path_to_file.display(),
118+
);
119+
self.fatal_proc_rec(
120+
&format!("{} errors occurred comparing output.", errors),
121+
&proc_res,
122+
);
123+
}
124+
125+
let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
126+
127+
if let WillExecute::Yes = should_run {
128+
let proc_res = self.exec_compiled_test();
129+
let run_output_errors = if self.props.check_run_results {
130+
self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
131+
} else {
132+
0
133+
};
134+
if run_output_errors > 0 {
135+
self.fatal_proc_rec(
136+
&format!("{} errors occurred comparing run output.", run_output_errors),
137+
&proc_res,
138+
);
139+
}
140+
if self.should_run_successfully(pm) {
141+
if !proc_res.status.success() {
142+
self.fatal_proc_rec("test run failed!", &proc_res);
143+
}
144+
} else if proc_res.status.success() {
145+
self.fatal_proc_rec("test run succeeded!", &proc_res);
146+
}
147+
148+
if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty()
149+
{
150+
// "// error-pattern" comments
151+
let output_to_check = self.get_output(&proc_res);
152+
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
153+
}
154+
}
155+
156+
debug!(
157+
"run_ui_test: explicit={:?} config.compare_mode={:?} expected_errors={:?} \
158+
proc_res.status={:?} props.error_patterns={:?}",
159+
explicit,
160+
self.config.compare_mode,
161+
expected_errors,
162+
proc_res.status,
163+
self.props.error_patterns
164+
);
165+
166+
let check_patterns = should_run == WillExecute::No
167+
&& (!self.props.error_patterns.is_empty()
168+
|| !self.props.regex_error_patterns.is_empty());
169+
if !explicit && self.config.compare_mode.is_none() {
170+
let check_annotations = !check_patterns || !expected_errors.is_empty();
171+
172+
if check_annotations {
173+
// "//~ERROR comments"
174+
self.check_expected_errors(expected_errors, &proc_res);
175+
}
176+
} else if explicit && !expected_errors.is_empty() {
177+
let msg = format!(
178+
"line {}: cannot combine `--error-format` with {} annotations; use `error-pattern` instead",
179+
expected_errors[0].line_num,
180+
expected_errors[0].kind.unwrap_or(ErrorKind::Error),
181+
);
182+
self.fatal(&msg);
183+
}
184+
if check_patterns {
185+
// "// error-pattern" comments
186+
let output_to_check = self.get_output(&proc_res);
187+
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
188+
}
189+
190+
if self.props.run_rustfix && self.config.compare_mode.is_none() {
191+
// And finally, compile the fixed code and make sure it both
192+
// succeeds and has no diagnostics.
193+
let mut rustc = self.make_compile_args(
194+
&self.expected_output_path(UI_FIXED),
195+
TargetLocation::ThisFile(self.make_exe_name()),
196+
emit_metadata,
197+
AllowUnused::No,
198+
LinkToAux::Yes,
199+
Vec::new(),
200+
);
201+
202+
// If a test is revisioned, it's fixed source file can be named "a.foo.fixed", which,
203+
// well, "a.foo" isn't a valid crate name. So we explicitly mangle the test name
204+
// (including the revision) here to avoid the test writer having to manually specify a
205+
// `#![crate_name = "..."]` as a workaround. This is okay since we're only checking if
206+
// the fixed code is compilable.
207+
if self.revision.is_some() {
208+
let crate_name =
209+
self.testpaths.file.file_stem().expect("test must have a file stem");
210+
// crate name must be alphanumeric or `_`.
211+
let crate_name =
212+
crate_name.to_str().expect("crate name implies file name must be valid UTF-8");
213+
// replace `a.foo` -> `a__foo` for crate name purposes.
214+
// replace `revision-name-with-dashes` -> `revision_name_with_underscore`
215+
let crate_name = crate_name.replace('.', "__");
216+
let crate_name = crate_name.replace('-', "_");
217+
rustc.arg("--crate-name");
218+
rustc.arg(crate_name);
219+
}
220+
221+
let res = self.compose_and_run_compiler(rustc, None, self.testpaths);
222+
if !res.status.success() {
223+
self.fatal_proc_rec("failed to compile fixed code", &res);
224+
}
225+
if !res.stderr.is_empty()
226+
&& !self.props.rustfix_only_machine_applicable
227+
&& !json::rustfix_diagnostics_only(&res.stderr).is_empty()
228+
{
229+
self.fatal_proc_rec("fixed code is still producing diagnostics", &res);
230+
}
231+
}
232+
}
233+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use super::{Emit, TestCx, WillExecute};
2+
3+
impl TestCx<'_> {
4+
pub(super) fn run_valgrind_test(&self) {
5+
assert!(self.revision.is_none(), "revisions not relevant here");
6+
7+
// FIXME(jieyouxu): does this really make any sense? If a valgrind test isn't testing
8+
// valgrind, what is it even testing?
9+
if self.config.valgrind_path.is_none() {
10+
assert!(!self.config.force_valgrind);
11+
return self.run_rpass_test();
12+
}
13+
14+
let should_run = self.run_if_enabled();
15+
let mut proc_res = self.compile_test(should_run, Emit::None);
16+
17+
if !proc_res.status.success() {
18+
self.fatal_proc_rec("compilation failed!", &proc_res);
19+
}
20+
21+
if let WillExecute::Disabled = should_run {
22+
return;
23+
}
24+
25+
let mut new_config = self.config.clone();
26+
new_config.runner = new_config.valgrind_path.clone();
27+
let new_cx = TestCx { config: &new_config, ..*self };
28+
proc_res = new_cx.exec_compiled_test();
29+
30+
if !proc_res.status.success() {
31+
self.fatal_proc_rec("test run failed!", &proc_res);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)
Please sign in to comment.