Files
brahman/crates/modules/semantic_dht/minga-cli/tests/watcher.rs
T
Sergio 53dbdf0f1d chore: monorepo inicial con arje + minga + yahweh absorbidos
Workspace en 4 ejes (core/modules/apps/shared):

- core/: 24 crates de arje (Init systemd-compatible: ente-card, ente-zero,
  ente-kernel, ente-bus, ente-cas, ente-soma, ente-wasm, ente-snapshot,
  ente-brain, ente-echo, ente-policy-provider, + 12 crates *-compat)
- modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST,
  minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli)
- modules/ui_engine/: 11 crates de yahweh (libs/{core,theme,bus,providers},
  widgets/{tree,splitter,tabs,tiled,container_core,text_input})
- apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer,
  image_viewer, yahweh-shell)
- shared_wit/protocol.wit: handshake/lifecycle inicial

Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio
"full", paths intra-workspace de yahweh redirigidos a su nueva ubicación.

cargo check --workspace: 0 errores, 17 warnings (dead code preexistente).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 04:45:44 +00:00

130 lines
4.3 KiB
Rust

//! Tests del file watcher: el "puente humano" que convierte Minga en
//! un VCS de fondo — el usuario edita archivos y Minga los versiona
//! sin acción explícita.
use std::fs;
use std::time::Duration;
use minga_cli::{cmd_init, cmd_status, cmd_watch};
use tempfile::TempDir;
/// Espera hasta que el `cmd_status` reporte `expected` claves en MST,
/// o hasta `timeout`. Devuelve `true` si se alcanzó la cuenta.
async fn wait_until_mst_size(
repo: &std::path::Path,
pass: &str,
expected: usize,
timeout: Duration,
) -> bool {
let deadline = std::time::Instant::now() + timeout;
loop {
if let Ok(s) = cmd_status(repo, pass) {
if s.mst_len >= expected {
return true;
}
}
if std::time::Instant::now() >= deadline {
return false;
}
tokio::time::sleep(Duration::from_millis(80)).await;
}
}
#[tokio::test]
async fn watcher_initial_scan_picks_up_existing_files() {
let dir = TempDir::new().unwrap();
let repo = dir.path().join("repo");
let watch = dir.path().join("src");
fs::create_dir(&watch).unwrap();
cmd_init(&repo, "p").unwrap();
// Escribimos archivos ANTES de arrancar el watcher.
fs::write(watch.join("a.rs"), "fn a() -> i32 { 1 }").unwrap();
fs::write(watch.join("b.rs"), "fn b() -> i32 { 2 }").unwrap();
// Arrancamos el watcher en una task. La pasada inicial debería
// ingerir ambos.
let repo_clone = repo.clone();
let handle = tokio::spawn(async move {
let _ = cmd_watch(&repo_clone, "p", &watch).await;
});
// Damos margen para la pasada inicial. cmd_watch tiene el repo
// abierto, pero cmd_status no puede mientras tanto (sled lock).
// Solución: cancelamos el watcher antes de medir.
tokio::time::sleep(Duration::from_millis(500)).await;
handle.abort();
let _ = handle.await;
tokio::time::sleep(Duration::from_millis(150)).await;
let s = cmd_status(&repo, "p").unwrap();
assert_eq!(s.mst_len, 2, "esperaba 2 funciones del initial scan");
assert_eq!(s.attestations_len, 2);
}
#[tokio::test]
async fn watcher_ingests_new_file_after_creation() {
let dir = TempDir::new().unwrap();
let repo = dir.path().join("repo");
let watch = dir.path().join("src");
fs::create_dir(&watch).unwrap();
cmd_init(&repo, "p").unwrap();
// Watcher arranca con directorio vacío.
let repo_clone = repo.clone();
let watch_clone = watch.clone();
let handle = tokio::spawn(async move {
let _ = cmd_watch(&repo_clone, "p", &watch_clone).await;
});
// Margen para que el watcher se inicialice y registre con notify.
tokio::time::sleep(Duration::from_millis(300)).await;
// Creamos un archivo. notify debería emitir un evento y el
// watcher debería ingerirlo.
fs::write(watch.join("new.rs"), "fn new() -> i32 { 42 }").unwrap();
// Esperamos a que el evento se procese.
tokio::time::sleep(Duration::from_millis(800)).await;
// Detenemos el watcher para liberar el lock de sled antes de
// hacer cmd_status.
handle.abort();
let _ = handle.await;
tokio::time::sleep(Duration::from_millis(150)).await;
// Polling con timeout — algunos sistemas de archivos tienen
// latencia de eventos.
assert!(
wait_until_mst_size(&repo, "p", 1, Duration::from_secs(3)).await,
"el watcher no ingirió el archivo creado",
);
}
#[tokio::test]
async fn watcher_ignores_non_rs_files() {
let dir = TempDir::new().unwrap();
let repo = dir.path().join("repo");
let watch = dir.path().join("src");
fs::create_dir(&watch).unwrap();
cmd_init(&repo, "p").unwrap();
// Pre-poblamos con un .rs y varios archivos no-Rust.
fs::write(watch.join("real.rs"), "fn real() -> i32 { 0 }").unwrap();
fs::write(watch.join("readme.md"), "# proyecto").unwrap();
fs::write(watch.join("data.json"), "{}").unwrap();
let repo_clone = repo.clone();
let handle = tokio::spawn(async move {
let _ = cmd_watch(&repo_clone, "p", &watch).await;
});
tokio::time::sleep(Duration::from_millis(500)).await;
handle.abort();
let _ = handle.await;
tokio::time::sleep(Duration::from_millis(150)).await;
let s = cmd_status(&repo, "p").unwrap();
assert_eq!(s.mst_len, 1, "solo el .rs debe haberse ingerido");
}