use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
use rayforce::{q, q::QConnection, Runtime, Table};
fn long_vec(vals: &[i64]) -> Vec<u8> {
let mut b = vec![7u8, 0u8];
b.extend_from_slice(&(vals.len() as i32).to_le_bytes());
for v in vals {
b.extend_from_slice(&v.to_le_bytes());
}
b
}
fn sym_vec(syms: &[&str]) -> Vec<u8> {
let mut b = vec![11u8, 0u8];
b.extend_from_slice(&(syms.len() as i32).to_le_bytes());
for s in syms {
b.extend_from_slice(s.as_bytes());
b.push(0);
}
b
}
fn table(names: &[&str], cols: &[Vec<u8>]) -> Vec<u8> {
let mut b = vec![98u8, 0u8, 99u8];
b.extend(sym_vec(names));
b.push(0u8); b.push(0u8); b.extend_from_slice(&(cols.len() as i32).to_le_bytes());
for c in cols {
b.extend_from_slice(c);
}
b
}
fn msg(body: &[u8]) -> Vec<u8> {
let size = (8 + body.len()) as u32;
let mut b = vec![1u8, 2u8, 0u8, 0u8]; b.extend_from_slice(&size.to_le_bytes());
b.extend_from_slice(body);
b
}
fn msg_big_endian(body: &[u8]) -> Vec<u8> {
let size = (8 + body.len()) as u32;
let mut b = vec![0u8, 2u8, 0u8, 0u8]; b.extend_from_slice(&size.to_be_bytes());
b.extend_from_slice(body);
b
}
fn spawn_mock(response: Vec<u8>) -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
thread::spawn(move || {
if let Ok((mut sock, _)) = listener.accept() {
let mut hs = [0u8; 2];
let _ = sock.read_exact(&mut hs);
let _ = sock.write_all(&[3u8]);
let mut hdr = [0u8; 8];
if sock.read_exact(&mut hdr).is_ok() {
let size = u32::from_le_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]) as usize;
let mut body = vec![0u8; size.saturating_sub(8)];
let _ = sock.read_exact(&mut body);
}
let _ = sock.write_all(&response);
}
});
port
}
#[test]
fn q_pulls_a_table() {
Runtime::scope(|_rt| {
let response = msg(&table(
&["seq", "sym"],
&[long_vec(&[1, 2, 3]), sym_vec(&["AAPL", "MSFT", "GOOG"])],
));
let port = spawn_mock(response);
let conn = QConnection::connect("127.0.0.1", port).unwrap();
let v = conn.execute("select from fixmsgs where i > 0").unwrap();
let t = Table::from_value(v).unwrap();
assert_eq!(t.shape(), (3, 2));
assert_eq!(
t.column("seq").unwrap().as_slice::<i64>().unwrap(),
&[1, 2, 3]
);
let sym = t.column("sym").unwrap();
assert_eq!(sym.get(0).unwrap().as_sym().unwrap(), "AAPL");
assert_eq!(sym.get(2).unwrap().as_sym().unwrap(), "GOOG");
Ok(())
})
.unwrap();
}
#[test]
fn q_surfaces_server_error() {
Runtime::scope(|_rt| {
let mut body = vec![(-128i8) as u8];
body.extend_from_slice(b"type\0");
let port = spawn_mock(msg(&body));
let conn = QConnection::connect("127.0.0.1", port).unwrap();
assert!(conn.execute("1+`a").is_err());
Ok(())
})
.unwrap();
}
#[test]
fn decode_response_rejects_big_endian_messages() {
Runtime::scope(|_rt| {
let err = q::decode_response(&msg_big_endian(&long_vec(&[1, 2, 3]))).unwrap_err();
assert!(
err.to_string().contains("little-endian"),
"unexpected error: {err}"
);
Ok(())
})
.unwrap();
}
#[test]
fn encode_round_trips_through_decode() {
Runtime::scope(|_rt| {
let v = rayforce::Value::vec(&[1i64, 2, 3]);
let mut frame = rayforce::q::encode(&v).unwrap();
assert_eq!(frame[0], 1, "little-endian flag");
assert_eq!(frame[1], 1, "encoder stamps SYNC");
assert_eq!(frame[2], 0, "uncompressed");
assert_eq!(
u32::from_le_bytes([frame[4], frame[5], frame[6], frame[7]]) as usize,
frame.len()
);
frame[1] = 2;
let back = rayforce::q::decode_response(&frame).unwrap();
assert_eq!(back.as_slice::<i64>().unwrap(), &[1, 2, 3]);
Ok(())
})
.unwrap();
}
#[test]
fn decode_response_rejects_non_response_messages() {
Runtime::scope(|_rt| {
let mut response = msg(&long_vec(&[1, 2, 3]));
response[1] = 1;
let err = q::decode_response(&response).unwrap_err();
assert!(
err.to_string().contains("expected response message type 2"),
"unexpected error: {err}"
);
Ok(())
})
.unwrap();
}