mirror of
https://github.com/openai/codex.git
synced 2026-10-16 16:58:46 +00:00
Fix zsh alias quoting in sourced shell snapshots (#48187)
## Why Sourced snapshots parse options and aliases as a group before restored options take effect. Aliases serialized with `RC_QUOTES` enabled can therefore be misinterpreted by the replay shell. ## What changed Serialize zsh aliases for sourced snapshots with `NO_RC_QUOTES` in a subshell, preserving the captured shell options and avoiding a dependency on the optional `zsh/parameter` module. ## Testing Extend regression coverage to execute restored aliases with `RC_QUOTES` enabled and disabled across source and eval replay. Expand macOS zsh concurrent replay tests to cover quoted aliases, preserved options, and blocked descriptor paths with and without a TTY. GitOrigin-RevId: 48086f380c21318bece9226dfe6486c179c2a440
This commit is contained in:
@@ -14,6 +14,8 @@ enum SnapshotSandbox {
|
||||
#[cfg_attr(target_os = "macos", test_case("zsh", false, SnapshotSandbox::None; "zsh"))]
|
||||
#[cfg_attr(target_os = "macos", test_case("zsh", true, SnapshotSandbox::None; "zsh_tty"))]
|
||||
#[test_case("bash", false, SnapshotSandbox::DenyFdPath; "blocked_descriptor_path")]
|
||||
#[cfg_attr(target_os = "macos", test_case("zsh", false, SnapshotSandbox::DenyFdPath; "zsh_blocked_descriptor_path"))]
|
||||
#[cfg_attr(target_os = "macos", test_case("zsh", true, SnapshotSandbox::DenyFdPath; "zsh_blocked_descriptor_path_tty"))]
|
||||
#[test_case("bash", false, SnapshotSandbox::WorkspaceWrite; "bash_protected_transport")]
|
||||
#[test_case("bash", true, SnapshotSandbox::WorkspaceWrite; "bash_protected_transport_tty")]
|
||||
#[cfg_attr(target_os = "macos", test_case("zsh", false, SnapshotSandbox::WorkspaceWrite; "zsh_protected_transport"))]
|
||||
@@ -66,10 +68,15 @@ async fn shell_snapshot_concurrent_replays_keep_independent_readers(
|
||||
} else {
|
||||
format!("case \"{source}\" in /dev/fd/*) ;; *) return 42 ;; esac; ")
|
||||
};
|
||||
let aliases = if shell == "zsh" {
|
||||
"module_path=()\nsetopt RC_QUOTES\nalias snapshot_quoted=\"printf '%s|' 'one''two'\"\nalias eval='exit 44'\nalias case='exit 45'\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
std::fs::write(
|
||||
home.path().join(format!(".{shell}rc")),
|
||||
format!(
|
||||
"printf x >> \"$HOME/captures\"\nprofile_helper() {{ {source_check}local payload='{payload}'; [ \"${{#payload}}\" = {payload_len} ] || return 43; printf 'restored:%s' \"$1\"; }}\nexec() {{ exit 41; }}\nset -u\n"
|
||||
"printf x >> \"$HOME/captures\"\nprofile_helper() {{ {source_check}local payload='{payload}'; [ \"${{#payload}}\" = {payload_len} ] || return 43; printf 'restored:%s' \"$1\"; }}\nexec() {{ exit 41; }}\nset -u\n{aliases}"
|
||||
),
|
||||
)?;
|
||||
let protected_file =
|
||||
@@ -81,7 +88,13 @@ async fn shell_snapshot_concurrent_replays_keep_independent_readers(
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let command = format!("{command}IFS= read -r line; profile_helper \"$line\"; exit 7");
|
||||
let replay_checks = if shell == "zsh" {
|
||||
"eval snapshot_quoted\n[[ -o rcquotes ]] || exit 46\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let command =
|
||||
format!("{command}IFS= read -r line\n{replay_checks}profile_helper \"$line\"; exit 7");
|
||||
let sandbox = if use_sandbox {
|
||||
let mut policy = FileSystemSandboxPolicy::read_only();
|
||||
policy.entries.push(FileSystemSandboxEntry::new(
|
||||
@@ -161,6 +174,12 @@ async fn shell_snapshot_concurrent_replays_keep_independent_readers(
|
||||
output.ends_with(&format!("restored:input-{index}")),
|
||||
"{output:?}"
|
||||
);
|
||||
if shell == "zsh" {
|
||||
assert!(
|
||||
output.ends_with(&format!("one'two|restored:input-{index}")),
|
||||
"{output:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!((errors, status, closed), (String::new(), Some(7), true));
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
|
||||
@@ -88,6 +88,13 @@ fn capture_script(
|
||||
script
|
||||
.replace("SNAPSHOT_OPTIONS_BEGIN", match replay { Replay::Eval => "", Replay::Source => "printf '{\\n'" })
|
||||
.replace("SNAPSHOT_ALIASES_END", match replay { Replay::Eval => "", Replay::Source => "printf \"case '' in '') ;; esac\\n}\\n\"" })
|
||||
// The source group is parsed before its options take effect. Serialize
|
||||
// aliases with the replay shell's initial quoting rules, not RC_QUOTES.
|
||||
// setopt works without the optional zsh/parameter module.
|
||||
.replace("SNAPSHOT_ZSH_ALIASES", match replay {
|
||||
Replay::Eval => "\\alias -L",
|
||||
Replay::Source => "(\\setopt NO_RC_QUOTES; \\alias -L)",
|
||||
})
|
||||
.replace("SNAPSHOT_EXPORTS", &declarations)
|
||||
.replace(
|
||||
"SNAPSHOT_DECLARATION_ENVIRONMENT",
|
||||
@@ -133,7 +140,7 @@ print ''
|
||||
printf '\0'
|
||||
alias_count=$(\alias -L | __codex_snapshot_command wc -l | __codex_snapshot_command tr -d ' ')
|
||||
print "# aliases $alias_count"
|
||||
\alias -L
|
||||
SNAPSHOT_ZSH_ALIASES
|
||||
print ''
|
||||
SNAPSHOT_ALIASES_END
|
||||
printf '\0'
|
||||
|
||||
@@ -2236,50 +2236,72 @@ fn captured_script(shell_type: ShellType, source: &str) -> Result<String> {
|
||||
|
||||
#[test]
|
||||
fn streamed_snapshot_preserves_alias_and_option_parse_order() -> Result<()> {
|
||||
for (shell, shell_type, options) in [
|
||||
("/bin/bash", ShellType::Bash, "shopt -s expand_aliases"),
|
||||
("/bin/zsh", ShellType::Zsh, "setopt RC_QUOTES"),
|
||||
for (shell, shell_type, options, expected) in [
|
||||
(
|
||||
"/bin/bash",
|
||||
ShellType::Bash,
|
||||
"shopt -s expand_aliases",
|
||||
"onetwohelper",
|
||||
),
|
||||
(
|
||||
"/bin/zsh",
|
||||
ShellType::Zsh,
|
||||
"module_path=()\nsetopt RC_QUOTES",
|
||||
"one'twohelper",
|
||||
),
|
||||
(
|
||||
"/bin/zsh",
|
||||
ShellType::Zsh,
|
||||
"module_path=()\nsetopt NO_RC_QUOTES",
|
||||
"onetwohelper",
|
||||
),
|
||||
] {
|
||||
if !std::path::Path::new(shell).exists() {
|
||||
continue;
|
||||
}
|
||||
let dir = tempdir()?;
|
||||
let mut setup = format!(
|
||||
let setup = format!(
|
||||
"{options}\nhelper() {{ printf helper; }}\nfunction : () {{ exit 41; }}\nalias snapshot_alias=\"printf 'one''two'\"\n"
|
||||
);
|
||||
if shell_type == ShellType::Zsh {
|
||||
setup.push_str("alias alias=false\n");
|
||||
}
|
||||
let mut results = Vec::new();
|
||||
for source in [false, true] {
|
||||
let capture = if source {
|
||||
// Cover V1's native source, V2's source, and V2's environment fallback.
|
||||
for (source_capture, source_replay) in [(false, true), (true, true), (true, false)] {
|
||||
let capture = if source_capture {
|
||||
super::snapshot_source_capture_script(shell_type, CAPTURE_NON_INTERACTIVE)
|
||||
} else {
|
||||
snapshot_capture_script(shell_type, CAPTURE_NON_INTERACTIVE)
|
||||
}
|
||||
.unwrap();
|
||||
let shadow = if source_capture && shell_type == ShellType::Zsh {
|
||||
"alias alias=false\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let output = Command::new(shell)
|
||||
.args(["-c", &format!("{setup}{capture}")])
|
||||
.args(["-c", &format!("{setup}{shadow}{capture}")])
|
||||
.output()?;
|
||||
assert!(output.status.success(), "{shell}: {output:?}");
|
||||
let captured = CapturedSnapshot::parse(shell_type, &output.stdout).unwrap();
|
||||
let path = dir.path().join("state");
|
||||
std::fs::write(&path, captured.render_state())?;
|
||||
let restore = if source {
|
||||
let restore = if source_replay {
|
||||
". \"$1\""
|
||||
} else {
|
||||
"eval \"$(cat \"$1\")\""
|
||||
};
|
||||
let command = format!("{restore}\n\\alias snapshot_alias\nhelper\n");
|
||||
let command =
|
||||
format!("{restore}\n\\alias snapshot_alias\neval snapshot_alias\nhelper\n");
|
||||
let output = Command::new(shell)
|
||||
.args(["-c", &command, "snapshot-test"])
|
||||
.arg(&path)
|
||||
.output()?;
|
||||
assert!(output.status.success(), "{shell}: {output:?}");
|
||||
assert!(String::from_utf8_lossy(&output.stdout).contains("snapshot_alias="));
|
||||
assert!(String::from_utf8_lossy(&output.stdout).ends_with(expected));
|
||||
results.push((output.stdout, output.stderr));
|
||||
}
|
||||
assert_eq!(results[0], results[1], "{shell}");
|
||||
assert_eq!(results[0], results[2], "{shell}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user