#![cfg(unix)]
use ito_core::harness::{Harness, HarnessRunConfig, OpencodeHarness};
use std::collections::BTreeMap;
use std::os::unix::fs::PermissionsExt;
use std::time::Duration;
fn write_executable(path: &std::path::Path, contents: &str) {
std::fs::write(path, contents).unwrap();
let mut perms = std::fs::metadata(path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms).unwrap();
}
fn child_path_with_prepend(path: &std::path::Path) -> String {
let old = std::env::var("PATH").unwrap_or_default();
format!("{}:{}", path.to_string_lossy(), old)
}
#[test]
fn inactivity_timeout_kills_stalled_process() {
let dir = tempfile::tempdir().unwrap();
let bin = dir.path().join("opencode");
write_executable(
&bin,
"#!/bin/sh\necho 'Starting...'\nwhile true; do sleep 1; done\necho 'Should not reach here'\n",
);
let mut env = BTreeMap::new();
env.insert("PATH".to_string(), child_path_with_prepend(dir.path()));
let mut h = OpencodeHarness;
let start = std::time::Instant::now();
let r = h
.run(&HarnessRunConfig {
prompt: "test".to_string(),
model: None,
cwd: dir.path().to_path_buf(),
env,
interactive: false,
allow_all: false,
inactivity_timeout: Some(Duration::from_secs(2)),
})
.unwrap();
let elapsed = start.elapsed();
assert!(r.timed_out, "Process should have timed out");
assert_eq!(r.exit_code, -1, "Exit code should be -1 for timeout");
assert!(
r.stdout.contains("Starting..."),
"Should have captured initial output before timeout"
);
assert!(
!r.stdout.contains("Should not reach here"),
"Should not have output from after the sleep"
);
assert!(
elapsed < Duration::from_secs(10),
"Test should complete quickly, took {:?}",
elapsed
);
assert!(
elapsed >= Duration::from_secs(2),
"Should have waited at least the timeout duration, took {:?}",
elapsed
);
}
#[test]
fn no_timeout_when_process_exits_normally() {
let dir = tempfile::tempdir().unwrap();
let bin = dir.path().join("opencode");
write_executable(
&bin,
"#!/bin/sh\necho 'Line 1'\necho 'Line 2'\necho 'Line 3'\nexit 0\n",
);
let mut env = BTreeMap::new();
env.insert("PATH".to_string(), child_path_with_prepend(dir.path()));
let mut h = OpencodeHarness;
let start = std::time::Instant::now();
let r = h
.run(&HarnessRunConfig {
prompt: "test".to_string(),
model: None,
cwd: dir.path().to_path_buf(),
env,
interactive: false,
allow_all: false,
inactivity_timeout: Some(Duration::from_secs(2)),
})
.unwrap();
let elapsed = start.elapsed();
assert!(!r.timed_out, "Process should not have timed out");
assert_eq!(r.exit_code, 0, "Exit code should be 0 for normal exit");
assert!(
r.stdout.contains("Line 1"),
"Should have captured all output"
);
assert!(
r.stdout.contains("Line 2"),
"Should have captured all output"
);
assert!(
r.stdout.contains("Line 3"),
"Should have captured all output"
);
assert!(
elapsed < Duration::from_secs(10),
"Test should complete quickly, took {:?}",
elapsed
);
}