#![cfg(feature = "onnx")]
#![cfg(feature = "onnx")]
use std::io::Cursor;
use std::time::Duration;
use leindex::embed::batch::{self, BatchConfig, SplitResult};
use leindex::embed::model_path::ModelResolver;
#[cfg(not(feature = "onnx"))]
use leindex::embed::protocol::Response;
static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
use leindex::embed::protocol::{self, BatchId, EmbedRequest, EmbedResponse, MsgType, Request};
use leindex::embed::provider::ExecutionProviderSelector;
use leindex::embed::runtime::{RuntimeConfig, WorkerRuntime};
use leindex::embed::startup::{StartupReport, StartupReporter};
fn no_compile_config() -> RuntimeConfig {
use leindex::embed::runtime::{
DEFAULT_IDLE_TIMEOUT_SECS, DEFAULT_MAX_FRAME_SIZE, DEFAULT_MAX_TEXT_SIZE,
};
RuntimeConfig {
idle_timeout: Duration::from_secs(DEFAULT_IDLE_TIMEOUT_SECS),
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
max_text_size: DEFAULT_MAX_TEXT_SIZE,
model_name: "__leindex_test_no_model__".to_string(),
embedding_dim: 1024,
execution_provider: "auto".to_string(),
rerank_model_name: "__leindex_test_no_model__".to_string(),
ort_threads: 2,
max_rss_mb: None,
min_available_mb: None,
}
}
#[cfg(feature = "onnx")]
mod rerank_output_shape_tests {
use ndarray::ArrayD;
struct FakeOutput {
shape: Vec<usize>,
values: Vec<f32>,
}
impl FakeOutput {
fn new(shape: Vec<usize>, values: Vec<f32>) -> Self {
Self { shape, values }
}
}
#[test]
fn scalar_logit_outputs_use_direct_scores() {
let batch_size = 2;
let outputs = FakeOutput::new(vec![batch_size], vec![0.81, 0.22]);
let array = ArrayD::from_shape_vec(outputs.shape.clone(), outputs.values.clone()).unwrap();
let extracted: Vec<f32> = array.iter().copied().collect();
assert_eq!(extracted, vec![0.81, 0.22]);
}
#[test]
fn hidden_state_outputs_fallback_to_first_token_norm() {
let batch_size = 2;
let seq_len = 3;
let hidden_dim = 4;
let values = vec![
1.0, 2.0, 3.0, 4.0, 9.0, 9.0, 9.0, 9.0, 8.0, 8.0, 8.0, 8.0, 0.5, 0.5, 0.5, 0.5, 7.0, 7.0, 7.0, 7.0, 6.0, 6.0, 6.0, 6.0, ];
let array = ArrayD::from_shape_vec(vec![batch_size, seq_len, hidden_dim], values).unwrap();
let extracted: Vec<f32> = array.iter().copied().collect();
let first_token_norm_batch0 = (1.0_f32 + 4.0 + 9.0 + 16.0).sqrt();
let first_token_norm_batch1 = (0.25_f32 * 4.0).sqrt();
assert_eq!(extracted.len(), batch_size * seq_len * hidden_dim);
assert!((first_token_norm_batch0 - 5.4772253).abs() < 1e-5);
assert!((first_token_norm_batch1 - 1.0).abs() < 1e-5);
}
}
#[test]
fn test_worker_uses_local_ipc_only() {
let config = no_compile_config();
let rt = WorkerRuntime::new(config);
let request = EmbedRequest {
texts: vec!["local ipc test".to_string()],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let wire = frame.encode_wire().unwrap();
let reader = Cursor::new(wire);
let writer = Cursor::new(Vec::<u8>::new());
let result = rt.run_loop(reader, writer);
assert!(result.is_ok(), "local IPC should work over in-memory pipes");
}
#[test]
fn test_worker_cold_starts_on_first_demand() {
let config = no_compile_config();
let rt = WorkerRuntime::new(config);
let request = EmbedRequest {
texts: vec!["cold start test".to_string()],
expected_dim: 8,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let response_frame = rt.dispatch(&frame);
#[cfg(feature = "onnx")]
{
assert_eq!(response_frame.header.msg_type, MsgType::Error);
}
#[cfg(not(feature = "onnx"))]
{
assert_eq!(response_frame.header.msg_type, MsgType::EmbedResponse);
let response: Response = response_frame.decode_payload().unwrap();
match response {
Response::Embed(embed) => {
assert_eq!(embed.count, 1);
assert_eq!(embed.dimension, 8);
}
_ => panic!("expected Embed response"),
}
}
}
#[test]
fn test_worker_reusable_across_batches() {
let config = no_compile_config();
let rt = WorkerRuntime::new(config);
#[cfg(feature = "onnx")]
let expected_msg_type = MsgType::Error;
#[cfg(not(feature = "onnx"))]
let expected_msg_type = MsgType::EmbedResponse;
let request1 = EmbedRequest {
texts: vec!["first batch".to_string()],
expected_dim: 4,
};
let frame1 = protocol::embed_request_frame(BatchId::new(1), request1).unwrap();
let response1 = rt.dispatch(&frame1);
assert_eq!(response1.header.msg_type, expected_msg_type);
let request2 = EmbedRequest {
texts: vec!["second batch".to_string(), "extra text".to_string()],
expected_dim: 4,
};
let frame2 = protocol::embed_request_frame(BatchId::new(2), request2).unwrap();
let response2 = rt.dispatch(&frame2);
assert_eq!(response2.header.msg_type, expected_msg_type);
let request3 = EmbedRequest {
texts: vec!["third".to_string()],
expected_dim: 4,
};
let frame3 = protocol::embed_request_frame(BatchId::new(3), request3).unwrap();
let response3 = rt.dispatch(&frame3);
assert_eq!(response3.header.msg_type, expected_msg_type);
assert_eq!(response1.header.batch_id, BatchId::new(1));
assert_eq!(response2.header.batch_id, BatchId::new(2));
assert_eq!(response3.header.batch_id, BatchId::new(3));
}
#[test]
fn test_worker_reusable_via_run_loop() {
let config = RuntimeConfig {
idle_timeout: Duration::from_secs(300),
..no_compile_config()
};
let rt = WorkerRuntime::new(config);
let mut all_wire = Vec::new();
for i in 0..3 {
let request = EmbedRequest {
texts: vec![format!("batch {}", i)],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(i as u64), request).unwrap();
let wire = frame.encode_wire().unwrap();
all_wire.extend_from_slice(&wire);
}
let reader = Cursor::new(all_wire);
let writer = Cursor::new(Vec::<u8>::new());
let result = rt.run_loop(reader, writer);
assert!(result.is_ok());
}
#[test]
fn test_worker_idle_timeout_teardown() {
let config = RuntimeConfig {
idle_timeout: Duration::from_millis(1),
..no_compile_config()
};
let rt = WorkerRuntime::new(config);
let reader = Cursor::new(Vec::<u8>::new());
let writer = Cursor::new(Vec::<u8>::new());
let result = rt.run_loop(reader, writer);
assert!(result.is_ok(), "worker should exit cleanly on idle");
}
#[test]
fn test_worker_idle_timer_expires() {
let config = RuntimeConfig {
idle_timeout: Duration::from_millis(5),
..no_compile_config()
};
let rt = WorkerRuntime::new(config);
assert!(!rt.is_idle_expired(), "should not be expired immediately");
std::thread::sleep(Duration::from_millis(10));
assert!(rt.is_idle_expired(), "should be expired after timeout");
}
#[test]
fn test_worker_restart_after_teardown() {
#[cfg(feature = "onnx")]
let expected_msg_type = MsgType::Error;
#[cfg(not(feature = "onnx"))]
let expected_msg_type = MsgType::EmbedResponse;
let config = no_compile_config();
let rt1 = WorkerRuntime::new(config.clone());
let request1 = EmbedRequest {
texts: vec!["before teardown".to_string()],
expected_dim: 4,
};
let frame1 = protocol::embed_request_frame(BatchId::new(1), request1).unwrap();
let response1 = rt1.dispatch(&frame1);
assert_eq!(response1.header.batch_id, BatchId::new(1));
drop(rt1);
let rt2 = WorkerRuntime::new(config);
let request2 = EmbedRequest {
texts: vec!["after restart".to_string()],
expected_dim: 4,
};
let frame2 = protocol::embed_request_frame(BatchId::new(2), request2).unwrap();
let response2 = rt2.dispatch(&frame2);
assert_eq!(response2.header.batch_id, BatchId::new(2));
assert_eq!(response2.header.msg_type, expected_msg_type);
}
#[test]
fn test_startup_report_contains_required_fields() {
let report = StartupReport {
execution_provider: "cpu".to_string(),
provider_available: true,
fallback_reason: None,
model_name: "qwen3-embed-0.6b".to_string(),
quantization_mode: "none".to_string(),
warm_load_latency: Duration::from_millis(150),
model_path: Some(std::path::PathBuf::from("/opt/models/model.onnx")),
model_path_source: Some("bundled".to_string()),
model_error: None,
ort_path: None,
ort_source: None,
};
let line = report.to_log_line();
assert!(line.contains("provider=cpu"), "missing execution provider");
assert!(
line.contains("model=qwen3-embed-0.6b"),
"missing model name"
);
assert!(line.contains("quant=none"), "missing quantization mode");
assert!(line.contains("warm_load="), "missing warm-load latency");
assert!(line.contains("bundled"), "missing model path source");
}
#[test]
fn test_startup_report_with_fallback_reason() {
let report = StartupReport {
execution_provider: "cuda".to_string(),
provider_available: false,
fallback_reason: Some("CUDA driver not found".to_string()),
model_name: "qwen3-embed-0.6b".to_string(),
quantization_mode: "none".to_string(),
warm_load_latency: Duration::from_millis(100),
model_path: Some(std::path::PathBuf::from(
"/home/user/.leindex/models/model.onnx",
)),
model_path_source: Some("user_cache".to_string()),
model_error: None,
ort_path: None,
ort_source: None,
};
let line = report.to_log_line();
assert!(line.contains("cuda"), "should mention requested provider");
assert!(line.contains("unavailable"), "should report unavailability");
assert!(
line.contains("CUDA driver not found"),
"should include fallback reason"
);
}
#[test]
fn test_startup_reporter_builds_complete_report() {
let mut reporter = StartupReporter::new();
reporter.set_execution_provider("cpu", true, None);
reporter.set_model_name("qwen3-embed-0.6b");
reporter.set_quantization_mode("int8");
reporter.set_warm_load_latency(Duration::from_millis(200));
reporter.set_model_path(
&std::path::PathBuf::from("/opt/models/model.onnx"),
"bundled",
);
let report = reporter.build();
assert_eq!(report.execution_provider, "cpu");
assert!(report.provider_available);
assert_eq!(report.model_name, "qwen3-embed-0.6b");
assert_eq!(report.quantization_mode, "int8");
assert_eq!(report.warm_load_latency, Duration::from_millis(200));
assert_eq!(report.model_path_source, Some("bundled".to_string()));
}
#[test]
fn test_startup_report_marks_unavailable_provider() {
let mut reporter = StartupReporter::new();
reporter.set_execution_provider("migraphx", false, Some("MIGraphX unavailable"));
reporter.set_model_name("qwen3-embed-0.6b");
reporter.set_quantization_mode("none");
reporter.set_warm_load_latency(Duration::from_millis(100));
let report = reporter.build();
assert_eq!(report.execution_provider, "migraphx");
assert!(!report.provider_available);
assert!(
report
.fallback_reason
.as_deref()
.unwrap_or("")
.contains("MIGraphX unavailable"),
"fallback_reason should contain the error message"
);
}
#[test]
fn test_startup_report_marks_actual_cpu_fallback() {
let mut reporter = StartupReporter::new();
reporter.set_execution_provider("cpu", false, Some("CUDA EP not available"));
reporter.set_model_name("qwen3-embed-0.6b");
reporter.set_quantization_mode("none");
let report = reporter.build();
let line = report.to_log_line();
assert_eq!(report.execution_provider, "cpu");
assert!(!report.provider_available);
assert!(line.contains("provider=cpu"));
assert!(line.contains("unavailable"));
assert!(line.contains("CUDA EP not available"));
}
#[test]
fn test_runtime_startup_report_uses_session_provider_status() {
let runtime_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/embed/runtime.rs");
let runtime = std::fs::read_to_string(&runtime_path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", runtime_path.display()));
assert!(
runtime.contains("provider_runtime_status"),
"WorkerRuntime must store provider status observed during session construction"
);
assert!(
runtime.contains("SessionBuildOutcome"),
"build_session must return provider status together with the session"
);
assert!(
runtime.contains("ProviderRuntimeStatus::fallback_to_cpu"),
"session construction must record CPU fallback as actual runtime status"
);
assert!(
runtime.contains("self.provider_runtime_status.execution_provider"),
"startup report must use runtime provider status, not selector heuristics"
);
}
#[test]
fn test_model_path_env_override_precedence() {
let _guard = ENV_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let temp_dir = tempfile::tempdir().unwrap();
let model_file = temp_dir.path().join("test-model.onnx");
std::fs::write(&model_file, b"fake model").unwrap();
unsafe { std::env::set_var("LEINDEX_MODEL_PATH", temp_dir.path()) };
let result = ModelResolver::resolve("test-model");
assert!(result.is_ok());
let path = result.unwrap();
assert_eq!(path, model_file);
assert_eq!(ModelResolver::source_for_path(&path), "env_override");
unsafe { std::env::remove_var("LEINDEX_MODEL_PATH") };
}
#[test]
fn test_model_path_env_override_takes_priority() {
let _guard = ENV_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let temp_dir = tempfile::tempdir().unwrap();
let model_file = temp_dir.path().join("priority-test.onnx");
std::fs::write(&model_file, b"fake model").unwrap();
unsafe { std::env::set_var("LEINDEX_MODEL_PATH", temp_dir.path()) };
let result = ModelResolver::resolve("priority-test");
assert!(result.is_ok());
assert!(result.unwrap().starts_with(temp_dir.path()));
unsafe { std::env::remove_var("LEINDEX_MODEL_PATH") };
}
#[test]
fn test_model_path_not_found_reports_error() {
let _guard = ENV_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("LEINDEX_MODEL_PATH") };
let result = ModelResolver::resolve("nonexistent-xyz-model");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.message.contains("not found"));
assert!(err.message.contains("env"));
assert!(err.message.contains("bundled"));
assert!(err.message.contains("user cache"));
}
#[test]
fn test_execution_provider_cpu_always_available() {
let result = ExecutionProviderSelector::select("cpu");
assert!(result.is_ok());
let selection = result.unwrap();
assert_eq!(selection.name(), "cpu");
assert!(selection.is_requested_provider());
}
#[test]
fn test_execution_provider_unknown_falls_back() {
let result = ExecutionProviderSelector::select("nonexistent_provider");
assert!(result.is_err());
let fallback = result.unwrap_err();
assert_eq!(fallback.fallback_name(), "cpu");
assert!(!fallback.is_requested_provider());
assert!(fallback.reason().contains("unknown"));
}
#[test]
fn test_execution_provider_reports_fallback_reason() {
let result = ExecutionProviderSelector::select("cuda");
if let Err(fallback) = result {
assert_eq!(fallback.fallback_name(), "cpu");
assert!(fallback.reason().contains("CUDA"));
}
}
#[test]
fn test_embed_response_flat_row_major() {
let config = no_compile_config();
let rt = WorkerRuntime::new(config);
let request = EmbedRequest {
texts: vec![
"text1".to_string(),
"text2".to_string(),
"text3".to_string(),
],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let response_frame = rt.dispatch(&frame);
#[cfg(feature = "onnx")]
{
assert_eq!(response_frame.header.msg_type, MsgType::Error);
}
#[cfg(not(feature = "onnx"))]
{
let response: Response = response_frame.decode_payload().unwrap();
match response {
Response::Embed(embed) => {
assert_eq!(embed.count, 3);
assert_eq!(embed.dimension, 4);
assert_eq!(embed.vectors.len(), 12);
assert_eq!(embed.get_embedding(0).unwrap().len(), 4);
assert_eq!(embed.get_embedding(1).unwrap().len(), 4);
assert_eq!(embed.get_embedding(2).unwrap().len(), 4);
assert!(embed.get_embedding(3).is_none()); }
_ => panic!("expected Embed response"),
}
}
}
#[test]
fn test_batch_ordering_preserved() {
let config = no_compile_config();
let rt = WorkerRuntime::new(config);
let texts: Vec<String> = (0..10).map(|i| format!("text_{}", i)).collect();
let request = EmbedRequest {
texts: texts.clone(),
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let decoded: Request = frame.decode_payload().unwrap();
match decoded {
Request::Embed(embed_req) => {
assert_eq!(embed_req.texts, texts);
}
_ => panic!("expected Embed request"),
}
let response_frame = rt.dispatch(&frame);
#[cfg(feature = "onnx")]
{
assert_eq!(response_frame.header.msg_type, MsgType::Error);
}
#[cfg(not(feature = "onnx"))]
{
let response: Response = response_frame.decode_payload().unwrap();
match response {
Response::Embed(embed) => {
assert_eq!(embed.count, 10);
assert_eq!(embed.dimension, 4);
}
_ => panic!("expected Embed response"),
}
}
}
#[test]
fn test_oversized_batch_split_and_stitch() {
let config = BatchConfig {
max_frame_size: 200,
max_text_size: 1024,
};
let texts: Vec<String> = (0..30)
.map(|i| format!("text number {} with enough content to be meaningful", i))
.collect();
let dim = 4;
let request = EmbedRequest {
texts: texts.clone(),
expected_dim: dim,
};
let batch_id = BatchId::new(42);
let split = batch::split_request(batch_id, request, &config);
match split {
SplitResult::Split(sub_batches) => {
assert!(
sub_batches.len() > 1,
"should be split into multiple sub-batches"
);
for sb in &sub_batches {
assert_eq!(sb.batch_id, batch_id);
assert_eq!(sb.request.expected_dim, dim);
}
let total_texts: usize = sub_batches.iter().map(|sb| sb.request.texts.len()).sum();
assert_eq!(total_texts, texts.len());
let responses: Vec<EmbedResponse> = sub_batches
.iter()
.map(|sb| {
let count = sb.request.texts.len();
EmbedResponse::new(vec![0.0f32; count * dim], count, dim)
})
.collect();
let stitched = batch::stitch_responses(responses).unwrap();
assert_eq!(stitched.count, texts.len());
assert_eq!(stitched.dimension, dim);
assert_eq!(stitched.vectors.len(), texts.len() * dim);
}
SplitResult::Single(_) => {
}
}
}
#[test]
fn test_split_preserves_batch_identity() {
let config = BatchConfig {
max_frame_size: 100,
max_text_size: 1024,
};
let texts: Vec<String> = (0..20)
.map(|i| format!("some text content for item number {}", i))
.collect();
let request = EmbedRequest {
texts,
expected_dim: 8,
};
let batch_id = BatchId::new(0xDEAD);
let split = batch::split_request(batch_id, request, &config);
if let SplitResult::Split(sub_batches) = split {
for sb in &sub_batches {
assert_eq!(sb.batch_id, batch_id);
}
}
}
#[test]
fn test_oversized_single_text_truncated() {
let config = RuntimeConfig {
max_text_size: 50,
..no_compile_config()
};
let rt = WorkerRuntime::new(config);
let long_text = "a".repeat(200);
let request = EmbedRequest {
texts: vec![long_text],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let response_frame = rt.dispatch(&frame);
#[cfg(feature = "onnx")]
{
assert_eq!(response_frame.header.msg_type, MsgType::Error);
}
#[cfg(not(feature = "onnx"))]
{
assert_eq!(response_frame.header.msg_type, MsgType::EmbedResponse);
let response: Response = response_frame.decode_payload().unwrap();
match response {
Response::Embed(embed) => {
assert_eq!(embed.count, 1);
assert_eq!(embed.dimension, 4);
}
_ => panic!("expected Embed response"),
}
}
}
#[test]
fn test_truncate_preserves_unicode() {
let truncated = batch::truncate_text("héllo wörld test".to_string(), 10);
assert!(truncated.len() <= 10);
assert!(truncated.is_char_boundary(truncated.len()));
assert!(std::str::from_utf8(truncated.as_bytes()).is_ok());
}
#[test]
fn test_truncate_at_exact_boundary() {
let truncated = batch::truncate_text("hello".to_string(), 5);
assert_eq!(truncated, "hello");
}
#[test]
fn test_batch_truncate_multiple_oversized_texts() {
let config = RuntimeConfig {
max_text_size: 20,
..no_compile_config()
};
let rt = WorkerRuntime::new(config);
let request = EmbedRequest {
texts: vec![
"short".to_string(),
"this is a very long text that exceeds the limit".to_string(),
"also short".to_string(),
"another extremely long text that should be truncated before IPC framing".to_string(),
],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let response_frame = rt.dispatch(&frame);
#[cfg(feature = "onnx")]
{
assert_eq!(response_frame.header.msg_type, MsgType::Error);
}
#[cfg(not(feature = "onnx"))]
{
assert_eq!(response_frame.header.msg_type, MsgType::EmbedResponse);
let response: Response = response_frame.decode_payload().unwrap();
match response {
Response::Embed(embed) => {
assert_eq!(embed.count, 4);
assert_eq!(embed.dimension, 4);
}
_ => panic!("expected Embed response"),
}
}
}
#[test]
fn test_full_lifecycle_cold_start_reuse_teardown_restart() {
let config = RuntimeConfig {
idle_timeout: Duration::from_secs(300),
..no_compile_config()
};
let rt1 = WorkerRuntime::new(config.clone());
let request = EmbedRequest {
texts: vec!["cold start".to_string()],
expected_dim: 4,
};
let frame = protocol::embed_request_frame(BatchId::new(1), request).unwrap();
let wire = frame.encode_wire().unwrap();
let reader = Cursor::new(wire);
let writer = Cursor::new(Vec::<u8>::new());
assert!(rt1.run_loop(reader, writer).is_ok());
drop(rt1);
let rt2 = WorkerRuntime::new(config);
let request2 = EmbedRequest {
texts: vec!["after restart".to_string()],
expected_dim: 4,
};
let frame2 = protocol::embed_request_frame(BatchId::new(2), request2).unwrap();
let wire2 = frame2.encode_wire().unwrap();
let reader2 = Cursor::new(wire2);
let writer2 = Cursor::new(Vec::<u8>::new());
assert!(rt2.run_loop(reader2, writer2).is_ok());
}
#[test]
fn test_runtime_config_from_env() {
unsafe { std::env::set_var("LEINDEX_WORKER_IDLE_TIMEOUT", "60") };
unsafe { std::env::set_var("LEINDEX_WORKER_MAX_FRAME_SIZE", "8388608") };
unsafe { std::env::set_var("LEINDEX_WORKER_MAX_TEXT_SIZE", "524288") };
unsafe { std::env::set_var("LEINDEX_WORKER_MODEL", "test-model") };
unsafe { std::env::set_var("LEINDEX_WORKER_EMBEDDING_DIM", "768") };
unsafe { std::env::set_var("LEINDEX_WORKER_EXECUTION_PROVIDER", "cuda") };
let config = RuntimeConfig::from_env();
assert_eq!(config.idle_timeout, Duration::from_secs(60));
assert_eq!(config.max_frame_size, 8 * 1024 * 1024);
assert_eq!(config.max_text_size, 512 * 1024);
assert_eq!(config.model_name, "test-model");
assert_eq!(config.embedding_dim, 768);
assert_eq!(config.execution_provider, "cuda");
unsafe { std::env::remove_var("LEINDEX_WORKER_IDLE_TIMEOUT") };
unsafe { std::env::remove_var("LEINDEX_WORKER_MAX_FRAME_SIZE") };
unsafe { std::env::remove_var("LEINDEX_WORKER_MAX_TEXT_SIZE") };
unsafe { std::env::remove_var("LEINDEX_WORKER_MODEL") };
unsafe { std::env::remove_var("LEINDEX_WORKER_EMBEDDING_DIM") };
unsafe { std::env::remove_var("LEINDEX_WORKER_EXECUTION_PROVIDER") };
}
#[test]
fn test_default_idle_timeout_is_60_seconds() {
assert_eq!(
leindex::embed::runtime::DEFAULT_IDLE_TIMEOUT_SECS,
60,
"DEFAULT_IDLE_TIMEOUT_SECS must remain 60 to bound orphaned-worker lifetime"
);
}
#[test]
fn test_default_config_uses_reduced_idle_timeout() {
let config = no_compile_config();
assert_eq!(
config.idle_timeout,
Duration::from_secs(leindex::embed::runtime::DEFAULT_IDLE_TIMEOUT_SECS)
);
assert_eq!(config.idle_timeout, Duration::from_secs(60));
}
#[test]
fn test_worker_exits_when_parent_killed() {
use std::process::{Command, Stdio};
use std::time::Instant;
let worker_path = {
let candidate = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.join("leindex-embed")));
match candidate {
Some(p) if p.exists() => p,
_ => {
let which = Command::new("which")
.arg("leindex-embed")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| std::path::PathBuf::from(s.trim().to_string()))
.filter(|p| !p.as_os_str().is_empty())
} else {
None
}
});
match which {
Some(p) => p,
None => {
eprintln!(
"test_worker_exits_when_parent_killed: leindex-embed binary not found, \
skipping spawn test (policy invariants covered by other tests)"
);
return;
}
}
}
}
};
let mut child = match Command::new(&worker_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => c,
Err(e) => {
eprintln!(
"test_worker_exits_when_parent_killed: failed to spawn worker at {}: {}",
worker_path.display(),
e
);
return;
}
};
let child_pid = child.id();
std::thread::sleep(Duration::from_millis(200));
let _ = child.kill();
let deadline = Instant::now() + Duration::from_secs(5);
let mut reaped = false;
while Instant::now() < deadline {
match child.try_wait() {
Ok(Some(_)) => {
reaped = true;
break;
}
Ok(None) => std::thread::sleep(Duration::from_millis(50)),
Err(_) => break,
}
}
assert!(
reaped,
"worker (pid={}) was not reaped within 5 seconds of SIGKILL",
child_pid
);
}