mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
d007b0852a
commit
1fe89de576
@@ -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 }}
|
||||
|
||||
|
||||
@@ -2,5 +2,4 @@ package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files([
|
||||
"foreign_platform_binary.bzl",
|
||||
"wine.bzl",
|
||||
])
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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()
|
||||
},
|
||||
)
|
||||
@@ -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").
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
() => {{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(()));
|
||||
|
||||
|
||||
@@ -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,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"),
|
||||
])
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user