vtcode-memory 0.181.10

Unified per-session state store: append-only ThreadEvent log plus derived views, retention, and cross-session query.
Documentation
use super::*;
use vtcode_exec_events::{ThreadEvent, ThreadStartedEvent};

#[test]
fn failed_sync_is_retried_before_metadata_without_reappending() {
    let dir = tempfile::tempdir().unwrap();
    let log = SessionEventLog::open_exclusive(dir.path(), "sync-retry", DEFAULT_MAX_EVENTS).unwrap();
    log.append(&ThreadEvent::ThreadStarted(ThreadStartedEvent { thread_id: "one".to_string() }))
        .unwrap();
    let bytes_after_write = {
        let mut state = log.shared.state.lock().unwrap();
        let fail_sync = |_: &File| Err(std::io::Error::other("injected sync failure"));
        assert!(log.flush_write_buf_with_sync_locked(&mut state, fail_sync).is_err());
        assert!(state.needs_sync);
        assert!(state.write_buf.is_empty());
        let bytes = std::fs::read(&log.events_path).unwrap();
        assert!(log.flush_write_buf_with_sync_locked(&mut state, fail_sync).is_err());
        assert_eq!(std::fs::read(&log.events_path).unwrap(), bytes);
        bytes
    };
    log.flush().unwrap();
    assert_eq!(std::fs::read(&log.events_path).unwrap(), bytes_after_write);
    assert!(!log.shared.state.lock().unwrap().needs_sync);
    drop(log);
    let reopened = SessionEventLog::open_exclusive(dir.path(), "sync-retry", DEFAULT_MAX_EVENTS).unwrap();
    assert_eq!(reopened.event_count(), 1);
}

#[test]
fn failed_write_and_rollback_stop_the_live_writer_until_reopen() {
    let dir = tempfile::tempdir().unwrap();
    let log = SessionEventLog::open_exclusive(dir.path(), "write-failure", DEFAULT_MAX_EVENTS).unwrap();
    let event = ThreadEvent::ThreadStarted(ThreadStartedEvent { thread_id: "first".to_string() });
    log.append(&event).unwrap();
    {
        let mut state = log.shared.state.lock().unwrap();
        let error = log
            .flush_write_buf_with_ops_locked(
                &mut state,
                |file, bytes| {
                    file.write_all(&bytes[..8])?;
                    Err(std::io::Error::other("injected write failure"))
                },
                |_, _| Err(std::io::Error::other("injected rollback failure")),
                File::sync_data,
            )
            .unwrap_err();
        let message = error.to_string();
        assert!(message.contains("injected write failure") && message.contains("injected rollback failure"));
        assert!(state.write_failed);
    }
    assert!(log.flush().is_err());
    assert!(log.append(&event).is_err());
    drop(log);

    let reopened = SessionEventLog::open_exclusive(dir.path(), "write-failure", DEFAULT_MAX_EVENTS).unwrap();
    assert_eq!(reopened.event_count(), 0);
    reopened.append(&event).unwrap();
    reopened.flush().unwrap();
    drop(reopened);
    let reopened = SessionEventLog::open_exclusive(dir.path(), "write-failure", DEFAULT_MAX_EVENTS).unwrap();
    assert_eq!(reopened.event_count(), 1);
}

#[test]
fn successful_rollback_keeps_buffer_available_for_retry() {
    let dir = tempfile::tempdir().unwrap();
    let log = SessionEventLog::open_exclusive(dir.path(), "write-retry", DEFAULT_MAX_EVENTS).unwrap();
    log.append(&ThreadEvent::ThreadStarted(ThreadStartedEvent { thread_id: "first".to_string() }))
        .unwrap();
    {
        let mut state = log.shared.state.lock().unwrap();
        assert!(
            log.flush_write_buf_with_ops_locked(
                &mut state,
                |file, bytes| {
                    file.write_all(&bytes[..8])?;
                    Err(std::io::Error::other("injected write failure"))
                },
                File::set_len,
                File::sync_data,
            )
            .is_err()
        );
        assert!(!state.write_failed);
        assert!(!state.write_buf.is_empty());
    }
    log.flush().unwrap();
    drop(log);
    assert_eq!(
        SessionEventLog::open_exclusive(dir.path(), "write-retry", DEFAULT_MAX_EVENTS)
            .unwrap()
            .event_count(),
        1
    );
}