//! Plumbing shared by the per-target tests: emit a target onto a clean scratch //! root, drop the consumer fixtures beside it, and run the language toolchain //! over the result. A toolchain that cannot be located panics with the search //! it performed rather than letting the test pass without checking anything. use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use mizan_codegen::config::Config; use mizan_codegen::emit::EmittedFile; use mizan_codegen::fetch::parse_ir_from_str; use mizan_codegen::ir::MizanIR; pub fn manifest_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) } pub fn load_ir(fixture: &str) -> MizanIR { let path = manifest_dir().join("tests/fixtures").join(fixture); let raw = fs::read_to_string(&path) .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); parse_ir_from_str(&raw) .unwrap_or_else(|e| panic!("parse {}: {e}", path.display())) } /// Emitters read the target list and write relative to `output`; the scratch /// root is the process working directory for every toolchain run, so `.` is /// the whole of the path configuration these tests need. pub fn config_for(target: &str) -> Config { Config { output: PathBuf::from("."), targets: vec![target.to_string()], ..Config::default() } } /// Wipes and recreates `target/toolchain/`. A failing run leaves its /// sources there for inspection; the next run clears them. pub fn scratch_root(name: &str) -> PathBuf { let root = manifest_dir().join("target/toolchain").join(name); if root.exists() { fs::remove_dir_all(&root) .unwrap_or_else(|e| panic!("clear {}: {e}", root.display())); } fs::create_dir_all(&root) .unwrap_or_else(|e| panic!("create {}: {e}", root.display())); root } pub fn write_emitted(root: &Path, files: &[EmittedFile]) { assert!(!files.is_empty(), "target emitted no files"); for file in files { write_file(&root.join(&file.rel_path), &file.content); } } pub fn copy_fixture(rel: &str, dest: &Path) { let src = manifest_dir().join("tests/fixtures/toolchain").join(rel); let contents = fs::read_to_string(&src) .unwrap_or_else(|e| panic!("read fixture {}: {e}", src.display())); write_file(dest, &contents); } fn write_file(dest: &Path, contents: &str) { let parent = match dest.parent() { Some(dir) => dir, None => Path::new("."), }; fs::create_dir_all(parent) .unwrap_or_else(|e| panic!("create {}: {e}", parent.display())); fs::write(dest, contents) .unwrap_or_else(|e| panic!("write {}: {e}", dest.display())); } pub fn resolve_on_path(program: &str) -> PathBuf { let path_var = std::env::var("PATH") .unwrap_or_else(|e| panic!("PATH must be readable to locate `{program}`: {e}")); for dir in std::env::split_paths(&path_var) { let candidate = dir.join(program); if candidate.is_file() { return candidate; } } panic!( "`{program}` is required to check the emitted client and is not on PATH:\n {path_var}" ); } pub fn run_tool( program: &Path, args: &[&str], cwd: &Path, env: &[(&str, &Path)], what: &str, ) { let mut command = Command::new(program); command.args(args).current_dir(cwd); for (key, value) in env { command.env(key, value); } let output = command.output() .unwrap_or_else(|e| panic!("spawn {}: {e}", program.display())); if output.status.success() { return; } let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); panic!( "{what} (exit {:?}); sources left at {}\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}", output.status.code(), cwd.display(), ); }