use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::model::JournalId;
use crate::session::EventStore;
use crate::session::event::{EventTarget, EventType, ReviewInitializedPayload, ShoreEvent, Writer};
pub struct ByteUsage {
pub logical: u64,
pub physical: u64,
}
pub struct StoreBenchHarness {
store_dir: PathBuf,
store: EventStore,
next_index: AtomicUsize,
}
impl StoreBenchHarness {
pub fn open(store_dir: impl AsRef<Path>) -> Self {
let store_dir = store_dir.as_ref().to_path_buf();
Self {
store: EventStore::open(&store_dir),
store_dir,
next_index: AtomicUsize::new(0),
}
}
pub fn populate(&self, count: usize) {
for _ in 0..count {
self.write_next();
}
}
pub fn append_one(&self) {
self.write_next();
}
pub fn read_all(&self) -> usize {
self.try_read_all()
.expect("a synthetic store lists cleanly")
}
pub fn try_read_all(&self) -> Result<usize, String> {
self.store
.list_events()
.map(|events| events.len())
.map_err(|error| error.to_string())
}
pub fn byte_usage(&self) -> ByteUsage {
dir_byte_usage(&self.store_dir)
}
fn write_next(&self) {
let index = self.next_index.fetch_add(1, Ordering::Relaxed);
self.store
.record_event_once(&synthetic_event(index))
.expect("a synthetic event records");
}
}
fn synthetic_event(index: usize) -> ShoreEvent {
let session = format!("session:bench-{index}");
ShoreEvent::new(
EventType::ReviewInitialized,
format!("review_initialized:{session}:work:default"),
EventTarget::for_journal(JournalId::new(session.as_str())),
Writer::shore_local("0.1.0"),
ReviewInitializedPayload {},
"2026-05-10T00:00:00Z",
)
.expect("a synthetic event builds")
}
fn dir_byte_usage(dir: &Path) -> ByteUsage {
let mut usage = ByteUsage {
logical: 0,
physical: 0,
};
accumulate_byte_usage(dir, &mut usage);
usage
}
fn accumulate_byte_usage(dir: &Path, usage: &mut ByteUsage) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let Ok(metadata) = entry.metadata() else {
continue;
};
if metadata.is_dir() {
accumulate_byte_usage(&entry.path(), usage);
} else if metadata.is_file() {
usage.logical += metadata.len();
usage.physical += physical_bytes(&metadata);
}
}
}
#[cfg(unix)]
fn physical_bytes(metadata: &std::fs::Metadata) -> u64 {
use std::os::unix::fs::MetadataExt;
metadata.blocks() * 512
}
#[cfg(not(unix))]
fn physical_bytes(metadata: &std::fs::Metadata) -> u64 {
metadata.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn harness_measures_a_synthetic_store_without_panicking() {
let root = tempfile::tempdir().expect("a temp dir");
let harness = StoreBenchHarness::open(root.path().join(".shore/data"));
harness.populate(50);
assert_eq!(harness.read_all(), 50, "every populated event reads back");
harness.append_one();
assert_eq!(
harness.read_all(),
51,
"the appended event is visible on the next read"
);
assert_eq!(harness.try_read_all().unwrap(), 51);
let usage = harness.byte_usage();
assert!(
usage.logical > 0,
"the synthetic store occupies logical bytes"
);
assert!(
usage.physical > 0,
"the synthetic store occupies on-disk bytes"
);
}
#[test]
fn a_current_schema_store_reads_back_through_the_harness() {
let repo = author_current_schema_store();
let store_dir = crate::session::store_dir_for_repo(repo.path()).expect("resolve store dir");
let harness = StoreBenchHarness::open(&store_dir);
let count = harness
.try_read_all()
.expect("a current-schema store must read back under strict list_events");
assert!(
count >= 2,
"expected at least the capture + observation events, got {count}"
);
}
fn author_current_schema_store() -> tempfile::TempDir {
use crate::session::{
CaptureOptions, ObservationAddOptions, capture_worktree_review, record_observation,
};
let repo = tempfile::tempdir().expect("temp repo");
let path = repo.path();
run_git(path, &["init"]);
run_git(path, &["config", "user.name", "Bench Fixture"]);
run_git(path, &["config", "user.email", "bench@example.com"]);
run_git(path, &["config", "commit.gpgsign", "false"]);
std::fs::write(path.join("lib.rs"), "pub fn v() -> u32 { 1 }\n").unwrap();
run_git(path, &["add", "--all"]);
run_git(path, &["commit", "-m", "base"]);
std::fs::write(path.join("lib.rs"), "pub fn v() -> u32 { 2 }\n").unwrap();
let captured =
capture_worktree_review(CaptureOptions::new(path)).expect("capture worktree review");
record_observation(
ObservationAddOptions::new(path)
.with_revision_id(captured.revision_id)
.with_track("agent:bench")
.with_title("bench fixture note")
.with_body("A current-schema observation for the read-all fixture."),
)
.expect("record observation");
repo
}
fn run_git(dir: &std::path::Path, args: &[&str]) {
let status = std::process::Command::new("git")
.args(args)
.current_dir(dir)
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
}