Path: blob/main/cranelift/filetests/src/runone.rs
1692 views
//! Run the tests in a single test file.12use crate::new_subtest;3use crate::subtest::SubTest;4use anyhow::{Context as _, Result, bail};5use cranelift_codegen::isa::TargetIsa;6use cranelift_codegen::print_errors::pretty_verifier_error;7use cranelift_codegen::settings::{Flags, FlagsOrIsa};8use cranelift_codegen::timing;9use cranelift_codegen::verify_function;10use cranelift_reader::{IsaSpec, Location, ParseOptions, TestFile, parse_test};11use log::info;12use std::cell::Cell;13use std::fs;14use std::path::{Path, PathBuf};15use std::str::Lines;16use std::time;1718/// Load `path` and run the test in it.19///20/// If running this test causes a panic, it will propagate as normal.21pub fn run(22path: &Path,23passes: Option<&[String]>,24target: Option<&str>,25) -> anyhow::Result<time::Duration> {26let _tt = timing::process_file();27info!("---\nFile: {}", path.to_string_lossy());28let started = time::Instant::now();29let buffer =30fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;3132let options = ParseOptions {33target,34passes,35machine_code_cfg_info: true,36..ParseOptions::default()37};3839let testfile = match parse_test(&buffer, options) {40Ok(testfile) => testfile,41Err(e) => {42if e.is_warning {43log::warn!(44"skipping test {:?} (line {}): {}",45path,46e.location.line_number,47e.message48);49return Ok(started.elapsed());50}51return Err(e).context(format!("failed to parse {}", path.display()));52}53};5455if testfile.functions.is_empty() {56anyhow::bail!("no functions found");57}5859// Parse the test commands.60let mut tests = testfile61.commands62.iter()63.map(new_subtest)64.collect::<anyhow::Result<Vec<_>>>()?;6566// Flags to use for those tests that don't need an ISA.67// This is the cumulative effect of all the `set` commands in the file.68let flags = match testfile.isa_spec {69IsaSpec::None(ref f) => f,70IsaSpec::Some(ref v) => v.last().expect("Empty ISA list").flags(),71};7273// Sort the tests so the mutators are at the end, and those that don't need the verifier are at74// the front.75tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));7677// Expand the tests into (test, flags, isa) tuples.78let tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;7980// Bail if the test has no runnable commands81if tuples.is_empty() {82anyhow::bail!("no test commands found");83}8485let mut file_update = FileUpdate::new(&path);86let file_path = path.to_string_lossy();87for (test, flags, isa) in &tuples {88// Should we run the verifier before this test?89if test.needs_verifier() {90let fisa = FlagsOrIsa { flags, isa: *isa };91verify_testfile(&testfile, fisa)?;92}9394test.run_target(&testfile, &mut file_update, file_path.as_ref(), flags, *isa)?;95}9697Ok(started.elapsed())98}99100// Verifies all functions in a testfile101fn verify_testfile(testfile: &TestFile, fisa: FlagsOrIsa) -> anyhow::Result<()> {102for (func, _) in &testfile.functions {103verify_function(func, fisa)104.map_err(|errors| anyhow::anyhow!("{}", pretty_verifier_error(&func, None, errors)))?;105}106107Ok(())108}109110// Given a slice of tests, generate a vector of (test, flags, isa) tuples.111fn test_tuples<'a>(112tests: &'a [Box<dyn SubTest>],113isa_spec: &'a IsaSpec,114no_isa_flags: &'a Flags,115) -> anyhow::Result<Vec<(&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>)>> {116let mut out = Vec::new();117for test in tests {118if test.needs_isa() {119match *isa_spec {120IsaSpec::None(_) => {121// TODO: Generate a list of default ISAs.122anyhow::bail!("test {} requires an ISA", test.name());123}124IsaSpec::Some(ref isas) => {125for isa in isas {126out.push((&**test, isa.flags(), Some(&**isa)));127}128}129}130} else {131// This test doesn't require an ISA, and we only want to run one instance of it.132// Still, give it an ISA ref if we happen to have a unique one.133// For example, `test cat` can use this to print encodings and register names.134out.push((&**test, no_isa_flags, isa_spec.unique_isa()));135}136}137Ok(out)138}139140/// A helper struct to update a file in-place as test expectations are141/// automatically updated.142///143/// This structure automatically handles multiple edits to one file. Our edits144/// are line-based but if editing a previous portion of the file adds lines then145/// all future edits need to know to skip over those previous lines. Note that146/// this assumes that edits are done front-to-back.147pub struct FileUpdate {148path: PathBuf,149line_diff: Cell<isize>,150last_update: Cell<usize>,151}152153impl FileUpdate {154fn new(path: &Path) -> FileUpdate {155FileUpdate {156path: path.to_path_buf(),157line_diff: Cell::new(0),158last_update: Cell::new(0),159}160}161162/// Updates the file that this structure references at the `location`163/// specified.164///165/// The closure `f` is given first a buffer to push the new test into along166/// with a lines iterator for the old test.167pub fn update_at(168&self,169location: &Location,170f: impl FnOnce(&mut String, &mut Lines<'_>),171) -> Result<()> {172// This is required for correctness of this update.173assert!(location.line_number > self.last_update.get());174self.last_update.set(location.line_number);175176// Read the old test file and calculate the new line number we're177// preserving up to based on how many lines prior to this have been178// removed or added.179let old_test = std::fs::read_to_string(&self.path)?;180let mut new_test = String::new();181let mut lines = old_test.lines();182let lines_to_preserve =183(((location.line_number - 1) as isize) + self.line_diff.get()) as usize;184185// Push everything leading up to the start of the function186for _ in 0..lines_to_preserve {187new_test.push_str(lines.next().unwrap());188new_test.push_str("\n");189}190191// Push the whole function, leading up to the trailing `}`192let mut first = true;193while let Some(line) = lines.next() {194if first && !line.starts_with("function") {195bail!(196"line {} in test file {:?} did not start with `function`, \197cannot automatically update test",198location.line_number,199self.path,200);201}202first = false;203new_test.push_str(line);204new_test.push_str("\n");205if line.starts_with("}") {206break;207}208}209210// Use our custom update function to further update the test.211f(&mut new_test, &mut lines);212213// Record the difference in line count so future updates can be adjusted214// accordingly, and then write the file back out to the filesystem.215let old_line_count = old_test.lines().count();216let new_line_count = new_test.lines().count();217self.line_diff218.set(self.line_diff.get() + (new_line_count as isize - old_line_count as isize));219220std::fs::write(&self.path, new_test)?;221Ok(())222}223}224225226