codex: fix cross-platform OAuth race tests

This commit is contained in:
Adam Perry
2026-06-05 09:11:20 +00:00
parent 5e33617335
commit 9760a978b9
3 changed files with 83 additions and 19 deletions
+4 -6
View File
@@ -736,13 +736,10 @@ fn os_shared_temp_dir() -> Result<PathBuf> {
use windows_sys::Win32::System::SystemInformation::GetSystemWindowsDirectoryW;
let mut buffer = vec![0_u16; 32_768];
let buffer_len =
u32::try_from(buffer.len()).context("Windows path buffer length did not fit u32")?;
// SAFETY: buffer is writable for the length passed to GetSystemWindowsDirectoryW.
let length = unsafe {
GetSystemWindowsDirectoryW(
buffer.as_mut_ptr(),
u32::try_from(buffer.len()).expect("Windows path buffer length fits in u32"),
)
};
let length = unsafe { GetSystemWindowsDirectoryW(buffer.as_mut_ptr(), buffer_len) };
if length == 0 {
return Err(io::Error::last_os_error())
.context("failed to resolve the system Windows directory");
@@ -1321,6 +1318,7 @@ mod tests {
}
}
#[cfg(unix)]
fn path(&self) -> &Path {
self._dir.path()
}
+51 -5
View File
@@ -1119,57 +1119,98 @@ async fn create_oauth_transport_and_runtime(
#[cfg(test)]
mod tests {
#[cfg(unix)]
use std::ffi::OsString;
#[cfg(unix)]
use std::fs::OpenOptions;
#[cfg(unix)]
use std::path::Path;
#[cfg(unix)]
use std::path::PathBuf;
#[cfg(unix)]
use std::sync::Arc;
use std::time::Duration;
#[cfg(unix)]
use std::time::SystemTime;
#[cfg(unix)]
use std::time::UNIX_EPOCH;
#[cfg(unix)]
use anyhow::Context;
#[cfg(unix)]
use codex_exec_server::Environment;
#[cfg(unix)]
use oauth2::AccessToken;
#[cfg(unix)]
use oauth2::RefreshToken;
#[cfg(unix)]
use oauth2::basic::BasicTokenType;
use pretty_assertions::assert_eq;
#[cfg(unix)]
use rmcp::model::ClientCapabilities;
#[cfg(unix)]
use rmcp::model::Implementation;
#[cfg(unix)]
use rmcp::model::ProtocolVersion;
#[cfg(unix)]
use rmcp::transport::auth::CredentialStore;
#[cfg(unix)]
use rmcp::transport::auth::OAuthTokenResponse;
#[cfg(unix)]
use rmcp::transport::auth::StoredCredentials;
#[cfg(unix)]
use rmcp::transport::auth::VendorExtraTokenFields;
#[cfg(unix)]
use serde_json::Value;
#[cfg(unix)]
use serde_json::json;
#[cfg(unix)]
use sha2::Digest;
#[cfg(unix)]
use sha2::Sha256;
#[cfg(unix)]
use tempfile::TempDir;
use tokio::time;
#[cfg(unix)]
use wiremock::Mock;
#[cfg(unix)]
use wiremock::MockServer;
#[cfg(unix)]
use wiremock::Request;
#[cfg(unix)]
use wiremock::ResponseTemplate;
#[cfg(unix)]
use wiremock::matchers::method;
#[cfg(unix)]
use wiremock::matchers::path;
use super::*;
#[cfg(unix)]
use crate::oauth::WrappedOAuthTokenResponse;
#[cfg(unix)]
use crate::oauth::load_oauth_tokens;
#[cfg(unix)]
use crate::oauth::save_oauth_tokens;
#[cfg(unix)]
const OPERATION_TEST_SERVER_NAME: &str = "test-completed-operation-persistence";
#[cfg(unix)]
const OPERATION_TEST_CLIENT_ID: &str = "test-operation-client-id";
#[cfg(unix)]
const OPERATION_TEST_OLD_ACCESS_TOKEN: &str = "old-operation-access-token";
#[cfg(unix)]
const OPERATION_TEST_OLD_REFRESH_TOKEN: &str = "old-operation-refresh-token";
#[cfg(unix)]
const OPERATION_TEST_NEW_ACCESS_TOKEN: &str = "new-operation-access-token";
#[cfg(unix)]
const OPERATION_TEST_NEW_REFRESH_TOKEN: &str = "new-operation-refresh-token";
#[cfg(unix)]
struct CodexHomeGuard {
original: Option<OsString>,
}
#[cfg(unix)]
impl CodexHomeGuard {
fn set(path: &Path) -> Self {
let original = std::env::var_os("CODEX_HOME");
@@ -1181,6 +1222,7 @@ mod tests {
}
}
#[cfg(unix)]
impl Drop for CodexHomeGuard {
fn drop(&mut self) {
if let Some(original) = &self.original {
@@ -1346,7 +1388,7 @@ mod tests {
.write(true)
.create(true)
.truncate(false)
.open(operation_test_fallback_lock_path(codex_home.path()))?;
.open(operation_test_fallback_lock_path(codex_home.path())?)?;
fallback_lock.lock_shared()?;
let new_response = operation_test_token_response(
@@ -1445,6 +1487,7 @@ mod tests {
Ok(())
}
#[cfg(unix)]
fn operation_test_token_response(
access_token: &str,
refresh_token: &str,
@@ -1460,18 +1503,20 @@ mod tests {
}
#[cfg(unix)]
fn operation_test_fallback_lock_path(codex_home: &Path) -> PathBuf {
fn operation_test_fallback_lock_path(codex_home: &Path) -> Result<PathBuf> {
let canonical_home = codex_home.canonicalize()?;
let mut hasher = Sha256::new();
hasher.update(codex_home.as_os_str().to_string_lossy().as_bytes());
hasher.update(canonical_home.as_os_str().to_string_lossy().as_bytes());
let digest = format!("{:x}", hasher.finalize());
// SAFETY: getuid has no preconditions and returns the real UID.
let user_namespace = format!("uid-{}", unsafe { libc::getuid() });
PathBuf::from("/tmp").join(format!(
Ok(PathBuf::from("/tmp").join(format!(
"codex-mcp-oauth-fallback-{user_namespace}-{}.lock",
&digest[..16]
))
)))
}
#[cfg(unix)]
fn unix_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -1479,6 +1524,7 @@ mod tests {
.as_millis() as u64
}
#[cfg(unix)]
fn unix_seconds() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -1669,6 +1669,7 @@ fn spawn_client_child_held_open(
Ok(command.spawn()?)
}
#[cfg(unix)]
fn spawn_client_child_with_attempt(
codex_home: &TempDir,
server_url: &str,
@@ -1745,6 +1746,7 @@ fn spawn_operation_timeout_child_held_open(
Ok(command.spawn()?)
}
#[cfg(unix)]
fn spawn_concurrent_waiter_child(
codex_home: &TempDir,
server_url: &str,
@@ -1769,6 +1771,7 @@ fn spawn_concurrent_waiter_child(
Ok(command.spawn()?)
}
#[cfg(unix)]
fn spawn_read_only_refresh_child(
codex_home: &TempDir,
server_url: &str,
@@ -1861,6 +1864,7 @@ fn file_oauth_refresh_lock_path(codex_home: &TempDir, server_url: &str) -> anyho
)))
}
#[cfg(unix)]
fn legacy_file_oauth_refresh_lock_path(
codex_home: &TempDir,
server_url: &str,
@@ -1885,8 +1889,10 @@ fn fallback_lock_path_and_user_namespace(
codex_home: &TempDir,
) -> anyhow::Result<(PathBuf, String)> {
let canonical_home = codex_home.path().canonicalize()?;
let codex_home_id =
sha_256_bytes_prefix(canonical_home.as_os_str().to_string_lossy().as_bytes());
let canonical_home = canonical_home.as_os_str().to_string_lossy().into_owned();
#[cfg(windows)]
let canonical_home = normalize_windows_device_path(canonical_home);
let codex_home_id = sha_256_bytes_prefix(canonical_home.as_bytes());
let prefix = format!("{FALLBACK_LOCK_PREFIX}-");
let suffix = format!("-{codex_home_id}.lock");
for entry in fs::read_dir(shared_lock_root()?)? {
@@ -1917,13 +1923,10 @@ fn shared_lock_root() -> anyhow::Result<PathBuf> {
use windows_sys::Win32::System::SystemInformation::GetSystemWindowsDirectoryW;
let mut buffer = vec![0_u16; 32_768];
let buffer_len =
u32::try_from(buffer.len()).context("Windows path buffer length did not fit u32")?;
// SAFETY: buffer is writable for the length passed to GetSystemWindowsDirectoryW.
let length = unsafe {
GetSystemWindowsDirectoryW(
buffer.as_mut_ptr(),
u32::try_from(buffer.len()).expect("Windows path buffer length fits in u32"),
)
};
let length = unsafe { GetSystemWindowsDirectoryW(buffer.as_mut_ptr(), buffer_len) };
if length == 0 {
return Err(io::Error::last_os_error())
.context("failed to resolve the system Windows directory");
@@ -1936,6 +1939,23 @@ fn shared_lock_root() -> anyhow::Result<PathBuf> {
Ok(PathBuf::from(OsString::from_wide(&buffer[..length])).join("Temp"))
}
#[cfg(windows)]
fn normalize_windows_device_path(path: String) -> String {
if let Some(unc) = path.strip_prefix(r"\\?\UNC\") {
return format!(r"\\{unc}");
}
if let Some(unc) = path.strip_prefix(r"\\.\UNC\") {
return format!(r"\\{unc}");
}
if let Some(path) = path
.strip_prefix(r"\\?\")
.or_else(|| path.strip_prefix(r"\\.\"))
{
return path.to_string();
}
path
}
#[cfg(not(any(unix, windows)))]
fn shared_lock_root() -> anyhow::Result<PathBuf> {
anyhow::bail!("OAuth refresh race tests do not support this platform")