use crate::common::*;
use std::time::Duration;
use oxicode_agent::runtime::{PersistentShellSession, ShellSession};
#[tokio::test]
async fn persistent_shell_session_contract() {
let dir = tempfile::tempdir().unwrap();
let ws = dir.path().to_path_buf();
std::fs::create_dir(dir.path().join("sub")).unwrap();
let session: Arc<dyn ShellSession> =
Arc::new(PersistentShellSession::new(ws.clone()).with_max_output(4_096));
let mut services = minimal_services(&ws);
services.shell_session = Some(session.clone());
let mut installer = RecordingInstaller::new(WrapMode::Trace);
let (manifest, _patch) = install_pack_with_services(&services, &mut installer);
assert!(
!manifest
.degraded
.iter()
.any(|d| d.feature == "shell-session"),
"shell-session must be satisfied: {:?}",
manifest.degraded
);
assert!(
manifest.degraded.iter().any(|d| d.feature == "eval-kernel"),
"eval-kernel must still degrade"
);
let out = session
.execute("cd sub && export OXI_FIXTURE=1", Duration::from_secs(5))
.await
.unwrap();
assert_eq!(out.exit_code, 0);
let out = session
.execute("echo \"$PWD $OXI_FIXTURE\"", Duration::from_secs(5))
.await
.unwrap();
assert!(out.stdout.contains("sub"), "cwd must persist: {out:?}");
assert!(
out.stdout.trim_end().ends_with(" 1"),
"env must persist: {out:?}"
);
let worker_session = session.clone();
let worker = tokio::spawn(async move {
worker_session
.execute("sleep 30", Duration::from_secs(60))
.await
});
tokio::time::sleep(Duration::from_millis(300)).await;
session.cancel();
let started = std::time::Instant::now();
let out = tokio::time::timeout(Duration::from_secs(10), worker)
.await
.expect("execute must return after cancel")
.expect("join")
.expect("execute ok");
assert!(
started.elapsed() < Duration::from_secs(20),
"cancel must be prompt"
);
assert_eq!(out.exit_code, 130, "SIGINT must surface as 130: {out:?}");
session
.execute("cd /", Duration::from_secs(5))
.await
.unwrap();
session.reset().await.unwrap();
let out = session
.execute(
"pwd && echo \"${OXI_FIXTURE:-unset}\"",
Duration::from_secs(5),
)
.await
.unwrap();
assert!(
out.stdout.contains(&dir_path_marker(&ws)),
"reset must return to the workspace root: {out:?}"
);
assert!(
out.stdout.contains("unset"),
"reset must drop exported state: {out:?}"
);
let out = session
.execute("seq 1 200000", Duration::from_secs(10))
.await
.unwrap();
assert!(out.truncated, "bound must be reported");
assert!(
out.stdout.len() <= 4_096 + 8,
"output must be bounded: {}",
out.stdout.len()
);
}
fn dir_path_marker(ws: &std::path::Path) -> String {
ws.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default()
}