use std::{
fs::read,
io::{Read, Write},
net::{SocketAddr, TcpStream},
path::PathBuf,
process::{Command, Stdio as ProcessStdio},
str,
thread::{sleep, spawn},
time::Duration,
};
use serial_test::serial;
use memfd_exec::{MemFdExecutable, Stdio};
const TEST_STATIC_CODE: &[u8] = include_bytes!("./test_static.c");
const CARGO_TARGET_TMPDIR: &str = env!("CARGO_TARGET_TMPDIR");
fn build_test_static() {
let mut clang = Command::new("clang")
.arg("-x")
.arg("c")
.arg("-static")
.arg("-v")
.arg("-o")
.arg(PathBuf::from(CARGO_TARGET_TMPDIR).join("test_static.bin"))
.arg("-")
.stdin(ProcessStdio::piped())
.stdout(ProcessStdio::piped())
.stderr(ProcessStdio::piped())
.spawn()
.expect("Failed to run clang");
let mut clang_stdin = clang.stdin.take().expect("Failed to open stdin");
spawn(move || {
clang_stdin
.write_all(TEST_STATIC_CODE)
.expect("Could not write to clang stdin");
});
let output = clang.wait_with_output().expect("Failed to run clang");
assert!(
output.status.success(),
"Failed to compile static test:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn build_test_dynamic() {
let mut clang = Command::new("clang")
.arg("-x")
.arg("c")
.arg("-o")
.arg(PathBuf::from(CARGO_TARGET_TMPDIR).join("test_dynamic.bin"))
.arg("-")
.stdin(ProcessStdio::piped())
.stdout(ProcessStdio::piped())
.stderr(ProcessStdio::piped())
.spawn()
.expect("Failed to run clang");
let mut clang_stdin = clang.stdin.take().expect("Failed to open stdin");
spawn(move || {
clang_stdin
.write_all(TEST_STATIC_CODE)
.expect("Could not write to clang stdin");
});
let output = clang.wait_with_output().expect("Failed to run clang");
assert!(
output.status.success(),
"Failed to compile static test:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_ls() {
let ls_contents = read("/bin/ls").expect("Could not read /bin/ls");
let _ls = MemFdExecutable::new("ls", &ls_contents)
.arg(".")
.spawn()
.expect("Failed to run ls");
}
#[test]
fn test_cat_simple() {
let cat_contents = read("/bin/cat").expect("Could not read /bin/cat");
let _cat = MemFdExecutable::new("cat", &cat_contents)
.arg("Cargo.toml")
.spawn()
.expect("Failed to run cat");
}
#[test]
fn test_cat_stdin() {
let cat_contents = read("/bin/cat").expect("Could not read /bin/cat");
let mut cat = MemFdExecutable::new("cat", &cat_contents)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to run cat");
let mut stdin = cat.stdin.take().expect("Failed to open stdin");
spawn(move || {
stdin
.write_all(b"Hello, world!")
.expect("Failed to write to cat stdin");
});
let output = cat.wait_with_output().expect("Failed to run cat");
assert!(
output.stdout.len() == b"Hello, world!".len(),
"Output was too short (wanted at least {} bytes, got {})",
b"Hello, world!".len(),
output.stdout.len()
);
}
#[test]
#[serial]
fn test_static_included() {
build_test_static();
let test_static_exe = PathBuf::from(CARGO_TARGET_TMPDIR).join("test_static.bin");
let test_static_exe_contents = read(test_static_exe).expect("Could not read static exe");
let test_static = MemFdExecutable::new("test_static.bin", &test_static_exe_contents)
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn test_static");
let port = {
let mut array = [0u8; 32];
let len = test_static
.stdout
.as_ref()
.unwrap()
.read(&mut array)
.unwrap();
let port: u16 = str::from_utf8(&array[..len]).unwrap().parse().unwrap();
port
};
let output_thread = spawn(move || {
let output = test_static
.wait_with_output()
.expect("Failed to run test_static");
assert!(
output.stdout.len() == b"Hello, world!\n\n".len(),
"Output was too short (wanted at least {} bytes, got {})",
b"Hello, world!".len(),
output.stdout.len()
);
});
let sock: SocketAddr = format!("127.0.0.1:{}", port)
.parse()
.expect("Failed to parse socket address");
let mut stream = TcpStream::connect(sock).unwrap();
stream
.write_all(b"Hello, world!\n\n")
.expect("Failed to write to socket");
drop(stream);
output_thread.join().expect("Failed to join output thread");
}
#[test]
#[serial]
fn test_dynamic_included() {
build_test_dynamic();
let test_dynamic_exe = PathBuf::from(CARGO_TARGET_TMPDIR).join("test_dynamic.bin");
let test_dynamic_exe_contents = read(test_dynamic_exe).expect("Could not read dynamic exe");
let test_dynamic = MemFdExecutable::new("test_dynamic.bin", &test_dynamic_exe_contents)
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn test_dynamic");
let port = {
let mut array = [0u8; 32];
let len = test_dynamic
.stdout
.as_ref()
.unwrap()
.read(&mut array)
.unwrap();
let port: u16 = str::from_utf8(&array[..len]).unwrap().parse().unwrap();
port
};
let output_thread = spawn(move || {
let output = test_dynamic
.wait_with_output()
.expect("Failed to run test_dynamic");
assert!(
output.stdout.len() == b"Hello, world!\n\n".len(),
"Output was too short (wanted at least {} bytes, got {})",
b"Hello, world!".len(),
output.stdout.len()
);
});
let sock: SocketAddr = format!("127.0.0.1:{}", port)
.parse()
.expect("Failed to parse socket address");
for _ in 0..10 {
if let Ok(mut stream) = TcpStream::connect(sock) {
stream
.write_all(b"Hello, world!\n\n")
.expect("Failed to write to socket");
drop(stream);
break;
}
sleep(Duration::from_millis(100));
}
output_thread.join().expect("Failed to join output thread");
}