Files
sq/todos/SQ-003-simulation-io-traits.md
2026-02-26 21:52:50 +01:00

2.3 KiB

SQ-003: Simulation I/O Traits

Status: [ ] TODO Blocked by: SQ-000 Priority: High

Description

Define the trait abstractions for Clock and FileSystem that allow swapping real I/O for deterministic simulated I/O. This is the foundation of TigerBeetle-style testing.

Files to Create/Modify

  • crates/sq-sim/src/lib.rs - re-exports
  • crates/sq-sim/src/clock.rs - Clock trait + RealClock + SimClock
  • crates/sq-sim/src/fs.rs - FileSystem trait + FileHandle trait + RealFileSystem + InMemoryFileSystem

Key Traits

pub trait Clock: Send + Sync {
    fn now(&self) -> std::time::Instant;
    async fn sleep(&self, duration: Duration);
}

pub trait FileSystem: Send + Sync {
    async fn create_dir_all(&self, path: &Path) -> Result<()>;
    async fn open_read(&self, path: &Path) -> Result<Box<dyn FileHandle>>;
    async fn open_write(&self, path: &Path) -> Result<Box<dyn FileHandle>>;
    async fn open_append(&self, path: &Path) -> Result<Box<dyn FileHandle>>;
    async fn remove_file(&self, path: &Path) -> Result<()>;
    async fn list_dir(&self, path: &Path) -> Result<Vec<PathBuf>>;
    async fn exists(&self, path: &Path) -> bool;
}

pub trait FileHandle: Send + Sync {
    async fn write_all(&mut self, buf: &[u8]) -> Result<usize>;
    async fn read_exact(&mut self, buf: &mut [u8]) -> Result<usize>;
    async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>;
    async fn fsync(&mut self) -> Result<()>;
    fn position(&self) -> u64;
    async fn seek(&mut self, pos: u64) -> Result<()>;
}

Fault Injection (InMemoryFileSystem)

impl InMemoryFileSystem {
    pub fn fail_next_fsync(&self, error: io::Error);
    pub fn simulate_disk_full(&self);
    pub fn corrupt_bytes(&self, path: &Path, offset: u64, len: usize);
    pub fn clear_faults(&self);
}

Acceptance Criteria

  • InMemoryFileSystem: write, read back, verify content
  • InMemoryFileSystem: create_dir_all, list_dir
  • InMemoryFileSystem: fsync succeeds normally
  • InMemoryFileSystem: fail_next_fsync causes next fsync to error
  • InMemoryFileSystem: simulate_disk_full causes writes to fail
  • SimClock: starts at time 0, advance(Duration) changes now()
  • SimClock: sleep returns immediately when time is advanced
  • RealClock: delegates to std::time
  • RealFileSystem: delegates to tokio::fs (basic smoke test)