mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
1aa3e432cc
## Why Shell snapshot lifecycle state was split between `Shell` and `SessionServices`: `Shell` carried the receiver while session code exposed and forwarded the raw sender. That coupled shell identity to mutable snapshot state and made refresh, inheritance, and file lifetime harder to reason about. ## What changed - make each `Arc<ShellSnapshot>` represent one cwd-specific snapshot generation - store the active generation in `SessionServices` with `ArcSwapOption` - have construction start the background build and expose only a cwd-validated snapshot path - use `ShellSnapshotFile` ownership to delete snapshot files automatically - pass snapshot paths explicitly to shell runtimes instead of storing snapshot state on `Shell` - preserve inherited and in-flight generations by pinning their `Arc` while they are in use ## Test plan - `cargo check -p codex-core --lib` - `just test -p codex-core 'shell_snapshot::tests'` - `just test -p codex-core shell_command_snapshot_still_intercepts_apply_patch` - `just test -p codex-core shell_snapshot_deleted_after_shutdown_with_skills`
189 lines
5.1 KiB
Rust
189 lines
5.1 KiB
Rust
use super::*;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
#[test]
|
|
#[cfg(target_os = "macos")]
|
|
fn detects_zsh() {
|
|
let zsh_shell = get_shell(ShellType::Zsh, /*path*/ None).unwrap();
|
|
|
|
let shell_path = zsh_shell.shell_path;
|
|
|
|
assert_eq!(shell_path, std::path::Path::new("/bin/zsh"));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "macos")]
|
|
fn fish_fallback_to_zsh() {
|
|
let zsh_shell = default_user_shell_from_path(Some(PathBuf::from("/bin/fish")));
|
|
|
|
let shell_path = zsh_shell.shell_path;
|
|
|
|
assert_eq!(shell_path, std::path::Path::new("/bin/zsh"));
|
|
}
|
|
|
|
#[test]
|
|
fn detects_bash() {
|
|
let bash_shell = get_shell(ShellType::Bash, /*path*/ None).unwrap();
|
|
let shell_path = bash_shell.shell_path;
|
|
|
|
assert!(
|
|
shell_path.file_name().and_then(|name| name.to_str()) == Some("bash"),
|
|
"shell path: {shell_path:?}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_sh() {
|
|
let sh_shell = get_shell(ShellType::Sh, /*path*/ None).unwrap();
|
|
let shell_path = sh_shell.shell_path;
|
|
assert!(
|
|
shell_path.file_name().and_then(|name| name.to_str()) == Some("sh"),
|
|
"shell path: {shell_path:?}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn can_run_on_shell_test() {
|
|
let cmd = "echo \"Works\"";
|
|
if cfg!(windows) {
|
|
assert!(shell_works(
|
|
get_shell(ShellType::PowerShell, /*path*/ None),
|
|
"Out-String 'Works'",
|
|
/*required*/ true,
|
|
));
|
|
assert!(shell_works(
|
|
get_shell(ShellType::Cmd, /*path*/ None),
|
|
cmd,
|
|
/*required*/ true,
|
|
));
|
|
assert!(shell_works(
|
|
Some(ultimate_fallback_shell()),
|
|
cmd,
|
|
/*required*/ true
|
|
));
|
|
} else {
|
|
assert!(shell_works(
|
|
Some(ultimate_fallback_shell()),
|
|
cmd,
|
|
/*required*/ true
|
|
));
|
|
assert!(shell_works(
|
|
get_shell(ShellType::Zsh, /*path*/ None),
|
|
cmd,
|
|
/*required*/ false
|
|
));
|
|
assert!(shell_works(
|
|
get_shell(ShellType::Bash, /*path*/ None),
|
|
cmd,
|
|
/*required*/ true
|
|
));
|
|
assert!(shell_works(
|
|
get_shell(ShellType::Sh, /*path*/ None),
|
|
cmd,
|
|
/*required*/ true
|
|
));
|
|
}
|
|
}
|
|
|
|
fn shell_works(shell: Option<Shell>, command: &str, required: bool) -> bool {
|
|
if let Some(shell) = shell {
|
|
let args = shell.derive_exec_args(command, /*use_login_shell*/ false);
|
|
let output = Command::new(args[0].clone())
|
|
.args(&args[1..])
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success());
|
|
assert!(String::from_utf8_lossy(&output.stdout).contains("Works"));
|
|
true
|
|
} else {
|
|
!required
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn derive_exec_args() {
|
|
let test_bash_shell = Shell {
|
|
shell_type: ShellType::Bash,
|
|
shell_path: PathBuf::from("/bin/bash"),
|
|
};
|
|
assert_eq!(
|
|
test_bash_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
|
vec!["/bin/bash", "-c", "echo hello"]
|
|
);
|
|
assert_eq!(
|
|
test_bash_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
|
vec!["/bin/bash", "-lc", "echo hello"]
|
|
);
|
|
|
|
let test_zsh_shell = Shell {
|
|
shell_type: ShellType::Zsh,
|
|
shell_path: PathBuf::from("/bin/zsh"),
|
|
};
|
|
assert_eq!(
|
|
test_zsh_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
|
vec!["/bin/zsh", "-c", "echo hello"]
|
|
);
|
|
assert_eq!(
|
|
test_zsh_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
|
vec!["/bin/zsh", "-lc", "echo hello"]
|
|
);
|
|
|
|
let test_powershell_shell = Shell {
|
|
shell_type: ShellType::PowerShell,
|
|
shell_path: PathBuf::from("pwsh.exe"),
|
|
};
|
|
assert_eq!(
|
|
test_powershell_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
|
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"]
|
|
);
|
|
assert_eq!(
|
|
test_powershell_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
|
vec!["pwsh.exe", "-Command", "echo hello"]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_current_shell_detects_zsh() {
|
|
let shell = Command::new("sh")
|
|
.arg("-c")
|
|
.arg("echo $SHELL")
|
|
.output()
|
|
.unwrap();
|
|
|
|
let shell_path = String::from_utf8_lossy(&shell.stdout).trim().to_string();
|
|
if shell_path.ends_with("/zsh") {
|
|
assert_eq!(
|
|
default_user_shell(),
|
|
Shell {
|
|
shell_type: ShellType::Zsh,
|
|
shell_path: PathBuf::from(shell_path),
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn detects_powershell_as_default() {
|
|
if !cfg!(windows) {
|
|
return;
|
|
}
|
|
|
|
let powershell_shell = default_user_shell();
|
|
let shell_path = powershell_shell.shell_path;
|
|
|
|
assert!(shell_path.ends_with("pwsh.exe") || shell_path.ends_with("powershell.exe"));
|
|
}
|
|
|
|
#[test]
|
|
fn finds_powershell() {
|
|
if !cfg!(windows) {
|
|
return;
|
|
}
|
|
|
|
let powershell_shell = get_shell(ShellType::PowerShell, /*path*/ None).unwrap();
|
|
let shell_path = powershell_shell.shell_path;
|
|
|
|
assert!(shell_path.ends_with("pwsh.exe") || shell_path.ends_with("powershell.exe"));
|
|
}
|