cranelift_filetests/
runone.rs1use crate::new_subtest;
4use crate::subtest::SubTest;
5use anyhow::{bail, Context as _, Result};
6use cranelift_codegen::isa::TargetIsa;
7use cranelift_codegen::print_errors::pretty_verifier_error;
8use cranelift_codegen::settings::{Flags, FlagsOrIsa};
9use cranelift_codegen::timing;
10use cranelift_codegen::verify_function;
11use cranelift_reader::{parse_test, IsaSpec, Location, ParseOptions, TestFile};
12use log::info;
13use std::cell::Cell;
14use std::fs;
15use std::path::{Path, PathBuf};
16use std::str::Lines;
17use std::time;
18
19pub 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)
53 .context(format!("failed to parse {}", path.display()))
54 .into();
55 }
56 };
57
58 if testfile.functions.is_empty() {
59 anyhow::bail!("no functions found");
60 }
61
62 let mut tests = testfile
64 .commands
65 .iter()
66 .map(new_subtest)
67 .collect::<anyhow::Result<Vec<_>>>()?;
68
69 let flags = match testfile.isa_spec {
72 IsaSpec::None(ref f) => f,
73 IsaSpec::Some(ref v) => v.last().expect("Empty ISA list").flags(),
74 };
75
76 tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));
79
80 let tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;
82
83 if tuples.is_empty() {
85 anyhow::bail!("no test commands found");
86 }
87
88 let mut file_update = FileUpdate::new(&path);
89 let file_path = path.to_string_lossy();
90 for (test, flags, isa) in &tuples {
91 if test.needs_verifier() {
93 let fisa = FlagsOrIsa { flags, isa: *isa };
94 verify_testfile(&testfile, fisa)?;
95 }
96
97 test.run_target(&testfile, &mut file_update, file_path.as_ref(), flags, *isa)?;
98 }
99
100 Ok(started.elapsed())
101}
102
103fn verify_testfile(testfile: &TestFile, fisa: FlagsOrIsa) -> anyhow::Result<()> {
105 for (func, _) in &testfile.functions {
106 verify_function(func, fisa)
107 .map_err(|errors| anyhow::anyhow!("{}", pretty_verifier_error(&func, None, errors)))?;
108 }
109
110 Ok(())
111}
112
113fn test_tuples<'a>(
115 tests: &'a [Box<dyn SubTest>],
116 isa_spec: &'a IsaSpec,
117 no_isa_flags: &'a Flags,
118) -> anyhow::Result<Vec<(&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>)>> {
119 let mut out = Vec::new();
120 for test in tests {
121 if test.needs_isa() {
122 match *isa_spec {
123 IsaSpec::None(_) => {
124 anyhow::bail!("test {} requires an ISA", test.name());
126 }
127 IsaSpec::Some(ref isas) => {
128 for isa in isas {
129 out.push((&**test, isa.flags(), Some(&**isa)));
130 }
131 }
132 }
133 } else {
134 out.push((&**test, no_isa_flags, isa_spec.unique_isa()));
138 }
139 }
140 Ok(out)
141}
142
143pub struct FileUpdate {
151 path: PathBuf,
152 line_diff: Cell<isize>,
153 last_update: Cell<usize>,
154}
155
156impl FileUpdate {
157 fn new(path: &Path) -> FileUpdate {
158 FileUpdate {
159 path: path.to_path_buf(),
160 line_diff: Cell::new(0),
161 last_update: Cell::new(0),
162 }
163 }
164
165 pub fn update_at(
171 &self,
172 location: &Location,
173 f: impl FnOnce(&mut String, &mut Lines<'_>),
174 ) -> Result<()> {
175 assert!(location.line_number > self.last_update.get());
177 self.last_update.set(location.line_number);
178
179 let old_test = std::fs::read_to_string(&self.path)?;
183 let mut new_test = String::new();
184 let mut lines = old_test.lines();
185 let lines_to_preserve =
186 (((location.line_number - 1) as isize) + self.line_diff.get()) as usize;
187
188 for _ in 0..lines_to_preserve {
190 new_test.push_str(lines.next().unwrap());
191 new_test.push_str("\n");
192 }
193
194 let mut first = true;
196 while let Some(line) = lines.next() {
197 if first && !line.starts_with("function") {
198 bail!(
199 "line {} in test file {:?} did not start with `function`, \
200 cannot automatically update test",
201 location.line_number,
202 self.path,
203 );
204 }
205 first = false;
206 new_test.push_str(line);
207 new_test.push_str("\n");
208 if line.starts_with("}") {
209 break;
210 }
211 }
212
213 f(&mut new_test, &mut lines);
215
216 let old_line_count = old_test.lines().count();
219 let new_line_count = new_test.lines().count();
220 self.line_diff
221 .set(self.line_diff.get() + (new_line_count as isize - old_line_count as isize));
222
223 std::fs::write(&self.path, new_test)?;
224 Ok(())
225 }
226}