//! Allocation-count bounds on one agent turn (`SMH-SPEC-SPEC0001`, memory //! accounting). Bounds only tighten. use smith_alloc::{Counting, Scope, measure}; use smith_harness::{RuntimeConfig, build_agent, mock_text_reply}; use std::sync::mpsc; use std::time::Duration; #[global_allocator] static ALLOC: Counting = Counting; /// One mock text turn on a fresh session: request build (one decode of the /// user message), stream consumption, two recorded entries encoded once into /// the arena, one worker request + bounded acknowledgement per entry. /// Worker ownership adds two channels and two owned frame transfers per turn. const MOCK_TURN_ALLOCATIONS: u64 = 47; /// Initialize std MPMC's per-thread wait context before turn accounting. /// /// `sync_channel().recv()` allocates its `Context` only when it blocks. /// A worker acknowledgement can race between immediate delivery and that path, /// so this bounded deadline fixes the lazy-state boundary explicitly. fn initialize_mpsc_wait_context() { let (_sender, receiver) = mpsc::sync_channel::<()>(1); assert_eq!( receiver.recv_timeout(Duration::from_secs(1)), Err(mpsc::RecvTimeoutError::Timeout) ); } fn mock_turn(tag: &str) -> (u64, u64) { let dir = std::env::temp_dir().join(format!("smith_alloc_{tag}_{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let mut agent = build_agent(&RuntimeConfig::mock(&dir, vec![mock_text_reply("hi")])).unwrap(); let (outcome, cost) = measure(Scope::Turn, || agent.run_turn("hello")); assert_eq!(outcome.unwrap().text, "hi"); drop(agent); let _ = std::fs::remove_dir_all(&dir); (cost.allocations, cost.bytes) } #[test] fn provider_effect_channel_moves_records_without_allocating() { use smith::provider::{ProviderEffectOutcome, ProviderEffectRecord, record_channel}; let (sender, receiver) = record_channel(); let record = ProviderEffectRecord::Outcome { status: 200, response_bytes: 12, outcome: ProviderEffectOutcome::Completed, }; let (received, cost) = measure(Scope::IoRequest, || { sender.try_send(record).unwrap(); receiver.try_recv().unwrap() }); assert!(matches!( received, ProviderEffectRecord::Outcome { response_bytes: 12, .. } )); assert_eq!(cost.allocations, 0); assert_eq!(cost.peak, 0); } #[test] fn mock_turn_is_bounded_and_reproducible() { // SMH-SPEC-SPEC0001: turn accounting excludes process initialization. initialize_mpsc_wait_context(); let _warmup = mock_turn("warmup"); let first = mock_turn("a"); let second = mock_turn("b"); assert_eq!(first, second); assert_eq!( first.0, MOCK_TURN_ALLOCATIONS, "turn allocated {} times, bound is {MOCK_TURN_ALLOCATIONS}", first.0 ); }