mirror of
https://github.com/openai/codex.git
synced 2026-05-01 18:06:47 +00:00
Compare commits
18 Commits
automation
...
starr/appl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bcfa8d352 | ||
|
|
e9ce436101 | ||
|
|
b2d7ebd073 | ||
|
|
43b83b35f0 | ||
|
|
dd0837c15c | ||
|
|
27aaabe8d9 | ||
|
|
3b298307b1 | ||
|
|
887c09d1be | ||
|
|
562dbc34f8 | ||
|
|
e212728b6e | ||
|
|
d013bf0121 | ||
|
|
9e054409c2 | ||
|
|
66d02bd8b1 | ||
|
|
def5e0f9a3 | ||
|
|
0b6ba8dbf9 | ||
|
|
277d70913e | ||
|
|
c2ea13e30f | ||
|
|
d7f435bb60 |
@@ -751,7 +751,7 @@ impl MessageProcessor {
|
||||
| ClientRequest::TurnSteer { request_id, .. } = &codex_request
|
||||
{
|
||||
self.analytics_events_client.track_request(
|
||||
connection_id.0,
|
||||
connection_request_id.connection_id.0,
|
||||
request_id.clone(),
|
||||
codex_request.clone(),
|
||||
);
|
||||
|
||||
@@ -207,14 +207,34 @@ fn linux_sandbox_exe_path(
|
||||
path_entry_guard: Option<&Arg0PathEntryGuard>,
|
||||
current_exe: Option<PathBuf>,
|
||||
) -> Option<PathBuf> {
|
||||
// Prefer the `codex-linux-sandbox` alias when available so callers can
|
||||
// re-exec through a path whose basename still triggers arg0 dispatch on
|
||||
if let Some(current_exe) = current_exe.as_ref() {
|
||||
let sibling_helper = helper_target_for_alias(current_exe, CODEX_LINUX_SANDBOX_ARG0);
|
||||
if sibling_helper != *current_exe {
|
||||
return Some(sibling_helper);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to the `codex-linux-sandbox` alias when no standalone helper
|
||||
// binary exists so callers can still re-exec through argv0 dispatch on
|
||||
// bubblewrap builds that do not support `--argv0`.
|
||||
path_entry_guard
|
||||
.and_then(|path_entry| path_entry.paths().codex_linux_sandbox_exe.clone())
|
||||
.or(current_exe)
|
||||
}
|
||||
|
||||
fn helper_target_for_alias(exe: &Path, filename: &str) -> PathBuf {
|
||||
if filename == CODEX_LINUX_SANDBOX_ARG0
|
||||
&& let Some(parent) = exe.parent()
|
||||
{
|
||||
let sibling = parent.join(CODEX_LINUX_SANDBOX_ARG0);
|
||||
if sibling.is_file() {
|
||||
return sibling;
|
||||
}
|
||||
}
|
||||
|
||||
exe.to_path_buf()
|
||||
}
|
||||
|
||||
fn build_runtime() -> anyhow::Result<tokio::runtime::Runtime> {
|
||||
let mut builder = tokio::runtime::Builder::new_multi_thread();
|
||||
builder.enable_all();
|
||||
@@ -320,17 +340,18 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
|
||||
EXECVE_WRAPPER_ARG0,
|
||||
] {
|
||||
let exe = std::env::current_exe()?;
|
||||
let target = helper_target_for_alias(&exe, filename);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let link = path.join(filename);
|
||||
symlink(&exe, &link)?;
|
||||
symlink(&target, &link)?;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let batch_script = path.join(format!("{filename}.bat"));
|
||||
let exe = exe.display();
|
||||
let exe = target.display();
|
||||
std::fs::write(
|
||||
&batch_script,
|
||||
format!(
|
||||
@@ -440,6 +461,7 @@ mod tests {
|
||||
use super::Arg0DispatchPaths;
|
||||
use super::Arg0PathEntryGuard;
|
||||
use super::LOCK_FILENAME;
|
||||
use super::helper_target_for_alias;
|
||||
use super::janitor_cleanup;
|
||||
use super::linux_sandbox_exe_path;
|
||||
use std::fs;
|
||||
@@ -459,7 +481,36 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linux_sandbox_exe_path_prefers_codex_linux_sandbox_alias() -> std::io::Result<()> {
|
||||
fn linux_sandbox_exe_path_prefers_sibling_helper_over_alias() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let exe = temp_dir.path().join("codex");
|
||||
let sandbox = temp_dir.path().join("codex-linux-sandbox");
|
||||
fs::write(&exe, [])?;
|
||||
fs::write(&sandbox, [])?;
|
||||
|
||||
let alias_dir = TempDir::new()?;
|
||||
let lock_file = create_lock(alias_dir.path())?;
|
||||
let alias_path = alias_dir.path().join("codex-linux-sandbox");
|
||||
let path_entry = Arg0PathEntryGuard::new(
|
||||
alias_dir,
|
||||
lock_file,
|
||||
Arg0DispatchPaths {
|
||||
codex_self_exe: Some(exe.clone()),
|
||||
codex_linux_sandbox_exe: Some(alias_path),
|
||||
main_execve_wrapper_exe: None,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
linux_sandbox_exe_path(Some(&path_entry), Some(exe)),
|
||||
Some(sandbox)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linux_sandbox_exe_path_prefers_codex_linux_sandbox_alias_without_sibling_helper()
|
||||
-> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let lock_file = create_lock(temp_dir.path())?;
|
||||
let alias_path = temp_dir.path().join("codex-linux-sandbox");
|
||||
@@ -480,6 +531,31 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_target_for_alias_prefers_sibling_linux_sandbox_binary() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let exe = temp_dir.path().join("codex");
|
||||
let sandbox = temp_dir.path().join("codex-linux-sandbox");
|
||||
fs::write(&exe, [])?;
|
||||
fs::write(&sandbox, [])?;
|
||||
|
||||
assert_eq!(
|
||||
helper_target_for_alias(&exe, super::CODEX_LINUX_SANDBOX_ARG0),
|
||||
sandbox
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_target_for_alias_keeps_main_binary_for_other_aliases() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let exe = temp_dir.path().join("codex");
|
||||
fs::write(&exe, [])?;
|
||||
|
||||
assert_eq!(helper_target_for_alias(&exe, "apply_patch"), exe);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn janitor_skips_dirs_without_lock_file() -> std::io::Result<()> {
|
||||
let root = tempfile::tempdir()?;
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::mem::swap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -64,6 +65,8 @@ type WorkspaceSetup = dyn FnOnce(AbsolutePathBuf, Arc<dyn ExecutorFileSystem>) -
|
||||
const TEST_MODEL_WITH_EXPERIMENTAL_TOOLS: &str = "test-gpt-5.1-codex";
|
||||
const REMOTE_EXEC_SERVER_START_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const REMOTE_EXEC_SERVER_POLL_INTERVAL: Duration = Duration::from_millis(25);
|
||||
const REMOTE_EXEC_SERVER_CLEANUP_TIMEOUT: Duration = Duration::from_secs(7);
|
||||
const REMOTE_CODEX_LINUX_SANDBOX_EXE: &str = "codex-linux-sandbox";
|
||||
static REMOTE_EXEC_SERVER_INSTANCE_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -84,13 +87,34 @@ impl Drop for RemoteExecServerProcess {
|
||||
format!("rm -rf {cleanup_paths}; ")
|
||||
};
|
||||
let script = format!(
|
||||
"if kill -0 {pid} 2>/dev/null; then kill {pid}; fi; {cleanup_paths_script}rm -f {remote_exec_server_path} {stdout_path}",
|
||||
"if kill -0 {pid} 2>/dev/null; then \
|
||||
kill {pid} 2>/dev/null || true; \
|
||||
for _ in $(seq 1 50); do \
|
||||
if ! kill -0 {pid} 2>/dev/null; then \
|
||||
break; \
|
||||
fi; \
|
||||
sleep 0.1; \
|
||||
done; \
|
||||
if kill -0 {pid} 2>/dev/null; then \
|
||||
kill -9 {pid} 2>/dev/null || true; \
|
||||
for _ in $(seq 1 10); do \
|
||||
if ! kill -0 {pid} 2>/dev/null; then \
|
||||
break; \
|
||||
fi; \
|
||||
sleep 0.1; \
|
||||
done; \
|
||||
fi; \
|
||||
fi; \
|
||||
{cleanup_paths_script}rm -f {remote_exec_server_path} {stdout_path}",
|
||||
pid = self.pid,
|
||||
cleanup_paths_script = cleanup_paths_script,
|
||||
remote_exec_server_path = self.remote_exec_server_path,
|
||||
stdout_path = self.stdout_path
|
||||
);
|
||||
let _ = docker_command_capture_stdout(["exec", &self.container_name, "sh", "-lc", &script]);
|
||||
let _ = docker_command_capture_stdout_with_timeout(
|
||||
["exec", &self.container_name, "sh", "-lc", &script],
|
||||
REMOTE_EXEC_SERVER_CLEANUP_TIMEOUT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +129,9 @@ pub struct TestEnv {
|
||||
environment: codex_exec_server::Environment,
|
||||
cwd: AbsolutePathBuf,
|
||||
local_cwd_temp_dir: Option<Arc<TempDir>>,
|
||||
remote_codex_self_exe: Option<PathBuf>,
|
||||
remote_codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
// Keep the remote exec-server process alive until the environment/client has dropped.
|
||||
_remote_exec_server_process: Option<RemoteExecServerProcess>,
|
||||
}
|
||||
|
||||
@@ -117,6 +144,8 @@ impl TestEnv {
|
||||
environment,
|
||||
cwd,
|
||||
local_cwd_temp_dir: Some(local_cwd_temp_dir),
|
||||
remote_codex_self_exe: None,
|
||||
remote_codex_linux_sandbox_exe: None,
|
||||
_remote_exec_server_process: None,
|
||||
})
|
||||
}
|
||||
@@ -136,6 +165,14 @@ impl TestEnv {
|
||||
fn local_cwd_temp_dir(&self) -> Option<Arc<TempDir>> {
|
||||
self.local_cwd_temp_dir.clone()
|
||||
}
|
||||
|
||||
fn remote_codex_self_exe(&self) -> Option<&PathBuf> {
|
||||
self.remote_codex_self_exe.as_ref()
|
||||
}
|
||||
|
||||
fn remote_codex_linux_sandbox_exe(&self) -> Option<&PathBuf> {
|
||||
self.remote_codex_linux_sandbox_exe.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_env() -> Result<TestEnv> {
|
||||
@@ -155,10 +192,18 @@ pub async fn test_env() -> Result<TestEnv> {
|
||||
)
|
||||
.await?;
|
||||
remote_process.process.register_cleanup_path(cwd.as_path());
|
||||
let remote_codex_self_exe =
|
||||
PathBuf::from(&remote_process.process.remote_exec_server_path);
|
||||
let remote_codex_linux_sandbox_exe = remote_codex_self_exe
|
||||
.parent()
|
||||
.expect("remote exec-server path should have a parent")
|
||||
.join(REMOTE_CODEX_LINUX_SANDBOX_EXE);
|
||||
Ok(TestEnv {
|
||||
environment,
|
||||
cwd,
|
||||
local_cwd_temp_dir: None,
|
||||
remote_codex_self_exe: Some(remote_codex_self_exe),
|
||||
remote_codex_linux_sandbox_exe: Some(remote_codex_linux_sandbox_exe),
|
||||
_remote_exec_server_process: Some(remote_process.process),
|
||||
})
|
||||
}
|
||||
@@ -174,8 +219,9 @@ struct RemoteExecServerStart {
|
||||
fn start_remote_exec_server(remote_env: &RemoteEnvConfig) -> Result<RemoteExecServerStart> {
|
||||
let container_name = remote_env.container_name.as_str();
|
||||
let instance_id = remote_exec_server_instance_id();
|
||||
let remote_exec_server_path = format!("/tmp/codex-{instance_id}");
|
||||
let remote_linux_sandbox_path = format!("/tmp/codex-linux-sandbox-{instance_id}");
|
||||
let remote_bin_dir = format!("/tmp/codex-bin-{instance_id}");
|
||||
let remote_exec_server_path = format!("{remote_bin_dir}/codex");
|
||||
let remote_linux_sandbox_path = format!("{remote_bin_dir}/codex-linux-sandbox");
|
||||
let stdout_path = format!("/tmp/codex-exec-server-{instance_id}.stdout");
|
||||
let local_binary = codex_utils_cargo_bin::cargo_bin("codex").context("resolve codex binary")?;
|
||||
let local_linux_sandbox = codex_utils_cargo_bin::cargo_bin("codex-linux-sandbox")
|
||||
@@ -185,6 +231,7 @@ fn start_remote_exec_server(remote_env: &RemoteEnvConfig) -> Result<RemoteExecSe
|
||||
let remote_binary = format!("{container_name}:{remote_exec_server_path}");
|
||||
let remote_linux_sandbox = format!("{container_name}:{remote_linux_sandbox_path}");
|
||||
|
||||
docker_command_success(["exec", container_name, "mkdir", "-p", &remote_bin_dir])?;
|
||||
docker_command_success(["cp", &local_binary, &remote_binary])?;
|
||||
docker_command_success(["cp", &local_linux_sandbox, &remote_linux_sandbox])?;
|
||||
docker_command_success([
|
||||
@@ -223,7 +270,7 @@ echo $!"
|
||||
pid,
|
||||
remote_exec_server_path,
|
||||
stdout_path,
|
||||
cleanup_paths: vec![remote_linux_sandbox_path],
|
||||
cleanup_paths: vec![remote_bin_dir],
|
||||
},
|
||||
listen_url,
|
||||
})
|
||||
@@ -348,6 +395,46 @@ fn docker_command_capture_stdout<const N: usize>(args: [&str; N]) -> Result<Stri
|
||||
String::from_utf8(output.stdout).context("docker stdout must be utf-8")
|
||||
}
|
||||
|
||||
fn docker_command_capture_stdout_with_timeout<const N: usize>(
|
||||
args: [&str; N],
|
||||
timeout: Duration,
|
||||
) -> Result<String> {
|
||||
let mut child = Command::new("docker")
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| format!("run docker {args:?}"))?;
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
if child.try_wait()?.is_some() {
|
||||
let output = child.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"docker {:?} failed: stdout={} stderr={}",
|
||||
args,
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
));
|
||||
}
|
||||
return String::from_utf8(output.stdout).context("docker stdout must be utf-8");
|
||||
}
|
||||
|
||||
if Instant::now() >= deadline {
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output()?;
|
||||
return Err(anyhow!(
|
||||
"docker {:?} timed out after {timeout:?}: stdout={} stderr={}",
|
||||
args,
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
));
|
||||
}
|
||||
|
||||
std::thread::sleep(REMOTE_EXEC_SERVER_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of different ways the model can output an apply_patch call
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ApplyPatchModelOutput {
|
||||
@@ -517,9 +604,15 @@ impl TestCodexBuilder {
|
||||
resume_from: Option<PathBuf>,
|
||||
test_env: TestEnv,
|
||||
) -> anyhow::Result<TestCodex> {
|
||||
let (config, fallback_cwd) = self
|
||||
let (mut config, fallback_cwd) = self
|
||||
.prepare_config(base_url, &home, test_env.cwd().clone())
|
||||
.await?;
|
||||
if let Some(path) = test_env.remote_codex_self_exe() {
|
||||
config.codex_self_exe = Some(path.clone());
|
||||
}
|
||||
if let Some(path) = test_env.remote_codex_linux_sandbox_exe() {
|
||||
config.codex_linux_sandbox_exe = Some(path.clone());
|
||||
}
|
||||
let environment_manager = Arc::new(codex_exec_server::EnvironmentManager::new(
|
||||
test_env.exec_server_url().map(str::to_owned),
|
||||
));
|
||||
@@ -609,12 +702,12 @@ impl TestCodexBuilder {
|
||||
};
|
||||
|
||||
Ok(TestCodex {
|
||||
home,
|
||||
cwd,
|
||||
config,
|
||||
codex: new_conversation.thread,
|
||||
session_configured: new_conversation.session_configured,
|
||||
config,
|
||||
thread_manager,
|
||||
home,
|
||||
cwd,
|
||||
_test_env: test_env,
|
||||
})
|
||||
}
|
||||
@@ -699,18 +792,21 @@ fn ensure_test_model_catalog(config: &mut Config) -> Result<()> {
|
||||
}
|
||||
|
||||
pub struct TestCodex {
|
||||
pub home: Arc<TempDir>,
|
||||
pub cwd: Arc<TempDir>,
|
||||
pub codex: Arc<CodexThread>,
|
||||
pub session_configured: SessionConfiguredEvent,
|
||||
pub config: Config,
|
||||
pub thread_manager: Arc<ThreadManager>,
|
||||
pub home: Arc<TempDir>,
|
||||
// Retain the local tempdir backing the workspace for callers that still
|
||||
// need direct filesystem access in local tests.
|
||||
pub cwd: Arc<TempDir>,
|
||||
// Drop the execution environment after the thread manager and conversation teardown.
|
||||
_test_env: TestEnv,
|
||||
}
|
||||
|
||||
impl TestCodex {
|
||||
pub fn cwd_path(&self) -> &Path {
|
||||
self.cwd.path()
|
||||
self.config.cwd.as_path()
|
||||
}
|
||||
|
||||
pub fn codex_home_path(&self) -> &Path {
|
||||
@@ -718,7 +814,7 @@ impl TestCodex {
|
||||
}
|
||||
|
||||
pub fn workspace_path(&self, rel: impl AsRef<Path>) -> PathBuf {
|
||||
self.cwd_path().join(rel)
|
||||
self.config.cwd.join(rel).into_path_buf()
|
||||
}
|
||||
|
||||
pub fn executor_environment(&self) -> &TestEnv {
|
||||
@@ -819,8 +915,8 @@ impl TestCodex {
|
||||
}
|
||||
|
||||
pub struct TestCodexHarness {
|
||||
server: MockServer,
|
||||
test: TestCodex,
|
||||
server: MockServer,
|
||||
}
|
||||
|
||||
impl TestCodexHarness {
|
||||
@@ -835,13 +931,13 @@ impl TestCodexHarness {
|
||||
pub async fn with_builder(mut builder: TestCodexBuilder) -> Result<Self> {
|
||||
let server = start_mock_server().await;
|
||||
let test = builder.build(&server).await?;
|
||||
Ok(Self { server, test })
|
||||
Ok(Self { test, server })
|
||||
}
|
||||
|
||||
pub async fn with_remote_aware_builder(mut builder: TestCodexBuilder) -> Result<Self> {
|
||||
let server = start_mock_server().await;
|
||||
let test = builder.build_remote_aware(&server).await?;
|
||||
Ok(Self { server, test })
|
||||
Ok(Self { test, server })
|
||||
}
|
||||
|
||||
pub fn server(&self) -> &MockServer {
|
||||
|
||||
@@ -21,6 +21,7 @@ use codex_protocol::user_input::UserInput;
|
||||
#[cfg(target_os = "linux")]
|
||||
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use core_test_support::assert_regex_match;
|
||||
use core_test_support::get_remote_test_env;
|
||||
use core_test_support::responses::ev_apply_patch_function_call;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -56,7 +57,7 @@ async fn apply_patch_harness_with(
|
||||
});
|
||||
// Box harness construction so apply_patch_cli tests do not inline the
|
||||
// full test-thread startup path into each test future.
|
||||
Box::pin(TestCodexHarness::with_remote_aware_builder(builder)).await
|
||||
Box::pin(TestCodexHarness::with_builder(builder)).await
|
||||
}
|
||||
|
||||
pub async fn mount_apply_patch(
|
||||
@@ -136,6 +137,44 @@ async fn apply_patch_cli_uses_codex_self_exe_with_linux_sandbox_helper_alias() -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn apply_patch_cli_updates_remote_workspace_when_configured() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let Some(_remote_env) = get_remote_test_env() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let builder = test_codex().with_config(|config| {
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let harness = Box::pin(TestCodexHarness::with_remote_aware_builder(builder)).await?;
|
||||
harness.write_file("remote.txt", "before\n").await?;
|
||||
|
||||
let patch = "*** Begin Patch\n*** Update File: remote.txt\n@@\n-before\n+after\n*** End Patch";
|
||||
let call_id = "apply-remote";
|
||||
mount_apply_patch(
|
||||
&harness,
|
||||
call_id,
|
||||
patch,
|
||||
"done",
|
||||
ApplyPatchModelOutput::Function,
|
||||
)
|
||||
.await;
|
||||
|
||||
harness.submit("please apply remote patch").await?;
|
||||
|
||||
let out = harness
|
||||
.apply_patch_output(call_id, ApplyPatchModelOutput::Function)
|
||||
.await;
|
||||
assert_regex_match(
|
||||
r"(?s)^Exit code: 0.*Success\. Updated the following files:\nM remote\.txt\n?$",
|
||||
&out,
|
||||
);
|
||||
assert_eq!(harness.read_file_text("remote.txt").await?, "after\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[test_case(ApplyPatchModelOutput::Freeform)]
|
||||
#[test_case(ApplyPatchModelOutput::Function)]
|
||||
|
||||
@@ -155,7 +155,7 @@ async fn assert_exec_process_write_then_read(use_remote: bool) -> Result<()> {
|
||||
argv: vec![
|
||||
"/usr/bin/python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"import sys; line = sys.stdin.readline(); sys.stdout.write(f'from-stdin:{line}'); sys.stdout.flush()".to_string(),
|
||||
"import sys; sys.stdout.write('ready\\n'); sys.stdout.flush(); line = sys.stdin.readline(); sys.stdout.write(f'from-stdin:{line}'); sys.stdout.flush()".to_string(),
|
||||
],
|
||||
cwd: std::env::current_dir()?,
|
||||
env_policy: /*env_policy*/ None,
|
||||
@@ -166,11 +166,67 @@ async fn assert_exec_process_write_then_read(use_remote: bool) -> Result<()> {
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
session.process.write(b"hello\n".to_vec()).await?;
|
||||
let StartedExecProcess { process } = session;
|
||||
let wake_rx = process.subscribe_wake();
|
||||
let (output, exit_code, closed) = collect_process_output_from_reads(process, wake_rx).await?;
|
||||
let mut output = String::new();
|
||||
let mut after_seq = None;
|
||||
let ready_deadline = tokio::time::Instant::now() + Duration::from_secs(5);
|
||||
while tokio::time::Instant::now() < ready_deadline {
|
||||
let response = process
|
||||
.read(
|
||||
after_seq,
|
||||
/*max_bytes*/ None,
|
||||
/*wait_ms*/ Some(250),
|
||||
)
|
||||
.await?;
|
||||
if let Some(message) = response.failure {
|
||||
anyhow::bail!("process failed before ready state: {message}");
|
||||
}
|
||||
for chunk in response.chunks {
|
||||
output.push_str(&String::from_utf8_lossy(&chunk.chunk.into_inner()));
|
||||
after_seq = Some(chunk.seq);
|
||||
}
|
||||
assert!(
|
||||
!response.closed,
|
||||
"process closed before signaling readiness: {output:?}"
|
||||
);
|
||||
if output.contains("ready") {
|
||||
break;
|
||||
}
|
||||
after_seq = response.next_seq.checked_sub(1).or(after_seq);
|
||||
}
|
||||
assert!(
|
||||
output.contains("ready"),
|
||||
"process did not signal readiness: {output:?}"
|
||||
);
|
||||
|
||||
process.write(b"hello\n".to_vec()).await?;
|
||||
let mut exit_code = None;
|
||||
let mut closed = false;
|
||||
let result_deadline = tokio::time::Instant::now() + Duration::from_secs(5);
|
||||
while tokio::time::Instant::now() < result_deadline {
|
||||
let response = process
|
||||
.read(
|
||||
after_seq,
|
||||
/*max_bytes*/ None,
|
||||
/*wait_ms*/ Some(250),
|
||||
)
|
||||
.await?;
|
||||
if let Some(message) = response.failure {
|
||||
anyhow::bail!("process failed after stdin write: {message}");
|
||||
}
|
||||
for chunk in response.chunks {
|
||||
output.push_str(&String::from_utf8_lossy(&chunk.chunk.into_inner()));
|
||||
after_seq = Some(chunk.seq);
|
||||
}
|
||||
if response.exited {
|
||||
exit_code = response.exit_code;
|
||||
}
|
||||
if response.closed {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
after_seq = response.next_seq.checked_sub(1).or(after_seq);
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.contains("from-stdin:hello"),
|
||||
|
||||
@@ -322,9 +322,11 @@ async fn file_system_copy_rejects_directory_without_recursive(use_remote: bool)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_sandboxed_read_allows_readable_root() -> Result<()> {
|
||||
let context = create_file_system_context(/*use_remote*/ false).await?;
|
||||
async fn file_system_sandboxed_read_allows_readable_root(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
let tmp = TempDir::new()?;
|
||||
@@ -336,7 +338,8 @@ async fn file_system_sandboxed_read_allows_readable_root() -> Result<()> {
|
||||
|
||||
let contents = file_system
|
||||
.read_file(&absolute_path(file_path), Some(&sandbox))
|
||||
.await?;
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
assert_eq!(contents, b"sandboxed hello");
|
||||
|
||||
Ok(())
|
||||
@@ -401,9 +404,13 @@ async fn file_system_sandboxed_read_rejects_symlink_escape(use_remote: bool) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_sandboxed_read_rejects_symlink_parent_dotdot_escape() -> Result<()> {
|
||||
let context = create_file_system_context(/*use_remote*/ false).await?;
|
||||
async fn file_system_sandboxed_read_rejects_symlink_parent_dotdot_escape(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
let tmp = TempDir::new()?;
|
||||
@@ -590,9 +597,11 @@ async fn file_system_copy_rejects_symlink_escape_destination(use_remote: bool) -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_remove_removes_symlink_not_target() -> Result<()> {
|
||||
let context = create_file_system_context(/*use_remote*/ false).await?;
|
||||
async fn file_system_remove_removes_symlink_not_target(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
let tmp = TempDir::new()?;
|
||||
@@ -615,7 +624,8 @@ async fn file_system_remove_removes_symlink_not_target() -> Result<()> {
|
||||
},
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
|
||||
assert!(!symlink_path.exists());
|
||||
assert!(outside_file.exists());
|
||||
@@ -624,9 +634,11 @@ async fn file_system_remove_removes_symlink_not_target() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
#[test_case(true ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_copy_preserves_symlink_source() -> Result<()> {
|
||||
let context = create_file_system_context(/*use_remote*/ false).await?;
|
||||
async fn file_system_copy_preserves_symlink_source(use_remote: bool) -> Result<()> {
|
||||
let context = create_file_system_context(use_remote).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
let tmp = TempDir::new()?;
|
||||
@@ -648,7 +660,8 @@ async fn file_system_copy_preserves_symlink_source() -> Result<()> {
|
||||
CopyOptions { recursive: false },
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.with_context(|| format!("mode={use_remote}"))?;
|
||||
|
||||
let copied_metadata = std::fs::symlink_metadata(&copied_symlink)?;
|
||||
assert!(copied_metadata.file_type().is_symlink());
|
||||
|
||||
Reference in New Issue
Block a user