#![allow(clippy::unwrap_used, reason = "test failures should be loud")]
mod common;
mod programs;
use backend_lang::x86_64::Emitter;
use object_lang::{Architecture, FileKind};
const ENTRY_STUB: [u8; 5] = [0xE8, 0, 0, 0, 0];
fn link(functions: &[backend_lang::mir::Function]) -> Vec<u8> {
let mut e = Emitter::new();
for f in functions {
let _ = e.function(f).unwrap_or_else(|err| panic!("{err}\n{f}"));
}
let p = e.finish().unwrap();
assert_eq!(p.symbol(functions[0].name()), Some(0));
let mut code = ENTRY_STUB.to_vec();
code.extend_from_slice(p.bytes());
code
}
fn executable(code: &[u8]) -> Vec<u8> {
let bytes = object_lang::elf::executable(Architecture::X86_64, code).unwrap();
let obj = object_lang::elf::read(&bytes).unwrap();
assert_eq!(obj.kind(), FileKind::Executable);
assert_eq!(obj.architecture(), Architecture::X86_64);
assert_eq!(
obj.entry(),
object_lang::elf::code_address(Architecture::X86_64)
);
assert_eq!(obj.sections()[0].data(), code);
bytes
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn run_native(bytes: &[u8], name: &str) -> std::process::Output {
use std::os::unix::fs::PermissionsExt;
let dir = std::env::temp_dir().join(format!("backend-lang-exec-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
std::fs::write(&path, bytes).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap();
const ETXTBSY: i32 = 26;
let mut attempts = 0;
let output = loop {
match std::process::Command::new(&path).output() {
Err(e) if e.raw_os_error() == Some(ETXTBSY) && attempts < 100 => {
attempts += 1;
std::thread::sleep(std::time::Duration::from_millis(10));
}
result => break result.unwrap(),
}
};
let _ = std::fs::remove_file(&path);
output
}
fn check(name: &str, code: &[u8], status: u64) {
assert_eq!(
common::run_process(code),
Ok(common::Stop::Exit(status)),
"{name} in the interpreter"
);
let bytes = executable(code);
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
{
let output = run_native(&bytes, name);
assert_eq!(
output.status.code(),
Some(status as i32),
"{name}: {output:?}"
);
}
let _ = bytes;
}
#[test]
fn fib_loop_with_parallel_copies_exits_55() {
let code = backend_lang::emit(&programs::fib().unwrap()).unwrap();
check("fib", &code, 55);
}
#[test]
fn recursive_factorial_exits_120() {
let code = link(&[programs::fact_main().unwrap(), programs::fact().unwrap()]);
check("fact", &code, 120);
}
#[test]
fn kitchen_sink_exits_200() {
let code = link(&[programs::kitchen().unwrap(), programs::helper().unwrap()]);
check("kitchen", &code, 200);
}