use crate::common::*;
#[test]
fn should_trigger_sync_after_file_change() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let mut watcher = FileWatcher::new(project.path(), sync_fn, inert_options(200));
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
assert!(__emit_watch_event_for_tests(project.path(), "src/new.ts"));
wait_for(
|| {
watcher.flush_due();
calls.load(Ordering::SeqCst) > 0
},
2000,
25,
);
assert!(calls.load(Ordering::SeqCst) > 0);
watcher.stop();
}
#[test]
fn should_flush_during_a_sustained_event_stream_before_it_stops() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let mut options = inert_options(100);
options.max_debounce_ms = Some(300);
let mut watcher = FileWatcher::new(project.path(), sync_fn, options);
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
let stream_start = Instant::now();
let mut fired_during_stream = false;
let mut i = 0;
while stream_start.elapsed() < Duration::from_millis(2000) {
assert!(__emit_watch_event_for_tests(
project.path(),
format!("src/copied{i}.ts")
));
i += 1;
thread::sleep(Duration::from_millis(40));
watcher.flush_due();
if calls.load(Ordering::SeqCst) > 0 {
fired_during_stream = true;
break;
}
}
assert!(
fired_during_stream,
"sync should fire under a sustained event stream (large-folder copy), not wait for it to stop"
);
watcher.stop();
}
#[test]
fn should_hold_sustained_stream_until_configured_batch_window() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let mut options = inert_options(60_000);
options.max_debounce_ms = Some(60_000);
let mut watcher = FileWatcher::new(project.path(), sync_fn, options);
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
assert!(__emit_watch_event_for_tests(
project.path(),
"src/batch-window.ts"
));
assert_eq!(
watcher.__scheduled_delay_ms_for_tests(),
Some(60_000),
"an explicit one-minute batch window must not be truncated before scheduling"
);
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"configured batch window should hold automatic sync work"
);
println!(
"one-minute batch window: scheduled_delay_ms={:?}, sync_calls={}",
watcher.__scheduled_delay_ms_for_tests(),
calls.load(Ordering::SeqCst)
);
watcher.stop();
}
#[test]
fn should_not_flush_before_scaled_batch_window_under_sustained_stream() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let debounce = Duration::from_millis(120);
let mut options = inert_options(debounce.as_millis() as u64);
options.max_debounce_ms = Some(5_000);
let mut watcher = FileWatcher::new(project.path(), sync_fn, options);
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
let stream_start = Instant::now();
let mut i = 0;
let max_wait = Duration::from_millis(5_000);
let ci_pause_margin = Duration::from_millis(1_000);
let debounce_pause_margin = Duration::from_millis(20);
while stream_start.elapsed() < Duration::from_millis(1_000) {
let event_sent_at = Instant::now();
assert!(__emit_watch_event_for_tests(
project.path(),
format!("src/batch{i}.ts")
));
i += 1;
thread::sleep(Duration::from_millis(40));
if max_wait.saturating_sub(stream_start.elapsed()) <= ci_pause_margin
|| debounce.saturating_sub(event_sent_at.elapsed()) <= debounce_pause_margin
{
break;
}
watcher.flush_due();
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"sustained stream should not flush before the configured batch window"
);
assert!(
!watcher.get_pending_files().is_empty(),
"pending files must be retained while the batch window is still open"
);
}
assert_eq!(calls.load(Ordering::SeqCst), 0);
println!(
"scaled sustained stream: elapsed_ms={}, sync_calls={}",
stream_start.elapsed().as_millis(),
calls.load(Ordering::SeqCst)
);
watcher.stop();
}
#[test]
fn should_flush_under_sustained_stream_with_default_max_wait() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let mut watcher = FileWatcher::new(project.path(), sync_fn, inert_options(60));
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
let stream_start = Instant::now();
let mut fired_during_stream = false;
let mut i = 0;
while stream_start.elapsed() < Duration::from_millis(1500) {
assert!(__emit_watch_event_for_tests(
project.path(),
format!("src/bulk{i}.ts")
));
i += 1;
thread::sleep(Duration::from_millis(25));
watcher.flush_due();
if calls.load(Ordering::SeqCst) > 0 {
fired_during_stream = true;
break;
}
}
assert!(
fired_during_stream,
"default max-wait must flush under a sustained event stream without any env/option tuning"
);
watcher.stop();
}
#[test]
fn should_debounce_rapid_changes_into_a_single_sync() {
let _guard = TestGuard::new();
let project = TempProject::new("codegraph-watcher");
let (calls, sync_fn) = sync_mock(Vec::new(), Ok(ok(1, 10)));
let mut watcher = FileWatcher::new(project.path(), sync_fn, inert_options(400));
watcher.start();
watcher
.wait_until_ready(1000)
.expect("watcher should be ready");
for i in 0..5 {
assert!(__emit_watch_event_for_tests(
project.path(),
format!("src/file{i}.ts")
));
thread::sleep(Duration::from_millis(50));
watcher.flush_due();
assert_eq!(calls.load(Ordering::SeqCst), 0);
}
wait_for(
|| {
watcher.flush_due();
calls.load(Ordering::SeqCst) > 0
},
2000,
25,
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
watcher.stop();
}