Run core integration tests against a Wine-backed Windows executor (#28401)

## Why

We want to exercise a linux app-server against a windows exec-server
without having to repeat every test case. This approach has slight
precedent in the remote docker test setup.

## What

Run the shared `codex-core` integration suite against Windows
exec-server behavior from Linux. This makes cross-OS path and shell
regressions visible while keeping unsupported cases owned by individual
tests.

- Add `local`, `docker`, and `wine-exec` test environment selection with
legacy Docker compatibility.
- Extend `codex_rust_crate` to generate a sharded Wine-exec variant
using a cross-built Windows server and pinned Bazel Wine/PowerShell
runtimes.
- Teach remote-aware helpers about Windows paths and track temporary
incompatibilities with source-local `skip_if_wine_exec!` calls and
follow-up reasons.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-15 17:38:41 -07:00
committed by GitHub
Unverified
parent d007b0852a
commit 1fe89de576
28 changed files with 789 additions and 157 deletions
+6 -3
View File
@@ -3,12 +3,15 @@ name: remote-tests
description: How to run tests using remote executor.
---
Some codex integration tests support a running against a remote executor.
This means that when CODEX_TEST_REMOTE_ENV environment variable is set they will attempt to start an executor process in a docker container CODEX_TEST_REMOTE_ENV points to and use it in tests.
Some Codex integration tests select `local`, `docker`, or `wine-exec` through
`CODEX_TEST_ENVIRONMENT`. The legacy `CODEX_TEST_REMOTE_ENV=<container>` still
selects Docker; otherwise execution is local.
Docker container is built and initialized via ./scripts/test-remote-env.sh
Currently running remote tests is only supported on Linux, so you need to use a devbox to run them
On x86-64 Linux, run Wine exec with
`bazel test //codex-rs/core:core-all-wine-exec-test --test_output=errors`.
Temporary blockers belong beside the test in `skip_if_wine_exec!` calls.
You can list devboxes via `applied_devbox ls`, pick the one with `codex` in the name.
Connect to devbox via `ssh <devbox_name>`.
@@ -343,7 +343,9 @@ jobs:
set -euo pipefail
export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME="codex-remote-test-env-${{ github.run_id }}-${{ matrix.shard }}"
source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh"
echo "CODEX_TEST_ENVIRONMENT=${CODEX_TEST_ENVIRONMENT}" >> "$GITHUB_ENV"
echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV"
echo "CODEX_TEST_REMOTE_ENV_CONTAINER_NAME=${CODEX_TEST_REMOTE_ENV_CONTAINER_NAME}" >> "$GITHUB_ENV"
echo "CODEX_TEST_REMOTE_EXEC_SERVER_URL=${CODEX_TEST_REMOTE_EXEC_SERVER_URL}" >> "$GITHUB_ENV"
- name: Download nextest archive
@@ -437,9 +439,9 @@ jobs:
run: |
set +e
if [[ "${STEPS_TEST_OUTCOME}" != "success" ]]; then
docker logs "${CODEX_TEST_REMOTE_ENV}" || true
docker logs "${CODEX_TEST_REMOTE_ENV_CONTAINER_NAME}" || true
fi
docker rm -f "${CODEX_TEST_REMOTE_ENV}" >/dev/null 2>&1 || true
docker rm -f "${CODEX_TEST_REMOTE_ENV_CONTAINER_NAME}" >/dev/null 2>&1 || true
env:
STEPS_TEST_OUTCOME: ${{ steps.test.outcome }}
-1
View File
@@ -2,5 +2,4 @@ package(default_visibility = ["//visibility:public"])
exports_files([
"foreign_platform_binary.bzl",
"wine.bzl",
])
+6 -1
View File
@@ -1,8 +1,13 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
load("//bazel/rules/testing:wine.bzl", "wine_rust_test")
load(":wine.bzl", "wine_rust_test")
package(default_visibility = ["//visibility:public"])
exports_files([
"wine.bzl",
"wine_runtime.bzl",
])
rust_library(
name = "wine_test_support",
testonly = True,
@@ -2,15 +2,8 @@
load("@rules_rust//rust:defs.bzl", "rust_test")
load("//:defs.bzl", "WINDOWS_GNULLVM_RUSTC_LINK_FLAGS")
load(":foreign_platform_binary.bzl", "foreign_platform_binary")
_WINE_RUNTIME_BINARIES = {
"pwsh": "@powershell_windows_x86_64//:pwsh",
"pwsh-runtime-marker": "@powershell_windows_x86_64//:runtime_marker",
"wine": "@wine_linux_x86_64//:wine",
"wine-runtime-marker": "@wine_linux_x86_64//:runtime_marker",
"wineserver": "@wine_linux_x86_64//:wineserver",
}
load("//bazel/rules/testing:foreign_platform_binary.bzl", "foreign_platform_binary")
load(":wine_runtime.bzl", "WINE_TEST_TARGET_COMPATIBLE_WITH", "wine_test_runtime")
def wine_rust_test(
name,
@@ -46,20 +39,10 @@ def wine_rust_test(
target_compatible_with: Additional compatibility constraints.
**kwargs: Remaining attributes forwarded to `rust_test`.
"""
binaries = dict(_WINE_RUNTIME_BINARIES)
runtime_data = [
"@powershell_windows_x86_64//:runtime",
"@wine_linux_x86_64//:runtime",
]
for binary_name in sorted(host_binaries.keys()):
if binary_name in binaries:
fail("host test binary name collides with test runtime: {}".format(binary_name))
binaries[binary_name] = host_binaries[binary_name]
binaries = dict(host_binaries)
for index, binary_name in enumerate(sorted(windows_binaries.keys())):
if binary_name in binaries:
fail("Windows test binary name collides with existing binary: {}".format(binary_name))
fail("Windows test binary name collides with host binary: {}".format(binary_name))
transitioned_binary = name + "-windows-binary-" + str(index)
foreign_platform_binary(
name = transitioned_binary,
@@ -76,17 +59,11 @@ def wine_rust_test(
)
binaries[binary_name] = ":" + transitioned_binary
runtime = wine_test_runtime(binaries)
rust_test(
name = name,
data = data + runtime_data + [binary for binary in binaries.values()],
env = {
"CARGO_BIN_EXE_{}".format(binary_name): "$(rlocationpath {})".format(binary)
for binary_name, binary in binaries.items()
},
target_compatible_with = target_compatible_with + [
"@llvm//constraints/libc:gnu.2.28",
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
data = data + runtime.data,
env = runtime.env,
target_compatible_with = target_compatible_with + WINE_TEST_TARGET_COMPATIBLE_WITH,
**kwargs
)
+40
View File
@@ -0,0 +1,40 @@
"""Runfiles shared by tests that execute Windows binaries through Wine."""
_WINE_RUNTIME_BINARIES = {
"pwsh": "@powershell_windows_x86_64//:pwsh",
"pwsh-runtime-marker": "@powershell_windows_x86_64//:runtime_marker",
"wine": "@wine_linux_x86_64//:wine",
"wine-runtime-marker": "@wine_linux_x86_64//:runtime_marker",
"wineserver": "@wine_linux_x86_64//:wineserver",
}
_WINE_RUNTIME_DATA = [
"@powershell_windows_x86_64//:runtime",
"@wine_linux_x86_64//:runtime",
]
WINE_TEST_TARGET_COMPATIBLE_WITH = [
"@llvm//constraints/libc:gnu.2.28",
"@platforms//cpu:x86_64",
"@platforms//os:linux",
]
def wine_test_runtime(test_binaries = {}):
"""Returns data and environment mappings for a Wine-backed test."""
binaries = dict(_WINE_RUNTIME_BINARIES)
for binary_name in sorted(test_binaries.keys()):
if binary_name in binaries:
fail("test binary name collides with Wine runtime: {}".format(binary_name))
binaries[binary_name] = test_binaries[binary_name]
return struct(
data = _WINE_RUNTIME_DATA + [binary for binary in binaries.values()],
env = {
"CARGO_BIN_EXE_{}".format(binary_name): "$(rlocationpath {})".format(binary)
for binary_name, binary in binaries.items()
},
runfile_env = {
binary_label: "CARGO_BIN_EXE_" + binary_name
for binary_name, binary_label in binaries.items()
},
)
+1
View File
@@ -22,6 +22,7 @@ codex_rust_crate(
"//codex-rs/windows-sandbox-rs:codex-windows-sandbox-setup",
],
integration_test_timeout = "long",
run_tests_with_wine_exec = True,
rustc_env = {
# Keep manifest-root path lookups inside the Bazel execroot for code
# that relies on env!("CARGO_MANIFEST_DIR").
+6
View File
@@ -2,6 +2,12 @@
This crate implements the business logic for Codex. It is designed to be used by the various Codex UIs written in Rust.
## Wine-exec integration tests
On x86-64 Linux, run the shared suite against the Windows exec server with
`bazel test //codex-rs/core:core-all-wine-exec-test`. Temporary blockers use a
source-local `skip_if_wine_exec!` call and reason.
## Dependencies
Note that `codex-core` makes some assumptions about certain helper utilities being available in the environment. Currently, this support matrix is:
+48 -42
View File
@@ -32,9 +32,14 @@ pub mod responses;
pub mod streaming_sse;
pub mod test_codex;
pub mod test_codex_exec;
mod test_environment;
pub mod tracing;
pub mod zsh_fork;
pub use test_environment::TestEnvironment;
pub use test_environment::get_remote_test_env;
pub use test_environment::test_environment;
static TEST_ARG0_PATH_ENTRY: OnceLock<Option<Arg0PathEntryGuard>> = OnceLock::new();
#[ctor]
@@ -357,33 +362,6 @@ pub fn sandbox_network_env_var() -> &'static str {
codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR
}
const REMOTE_ENV_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV";
pub fn remote_env_env_var() -> &'static str {
REMOTE_ENV_ENV_VAR
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RemoteEnvConfig {
pub container_name: String,
}
pub fn get_remote_test_env() -> Option<RemoteEnvConfig> {
if std::env::var_os(REMOTE_ENV_ENV_VAR).is_none() {
eprintln!("Skipping test because {REMOTE_ENV_ENV_VAR} is not set.");
return None;
}
let container_name = std::env::var(REMOTE_ENV_ENV_VAR)
.unwrap_or_else(|_| panic!("{REMOTE_ENV_ENV_VAR} must be set"));
assert!(
!container_name.trim().is_empty(),
"{REMOTE_ENV_ENV_VAR} must not be empty"
);
Some(RemoteEnvConfig { container_name })
}
pub fn format_with_current_shell(command: &str) -> Vec<String> {
codex_core::shell::default_user_shell().derive_exec_args(command, /*use_login_shell*/ true)
}
@@ -597,30 +575,58 @@ macro_rules! skip_if_no_network {
}};
}
// Exported so the public skip macros can expand in downstream test crates.
// Call `skip_if_remote!` or `skip_if_wine_exec!` instead.
#[macro_export]
macro_rules! skip_if_remote {
($reason:expr $(,)?) => {{
if ::std::env::var_os($crate::remote_env_env_var()).is_some() {
eprintln!(
"Skipping test under {}: {}",
$crate::remote_env_env_var(),
$reason
);
#[doc(hidden)]
macro_rules! skip_if_test_environment {
($pattern:pat, $reason:expr $(,)?) => {{
let environment = $crate::test_environment();
if ::std::matches!(&environment, $pattern) {
eprintln!("Skipping test in {environment:?}: {}", $reason);
return;
}
}};
($return_value:expr, $reason:expr $(,)?) => {{
if ::std::env::var_os($crate::remote_env_env_var()).is_some() {
eprintln!(
"Skipping test under {}: {}",
$crate::remote_env_env_var(),
$reason
);
($return_value:expr, $pattern:pat, $reason:expr $(,)?) => {{
let environment = $crate::test_environment();
if ::std::matches!(&environment, $pattern) {
eprintln!("Skipping test in {environment:?}: {}", $reason);
return $return_value;
}
}};
}
#[macro_export]
macro_rules! skip_if_remote {
($reason:expr $(,)?) => {{
$crate::skip_if_test_environment!(
$crate::TestEnvironment::Docker { .. } | $crate::TestEnvironment::WineExec,
$reason,
);
}};
($return_value:expr, $reason:expr $(,)?) => {{
$crate::skip_if_test_environment!(
$return_value,
$crate::TestEnvironment::Docker { .. } | $crate::TestEnvironment::WineExec,
$reason,
);
}};
}
#[macro_export]
macro_rules! skip_if_wine_exec {
($reason:expr $(,)?) => {{
$crate::skip_if_test_environment!($crate::TestEnvironment::WineExec, $reason);
}};
($return_value:expr, $reason:expr $(,)?) => {{
$crate::skip_if_test_environment!(
$return_value,
$crate::TestEnvironment::WineExec,
$reason,
);
}};
}
#[macro_export]
macro_rules! codex_linux_sandbox_exe_or_skip {
() => {{
+6 -12
View File
@@ -53,7 +53,6 @@ use serde_json::Value;
use tempfile::TempDir;
use wiremock::MockServer;
use crate::PathBufExt;
use crate::TempDirExt;
use crate::get_remote_test_env;
use crate::load_default_config_for_test;
@@ -164,8 +163,10 @@ pub async fn test_env() -> Result<TestEnv> {
let websocket_url = remote_exec_server_url()?;
let environment =
codex_exec_server::Environment::create_for_tests(Some(websocket_url.clone()))?;
let cwd = remote_aware_cwd_path();
let cwd_uri = PathUri::from_path(&cwd)?;
let cwd = remote_env
.remote_cwd(&remote_test_instance_id())?
.context("remote test environment should define a cwd")?;
let cwd_uri = cwd.to_path_uri(remote_env.path_convention())?;
environment
.get_filesystem()
.create_directory(
@@ -174,26 +175,19 @@ pub async fn test_env() -> Result<TestEnv> {
/*sandbox*/ None,
)
.await?;
let cwd = cwd_uri.to_abs_path()?;
Ok(TestEnv {
environment,
exec_server_url: Some(websocket_url),
cwd,
local_cwd_temp_dir: None,
remote_container_name: Some(remote_env.container_name),
remote_container_name: remote_env.docker_container_name().map(str::to_owned),
})
}
None => TestEnv::local().await,
}
}
fn remote_aware_cwd_path() -> AbsolutePathBuf {
PathBuf::from(format!(
"/tmp/codex-core-test-cwd-{}",
remote_test_instance_id()
))
.abs()
}
fn remote_exec_server_url() -> Result<String> {
let listen_url = std::env::var(REMOTE_EXEC_SERVER_URL_ENV_VAR).with_context(|| {
format!("{REMOTE_EXEC_SERVER_URL_ENV_VAR} must be set for remote tests")
@@ -0,0 +1,134 @@
use std::ffi::OsStr;
use anyhow::Result;
use codex_utils_path_uri::ApiPathString;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
pub const TEST_ENVIRONMENT_ENV_VAR: &str = "CODEX_TEST_ENVIRONMENT";
pub const LEGACY_REMOTE_ENV_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV";
pub const DOCKER_CONTAINER_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV_CONTAINER_NAME";
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TestEnvironment {
Local,
Docker { container_name: String },
WineExec,
}
impl TestEnvironment {
pub fn is_remote(&self) -> bool {
!matches!(self, Self::Local)
}
pub fn docker_container_name(&self) -> Option<&str> {
match self {
Self::Docker { container_name } => Some(container_name),
Self::Local | Self::WineExec => None,
}
}
pub(crate) fn remote_cwd(&self, instance_id: &str) -> Result<Option<ApiPathString>> {
let path_uri = match self {
Self::Local => return Ok(None),
Self::Docker { .. } => {
PathUri::parse(&format!("file:///tmp/codex-core-test-cwd-{instance_id}"))?
}
Self::WineExec => {
// Each Wine-exec test process has an isolated filesystem root, so this drive-root
// path cannot collide with a different Bazel shard.
PathUri::parse(&format!("file:///C:/codex-core-test-cwd-{instance_id}"))?
}
};
Ok(Some(ApiPathString::from_path_uri(
&path_uri,
self.path_convention(),
)?))
}
pub(crate) fn path_convention(&self) -> PathConvention {
match self {
Self::Local => PathConvention::native(),
Self::Docker { .. } => PathConvention::Posix,
Self::WineExec => PathConvention::Windows,
}
}
}
pub fn test_environment() -> TestEnvironment {
let environment = parse_test_environment(
std::env::var_os(TEST_ENVIRONMENT_ENV_VAR).as_deref(),
std::env::var_os(LEGACY_REMOTE_ENV_ENV_VAR).as_deref(),
std::env::var_os(DOCKER_CONTAINER_ENV_VAR).as_deref(),
)
.unwrap_or_else(|error| panic!("invalid test environment configuration: {error}"));
if matches!(environment, TestEnvironment::WineExec) && !cfg!(target_os = "linux") {
panic!("{TEST_ENVIRONMENT_ENV_VAR}=wine-exec is only supported on Linux");
}
environment
}
pub fn get_remote_test_env() -> Option<TestEnvironment> {
let environment = test_environment();
environment.is_remote().then_some(environment)
}
fn parse_test_environment(
configured_environment: Option<&OsStr>,
legacy_remote_environment: Option<&OsStr>,
docker_container: Option<&OsStr>,
) -> Result<TestEnvironment, String> {
let configured_environment = configured_environment
.map(|value| {
value
.to_str()
.ok_or_else(|| format!("{TEST_ENVIRONMENT_ENV_VAR} must contain valid UTF-8"))
})
.transpose()?;
match configured_environment {
None => match legacy_remote_environment {
Some(container_name) => Ok(TestEnvironment::Docker {
container_name: non_empty_utf8(LEGACY_REMOTE_ENV_ENV_VAR, container_name)?,
}),
None => Ok(TestEnvironment::Local),
},
Some("local") => Ok(TestEnvironment::Local),
Some("docker") => {
let (container_name_env_var, container_name) = match docker_container {
Some(container_name) => (DOCKER_CONTAINER_ENV_VAR, container_name),
None => (
LEGACY_REMOTE_ENV_ENV_VAR,
legacy_remote_environment.ok_or_else(|| {
format!(
"{DOCKER_CONTAINER_ENV_VAR} must be set when {TEST_ENVIRONMENT_ENV_VAR}=docker"
)
})?,
),
};
Ok(TestEnvironment::Docker {
container_name: non_empty_utf8(container_name_env_var, container_name)?,
})
}
Some("wine-exec") => Ok(TestEnvironment::WineExec),
Some(value) => Err(format!(
"{TEST_ENVIRONMENT_ENV_VAR} must be one of local, docker, or wine-exec; got {value:?}"
)),
}
}
fn non_empty_utf8(name: &str, value: &OsStr) -> Result<String, String> {
let value = value
.to_str()
.ok_or_else(|| format!("{name} must contain valid UTF-8"))?;
if value.trim().is_empty() {
return Err(format!("{name} must not be empty"));
}
Ok(value.to_string())
}
#[cfg(test)]
#[path = "test_environment_tests.rs"]
mod tests;
@@ -0,0 +1,118 @@
use std::ffi::OsStr;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn defaults_to_local() {
assert_eq!(
parse_test_environment(
/*configured_environment*/ None, /*legacy_remote_environment*/ None,
/*docker_container*/ None,
),
Ok(TestEnvironment::Local)
);
}
#[test]
fn parses_each_explicit_environment() {
assert_eq!(
parse_test_environment(
Some(OsStr::new("local")),
/*legacy_remote_environment*/ None,
/*docker_container*/ None,
),
Ok(TestEnvironment::Local)
);
assert_eq!(
parse_test_environment(
Some(OsStr::new("docker")),
/*legacy_remote_environment*/ None,
Some(OsStr::new("container-1")),
),
Ok(TestEnvironment::Docker {
container_name: "container-1".to_string(),
})
);
assert_eq!(
parse_test_environment(
Some(OsStr::new("wine-exec")),
/*legacy_remote_environment*/ None,
/*docker_container*/ None,
),
Ok(TestEnvironment::WineExec)
);
}
#[test]
fn treats_the_legacy_remote_value_as_a_docker_container() {
assert_eq!(
parse_test_environment(
/*configured_environment*/ None,
Some(OsStr::new("legacy-container")),
/*docker_container*/ None,
),
Ok(TestEnvironment::Docker {
container_name: "legacy-container".to_string(),
})
);
}
#[test]
fn explicit_docker_accepts_the_legacy_container_value() {
assert_eq!(
parse_test_environment(
Some(OsStr::new("docker")),
Some(OsStr::new("legacy-container")),
/*docker_container*/ None,
),
Ok(TestEnvironment::Docker {
container_name: "legacy-container".to_string(),
})
);
assert_eq!(
parse_test_environment(
Some(OsStr::new("docker")),
Some(OsStr::new("")),
/*docker_container*/ None,
),
Err(format!("{LEGACY_REMOTE_ENV_ENV_VAR} must not be empty"))
);
}
#[test]
fn explicit_local_ignores_stale_remote_metadata() {
assert_eq!(
parse_test_environment(
Some(OsStr::new("local")),
Some(OsStr::new("legacy-container")),
Some(OsStr::new("container-1")),
),
Ok(TestEnvironment::Local)
);
}
#[test]
fn rejects_invalid_or_incomplete_configuration() {
assert_eq!(
parse_test_environment(
Some(OsStr::new("docker")),
/*legacy_remote_environment*/ None,
/*docker_container*/ None,
),
Err(format!(
"{DOCKER_CONTAINER_ENV_VAR} must be set when {TEST_ENVIRONMENT_ENV_VAR}=docker"
))
);
assert_eq!(
parse_test_environment(
Some(OsStr::new("other")),
/*legacy_remote_environment*/ None,
/*docker_container*/ None,
),
Err(format!(
"{TEST_ENVIRONMENT_ENV_VAR} must be one of local, docker, or wine-exec; got \"other\""
))
);
}
@@ -1,4 +1,4 @@
load("//bazel/rules/testing:wine.bzl", "wine_rust_test")
load("//bazel/rules/testing/wine:wine.bzl", "wine_rust_test")
wine_rust_test(
name = "smoke-test",
@@ -17,7 +17,7 @@ wineserver.
## Current limitations
- PowerShell and ConPTY/TTY behavior are not yet covered.
- ConPTY/TTY behavior is not yet covered.
- Wine loads shared objects and PE DLLs at runtime, so the host must still
provide the declared compatible glibc version.
- The target is intentionally limited to x86-64 for simplicity. It can expand
+11
View File
@@ -25,6 +25,7 @@ use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::RecordingUserInstructionsProvider;
use core_test_support::test_codex::TestCodexBuilder;
use core_test_support::test_codex::test_codex;
@@ -138,6 +139,8 @@ fn request_body_contains(request: &wiremock::Request, text: &str) -> bool {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agents_override_is_preferred_over_agents_md() -> Result<()> {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS instruction-source paths");
let instructions =
agents_instructions(test_codex().with_workspace_setup(|cwd, fs| async move {
let agents_md = cwd.join("AGENTS.md");
@@ -170,6 +173,8 @@ async fn agents_override_is_preferred_over_agents_md() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn configured_fallback_is_used_when_agents_candidate_is_directory() -> Result<()> {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS instruction-source paths");
let instructions = agents_instructions(
test_codex()
.with_config(|config| {
@@ -351,6 +356,8 @@ async fn symlinked_cwd_uses_logical_parent_for_agents_discovery() -> Result<()>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn selected_environment_sources_match_model_visible_instructions() -> Result<()> {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS instruction-source paths");
let server = start_mock_server().await;
let resp_mock = mount_sse_once(
&server,
@@ -478,6 +485,8 @@ async fn loads_user_instructions_without_a_primary_environment() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn fresh_thread_composes_global_before_project_and_reports_sources() -> Result<()> {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS instruction-source paths");
// Set up one global source, one project source, and two ordinary model turns.
let server = responses::start_mock_server().await;
let response_mock = responses::mount_sse_sequence(
@@ -588,6 +597,8 @@ async fn fresh_thread_composes_global_before_project_and_reports_sources() -> Re
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn multi_environment_thread_loads_every_project_and_keeps_creation_snapshot() -> Result<()> {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS instruction-source paths");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
@@ -51,6 +51,7 @@ use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_remote;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::TestCodexBuilder;
use core_test_support::test_codex::TestCodexHarness;
use core_test_support::test_codex::local;
@@ -911,6 +912,8 @@ async fn apply_patch_cli_preserves_existing_hard_link_outside_workspace() -> Res
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_cli_rejects_move_path_traversal_outside_workspace() -> Result<()> {
// TODO(anp): Remove after apply-patch fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "asserts POSIX workspace traversal behavior");
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness().await?;
@@ -1566,6 +1569,11 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_turn_diff_tracks_local_and_remote_environment_paths() -> Result<()> {
// TODO(anp): Remove after shared-cwd helpers use target-native paths.
skip_if_wine_exec!(
Ok(()),
"requires a cwd valid in local POSIX and remote Windows environments"
);
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
+26
View File
@@ -38,6 +38,7 @@ use core_test_support::responses::ev_custom_tool_call;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::sse;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::stdio_server_bin;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::test_codex;
@@ -972,6 +973,11 @@ text(result.output);
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_exec_explicit_max_above_default_preserves_output() -> Result<()> {
// TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode.
skip_if_wine_exec!(
Ok(()),
"only part of nested exec_command stdout reaches the code-mode result"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1002,6 +1008,11 @@ text(result.output);
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_exec_explicit_max_above_default_truncates_larger_output() -> Result<()> {
// TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode.
skip_if_wine_exec!(
Ok(()),
"only part of nested exec_command stdout reaches the code-mode result"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1036,6 +1047,11 @@ text(result.output);
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_exec_explicit_max_above_truncation_policy_preserves_output() -> Result<()> {
// TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode.
skip_if_wine_exec!(
Ok(()),
"only part of nested exec_command stdout reaches the code-mode result"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1069,6 +1085,11 @@ text(result.output);
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_exec_without_max_preserves_output_beyond_default() -> Result<()> {
// TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode.
skip_if_wine_exec!(
Ok(()),
"only part of nested exec_command stdout reaches the code-mode result"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1098,6 +1119,11 @@ text(result.output);
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_exec_without_max_preserves_output_beyond_truncation_policy() -> Result<()> {
// TODO(anp): Remove after Wine exec returns complete nested-tool output to code mode.
skip_if_wine_exec!(
Ok(()),
"only part of nested exec_command stdout reaches the code-mode result"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -5,6 +5,7 @@ use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::test_codex;
const HIERARCHICAL_AGENTS_SNIPPET: &str =
@@ -12,6 +13,8 @@ const HIERARCHICAL_AGENTS_SNIPPET: &str =
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn hierarchical_agents_appends_to_project_doc_in_user_instructions() {
// TODO(anp): Remove after instruction-source helpers use target-native paths.
skip_if_wine_exec!("requires native cross-OS instruction-source paths");
let server = start_mock_server().await;
let resp_mock = mount_sse_once(
&server,
+38 -19
View File
@@ -31,6 +31,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use core_test_support::TestEnvironment;
use core_test_support::get_remote_test_env;
use core_test_support::responses::ev_apply_patch_custom_tool_call;
use core_test_support::responses::ev_assistant_message;
@@ -42,6 +43,7 @@ use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::local;
use core_test_support::test_codex::test_codex;
@@ -156,7 +158,7 @@ async fn remote_test_env_can_connect_and_use_filesystem() -> Result<()> {
let test_env = test_env().await?;
let file_system = test_env.environment().get_filesystem();
let file_path_abs = remote_test_file_path().abs();
let file_path_abs = test_env.cwd().join("remote-test-env-ok");
let file_path_uri = PathUri::from_path(&file_path_abs)?;
let payload = b"remote-test-env-ok".to_vec();
@@ -183,7 +185,7 @@ async fn remote_test_env_can_connect_and_use_filesystem() -> Result<()> {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_exposes_bash_shell_to_model() -> Result<()> {
async fn remote_test_env_exposes_target_shell_to_model() -> Result<()> {
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -208,11 +210,19 @@ async fn remote_test_env_exposes_bash_shell_to_model() -> Result<()> {
.into_iter()
.find(|text| text.starts_with("<environment_context>"))
.context("environment context should be model visible")?;
// TODO(anp): Assert Wine-exec exposes a `C:\\...` cwd after model-visible paths preserve
// target-native spelling instead of the Linux orchestrator's `/C:/...` representation.
let expected_shell = match core_test_support::test_environment() {
TestEnvironment::Docker { .. } => "<shell>bash</shell>",
TestEnvironment::WineExec => "<shell>powershell</shell>",
TestEnvironment::Local => unreachable!("test requires a remote environment"),
};
assert_eq!(
environment_context
.lines()
.find(|line| line.trim_start().starts_with("<shell>")),
Some(" <shell>bash</shell>"),
.find(|line| line.trim_start().starts_with("<shell>"))
.map(str::trim),
Some(expected_shell),
);
Ok(())
@@ -272,8 +282,11 @@ fn assert_normalized_path_rejected(error: &std::io::Error) {
fn remote_exec(script: &str) -> Result<()> {
let remote_env = get_remote_test_env().context("remote env should be configured")?;
let container_name = remote_env
.docker_container_name()
.context("test requires direct access to the Docker container")?;
let output = Command::new("docker")
.args(["exec", &remote_env.container_name, "sh", "-lc", script])
.args(["exec", container_name, "sh", "-lc", script])
.output()?;
assert!(
output.status.success(),
@@ -319,6 +332,8 @@ async fn exec_command_routing_output(
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_command_routes_to_selected_remote_environment() -> Result<()> {
skip_if_no_network!(Ok(()));
// TODO(anp): Remove after remote path fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -394,6 +409,8 @@ async fn exec_command_routes_to_selected_remote_environment() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_request_permissions_grant_unblocks_later_remote_exec() -> Result<()> {
skip_if_no_network!(Ok(()));
// TODO(anp): Remove after remote path fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -598,6 +615,8 @@ async fn remote_request_permissions_grant_unblocks_later_remote_exec() -> Result
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_freeform_routes_to_selected_remote_environment() -> Result<()> {
skip_if_no_network!(Ok(()));
// TODO(anp): Remove after remote path fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -684,6 +703,8 @@ async fn apply_patch_freeform_routes_to_selected_remote_environment() -> Result<
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_approvals_are_remembered_per_environment() -> Result<()> {
skip_if_no_network!(Ok(()));
// TODO(anp): Remove after remote path fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -870,6 +891,8 @@ async fn apply_patch_approvals_are_remembered_per_environment() -> Result<()> {
async fn apply_patch_intercepted_exec_command_routes_to_selected_remote_environment() -> Result<()>
{
skip_if_no_network!(Ok(()));
// TODO(anp): Remove after remote path fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
@@ -964,6 +987,8 @@ async fn apply_patch_intercepted_exec_command_routes_to_selected_remote_environm
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_sandboxed_read_allows_readable_root() -> Result<()> {
// TODO(anp): Remove after remote sandbox fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "requires the Docker-backed POSIX executor");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
@@ -1013,6 +1038,7 @@ async fn remote_test_env_sandboxed_read_allows_readable_root() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_sandboxed_read_rejects_symlink_parent_dotdot_escape() -> Result<()> {
skip_if_wine_exec!(Ok(()), "tests POSIX symlink and parent traversal semantics");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
@@ -1048,6 +1074,7 @@ async fn remote_test_env_sandboxed_read_rejects_symlink_parent_dotdot_escape() -
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_remove_removes_symlink_not_target() -> Result<()> {
skip_if_wine_exec!(Ok(()), "tests POSIX symlink removal semantics");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
@@ -1118,6 +1145,7 @@ async fn remote_test_env_remove_removes_symlink_not_target() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_copy_preserves_symlink_source() -> Result<()> {
skip_if_wine_exec!(Ok(()), "tests POSIX symlink copy semantics");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
@@ -1153,12 +1181,14 @@ async fn remote_test_env_copy_preserves_symlink_source() -> Result<()> {
)
.await?;
let remote_env = get_remote_test_env().context("remote env should be configured")?;
let container_name = remote_env
.docker_container_name()
.context("test requires direct access to the Docker container")?;
let link_target = Command::new("docker")
.args([
"exec",
&get_remote_test_env()
.context("remote env should still be configured")?
.container_name,
container_name,
"readlink",
copied_symlink
.to_str()
@@ -1188,14 +1218,3 @@ async fn remote_test_env_copy_preserves_symlink_source() -> Result<()> {
.await?;
Ok(())
}
fn remote_test_file_path() -> PathBuf {
let nanos = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_nanos(),
Err(_) => 0,
};
PathBuf::from(format!(
"/tmp/codex-remote-test-env-{}-{nanos}.txt",
std::process::id()
))
}
+80 -23
View File
@@ -49,15 +49,16 @@ use codex_protocol::user_input::UserInput;
use codex_utils_cargo_bin::cargo_bin;
use codex_utils_path_uri::PathUri;
use core_test_support::assert_regex_match;
use core_test_support::remote_env_env_var;
use core_test_support::responses;
use core_test_support::responses::mount_models_once;
use core_test_support::responses::mount_sse_once;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::stdio_server_bin;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::test_codex;
use core_test_support::test_codex::turn_permission_fields;
use core_test_support::test_environment;
use core_test_support::wait_for_event;
use core_test_support::wait_for_mcp_server;
use image::DynamicImage;
@@ -166,12 +167,11 @@ enum McpCallEvent {
const REMOTE_MCP_ENVIRONMENT: &str = "remote";
fn remote_aware_environment_id() -> String {
// These tests run locally in normal CI and against the Docker-backed
// executor in full-ci. Match that shared test environment instead of
// parameterizing each stdio MCP test with its own local/remote cases.
std::env::var_os(remote_env_env_var())
.map(|_| REMOTE_MCP_ENVIRONMENT.to_string())
.unwrap_or_else(|| codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string())
if test_environment().is_remote() {
REMOTE_MCP_ENVIRONMENT.to_string()
} else {
codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string()
}
}
/// Returns the stdio MCP test server command path for the active test placement.
@@ -183,7 +183,8 @@ fn remote_aware_environment_id() -> String {
/// container and return that in-container path instead.
fn remote_aware_stdio_server_bin() -> anyhow::Result<String> {
let bin = stdio_server_bin()?;
let Some(container_name) = remote_env_container_name()? else {
let environment = test_environment();
let Some(container_name) = environment.docker_container_name() else {
return Ok(bin);
};
@@ -196,17 +197,7 @@ fn remote_aware_stdio_server_bin() -> anyhow::Result<String> {
// path instead of the host build artifact path.
// Several remote-aware MCP tests can run in parallel; give each copied
// binary its own path so one test cannot replace another test's executable.
copy_binary_to_remote_env(&container_name, Path::new(&bin), "test_stdio_server")
}
/// Returns the Docker container used by remote-aware MCP tests, when active.
fn remote_env_container_name() -> anyhow::Result<Option<String>> {
let Some(container_name) = std::env::var_os(remote_env_env_var()) else {
return Ok(None);
};
Ok(Some(container_name.into_string().map_err(|value| {
anyhow::anyhow!("remote env container name must be utf-8: {value:?}")
})?))
copy_binary_to_remote_env(container_name, Path::new(&bin), "test_stdio_server")
}
/// Builds a collision-resistant in-container path for copied test binaries.
@@ -406,7 +397,7 @@ fn assert_cwd_tool_output(structured: &Value, expected_cwd: &Path) {
.and_then(Value::as_str)
.expect("cwd tool should return a string cwd");
if std::env::var_os(remote_env_env_var()).is_some() {
if test_environment().is_remote() {
assert_eq!(
structured,
&json!({
@@ -431,6 +422,11 @@ fn assert_cwd_tool_output(structured: &Value, expected_cwd: &Path) {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_server_round_trip() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -614,6 +610,11 @@ async fn shutdown_cancels_startup_prewarm_waiting_for_mcp_startup() -> anyhow::R
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_cwd)]
async fn stdio_server_uses_configured_cwd_before_runtime_fallback() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -739,6 +740,11 @@ async fn local_stdio_server_uses_runtime_fallback_cwd_when_config_omits_cwd() ->
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn stdio_mcp_tool_call_includes_sandbox_state_meta() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -832,6 +838,11 @@ async fn stdio_mcp_tool_call_includes_sandbox_state_meta() -> anyhow::Result<()>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -947,6 +958,11 @@ async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in()
-> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1044,6 +1060,11 @@ async fn stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in()
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1132,6 +1153,11 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1269,6 +1295,11 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_image_responses_resize_large_image() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1370,6 +1401,11 @@ async fn stdio_image_responses_resize_large_image() -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1456,6 +1492,11 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1609,6 +1650,11 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_test_value)]
async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1726,6 +1772,11 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_env_source)]
async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1817,8 +1868,13 @@ async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Resu
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial(mcp_env_source)]
async fn remote_stdio_env_var_source_does_not_copy_local_env() -> anyhow::Result<()> {
// TODO(anp): Remove after packaging a Windows stdio test server for Wine exec.
skip_if_wine_exec!(
Ok(()),
"requires a Windows test_stdio_server in the Wine-exec environment"
);
skip_if_no_network!(Ok(()));
if std::env::var_os(remote_env_env_var()).is_none() {
if !test_environment().is_remote() {
return Ok(());
}
@@ -2334,10 +2390,11 @@ async fn start_streamable_http_test_server(
}
};
if let Some(container_name) = remote_env_container_name()? {
let environment = test_environment();
if let Some(container_name) = environment.docker_container_name() {
return Ok(Some(
start_remote_streamable_http_test_server(
&container_name,
container_name,
&rmcp_http_server_bin,
expected_env_value,
expected_token,
@@ -12,6 +12,7 @@ use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use regex_lite::Regex;
@@ -235,6 +236,8 @@ M {file_name}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_custom_tool_call_reports_failure_output() -> Result<()> {
// TODO(anp): Remove after apply-patch assertions use target-native paths.
skip_if_wine_exec!(Ok(()), "asserts POSIX apply_patch failure text");
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness().await?;
+3
View File
@@ -17,6 +17,7 @@ use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::local_selections;
use core_test_support::test_codex::test_codex;
use core_test_support::test_codex::turn_permission_fields;
@@ -47,6 +48,8 @@ async fn write_repo_skill(
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn user_turn_includes_skill_instructions() -> Result<()> {
// TODO(anp): Remove after skill-path helpers use target-native paths.
skip_if_wine_exec!(Ok(()), "requires native cross-OS skill paths");
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
+61
View File
@@ -34,6 +34,7 @@ use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_sandbox;
use core_test_support::skip_if_windows;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::TestCodexHarness;
use core_test_support::test_codex::test_codex;
@@ -384,6 +385,11 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_emits_exec_command_begin_event() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(
Ok(()),
"uses a POSIX command and does not assert successful execution"
);
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -443,6 +449,11 @@ async fn unified_exec_emits_exec_command_begin_event() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_resolves_relative_workdir() -> Result<()> {
// TODO(anp): Remove after workdir helpers use target-native paths.
skip_if_wine_exec!(
Ok(()),
"does not assert successful native-Windows workdir execution"
);
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -577,6 +588,8 @@ async fn unified_exec_respects_workdir_override() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_emits_exec_command_end_event() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -650,6 +663,8 @@ async fn unified_exec_emits_exec_command_end_event() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -708,6 +723,8 @@ async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_full_lifecycle_with_background_end_event() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -803,6 +820,8 @@ async fn unified_exec_full_lifecycle_with_background_end_event() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_network_denial_emits_failed_background_end_event() -> Result<()> {
// TODO(anp): Remove after network-denial fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses the POSIX/Python network-denial fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -846,6 +865,8 @@ async fn unified_exec_network_denial_emits_failed_background_end_event() -> Resu
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_short_lived_network_denial_emits_failed_end_event() -> Result<()> {
// TODO(anp): Remove after network-denial fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses the POSIX/Python network-denial fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -999,6 +1020,8 @@ async fn wait_for_unified_exec_end(
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_emits_terminal_interaction_for_write_stdin() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "uses POSIX interactive-process and EOF semantics");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1082,6 +1105,8 @@ async fn unified_exec_emits_terminal_interaction_for_write_stdin() -> Result<()>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_terminal_interaction_captures_delayed_output() -> Result<()> {
// TODO(anp): Remove after timing fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX sleep/echo timing fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1384,6 +1409,8 @@ async fn unified_exec_emits_one_begin_and_one_end_event() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_command_reports_chunk_and_exit_metadata() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1477,6 +1504,8 @@ async fn exec_command_reports_chunk_and_exit_metadata() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_command_clamps_model_requested_max_output_tokens_to_policy() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1539,6 +1568,8 @@ async fn exec_command_clamps_model_requested_max_output_tokens_to_policy() -> Re
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn write_stdin_clamps_model_requested_max_output_tokens_to_policy() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "uses POSIX read/while and Unix TTY semantics");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1628,6 +1659,8 @@ async fn write_stdin_clamps_model_requested_max_output_tokens_to_policy() -> Res
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_defaults_to_pipe() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "requires Python/Unix PTY support in the target");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1697,6 +1730,8 @@ async fn unified_exec_defaults_to_pipe() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_can_enable_tty() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "requires Python/Unix PTY support in the target");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1763,6 +1798,8 @@ async fn unified_exec_can_enable_tty() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_respects_early_exit_notifications() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1846,6 +1883,8 @@ async fn unified_exec_respects_early_exit_notifications() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn write_stdin_returns_exit_metadata_and_clears_session() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "uses POSIX interactive-process and EOF semantics");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -1999,6 +2038,8 @@ async fn write_stdin_returns_exit_metadata_and_clears_session() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn write_stdin_ctrl_c_interrupts_non_tty_session() -> Result<()> {
// TODO(anp): Add a Wine-exec test for explicit interrupt handling on Windows.
skip_if_wine_exec!(Ok(()), "asserts Unix SIGINT and trap semantics");
assert_write_stdin_ctrl_c_interrupts_non_tty_session(
"trap",
"trap 'echo INT-TRAP; exit 42' INT; echo READY; while true; do sleep 30; done",
@@ -2010,6 +2051,8 @@ async fn write_stdin_ctrl_c_interrupts_non_tty_session() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn write_stdin_ctrl_c_default_interrupt_reports_130_for_non_tty_session() -> Result<()> {
// TODO(anp): Add a Wine-exec test for Windows Ctrl+C termination and exit reporting.
skip_if_wine_exec!(Ok(()), "asserts Unix SIGINT and exit-code semantics");
assert_write_stdin_ctrl_c_interrupts_non_tty_session(
"default",
"echo READY; exec sleep 30",
@@ -2242,6 +2285,8 @@ async fn write_stdin_ctrl_c_reports_unsupported_interrupt_to_model_on_windows()
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_emits_end_event_when_session_dies_via_stdin() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "uses POSIX interactive-process and EOF semantics");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -2542,6 +2587,8 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
// TODO(anp): Remove after unified-exec interactive fixtures support Windows/ConPTY.
skip_if_wine_exec!(Ok(()), "uses POSIX interactive-process and EOF semantics");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -2640,6 +2687,8 @@ async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_streams_after_lagged_output() -> Result<()> {
// TODO(anp): Remove after output fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "requires Python/Unix PTY support in the target");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -2756,6 +2805,8 @@ PY
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
// TODO(anp): Remove after unified-exec fixtures use target-native commands.
skip_if_wine_exec!(Ok(()), "uses a POSIX-only command fixture");
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -2845,6 +2896,11 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
// Skipped on arm because the ctor logic to handle arg0 doesn't work on ARM
#[cfg(not(target_arch = "arm"))]
async fn unified_exec_formats_large_output_summary() -> Result<()> {
// TODO(anp): Remove after output fixtures use target-native commands.
skip_if_wine_exec!(
Ok(()),
"requires Python and POSIX heredoc support in the target"
);
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
skip_if_windows!(Ok(()));
@@ -3288,6 +3344,11 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_runs_on_all_platforms() -> Result<()> {
// TODO(anp): Remove after PowerShell execution passes through Wine exec.
skip_if_wine_exec!(
Ok(()),
"basic PowerShell execution through Wine exec is not passing yet"
);
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
+3
View File
@@ -43,6 +43,7 @@ use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_wine_exec;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::local;
use core_test_support::test_codex::test_codex;
@@ -592,6 +593,8 @@ async fn view_image_tool_applies_local_sandbox_read_denies() -> anyhow::Result<(
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn view_image_routes_to_selected_remote_environment() -> anyhow::Result<()> {
// TODO(anp): Remove after remote-cwd fixtures use target-native paths.
skip_if_wine_exec!(Ok(()), "hardcodes a POSIX remote cwd");
skip_if_no_network!(Ok(()));
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
+16 -1
View File
@@ -16,6 +16,21 @@ rust_library(
],
)
rust_binary(
name = "wine-exec-test-runner",
testonly = True,
srcs = ["wine_remote_test_runner.rs"],
crate_name = "wine_exec_test_runner",
crate_root = "wine_remote_test_runner.rs",
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:public"],
deps = [
":wine-exec-server-test-support",
"@crates//:anyhow",
"@crates//:tokio",
],
)
rust_binary(
name = "windows-exec-server",
testonly = True,
@@ -24,7 +39,7 @@ rust_binary(
crate_root = "windows_exec_server.rs",
tags = ["manual"],
target_compatible_with = ["@platforms//os:windows"],
visibility = ["//codex-rs/core/tests/remote_env_windows:__pkg__"],
visibility = ["//visibility:public"],
deps = [
"//codex-rs/exec-server",
"@crates//:tokio",
@@ -0,0 +1,62 @@
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Stdio;
use anyhow::Context;
use anyhow::Result;
use tokio::process::Command;
use wine_exec_server_test_support::WineExecServer;
const TEST_BINARY_ENV_VAR: &str = "CODEX_WINE_EXEC_TEST_BINARY";
const TEST_ENVIRONMENT_ENV_VAR: &str = "CODEX_TEST_ENVIRONMENT";
const REMOTE_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_TEST_REMOTE_EXEC_SERVER_URL";
const LEGACY_REMOTE_ENV_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV";
const DOCKER_CONTAINER_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV_CONTAINER_NAME";
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() -> Result<()> {
let test_binary = PathBuf::from(
std::env::var_os(TEST_BINARY_ENV_VAR)
.with_context(|| format!("{TEST_BINARY_ENV_VAR} must be set by the Bazel test rule"))?,
);
let forwarded_args = std::env::args_os().skip(1).collect::<Vec<_>>();
if is_terse_list_request(&forwarded_args) {
let status = Command::new(&test_binary)
.args(&forwarded_args)
.status()
.await
.context("list integration tests")?;
anyhow::ensure!(status.success(), "listing integration tests exited with {status}");
return Ok(());
}
WineExecServer
.scope(|exec_server_url| async move {
let mut command = Command::new(test_binary);
command
.env(TEST_ENVIRONMENT_ENV_VAR, "wine-exec")
.env(REMOTE_EXEC_SERVER_URL_ENV_VAR, exec_server_url)
.env_remove(LEGACY_REMOTE_ENV_ENV_VAR)
.env_remove(DOCKER_CONTAINER_ENV_VAR)
.args(forwarded_args)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.kill_on_drop(true);
let status = command.status().await.context("run integration tests")?;
anyhow::ensure!(status.success(), "integration tests exited with {status}");
Ok(())
})
.await
}
fn is_terse_list_request(args: &[OsString]) -> bool {
args.iter().map(OsString::as_os_str).eq([
OsStr::new("--list"),
OsStr::new("--format"),
OsStr::new("terse"),
])
}
+91 -20
View File
@@ -2,6 +2,8 @@ load("@crates//:data.bzl", "DEP_DATA")
load("@crates//:defs.bzl", "all_crate_deps")
load("@rules_rust//cargo/private:cargo_build_script_wrapper.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro", "rust_test")
load("//bazel/rules/testing:foreign_platform_binary.bzl", "foreign_platform_binary")
load("//bazel/rules/testing/wine:wine_runtime.bzl", "WINE_TEST_TARGET_COMPATIBLE_WITH", "wine_test_runtime")
# Match Cargo's Windows linker behavior so Bazel-built binaries and tests use
# the same stack reserve on both Windows ABIs and resolve UCRT imports on MSVC.
@@ -70,19 +72,19 @@ def _workspace_root_test_impl(ctx):
runfiles = runfiles.merge(ctx.runfiles(files = data_dep[DefaultInfo].files.to_list()))
runfiles = runfiles.merge(data_dep[DefaultInfo].default_runfiles)
for runfile_dep in ctx.attr.runfile_env:
executable = runfile_dep[DefaultInfo].files_to_run.executable
if executable == None:
fail("{} does not provide an executable for runfile_env".format(runfile_dep.label))
runfiles = runfiles.merge(ctx.runfiles(files = [executable]))
runfile = _runfile_env_file(runfile_dep)
runfiles = runfiles.merge(ctx.runfiles(files = [runfile]))
runfiles = runfiles.merge(runfile_dep[DefaultInfo].default_runfiles)
location_targets = (
location_targets = {}
for target in (
ctx.attr.data +
[ctx.attr.test_bin, ctx.attr.workspace_root_marker] +
ctx.attr.runfile_env.keys()
)
):
location_targets[target.label] = target
env = {
key: ctx.expand_location(value, targets = location_targets)
key: ctx.expand_location(value, targets = location_targets.values())
for key, value in ctx.attr.env.items()
}
@@ -100,22 +102,31 @@ def _workspace_root_test_impl(ctx):
def _bash_runfile_env_exports(ctx):
lines = []
for runfile_dep, env_var in ctx.attr.runfile_env.items():
executable = runfile_dep[DefaultInfo].files_to_run.executable
if executable == None:
fail("{} does not provide an executable for runfile_env".format(runfile_dep.label))
lines.append('RUNFILE_ENV_ARGS+=("{}=$(resolve_runfile "{}")")'.format(env_var, executable.short_path))
runfile = _runfile_env_file(runfile_dep)
lines.append('RUNFILE_ENV_ARGS+=("{}=$(resolve_runfile "{}")")'.format(env_var, _runfile_logical_path(runfile)))
return "\n".join(lines)
def _windows_runfile_env_exports(ctx):
lines = []
for runfile_dep, env_var in ctx.attr.runfile_env.items():
executable = runfile_dep[DefaultInfo].files_to_run.executable
if executable == None:
fail("{} does not provide an executable for runfile_env".format(runfile_dep.label))
lines.append('call :resolve_runfile {} "{}"'.format(env_var, executable.short_path))
runfile = _runfile_env_file(runfile_dep)
lines.append('call :resolve_runfile {} "{}"'.format(env_var, _runfile_logical_path(runfile)))
lines.append("if errorlevel 1 exit /b 1")
return "\n".join(lines)
def _runfile_env_file(target):
executable = target[DefaultInfo].files_to_run.executable
if executable != None:
return executable
files = target[DefaultInfo].files.to_list()
if len(files) != 1:
fail("{} must provide an executable or exactly one file for runfile_env".format(target.label))
return files[0]
def _runfile_logical_path(file):
return file.short_path.removeprefix("../")
def _bash_workspace_root_setup(ctx):
if not ctx.attr.chdir_workspace_root:
return ""
@@ -188,7 +199,8 @@ def codex_rust_crate(
test_tags = [],
unit_test_timeout = None,
extra_binaries = [],
extra_binaries_non_windows = []):
extra_binaries_non_windows = [],
run_tests_with_wine_exec = False):
"""Defines a Rust crate with library, binaries, and tests wired for Bazel + Cargo parity.
The macro mirrors Cargo conventions: it builds a library when `src/` exists,
@@ -236,6 +248,9 @@ def codex_rust_crate(
extra_binaries_non_windows: Like `extra_binaries`, but omitted from
Windows test data and environment variables. Tests using these
binaries must be excluded when targeting Windows.
run_tests_with_wine_exec: Boolean, defaults to False. Whether to emit a
Wine-exec variant for each integration test. Variants inherit the
native test's timeout, tags, and shard count.
"""
test_env = {
# The launcher resolves an absolute workspace root at runtime so
@@ -377,10 +392,10 @@ def codex_rust_crate(
integration_test_binaries = sanitized_binaries
integration_test_cargo_env = cargo_env
integration_test_cargo_env_runfiles = cargo_env_runfiles
non_windows_sanitized_binaries = []
non_windows_cargo_env = {}
non_windows_cargo_env_runfiles = {}
if extra_binaries_non_windows:
non_windows_sanitized_binaries = []
non_windows_cargo_env = {}
non_windows_cargo_env_runfiles = {}
for binary_label in extra_binaries_non_windows:
non_windows_sanitized_binaries.append(binary_label)
binary = Label(binary_label).name
@@ -400,6 +415,11 @@ def codex_rust_crate(
"//conditions:default": cargo_env_runfiles | non_windows_cargo_env_runfiles,
})
wine_host_binaries = {
env_var.removeprefix("CARGO_BIN_EXE_"): binary_label
for binary_label, env_var in (cargo_env_runfiles | non_windows_cargo_env_runfiles).items()
}
integration_test_kwargs = {}
if integration_test_args:
integration_test_kwargs["args"] = integration_test_args
@@ -428,7 +448,7 @@ def codex_rust_crate(
integration_test_binary = test_name + "-bin"
# There are three generated integration-test shapes:
# There are four generated integration-test shapes:
#
# 1. Unsharded native tests keep the plain rust_test label for minimal
# churn and the usual rules_rust Cargo-like environment.
@@ -439,6 +459,12 @@ def codex_rust_crate(
# 3. Windows cross tests always use the workspace_root_test wrapper so
# runfile env vars become Windows-native absolute paths before the
# Rust process starts.
# 4. Wine-exec tests reuse the native Rust test binary behind a shared
# Linux runner. The runner starts the cross-built Windows exec server
# under pinned Wine, injects its URL into the test environment, and
# owns cleanup. The outer workspace_root_test resolves the runner,
# test, and server from runfiles, sets a Cargo-like cwd, and applies
# the native test's shard count.
if test_shard_count:
# This target is intentionally a binary-like helper, not the public
# test target. The wrapper below owns cwd setup, runfile env
@@ -504,6 +530,51 @@ def codex_rust_crate(
**test_kwargs
)
if run_tests_with_wine_exec:
wine_test_name = test_name.removesuffix("-test") + "-wine-exec-test"
native_test_binary = ":" + (integration_test_binary if test_shard_count else test_name)
wine_test_binaries = dict(wine_host_binaries)
wine_exec_server = wine_test_name + "-windows-exec-server"
foreign_platform_binary(
name = wine_exec_server,
binary = "//codex-rs/exec-server/testing:windows-exec-server",
extra_rustc_flags = WINDOWS_GNULLVM_RUSTC_LINK_FLAGS,
platform = "//:windows_x86_64_gnullvm",
tags = ["manual"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
testonly = True,
visibility = ["//visibility:private"],
)
wine_test_binaries["wine-windows-exec-server"] = ":" + wine_exec_server
wine_runtime = wine_test_runtime(wine_test_binaries)
wine_runfile_env = dict(wine_runtime.runfile_env)
wine_runfile_env[native_test_binary] = "CODEX_WINE_EXEC_TEST_BINARY"
wine_test_kwargs = {}
wine_test_kwargs.update(integration_test_kwargs)
if test_shard_count:
wine_test_kwargs["shard_count"] = test_shard_count
wine_test_kwargs["flaky"] = True
# The Wine runner is a binary rather than a rust_test, but it still
# needs a Cargo-like cwd, Bazel sharding, and absolute runfile paths.
# `workspace_root_test` establishes all three before Wine starts.
workspace_root_test(
name = wine_test_name,
data = wine_runtime.data,
env = test_env,
runfile_env = wine_runfile_env,
test_bin = "//codex-rs/exec-server/testing:wine-exec-test-runner",
workspace_root_marker = "//codex-rs/utils/cargo-bin:repo_root.marker",
target_compatible_with = WINE_TEST_TARGET_COMPATIBLE_WITH,
tags = test_tags + ["manual"],
**wine_test_kwargs
)
windows_cross_test_kwargs = {}
windows_cross_test_kwargs.update(integration_test_kwargs)
if test_shard_count:
+5
View File
@@ -89,6 +89,8 @@ setup_remote_env() {
fi
export CODEX_TEST_REMOTE_ENV="${container_name}"
export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME="${container_name}"
export CODEX_TEST_ENVIRONMENT="docker"
}
wait_for_remote_exec_server_port() {
@@ -114,8 +116,10 @@ codex_remote_env_cleanup() {
docker rm -f "${CODEX_TEST_REMOTE_ENV}" >/dev/null 2>&1 || true
unset CODEX_TEST_REMOTE_ENV
fi
unset CODEX_TEST_REMOTE_ENV_CONTAINER_NAME
unset CODEX_TEST_REMOTE_EXEC_SERVER_PID
unset CODEX_TEST_REMOTE_EXEC_SERVER_URL
unset CODEX_TEST_ENVIRONMENT
}
if ! is_sourced; then
@@ -128,6 +132,7 @@ set -euo pipefail
if setup_remote_env; then
status=0
echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}"
echo "CODEX_TEST_ENVIRONMENT=${CODEX_TEST_ENVIRONMENT}"
echo "CODEX_TEST_REMOTE_EXEC_SERVER_URL=${CODEX_TEST_REMOTE_EXEC_SERVER_URL}"
echo "Remote env ready. Run your command, then call: codex_remote_env_cleanup"
else