mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
This PR introduces a `codex-utils-cargo-bin` utility crate that wraps/replaces our use of `assert_cmd::Command` and `escargot::CargoBuild`. As you can infer from the introduction of `buck_project_root()` in this PR, I am attempting to make it possible to build Codex under [Buck2](https://buck2.build) as well as `cargo`. With Buck2, I hope to achieve faster incremental local builds (largely due to Buck2's [dice](https://buck2.build/docs/insights_and_knowledge/modern_dice/) build strategy, as well as benefits from its local build daemon) as well as faster CI builds if we invest in remote execution and caching. See https://buck2.build/docs/getting_started/what_is_buck2/#why-use-buck2-key-advantages for more details about the performance advantages of Buck2. Buck2 enforces stronger requirements in terms of build and test isolation. It discourages assumptions about absolute paths (which is key to enabling remote execution). Because the `CARGO_BIN_EXE_*` environment variables that Cargo provides are absolute paths (which `assert_cmd::Command` reads), this is a problem for Buck2, which is why we need this `codex-utils-cargo-bin` utility. My WIP-Buck2 setup sets the `CARGO_BIN_EXE_*` environment variables passed to a `rust_test()` build rule as relative paths. `codex-utils-cargo-bin` will resolve these values to absolute paths, when necessary. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8496). * #8498 * __->__ #8496
166 lines
5.2 KiB
Rust
166 lines
5.2 KiB
Rust
use std::path::Path;
|
|
|
|
use anyhow::Result;
|
|
use codex_core::config::edit::ConfigEditsBuilder;
|
|
use codex_core::config::load_global_mcp_servers;
|
|
use codex_core::config::types::McpServerTransportConfig;
|
|
use predicates::prelude::PredicateBooleanExt;
|
|
use predicates::str::contains;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::Value as JsonValue;
|
|
use serde_json::json;
|
|
use tempfile::TempDir;
|
|
|
|
fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
|
|
let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
|
|
cmd.env("CODEX_HOME", codex_home);
|
|
Ok(cmd)
|
|
}
|
|
|
|
#[test]
|
|
fn list_shows_empty_state() -> Result<()> {
|
|
let codex_home = TempDir::new()?;
|
|
|
|
let mut cmd = codex_command(codex_home.path())?;
|
|
let output = cmd.args(["mcp", "list"]).output()?;
|
|
assert!(output.status.success());
|
|
let stdout = String::from_utf8(output.stdout)?;
|
|
assert!(stdout.contains("No MCP servers configured yet."));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn list_and_get_render_expected_output() -> Result<()> {
|
|
let codex_home = TempDir::new()?;
|
|
|
|
let mut add = codex_command(codex_home.path())?;
|
|
add.args([
|
|
"mcp",
|
|
"add",
|
|
"docs",
|
|
"--env",
|
|
"TOKEN=secret",
|
|
"--",
|
|
"docs-server",
|
|
"--port",
|
|
"4000",
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
let mut servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
let docs_entry = servers
|
|
.get_mut("docs")
|
|
.expect("docs server should exist after add");
|
|
match &mut docs_entry.transport {
|
|
McpServerTransportConfig::Stdio { env_vars, .. } => {
|
|
*env_vars = vec!["APP_TOKEN".to_string(), "WORKSPACE_ID".to_string()];
|
|
}
|
|
other => panic!("unexpected transport: {other:?}"),
|
|
}
|
|
ConfigEditsBuilder::new(codex_home.path())
|
|
.replace_mcp_servers(&servers)
|
|
.apply_blocking()?;
|
|
|
|
let mut list_cmd = codex_command(codex_home.path())?;
|
|
let list_output = list_cmd.args(["mcp", "list"]).output()?;
|
|
assert!(list_output.status.success());
|
|
let stdout = String::from_utf8(list_output.stdout)?;
|
|
assert!(stdout.contains("Name"));
|
|
assert!(stdout.contains("docs"));
|
|
assert!(stdout.contains("docs-server"));
|
|
assert!(stdout.contains("TOKEN=*****"));
|
|
assert!(stdout.contains("APP_TOKEN=*****"));
|
|
assert!(stdout.contains("WORKSPACE_ID=*****"));
|
|
assert!(stdout.contains("Status"));
|
|
assert!(stdout.contains("Auth"));
|
|
assert!(stdout.contains("enabled"));
|
|
assert!(stdout.contains("Unsupported"));
|
|
|
|
let mut list_json_cmd = codex_command(codex_home.path())?;
|
|
let json_output = list_json_cmd.args(["mcp", "list", "--json"]).output()?;
|
|
assert!(json_output.status.success());
|
|
let stdout = String::from_utf8(json_output.stdout)?;
|
|
let parsed: JsonValue = serde_json::from_str(&stdout)?;
|
|
assert_eq!(
|
|
parsed,
|
|
json!([
|
|
{
|
|
"name": "docs",
|
|
"enabled": true,
|
|
"transport": {
|
|
"type": "stdio",
|
|
"command": "docs-server",
|
|
"args": [
|
|
"--port",
|
|
"4000"
|
|
],
|
|
"env": {
|
|
"TOKEN": "secret"
|
|
},
|
|
"env_vars": [
|
|
"APP_TOKEN",
|
|
"WORKSPACE_ID"
|
|
],
|
|
"cwd": null
|
|
},
|
|
"startup_timeout_sec": null,
|
|
"tool_timeout_sec": null,
|
|
"auth_status": "unsupported"
|
|
}
|
|
]
|
|
)
|
|
);
|
|
|
|
let mut get_cmd = codex_command(codex_home.path())?;
|
|
let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?;
|
|
assert!(get_output.status.success());
|
|
let stdout = String::from_utf8(get_output.stdout)?;
|
|
assert!(stdout.contains("docs"));
|
|
assert!(stdout.contains("transport: stdio"));
|
|
assert!(stdout.contains("command: docs-server"));
|
|
assert!(stdout.contains("args: --port 4000"));
|
|
assert!(stdout.contains("env: TOKEN=*****"));
|
|
assert!(stdout.contains("APP_TOKEN=*****"));
|
|
assert!(stdout.contains("WORKSPACE_ID=*****"));
|
|
assert!(stdout.contains("enabled: true"));
|
|
assert!(stdout.contains("remove: codex mcp remove docs"));
|
|
|
|
let mut get_json_cmd = codex_command(codex_home.path())?;
|
|
get_json_cmd
|
|
.args(["mcp", "get", "docs", "--json"])
|
|
.assert()
|
|
.success()
|
|
.stdout(contains("\"name\": \"docs\"").and(contains("\"enabled\": true")));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_disabled_server_shows_single_line() -> Result<()> {
|
|
let codex_home = TempDir::new()?;
|
|
|
|
let mut add = codex_command(codex_home.path())?;
|
|
add.args(["mcp", "add", "docs", "--", "docs-server"])
|
|
.assert()
|
|
.success();
|
|
|
|
let mut servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
let docs = servers
|
|
.get_mut("docs")
|
|
.expect("docs server should exist after add");
|
|
docs.enabled = false;
|
|
ConfigEditsBuilder::new(codex_home.path())
|
|
.replace_mcp_servers(&servers)
|
|
.apply_blocking()?;
|
|
|
|
let mut get_cmd = codex_command(codex_home.path())?;
|
|
let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?;
|
|
assert!(get_output.status.success());
|
|
let stdout = String::from_utf8(get_output.stdout)?;
|
|
assert_eq!(stdout.trim_end(), "docs (disabled)");
|
|
|
|
Ok(())
|
|
}
|