extern crate rgrow;
#[cfg(windows)]
use named_pipe::{PipeOptions, PipeServer};
use rgrow::ui::ipc::{ControlMessage, InitMessage, IpcMessage, UpdateNotification};
use rgrow::ui::ipc_server::IpcClient;
use std::io::{Read, Write};
#[cfg(unix)]
use std::os::unix::net::UnixListener;
use std::path::PathBuf;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn create_temp_socket() -> PathBuf {
use std::env;
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let mut path = env::temp_dir();
#[cfg(unix)]
{
path.push(format!(
"rgrow-test-{}-{}.sock",
std::process::id(),
timestamp
));
let _ = std::fs::remove_file(&path);
}
#[cfg(windows)]
{
path.push(format!("rgrow-test-{}-{}", std::process::id(), timestamp));
}
path
}
#[cfg(unix)]
fn create_listener(path: &PathBuf) -> Result<UnixListener, std::io::Error> {
UnixListener::bind(path)
}
#[cfg(windows)]
fn create_listener(path: &PathBuf) -> Result<PipeServer, std::io::Error> {
let pipe_name = format!(
r"\\.\pipe\{}",
path.to_string_lossy().replace('/', "_").replace('\\', "_")
);
PipeOptions::new(pipe_name.as_str()).single()
}
#[test]
fn test_unix_socket_creation() {
let socket_path = create_temp_socket();
let listener = create_listener(&socket_path);
assert!(
listener.is_ok(),
"Should be able to create IPC listener: {:?}",
listener.err()
);
#[cfg(unix)]
{
let _ = std::fs::remove_file(&socket_path);
}
}
#[test]
fn test_ipc_client_connect() {
let socket_path = create_temp_socket();
let _listener = create_listener(&socket_path).expect("Failed to bind listener");
let result = IpcClient::connect(&socket_path);
#[cfg(unix)]
{
assert!(
socket_path.exists() || result.is_err(),
"Socket path should exist or connection should fail gracefully"
);
}
#[cfg(windows)]
{
assert!(
result.is_err() || result.is_ok(),
"Connection should either succeed or fail gracefully"
);
}
#[cfg(unix)]
{
let _ = std::fs::remove_file(&socket_path);
}
}
#[test]
fn test_ipc_init_ready_handshake() {
let socket_path = create_temp_socket();
let listener = create_listener(&socket_path).expect("Failed to bind listener");
let _socket_path_clone = socket_path.clone();
let server_thread = thread::spawn(move || {
#[cfg(unix)]
let stream_result = listener.accept().map(|(s, _)| s);
#[cfg(windows)]
let stream_result = listener.wait();
if let Ok(mut stream) = stream_result {
let mut len_bytes = [0u8; 8];
if stream.read_exact(&mut len_bytes).is_ok() {
let len = u64::from_le_bytes(len_bytes) as usize;
let mut buffer = vec![0u8; len];
if stream.read_exact(&mut buffer).is_ok() {
let _init_msg: IpcMessage = bincode::deserialize(&buffer).unwrap();
let ready_msg = IpcMessage::Ready;
let serialized = bincode::serialize(&ready_msg).unwrap();
let len = serialized.len() as u64;
let _ = stream.write_all(&len.to_le_bytes());
let _ = stream.write_all(&serialized);
let _ = stream.flush();
}
}
}
});
thread::sleep(Duration::from_millis(50));
let mut client = IpcClient::connect(&socket_path).expect("Failed to connect");
let init = InitMessage {
width: 100,
height: 100,
tile_colors: vec![[255, 0, 0, 255]],
block: Some(8),
shm_path: "/tmp/test-shm".to_string(),
shm_size: 4096,
start_paused: false,
model_name: "kTAM".to_string(),
has_temperature: false,
initial_temperature: None,
parameters: vec![],
initial_timescale: None,
initial_max_events_per_sec: None,
};
client.send_init(&init).expect("Failed to send init");
let result = client.wait_for_ready(Duration::from_secs(1));
assert!(result.is_ok(), "Should receive Ready message");
server_thread.join().unwrap();
#[cfg(unix)]
{
let _ = std::fs::remove_file(&socket_path);
}
}
#[test]
fn test_ipc_update_message() {
let socket_path = create_temp_socket();
let listener = create_listener(&socket_path).expect("Failed to bind listener");
let shm_path = format!("/tmp/rgrow-test-shm-{}", std::process::id());
let shm_size = 100 * 100 * 4; let shm_file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&shm_path)
.expect("Failed to create shm file");
shm_file
.set_len(shm_size as u64)
.expect("Failed to set shm size");
let _socket_path_clone = socket_path.clone();
let server_thread = thread::spawn(move || {
#[cfg(unix)]
let stream_result = listener.accept().map(|(s, _)| s);
#[cfg(windows)]
let stream_result = listener.wait();
if let Ok(mut stream) = stream_result {
let mut len_bytes = [0u8; 8];
if stream.read_exact(&mut len_bytes).is_ok() {
let len = u64::from_le_bytes(len_bytes) as usize;
let mut buffer = vec![0u8; len];
if stream.read_exact(&mut buffer).is_ok() {
let ready_msg = IpcMessage::Ready;
let serialized = bincode::serialize(&ready_msg).unwrap();
let len = serialized.len() as u64;
let _ = stream.write_all(&len.to_le_bytes());
let _ = stream.write_all(&serialized);
let _ = stream.flush();
if stream.read_exact(&mut len_bytes).is_ok() {
let len = u64::from_le_bytes(len_bytes) as usize;
buffer.resize(len, 0);
if stream.read_exact(&mut buffer).is_ok() {
let update_msg: IpcMessage = bincode::deserialize(&buffer).unwrap();
assert!(matches!(update_msg, IpcMessage::Update(_)));
}
}
}
}
}
});
thread::sleep(Duration::from_millis(50));
let mut client = IpcClient::connect(&socket_path).expect("Failed to connect");
let init = InitMessage {
width: 100,
height: 100,
tile_colors: vec![[255, 0, 0, 255]],
block: Some(8),
shm_path: shm_path.clone(),
shm_size,
start_paused: false,
model_name: "kTAM".to_string(),
has_temperature: false,
initial_temperature: None,
parameters: vec![],
initial_timescale: None,
initial_max_events_per_sec: None,
};
client.send_init(&init).expect("Failed to send init");
client.wait_for_ready(Duration::from_secs(1)).unwrap();
let frame_data = vec![255u8; shm_size];
let notification = UpdateNotification {
frame_width: 100,
frame_height: 100,
time: 1.0,
total_events: 100,
n_tiles: 10,
mismatches: 0,
energy: -10.0,
scale: 8,
data_len: shm_size,
};
client
.send_frame(&frame_data, notification)
.expect("Failed to send frame");
server_thread.join().unwrap();
#[cfg(unix)]
{
let _ = std::fs::remove_file(&socket_path);
}
let _ = std::fs::remove_file(&shm_path);
}
#[test]
fn test_ipc_control_message() {
let socket_path = create_temp_socket();
let listener = create_listener(&socket_path).expect("Failed to bind listener");
let _socket_path_clone = socket_path.clone();
let (tx, rx) = mpsc::channel();
let server_thread = thread::spawn(move || {
#[cfg(unix)]
let stream_result = listener.accept().map(|(s, _)| s);
#[cfg(windows)]
let stream_result = listener.wait();
if let Ok(mut stream) = stream_result {
let mut len_bytes = [0u8; 8];
if stream.read_exact(&mut len_bytes).is_ok() {
let len = u64::from_le_bytes(len_bytes) as usize;
let mut buffer = vec![0u8; len];
if stream.read_exact(&mut buffer).is_ok() {
let ready_msg = IpcMessage::Ready;
let serialized = bincode::serialize(&ready_msg).unwrap();
let len = serialized.len() as u64;
let _ = stream.write_all(&len.to_le_bytes());
let _ = stream.write_all(&serialized);
let _ = stream.flush();
thread::sleep(Duration::from_millis(100));
let control_msg = ControlMessage::Pause;
let serialized = bincode::serialize(&control_msg).unwrap();
let len = serialized.len() as u64;
let _ = stream.write_all(&len.to_le_bytes());
let _ = stream.write_all(&serialized);
let _ = stream.flush();
let _ = tx.send(());
}
}
}
});
thread::sleep(Duration::from_millis(50));
let mut client = IpcClient::connect(&socket_path).expect("Failed to connect");
let init = InitMessage {
width: 100,
height: 100,
tile_colors: vec![[255, 0, 0, 255]],
block: Some(8),
shm_path: "/tmp/test-shm".to_string(),
shm_size: 4096,
start_paused: false,
model_name: "kTAM".to_string(),
has_temperature: false,
initial_temperature: None,
parameters: vec![],
initial_timescale: None,
initial_max_events_per_sec: None,
};
client.send_init(&init).expect("Failed to send init");
client.wait_for_ready(Duration::from_secs(1)).unwrap();
thread::sleep(Duration::from_millis(150));
let control_msg = client.try_recv_control();
assert!(control_msg.is_some(), "Should receive control message");
assert!(matches!(control_msg.unwrap(), ControlMessage::Pause));
let _ = rx.recv_timeout(Duration::from_secs(1));
server_thread.join().unwrap();
#[cfg(unix)]
{
let _ = std::fs::remove_file(&socket_path);
}
}