#[test]
fn kiss_cov_session_and_spawn_names() {
let _ = super::spawn_bridge;
let _ = stringify!(pi_spawn_bridge);
let _ = stringify!(pi_provider_and_model);
let _ = stringify!(fake_embedded_session);
let _ = stringify!(live_embedded_session);
let _ = stringify!(start_embedded_mem_watch);
let _ = stringify!(watch_embedded_memory);
let _ = stringify!(isolated_tool_factory);
}
#[test]
fn pi_provider_and_model_first_slash() {
use crate::model_id::parse_model_id;
assert_eq!(
parse_model_id("pi:openai/gpt-4o")
.expect("ok")
.pi_provider_and_model()
.expect("pi"),
("openai", "gpt-4o")
);
assert_eq!(
parse_model_id("pi:openrouter/anthropic/claude-3-haiku")
.expect("ok")
.pi_provider_and_model()
.expect("pi"),
("openrouter", "anthropic/claude-3-haiku")
);
assert!(
parse_model_id("pi:noslash")
.expect_err("err")
.contains("provider")
);
}
#[tokio::test]
async fn fake_session_begin_end_leaves_no_pi_runtime_thread() {
let _guard = crate::test_utils::test_env_lock();
unsafe {
std::env::set_var(crate::acp::MALVIN_TEST_NO_REAL_AGENT_ENV, "1");
std::env::set_var("OPENAI_API_KEY", "test-key");
}
let tmp = tempfile::tempdir().expect("tmp");
let mut client = crate::pi_sdk::pi_sdk_client_from_raw(
"pi:openai/gpt-4o",
crate::acp::AgentIoOptions {
force: true,
no_tee: true,
raw_output: true,
show_thoughts_on_stdout: false,
emit_stdout_markdown: false,
log_full_outgoing_prompts: false,
},
1,
);
client.prompts_log_run_dir = Some(tmp.path().to_path_buf());
client.begin_coder_session(tmp.path()).await.expect("begin");
client.end_coder_session().await.expect("end");
let leftover = leftover_pi_runtime_threads();
unsafe {
std::env::remove_var(crate::acp::MALVIN_TEST_NO_REAL_AGENT_ENV);
}
assert!(
leftover.is_empty(),
"malvin-pi-sdk threads still alive: {leftover:?}"
);
}
#[test]
fn pi_runtime_lifecycle_starts_and_joins_named_thread() {
crate::test_utils::with_isolated_home(|work| {
let options = pi::sdk::SessionOptions {
provider: Some("openai".to_string()),
model: Some("gpt-4o".to_string()),
api_key: Some("dummy-key".to_string()),
working_directory: Some(work.to_path_buf()),
no_session: true,
tool_factory: Some(crate::pi_sdk::isolated_bash::isolated_tool_factory()),
..pi::sdk::SessionOptions::default()
};
#[cfg(target_os = "linux")]
assert!(
!pi_sdk_named_thread_exists(),
"precondition: no malvin-pi-sdk thread before PiRuntime::start"
);
let mut runtime = super::runtime::PiRuntime::start(options).expect("runtime starts");
#[cfg(target_os = "linux")]
assert!(
pi_sdk_named_thread_exists(),
"malvin-pi-sdk thread must exist while the runtime is live"
);
runtime
.shutdown()
.expect("malvin-pi-sdk thread must be joined after shutdown");
});
}
#[test]
fn pi_sdk_named_thread_helper_reads_proc_without_panicking() {
let _exists = pi_sdk_named_thread_exists();
}
fn pi_blocking_session_options(work: &std::path::Path) -> pi::sdk::SessionOptions {
pi::sdk::SessionOptions {
provider: Some("openai".to_string()),
model: Some("gpt-4o".to_string()),
api_key: Some("dummy-key".to_string()),
working_directory: Some(work.to_path_buf()),
no_session: true,
tool_factory: Some(crate::pi_sdk::isolated_bash::isolated_tool_factory()),
..pi::sdk::SessionOptions::default()
}
}
#[test]
fn pi_blocking_session_options_sets_working_directory() {
let dir = tempfile::tempdir().expect("tmpdir");
let options = pi_blocking_session_options(dir.path());
assert_eq!(options.working_directory.as_deref(), Some(dir.path()));
}
#[test]
fn pi_runtime_shutdown_returns_during_in_flight_prompt() {
crate::test_utils::with_isolated_home(|work| {
unsafe {
std::env::set_var("MALVIN_TEST_PI_PROMPT_BLOCK_SECS", "2");
}
let mut runtime = super::runtime::PiRuntime::start(pi_blocking_session_options(work))
.expect("runtime starts");
let (events_tx, _events_rx) = tokio::sync::mpsc::unbounded_channel();
runtime
.prompt("kpop block".into(), events_tx)
.expect("prompt queued");
let started = std::time::Instant::now();
let _ = runtime.shutdown();
unsafe {
std::env::remove_var("MALVIN_TEST_PI_PROMPT_BLOCK_SECS");
}
assert!(
started.elapsed() < std::time::Duration::from_secs(1),
"PiRuntime::shutdown must not block until in-flight prompt work completes: {:?}",
started.elapsed()
);
});
}
fn pi_sdk_named_thread_exists() -> bool {
let Ok(entries) = std::fs::read_dir("/proc/self/task") else {
return false;
};
entries.flatten().any(|entry| {
std::fs::read_to_string(entry.path().join("comm"))
.is_ok_and(|name| name.trim() == "malvin-pi-sdk")
})
}
fn leftover_pi_runtime_threads() -> Vec<String> {
let Ok(entries) = std::fs::read_dir("/proc/self/task") else {
return Vec::new();
};
let mut names = Vec::new();
for entry in entries.flatten() {
let comm = entry.path().join("comm");
let Ok(name) = std::fs::read_to_string(comm) else {
continue;
};
if name.contains("malvin-pi-sdk") {
names.push(name.trim().to_string());
}
}
names
}