Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/filetests/src/runone.rs
1692 views
1
//! Run the tests in a single test file.
2
3
use crate::new_subtest;
4
use crate::subtest::SubTest;
5
use anyhow::{Context as _, Result, bail};
6
use cranelift_codegen::isa::TargetIsa;
7
use cranelift_codegen::print_errors::pretty_verifier_error;
8
use cranelift_codegen::settings::{Flags, FlagsOrIsa};
9
use cranelift_codegen::timing;
10
use cranelift_codegen::verify_function;
11
use cranelift_reader::{IsaSpec, Location, ParseOptions, TestFile, parse_test};
12
use log::info;
13
use std::cell::Cell;
14
use std::fs;
15
use std::path::{Path, PathBuf};
16
use std::str::Lines;
17
use std::time;
18
19
/// Load `path` and run the test in it.
20
///
21
/// If running this test causes a panic, it will propagate as normal.
22
pub fn run(
23
path: &Path,
24
passes: Option<&[String]>,
25
target: Option<&str>,
26
) -> anyhow::Result<time::Duration> {
27
let _tt = timing::process_file();
28
info!("---\nFile: {}", path.to_string_lossy());
29
let started = time::Instant::now();
30
let buffer =
31
fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
32
33
let options = ParseOptions {
34
target,
35
passes,
36
machine_code_cfg_info: true,
37
..ParseOptions::default()
38
};
39
40
let testfile = match parse_test(&buffer, options) {
41
Ok(testfile) => testfile,
42
Err(e) => {
43
if e.is_warning {
44
log::warn!(
45
"skipping test {:?} (line {}): {}",
46
path,
47
e.location.line_number,
48
e.message
49
);
50
return Ok(started.elapsed());
51
}
52
return Err(e).context(format!("failed to parse {}", path.display()));
53
}
54
};
55
56
if testfile.functions.is_empty() {
57
anyhow::bail!("no functions found");
58
}
59
60
// Parse the test commands.
61
let mut tests = testfile
62
.commands
63
.iter()
64
.map(new_subtest)
65
.collect::<anyhow::Result<Vec<_>>>()?;
66
67
// Flags to use for those tests that don't need an ISA.
68
// This is the cumulative effect of all the `set` commands in the file.
69
let flags = match testfile.isa_spec {
70
IsaSpec::None(ref f) => f,
71
IsaSpec::Some(ref v) => v.last().expect("Empty ISA list").flags(),
72
};
73
74
// Sort the tests so the mutators are at the end, and those that don't need the verifier are at
75
// the front.
76
tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));
77
78
// Expand the tests into (test, flags, isa) tuples.
79
let tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;
80
81
// Bail if the test has no runnable commands
82
if tuples.is_empty() {
83
anyhow::bail!("no test commands found");
84
}
85
86
let mut file_update = FileUpdate::new(&path);
87
let file_path = path.to_string_lossy();
88
for (test, flags, isa) in &tuples {
89
// Should we run the verifier before this test?
90
if test.needs_verifier() {
91
let fisa = FlagsOrIsa { flags, isa: *isa };
92
verify_testfile(&testfile, fisa)?;
93
}
94
95
test.run_target(&testfile, &mut file_update, file_path.as_ref(), flags, *isa)?;
96
}
97
98
Ok(started.elapsed())
99
}
100
101
// Verifies all functions in a testfile
102
fn verify_testfile(testfile: &TestFile, fisa: FlagsOrIsa) -> anyhow::Result<()> {
103
for (func, _) in &testfile.functions {
104
verify_function(func, fisa)
105
.map_err(|errors| anyhow::anyhow!("{}", pretty_verifier_error(&func, None, errors)))?;
106
}
107
108
Ok(())
109
}
110
111
// Given a slice of tests, generate a vector of (test, flags, isa) tuples.
112
fn test_tuples<'a>(
113
tests: &'a [Box<dyn SubTest>],
114
isa_spec: &'a IsaSpec,
115
no_isa_flags: &'a Flags,
116
) -> anyhow::Result<Vec<(&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>)>> {
117
let mut out = Vec::new();
118
for test in tests {
119
if test.needs_isa() {
120
match *isa_spec {
121
IsaSpec::None(_) => {
122
// TODO: Generate a list of default ISAs.
123
anyhow::bail!("test {} requires an ISA", test.name());
124
}
125
IsaSpec::Some(ref isas) => {
126
for isa in isas {
127
out.push((&**test, isa.flags(), Some(&**isa)));
128
}
129
}
130
}
131
} else {
132
// This test doesn't require an ISA, and we only want to run one instance of it.
133
// Still, give it an ISA ref if we happen to have a unique one.
134
// For example, `test cat` can use this to print encodings and register names.
135
out.push((&**test, no_isa_flags, isa_spec.unique_isa()));
136
}
137
}
138
Ok(out)
139
}
140
141
/// A helper struct to update a file in-place as test expectations are
142
/// automatically updated.
143
///
144
/// This structure automatically handles multiple edits to one file. Our edits
145
/// are line-based but if editing a previous portion of the file adds lines then
146
/// all future edits need to know to skip over those previous lines. Note that
147
/// this assumes that edits are done front-to-back.
148
pub struct FileUpdate {
149
path: PathBuf,
150
line_diff: Cell<isize>,
151
last_update: Cell<usize>,
152
}
153
154
impl FileUpdate {
155
fn new(path: &Path) -> FileUpdate {
156
FileUpdate {
157
path: path.to_path_buf(),
158
line_diff: Cell::new(0),
159
last_update: Cell::new(0),
160
}
161
}
162
163
/// Updates the file that this structure references at the `location`
164
/// specified.
165
///
166
/// The closure `f` is given first a buffer to push the new test into along
167
/// with a lines iterator for the old test.
168
pub fn update_at(
169
&self,
170
location: &Location,
171
f: impl FnOnce(&mut String, &mut Lines<'_>),
172
) -> Result<()> {
173
// This is required for correctness of this update.
174
assert!(location.line_number > self.last_update.get());
175
self.last_update.set(location.line_number);
176
177
// Read the old test file and calculate the new line number we're
178
// preserving up to based on how many lines prior to this have been
179
// removed or added.
180
let old_test = std::fs::read_to_string(&self.path)?;
181
let mut new_test = String::new();
182
let mut lines = old_test.lines();
183
let lines_to_preserve =
184
(((location.line_number - 1) as isize) + self.line_diff.get()) as usize;
185
186
// Push everything leading up to the start of the function
187
for _ in 0..lines_to_preserve {
188
new_test.push_str(lines.next().unwrap());
189
new_test.push_str("\n");
190
}
191
192
// Push the whole function, leading up to the trailing `}`
193
let mut first = true;
194
while let Some(line) = lines.next() {
195
if first && !line.starts_with("function") {
196
bail!(
197
"line {} in test file {:?} did not start with `function`, \
198
cannot automatically update test",
199
location.line_number,
200
self.path,
201
);
202
}
203
first = false;
204
new_test.push_str(line);
205
new_test.push_str("\n");
206
if line.starts_with("}") {
207
break;
208
}
209
}
210
211
// Use our custom update function to further update the test.
212
f(&mut new_test, &mut lines);
213
214
// Record the difference in line count so future updates can be adjusted
215
// accordingly, and then write the file back out to the filesystem.
216
let old_line_count = old_test.lines().count();
217
let new_line_count = new_test.lines().count();
218
self.line_diff
219
.set(self.line_diff.get() + (new_line_count as isize - old_line_count as isize));
220
221
std::fs::write(&self.path, new_test)?;
222
Ok(())
223
}
224
}
225
226