use super::*;
use std::time::{Duration, Instant};
use trusty_common::memory_core::timeouts;
const TEST_BUDGET: Duration = Duration::from_millis(300);
const CEILING: Duration = Duration::from_secs(2);
fn budgeted_state() -> (AppState, tempfile::TempDir) {
skip_palace_enforcement();
seed_embedder();
let tmp = tempfile::tempdir().expect("tempdir");
let state = AppState::new(tmp.path().to_path_buf()).with_write_op_budget(TEST_BUDGET);
state.set_ready();
(state, tmp)
}
#[tokio::test]
async fn memory_remember_gives_up_within_one_budget_not_the_leg_sum() {
let (state, _tmp) = budgeted_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "budget"}))
.await
.expect("palace_create");
let pre_fix_worst_case = timeouts::write_lock_timeout() + timeouts::open_queue_timeout();
assert!(
pre_fix_worst_case > TEST_BUDGET,
"the composed pre-fix wait ({pre_fix_worst_case:?}) must exceed the injected budget \
({TEST_BUDGET:?}), or this test proves nothing (issue #4002)"
);
let write_lock = state.palace_write_lock("budget");
let _held = write_lock.lock().await;
let started = Instant::now();
let err = handle_memory_remember(
&state,
json!({"palace": "budget", "text": "a sufficiently long fact to clear the content gate"}),
)
.await
.expect_err("a permanently held write mutex must surface an error, never block forever");
let elapsed = started.elapsed();
assert!(
elapsed < CEILING,
"memory_remember must give up inside its {TEST_BUDGET:?} budget; got {elapsed:?} \
(pre-fix this leg alone waited {:?} — issue #4002)",
timeouts::write_lock_timeout()
);
let msg = format!("{err:#}");
assert!(
msg.contains("memory_remember") && msg.contains("write-lock acquisition timed out"),
"the error must name the tool and the leg that expired; got: {msg}"
);
}
#[tokio::test]
async fn memory_note_gives_up_within_one_budget_not_the_leg_sum() {
let (state, _tmp) = budgeted_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "budget"}))
.await
.expect("palace_create");
let write_lock = state.palace_write_lock("budget");
let _held = write_lock.lock().await;
let started = Instant::now();
let err = handle_memory_note(
&state,
json!({
"palace": "budget",
"content": "Masa prefers snake_case for every identifier in this workspace",
}),
)
.await
.expect_err("a permanently held write mutex must surface an error");
let elapsed = started.elapsed();
assert!(
elapsed < CEILING,
"memory_note must give up inside its {TEST_BUDGET:?} budget; got {elapsed:?} (issue #4002)"
);
assert!(
format!("{err:#}").contains("memory_note"),
"the error must name the tool that gave up"
);
}
#[tokio::test]
async fn a_short_budget_does_not_disturb_an_uncontended_write() {
let (state, _tmp) = budgeted_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "budget"}))
.await
.expect("palace_create");
let stored = handle_memory_remember(
&state,
json!({"palace": "budget", "text": "an uncontended write must still land on disk"}),
)
.await
.expect("an uncontended write must succeed under a short budget");
assert_eq!(stored["status"], "stored");
let listed = dispatch_tool(&state, "memory_list", json!({"palace": "budget"}))
.await
.expect("memory_list");
assert_eq!(
listed["drawers"].as_array().map(Vec::len),
Some(1),
"the budgeted write must be readable afterwards"
);
}
#[tokio::test]
async fn task_add_gives_up_within_one_budget_not_the_leg_sum() {
let (state, _tmp) = budgeted_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "budget"}))
.await
.expect("palace_create");
let write_lock = state.palace_write_lock("budget");
let _held = write_lock.lock().await;
let started = Instant::now();
let err = dispatch_tool(
&state,
"task_add",
json!({"palace": "budget", "content": "ship the joint budget"}),
)
.await
.expect_err("a permanently held write mutex must surface an error");
let elapsed = started.elapsed();
assert!(
elapsed < CEILING,
"task_add must give up inside its {TEST_BUDGET:?} budget; got {elapsed:?} (issue #4002)"
);
assert!(
format!("{err:#}").contains("task_add"),
"the error must name the tool that gave up"
);
}
#[test]
fn an_exhausted_write_budget_gives_the_open_queue_leg_nothing() {
let spent = timeouts::OpBudget::start(Duration::ZERO);
assert_eq!(
spent.leg(timeouts::open_queue_timeout()),
Duration::ZERO,
"once the write-lock leg has spent the budget, the open-queue leg must not open \
a fresh {:?} window (issue #4002)",
timeouts::open_queue_timeout()
);
}