Skip to content

Commit 3ad3adc

Browse files
committed
Indexing: Add concurrency stress tests
- Add `stress_tests.rs` with 5 tests exercising concurrent actors against real SQLite: scan + buffered event replay, concurrent batch inserts from 4 threads, scan + enrichment reads via `ReadPool`, live event storm + concurrent reads, and lifecycle transitions (shutdown with pending writes, rapid restart, double shutdown) - Shared test helpers: `build_synthetic_tree` (generates valid `EntryRow` trees), `check_db_consistency` (verifies 5 DB invariants: no orphans, all dirs have `dir_stats`, recursive stats correct, no duplicate keys) - No production code changes — tests use existing seams (`WriteMessage` channel, `ReadPool`, `EventReconciler`)
1 parent 50bd4fa commit 3ad3adc

3 files changed

Lines changed: 1058 additions & 11 deletions

File tree

apps/desktop/src-tauri/src/indexing/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Key test files are alongside each module (test functions within `#[cfg(test)]` b
9898
- Firmlinks: path normalization, edge cases
9999
- Writer: message processing, priority handling
100100
- mod.rs: end-to-end integration (scan → aggregate → enrich → watcher update → re-enrich), enrichment fast path, fallback, root-level enrichment
101+
- stress_tests.rs: concurrency stress tests — concurrent scan + replay, concurrent batch inserts, concurrent scan + enrichment reads, live event storm + reads, lifecycle transitions under load
101102

102103
## Key decisions
103104

apps/desktop/src-tauri/src/indexing/mod.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ pub(crate) mod scanner;
1818
mod verifier; // Placeholder: per-navigation background readdir diff (future milestone)
1919
pub(crate) mod watcher;
2020

21+
#[cfg(test)]
22+
mod stress_tests;
23+
2124
pub use enrichment::enrich_entries_with_index;
2225
pub use events::*;
2326

@@ -1045,15 +1048,14 @@ pub fn get_dir_stats(path: &str) -> Result<Option<DirStats>, String> {
10451048
let normalized = firmlinks::normalize_path(path);
10461049

10471050
pool.with_conn(|conn| {
1048-
let entry_id = match store::resolve_path(conn, &normalized)
1049-
.map_err(|e| format!("Couldn't resolve path: {e}"))?
1050-
{
1051-
Some(id) => id,
1052-
None => return Ok(None),
1053-
};
1051+
let entry_id =
1052+
match store::resolve_path(conn, &normalized).map_err(|e| format!("Couldn't resolve path: {e}"))? {
1053+
Some(id) => id,
1054+
None => return Ok(None),
1055+
};
10541056

1055-
let stats = IndexStore::get_dir_stats_by_id(conn, entry_id)
1056-
.map_err(|e| format!("Couldn't get dir stats: {e}"))?;
1057+
let stats =
1058+
IndexStore::get_dir_stats_by_id(conn, entry_id).map_err(|e| format!("Couldn't get dir stats: {e}"))?;
10571059

10581060
Ok(stats.map(|s| DirStats {
10591061
path: normalized.clone(),
@@ -1074,9 +1076,7 @@ pub fn get_dir_stats_batch(paths: &[String]) -> Result<Vec<Option<DirStats>>, St
10741076

10751077
for (i, path) in paths.iter().enumerate() {
10761078
let normalized = firmlinks::normalize_path(path);
1077-
match store::resolve_path(conn, &normalized)
1078-
.map_err(|e| format!("Couldn't resolve path: {e}"))?
1079-
{
1079+
match store::resolve_path(conn, &normalized).map_err(|e| format!("Couldn't resolve path: {e}"))? {
10801080
Some(id) => {
10811081
id_to_idx.push((id, i, normalized));
10821082
results.push(None);

0 commit comments

Comments
 (0)