Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -412,14 +412,6 @@ jobs:
- name: Install DotSlash
uses: facebook/install-dotslash@v2

- name: Pre-fetch DotSlash artifacts
# The Bash wrapper is not available on Windows.
if: ${{ !startsWith(matrix.runner, 'windows') }}
shell: bash
run: |
set -euo pipefail
dotslash -- fetch exec-server/tests/suite/bash

- uses: dtolnay/rust-toolchain@1.90
with:
targets: ${{ matrix.target }}
Expand Down
17 changes: 16 additions & 1 deletion codex-rs/exec-server/tests/common/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use std::sync::Arc;
use std::sync::Mutex;
use tokio::process::Command;

pub fn create_transport<P>(codex_home: P) -> anyhow::Result<TokioChildProcess>
pub async fn create_transport<P>(
codex_home: P,
dotslash_cache: P,
) -> anyhow::Result<TokioChildProcess>
where
P: AsRef<Path>,
{
Expand All @@ -36,11 +39,23 @@ where
.join("suite")
.join("bash");

// Need to ensure the artifact associated with the bash DotSlash file is
// available before it is run in a read-only sandbox.
let status = Command::new("dotslash")
.arg("--")
.arg("fetch")
.arg(bash.clone())
.env("DOTSLASH_CACHE", dotslash_cache.as_ref())
.status()
.await?;
assert!(status.success(), "dotslash fetch failed: {status:?}");

let transport =
TokioChildProcess::new(Command::new(mcp_executable.get_program()).configure(|cmd| {
cmd.arg("--bash").arg(bash);
cmd.arg("--execve").arg(execve_wrapper.get_program());
cmd.env("CODEX_HOME", codex_home.as_ref());
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());

// Important: pipe stdio so rmcp can speak JSON-RPC over stdin/stdout
cmd.stdin(Stdio::piped());
Expand Down
44 changes: 38 additions & 6 deletions codex-rs/exec-server/tests/suite/accept_elicitation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::borrow::Cow;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;

use anyhow::Context;
use anyhow::Result;
use anyhow::ensure;
use codex_exec_server::ExecResult;
use exec_server_test_support::InteractiveClient;
use exec_server_test_support::create_transport;
Expand All @@ -17,6 +20,7 @@ use rmcp::model::CallToolResult;
use rmcp::model::CreateElicitationRequestParam;
use rmcp::model::object;
use serde_json::json;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::fs::symlink;
use tempfile::TempDir;

Expand All @@ -42,7 +46,9 @@ prefix_rule(
codex_home.as_ref(),
)
.await?;
let transport = create_transport(codex_home.as_ref())?;
let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path();
let transport = create_transport(codex_home.as_ref(), dotslash_cache).await?;

// Create an MCP client that approves expected elicitation messages.
let project_root = TempDir::new()?;
Expand All @@ -68,11 +74,8 @@ prefix_rule(
let linux_sandbox_exe_folder = TempDir::new()?;
let codex_linux_sandbox_exe = if cfg!(target_os = "linux") {
let codex_linux_sandbox_exe = linux_sandbox_exe_folder.path().join("codex-linux-sandbox");
let codex_cli = assert_cmd::Command::cargo_bin("codex")?
.get_program()
.to_os_string();
let codex_cli_path = std::path::PathBuf::from(codex_cli);
symlink(&codex_cli_path, &codex_linux_sandbox_exe)?;
let codex_cli = ensure_codex_cli()?;
symlink(&codex_cli, &codex_linux_sandbox_exe)?;
Some(codex_linux_sandbox_exe)
} else {
None
Expand Down Expand Up @@ -129,3 +132,32 @@ prefix_rule(

Ok(())
}

fn ensure_codex_cli() -> Result<PathBuf> {
let codex_cli = PathBuf::from(
assert_cmd::Command::cargo_bin("codex")?
.get_program()
.to_os_string(),
);

let metadata = codex_cli.metadata().with_context(|| {
format!(
"failed to read metadata for codex binary at {}",
codex_cli.display()
)
})?;
ensure!(
metadata.is_file(),
"expected codex binary at {} to be a file; run `cargo build -p codex-cli --bin codex` before this test",
codex_cli.display()
);

let mode = metadata.permissions().mode();
ensure!(
mode & 0o111 != 0,
"codex binary at {} is not executable (mode {mode:o}); run `cargo build -p codex-cli --bin codex` before this test",
codex_cli.display()
);

Ok(codex_cli)
}
4 changes: 3 additions & 1 deletion codex-rs/exec-server/tests/suite/list_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ async fn list_tools() -> Result<()> {
policy_dir.join("default.codexpolicy"),
r#"prefix_rule(pattern=["ls"], decision="prompt")"#,
)?;
let transport = create_transport(codex_home.path())?;
let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path();
let transport = create_transport(codex_home.path(), dotslash_cache).await?;

let service = ().serve(transport).await?;
let tools = service.list_tools(Default::default()).await?.tools;
Expand Down
Loading