mod common;
use hyperdb_api::{Connection, CreateMode, HyperProcess};
use std::process::Command;
use std::thread;
use std::time::Duration;
#[test]
fn test_hyper_process_start_stop() {
let params = common::test_hyper_params("test_hyper_process_start_stop")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let endpoint = hyper.endpoint().expect("No endpoint");
let descriptor = endpoint.to_string();
assert!(!descriptor.is_empty());
let conn = Connection::without_database(endpoint).expect("Failed to connect");
let mut rowset = conn
.execute_query("SELECT 17")
.expect("Failed to execute query");
let chunk = rowset
.next_chunk()
.expect("Failed to get chunk")
.expect("Expected chunk");
let row = chunk.first().expect("Expected row");
let result = row.get_i32(0).expect("NULL value");
assert_eq!(result, 17);
}
#[test]
fn test_hyper_process_multiple_instances() {
let params1 = common::test_hyper_params("test_hyper_process_multiple_instances_1")
.expect("Failed to create test parameters");
let params2 = common::test_hyper_params("test_hyper_process_multiple_instances_2")
.expect("Failed to create test parameters");
let hyper1 =
HyperProcess::new(None, Some(¶ms1)).expect("Failed to start first Hyper process");
let hyper2 =
HyperProcess::new(None, Some(¶ms2)).expect("Failed to start second Hyper process");
let conn1 = Connection::without_database(hyper1.endpoint().unwrap())
.expect("Failed to connect to first instance");
let conn2 = Connection::without_database(hyper2.endpoint().unwrap())
.expect("Failed to connect to second instance");
let mut rowset1 = conn1
.execute_query("SELECT 42")
.expect("Failed to execute query");
let chunk1 = rowset1
.next_chunk()
.expect("Failed to get chunk")
.expect("Expected chunk");
let result1 = chunk1
.first()
.expect("Expected row")
.get_i32(0)
.expect("NULL value");
let mut rowset2 = conn2
.execute_query("SELECT 99")
.expect("Failed to execute query");
let chunk2 = rowset2
.next_chunk()
.expect("Failed to get chunk")
.expect("Expected chunk");
let result2 = chunk2
.first()
.expect("Expected row")
.get_i32(0)
.expect("NULL value");
assert_eq!(result1, 42);
assert_eq!(result2, 99);
}
#[test]
fn test_hyper_process_endpoint_descriptor() {
let params = common::test_hyper_params("test_hyper_process_endpoint_descriptor")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let endpoint = hyper.endpoint().expect("No endpoint");
let descriptor = endpoint.to_string();
assert!(
descriptor.contains(':'),
"Endpoint should be host:port format"
);
}
#[test]
fn test_hyper_process_connection_new() {
let params = common::test_hyper_params("test_hyper_process_connection_new")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
let db_path = temp_dir.path().join("test.hyper");
let conn =
Connection::new(&hyper, &db_path, CreateMode::CreateAndReplace).expect("Failed to connect");
conn.execute_command("CREATE TABLE test (id INT)")
.expect("Failed to create table");
let mut result = conn.execute_query("SELECT 123").expect("Failed to query");
let chunk = result
.next_chunk()
.expect("Failed to get chunk")
.expect("Expected chunk");
let value = chunk
.first()
.expect("Expected row")
.get_i32(0)
.expect("NULL value");
assert_eq!(value, 123);
}
#[test]
fn test_hyper_process_telemetry() {
let params = common::test_hyper_params("test_hyper_process_telemetry")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let _conn = Connection::without_database(hyper.endpoint().unwrap()).expect("Failed to connect");
}
#[test]
fn test_hyper_process_drop() {
let endpoint_str;
{
let params = common::test_hyper_params("test_hyper_process_drop")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
endpoint_str = hyper.endpoint().unwrap().to_string();
let conn =
Connection::without_database(hyper.endpoint().unwrap()).expect("Failed to connect");
conn.execute_query("SELECT 1").expect("Failed to query");
}
assert!(!endpoint_str.is_empty());
}
#[test]
fn test_hyper_process_create_multiple_databases() {
let params = common::test_hyper_params("test_hyper_process_create_multiple_databases")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
let db1_path = temp_dir.path().join("db1.hyper");
let db2_path = temp_dir.path().join("db2.hyper");
{
let conn1 = Connection::new(&hyper, &db1_path, CreateMode::CreateAndReplace)
.expect("Failed to create db1");
conn1
.execute_command("CREATE TABLE t1 (id INT)")
.expect("Failed to create table");
}
{
let conn2 = Connection::new(&hyper, &db2_path, CreateMode::CreateAndReplace)
.expect("Failed to create db2");
conn2
.execute_command("CREATE TABLE t2 (id INT)")
.expect("Failed to create table");
}
{
let conn1 = Connection::new(&hyper, &db1_path, CreateMode::DoNotCreate)
.expect("Failed to open db1");
assert!(conn1.execute_query("SELECT * FROM t1").is_ok());
}
{
let conn2 = Connection::new(&hyper, &db2_path, CreateMode::DoNotCreate)
.expect("Failed to open db2");
assert!(conn2.execute_query("SELECT * FROM t2").is_ok());
}
}
#[test]
fn test_callback_connection_graceful_shutdown() {
let pid;
{
let params = common::test_hyper_params("test_callback_connection_graceful_shutdown")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
pid = hyper.pid().expect("Should have PID");
assert!(
is_process_running(pid),
"Process should be running after start"
);
let conn =
Connection::without_database(hyper.endpoint().unwrap()).expect("Failed to connect");
conn.execute_query("SELECT 1").expect("Query should work");
}
let mut shutdown_detected = false;
for _ in 0..50 {
thread::sleep(Duration::from_millis(100));
if !is_process_running(pid) {
shutdown_detected = true;
break;
}
}
assert!(
shutdown_detected,
"Hyper process (pid={pid}) should have shut down after callback connection closed"
);
}
#[test]
fn test_shutdown_timeout() {
let params = common::test_hyper_params("test_shutdown_timeout")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let pid = hyper.pid().expect("Should have PID");
assert!(is_process_running(pid), "Process should be running");
let conn = Connection::without_database(hyper.endpoint().unwrap()).expect("Failed to connect");
conn.execute_query("SELECT 42").expect("Query should work");
drop(conn);
hyper
.shutdown_timeout(Duration::from_secs(5))
.expect("Shutdown should succeed");
assert!(
!is_process_running(pid),
"Process should not be running after shutdown"
);
}
#[test]
fn test_callback_endpoint_format() {
let params = common::test_hyper_params("test_callback_endpoint_format")
.expect("Failed to create test parameters");
let hyper = HyperProcess::new(None, Some(¶ms)).expect("Failed to start Hyper process");
let endpoint = hyper.endpoint().expect("No endpoint");
assert!(endpoint.contains(':'), "Endpoint should contain ':'");
let colon_idx = endpoint.rfind(':').expect("No colon in endpoint");
let host = &endpoint[..colon_idx];
let port_str = &endpoint[colon_idx + 1..];
let port: u16 = port_str
.parse()
.unwrap_or_else(|e| panic!("Port '{port_str}' should be a valid number: {e:?}"));
assert!(port > 0, "Port should be positive");
assert!(
host == "localhost" || host == "127.0.0.1",
"Host should be localhost or 127.0.0.1, got: {host}"
);
}
fn is_process_running(pid: u32) -> bool {
#[cfg(unix)]
{
Command::new("kill")
.args(["-0", &pid.to_string()])
.output()
.is_ok_and(|output| output.status.success())
}
#[cfg(windows)]
{
Command::new("tasklist")
.args(["/FI", &format!("PID eq {pid}")])
.output()
.is_ok_and(|output| {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.contains(&pid.to_string())
})
}
}