use chrono::Utc;
use evolve_adapters::AdapterRegistry;
use evolve_cli::engine;
use evolve_core::agent_config::AgentConfig;
use evolve_core::ids::{AdapterId, ConfigId, ProjectId, SessionId, SignalId};
use evolve_storage::Storage;
use evolve_storage::agent_configs::{AgentConfigRepo, AgentConfigRow, ConfigRole};
use evolve_storage::projects::{Project, ProjectRepo};
use evolve_storage::sessions::{Session, SessionRepo, SessionVariant};
use evolve_storage::signals::{Signal, SignalKind, SignalRepo};
#[tokio::test]
async fn soak_10k_sessions_complete_in_reasonable_time() {
let storage = Storage::in_memory_for_tests().await.unwrap();
let pid = ProjectId::new();
ProjectRepo::new(&storage)
.insert(&Project {
id: pid,
adapter_id: AdapterId::new("claude-code"),
root_path: "/tmp/soak".into(),
name: "soak".into(),
created_at: Utc::now(),
champion_config_id: None,
})
.await
.unwrap();
let current_champion_id = ConfigId::new();
let payload = AgentConfig::default_for("claude-code");
AgentConfigRepo::new(&storage)
.insert(&AgentConfigRow {
id: current_champion_id,
project_id: pid,
adapter_id: AdapterId::new("claude-code"),
role: ConfigRole::Champion,
fingerprint: payload.fingerprint(),
payload,
created_at: Utc::now(),
})
.await
.unwrap();
ProjectRepo::new(&storage)
.set_champion(pid, current_champion_id)
.await
.unwrap();
let session_repo = SessionRepo::new(&storage);
let signal_repo = SignalRepo::new(&storage);
let total_sessions = 10_000;
let start = std::time::Instant::now();
for i in 0..total_sessions {
let sid = SessionId::new();
let variant = if i % 2 == 0 {
SessionVariant::Champion
} else {
SessionVariant::Challenger
};
session_repo
.insert(&Session {
id: sid,
project_id: pid,
experiment_id: None,
variant,
config_id: current_champion_id,
started_at: Utc::now(),
ended_at: Utc::now(),
adapter_session_ref: None,
})
.await
.unwrap();
let value = if i % 7 == 0 { 0.0 } else { 1.0 };
signal_repo
.insert(&Signal {
id: SignalId::new(),
session_id: sid,
kind: SignalKind::Implicit,
source: "soak".into(),
value,
recorded_at: Utc::now(),
payload_json: None,
})
.await
.unwrap();
}
let elapsed = start.elapsed();
let per_session_us = (elapsed.as_micros() as f64) / (total_sessions as f64);
assert!(
elapsed.as_secs() < 120,
"10k inserts took {elapsed:?}; expected < 120s",
);
let recent = session_repo.list_recent(pid, 20_000).await.unwrap();
assert_eq!(recent.len(), total_sessions);
println!(
"soak: {} sessions in {:?} ({:.0} us/session)",
total_sessions, elapsed, per_session_us
);
}
#[tokio::test]
async fn promote_evaluate_loop_completes_quickly_after_10k_history() {
let storage = Storage::in_memory_for_tests().await.unwrap();
let _registry = AdapterRegistry::new();
let pid = ProjectId::new();
ProjectRepo::new(&storage)
.insert(&Project {
id: pid,
adapter_id: AdapterId::new("claude-code"),
root_path: "/tmp/soak2".into(),
name: "soak2".into(),
created_at: Utc::now(),
champion_config_id: None,
})
.await
.unwrap();
let cfg_id = ConfigId::new();
let payload = AgentConfig::default_for("claude-code");
AgentConfigRepo::new(&storage)
.insert(&AgentConfigRow {
id: cfg_id,
project_id: pid,
adapter_id: AdapterId::new("claude-code"),
role: ConfigRole::Champion,
fingerprint: payload.fingerprint(),
payload,
created_at: Utc::now(),
})
.await
.unwrap();
ProjectRepo::new(&storage)
.set_champion(pid, cfg_id)
.await
.unwrap();
let session_repo = SessionRepo::new(&storage);
let signal_repo = SignalRepo::new(&storage);
for i in 0..5_000 {
let sid = SessionId::new();
session_repo
.insert(&Session {
id: sid,
project_id: pid,
experiment_id: None,
variant: SessionVariant::Champion,
config_id: cfg_id,
started_at: Utc::now(),
ended_at: Utc::now(),
adapter_session_ref: None,
})
.await
.unwrap();
signal_repo
.insert(&Signal {
id: SignalId::new(),
session_id: sid,
kind: SignalKind::Implicit,
source: "soak".into(),
value: if i % 3 == 0 { 0.0 } else { 1.0 },
recorded_at: Utc::now(),
payload_json: None,
})
.await
.unwrap();
}
let start = std::time::Instant::now();
let scores = engine::collect_scores_for_config(&storage, cfg_id)
.await
.unwrap();
let elapsed = start.elapsed();
assert_eq!(scores.len(), 5_000);
assert!(
elapsed.as_secs() < 30,
"score collection over 5k sessions took {elapsed:?}; expected < 30s",
);
println!("collect_scores over 5k sessions: {:?}", elapsed);
}