Compare commits

...

18 Commits

Author SHA1 Message Date
starr-openai
9bcfa8d352 Fix app-server analytics clippy warning
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 13:30:16 -07:00
starr-openai
e9ce436101 Limit remote apply_patch CLI coverage 2026-04-14 13:20:22 -07:00
starr-openai
b2d7ebd073 codex: fix app-server analytics compile error
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:41:13 -07:00
starr-openai
43b83b35f0 Restore test cwd field compatibility
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:28 -07:00
starr-openai
dd0837c15c Use configured cwd in remote test helpers
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:28 -07:00
starr-openai
27aaabe8d9 Bound remote exec-server cleanup in tests
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
3b298307b1 Wait for remote exec-server shutdown before cleanup
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
887c09d1be Keep test tempdirs alive through teardown
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
562dbc34f8 Fix remote test harness drop order
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
e212728b6e Fix remote test harness path staging move
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
d013bf0121 Fix remote core test teardown ordering
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:27 -07:00
starr-openai
9e054409c2 Decouple core test helper from sandboxing crate\n\nCo-authored-by: Codex <noreply@openai.com> 2026-04-14 12:16:26 -07:00
starr-openai
66d02bd8b1 Fix core test helper import\n\nCo-authored-by: Codex <noreply@openai.com> 2026-04-14 12:16:26 -07:00
starr-openai
def5e0f9a3 Fix remote helper constant import
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:26 -07:00
starr-openai
0b6ba8dbf9 Route core remote tests through staged helper binaries
Override codex_self_exe and codex_linux_sandbox_exe in the remote-aware core harness so sandboxed exec requests target the binaries copied into the remote container.

Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:26 -07:00
starr-openai
277d70913e Stabilize exec-process PTY stdin test
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:26 -07:00
starr-openai
c2ea13e30f Restore remote exec-server filesystem coverage
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:26 -07:00
starr-openai
d7f435bb60 Fix remote apply-patch helper resolution
Co-authored-by: Codex <noreply@openai.com>
2026-04-14 12:16:26 -07:00
6 changed files with 319 additions and 39 deletions

View File

@@ -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(),
);

View File

@@ -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()?;

View File

@@ -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 {

View File

@@ -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)]

View File

@@ -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"),

View File

@@ -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());