mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
codex: fix cross-platform OAuth race tests
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user