use super::*;
#[test]
fn default_options_are_sane() {
let opts = ReindexOptions::default();
assert!(!opts.verify_after);
assert!(opts.prior_chunk_count.is_none());
assert!(!opts.force);
assert!(!opts.timeout_explicit);
assert_eq!(opts.stall_secs, 120);
}
#[test]
fn default_outcome_is_zero() {
let o = ReindexOutcome::default();
assert_eq!(o.indexed, 0);
assert_eq!(o.total_chunks, 0);
assert!(!o.completed);
assert!(o.timings.is_none());
}
#[test]
fn bar_style_does_not_panic() {
use super::super::reindex_ui::ReindexUi;
let ui = ReindexUi::new("test", false);
ui.finish("ok".to_string());
}
#[test]
fn progress_aware_wait_no_hard_deadline_when_implicit() {
let opts = ReindexOptions {
timeout_explicit: false,
stall_secs: 120,
..ReindexOptions::default()
};
assert!(
!opts.timeout_explicit,
"implicit timeout must not set a hard cap"
);
assert_eq!(opts.stall_secs, 120);
let hard_deadline: Option<std::time::Duration> = if opts.timeout_explicit {
Some(std::time::Duration::from_secs(opts.timeout_secs))
} else {
None
};
assert!(
hard_deadline.is_none(),
"progress-aware mode must not produce a hard deadline"
);
}
#[test]
fn progress_aware_wait_hard_deadline_when_explicit() {
let opts = ReindexOptions {
timeout_secs: 300,
timeout_explicit: true,
..ReindexOptions::default()
};
assert!(
opts.timeout_explicit,
"explicit timeout must set a hard cap"
);
let hard_deadline: Option<std::time::Duration> =
if opts.timeout_explicit && opts.timeout_secs > 0 {
Some(std::time::Duration::from_secs(opts.timeout_secs))
} else {
None
};
assert_eq!(
hard_deadline,
Some(std::time::Duration::from_secs(300)),
"explicit 300 s timeout must produce a 300 s hard deadline"
);
}
#[test]
fn progress_aware_wait_timeout_zero_explicit_means_no_deadline() {
let opts = ReindexOptions {
timeout_secs: 0,
timeout_explicit: true,
..ReindexOptions::default()
};
let hard_deadline: Option<std::time::Duration> = if opts.timeout_explicit {
if opts.timeout_secs > 0 {
Some(std::time::Duration::from_secs(opts.timeout_secs))
} else {
None }
} else {
None
};
assert!(
hard_deadline.is_none(),
"--timeout 0 must not produce a hard deadline (wait forever)"
);
}
#[test]
fn stall_detection_triggers_on_frozen_counter() {
let last_indexed_snapshot: u64 = 100;
let current_indexed: u64 = 100;
let counter_advanced = current_indexed > last_indexed_snapshot;
assert!(!counter_advanced, "frozen counter must not advance");
let last_progress = std::time::Instant::now() - std::time::Duration::from_secs(200);
let stall_deadline_dur = std::time::Duration::from_secs(120);
let is_stalled = !counter_advanced && last_progress.elapsed() >= stall_deadline_dur;
assert!(
is_stalled,
"must detect stall after stall_secs with no counter advance"
);
}
#[test]
fn stall_detection_does_not_trigger_while_progressing() {
let last_indexed_snapshot: u64 = 100;
let current_indexed: u64 = 150;
let counter_advanced = current_indexed > last_indexed_snapshot;
assert!(
counter_advanced,
"advancing counter must register as progress"
);
let stalled = !counter_advanced; assert!(!stalled, "progressing counter must not trigger stall");
}
#[test]
fn total_files_atomic_zero_until_set() {
use std::sync::atomic::{AtomicU64, Ordering};
let total_files_now = AtomicU64::new(0);
assert_eq!(
total_files_now.load(Ordering::Acquire),
0,
"total_files_now must be zero until set by walk_complete/start"
);
total_files_now.store(3_327, Ordering::Release);
assert_eq!(
total_files_now.load(Ordering::Acquire),
3_327,
"total_files_now must reflect the value stored by the SSE handler"
);
}
#[test]
fn eta_logic_loading_model_and_zero_denom() {
use super::super::reindex_ui::ReindexPhase;
use std::sync::atomic::{AtomicU64, Ordering};
fn phase_to_u64_test(p: ReindexPhase) -> u64 {
match p {
ReindexPhase::InitializingEmbedder => 3,
_ => 4,
}
}
let total_files_now = AtomicU64::new(0);
let indexed = 0u64;
let elapsed = 5u64;
let phase = phase_to_u64_test(ReindexPhase::InitializingEmbedder);
let is_model_loading = phase == 3;
let fps = indexed.checked_div(elapsed).unwrap_or(0);
let total = total_files_now.load(Ordering::Acquire);
let eta = if is_model_loading {
"loading model\u{2026}".to_string()
} else if fps > 0 && total > indexed {
super::super::format::fmt_secs((total - indexed) / fps)
} else {
"?".to_string()
};
assert_eq!(
eta, "loading model\u{2026}",
"ETA must be 'loading model…' during InitializingEmbedder"
);
let phase2 = phase_to_u64_test(ReindexPhase::Embedding);
let is_loading2 = phase2 == 3;
let eta2 = if is_loading2 {
"loading model\u{2026}".to_string()
} else if fps > 0 && total > indexed {
super::super::format::fmt_secs((total - indexed) / fps)
} else {
"?".to_string()
};
assert_eq!(
eta2, "?",
"ETA must be '?' when total_files is 0 and not loading model"
);
}
#[test]
fn embed_bar_total_is_set_before_first_batch() {
use super::super::reindex_ui::{ReindexPhase, ReindexUi};
let mut ui = ReindexUi::new("idx", false);
ui.set_phase(ReindexPhase::Walking, "idx");
ui.set_total(500);
ui.set_position(500);
ui.mark_stage_done(0, 100);
ui.set_phase(ReindexPhase::Chunking, "idx");
ui.set_total(500);
ui.set_embed_total(500);
assert_eq!(
ui.stage_bars[2].length(),
Some(500),
"Embed bar must be primed with total_files before the first batch"
);
ui.finish("done".to_string());
}
#[test]
fn chunk_bar_not_frozen_at_first_batch() {
use super::super::reindex_ui::{ReindexPhase, ReindexUi};
let mut ui = ReindexUi::new("idx", false);
ui.set_phase(ReindexPhase::Walking, "idx");
ui.set_total(200);
ui.set_position(200);
ui.mark_stage_done(0, 100);
ui.set_phase(ReindexPhase::Chunking, "idx");
ui.set_total(200);
ui.set_embed_total(200);
ui.activate_embed_bar();
ui.set_position(128);
ui.set_phase(ReindexPhase::Embedding, "idx");
ui.advance_embed_bar(128);
assert_eq!(
ui.bar_states[1],
super::super::reindex_ui::BarState::Active,
"Chunk bar must remain Active after the first batch event, not be frozen"
);
assert_eq!(ui.bar_states[2], super::super::reindex_ui::BarState::Active);
assert_eq!(ui.stage_bars[2].position(), 128);
ui.mark_stage_done(1, 5_000);
assert_eq!(
ui.bar_states[1],
super::super::reindex_ui::BarState::Done,
"Chunk bar must be Done after kg_start marks it"
);
ui.finish("done".to_string());
}
#[test]
fn embedder_ready_fires_for_in_process_embedder() {
let first_batch_ever = true;
let embedder_pid_slot: Option<u32> = None;
let needs_init = if let Some(pid) = embedder_pid_slot {
pid == 0
} else {
first_batch_ever
};
assert!(
needs_init,
"needs_embedder_init must be true for in-process embedder on first batch"
);
let pid_slot_zero: Option<u32> = Some(0);
let needs_init_sidecar = if let Some(pid) = pid_slot_zero {
pid == 0
} else {
first_batch_ever
};
assert!(
needs_init_sidecar,
"needs_embedder_init must be true for sidecar with PID=0"
);
let first_batch_ever_no = false;
let embedder_pid_slot_warm: Option<u32> = None; let needs_init_warm = if let Some(pid) = embedder_pid_slot_warm {
pid == 0
} else {
first_batch_ever_no
};
assert!(
!needs_init_warm,
"needs_embedder_init must be false on subsequent batches"
);
}