use super::*;
#[test]
fn print_mode_joins_already_finished_title_generation_before_returning() {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let mut handle = Some(std::thread::spawn(move || {
completed_for_thread.store(true, Ordering::SeqCst);
}));
while !handle.as_ref().unwrap().is_finished() {
std::thread::yield_now();
}
finish_title_generation_for_mode(&mut handle, InvocationMode::Print);
assert!(completed.load(Ordering::SeqCst));
assert!(handle.is_none());
}
#[test]
fn print_mode_detaches_active_title_generation_non_blocking() {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let (sender, receiver) = std::sync::mpsc::channel();
let mut handle = Some(std::thread::spawn(move || {
receiver.recv().unwrap();
completed_for_thread.store(true, Ordering::SeqCst);
}));
let started = std::time::Instant::now();
finish_title_generation_for_mode(&mut handle, InvocationMode::Print);
assert!(started.elapsed() < std::time::Duration::from_millis(200));
assert!(!completed.load(Ordering::SeqCst));
assert!(handle.is_none());
sender.send(()).unwrap();
while !completed.load(Ordering::SeqCst) {
std::thread::yield_now();
}
}
#[test]
fn print_mode_title_guard_finish_detaches_blocked_thread_non_blocking() {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let (sender, receiver) = std::sync::mpsc::channel();
let handle = Some(std::thread::spawn(move || {
receiver.recv().unwrap();
completed_for_thread.store(true, Ordering::SeqCst);
}));
let mut guard =
TitleGenerationGuard::new(handle, InvocationMode::Print, AgentCancellation::default());
let started = std::time::Instant::now();
guard.finish();
assert!(started.elapsed() < std::time::Duration::from_millis(200));
assert!(!completed.load(Ordering::SeqCst));
sender.send(()).unwrap();
while !completed.load(Ordering::SeqCst) {
std::thread::yield_now();
}
}
#[test]
fn print_mode_title_guard_drop_detaches_blocked_thread_non_blocking() {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let (sender, receiver) = std::sync::mpsc::channel();
let handle = Some(std::thread::spawn(move || {
receiver.recv().unwrap();
completed_for_thread.store(true, Ordering::SeqCst);
}));
let started = std::time::Instant::now();
{
let _guard =
TitleGenerationGuard::new(handle, InvocationMode::Print, AgentCancellation::default());
}
assert!(started.elapsed() < std::time::Duration::from_millis(200));
assert!(!completed.load(Ordering::SeqCst));
sender.send(()).unwrap();
while !completed.load(Ordering::SeqCst) {
std::thread::yield_now();
}
}
#[test]
fn print_mode_title_guard_skips_join_when_canceled() {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let cancel = Arc::new(AtomicBool::new(true));
let (sender, receiver) = std::sync::mpsc::channel();
let handle = Some(std::thread::spawn(move || {
receiver.recv().unwrap();
completed_for_thread.store(true, Ordering::SeqCst);
}));
{
let _guard = TitleGenerationGuard::new(
handle,
InvocationMode::Print,
AgentCancellation::new(cancel),
);
}
assert!(!completed.load(Ordering::SeqCst));
sender.send(()).unwrap();
while !completed.load(Ordering::SeqCst) {
std::thread::yield_now();
}
}
#[test]
fn non_print_modes_leave_active_title_generation_non_blocking() {
for mode in [
InvocationMode::Shell,
InvocationMode::MissionControl,
InvocationMode::Subagent,
] {
let completed = Arc::new(AtomicBool::new(false));
let completed_for_thread = Arc::clone(&completed);
let (sender, receiver) = std::sync::mpsc::channel();
let mut handle = Some(std::thread::spawn(move || {
receiver.recv().unwrap();
completed_for_thread.store(true, Ordering::SeqCst);
}));
let started = std::time::Instant::now();
finish_title_generation_for_mode(&mut handle, mode);
assert!(started.elapsed() < std::time::Duration::from_millis(200));
assert!(!completed.load(Ordering::SeqCst));
let handle = handle.take().unwrap();
sender.send(()).unwrap();
handle.join().unwrap();
}
}
#[test]
fn non_print_modes_join_already_finished_title_generation() {
for mode in [
InvocationMode::Shell,
InvocationMode::MissionControl,
InvocationMode::Subagent,
] {
let mut handle = Some(std::thread::spawn(|| {}));
while !handle.as_ref().unwrap().is_finished() {
std::thread::yield_now();
}
finish_title_generation_for_mode(&mut handle, mode);
assert!(handle.is_none());
}
}
#[test]
fn herdr_primary_lifecycle_reports_thinking_tool_done_in_order() {
let temp = tempfile::TempDir::new().unwrap();
std::fs::write(temp.path().join("file.txt"), "file text").unwrap();
let provider = ScriptedProvider::new(vec![read_done("herdr_read"), text_done("done")]);
let tools = ToolRuntime::new(temp.path()).unwrap();
let agent = AgentSession::new("model", &[], &SkillDiscovery::default());
let (reporter, lines) = crate::herdr::HerdrReporter::new_for_test();
agent
.run_print_with_tools_streaming_output_cancellable(
&provider,
AgentRunRequest {
tools: Some(&tools),
herdr_reporter: Some(reporter),
..run_request("read file", temp.path())
},
)
.unwrap();
assert_eq!(
herdr_statuses(&lines),
vec![
(
"working".to_string(),
"thinking".to_string(),
"magi-code: thinking".to_string(),
),
(
"working".to_string(),
"tool".to_string(),
"magi-code: running read".to_string(),
),
(
"idle".to_string(),
"done".to_string(),
"magi-code: done".to_string(),
),
]
);
}
#[test]
fn herdr_subagent_invocation_reports_nothing_even_with_parent_reporter() {
let temp = tempfile::TempDir::new().unwrap();
let provider = ScriptedProvider::new(vec![text_done("child done")]);
let tools = ToolRuntime::new(temp.path()).unwrap();
let agent = AgentSession::new("model", &[], &SkillDiscovery::default());
let (reporter, lines) = crate::herdr::HerdrReporter::new_for_test();
agent
.run_print_with_tools_streaming_output_cancellable(
&provider,
AgentRunRequest {
tools: Some(&tools),
invocation_mode: InvocationMode::Subagent,
herdr_reporter: Some(reporter),
..run_request("child task", temp.path())
},
)
.unwrap();
assert!(lines.lock().unwrap().is_empty());
}