mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Remove CODEX_RS_SSE_FIXTURE test hook (#22413)
## Why `CODEX_RS_SSE_FIXTURE` let integration-style CLI, exec, and TUI tests bypass the normal Responses transport by reading SSE from local files. That kept test-only behavior wired through production client code. The affected tests can stay hermetic by using the existing `core_test_support::responses` mock server and passing `openai_base_url` instead. ## What Changed - Removed the `CODEX_RS_SSE_FIXTURE` flag, `codex_api::stream_from_fixture`, the `env-flags` dependency, and the checked-in SSE fixture files. - Repointed the affected core, exec, and TUI tests at `MockServer` with the existing SSE event constructors. - Removed the Bazel test data plumbing for the deleted fixtures and refreshed cargo/Bazel lock state. ## Verification - `cargo build -p codex-cli` - `cargo test -p codex-api` - `cargo test -p codex-core --test all responses_api_stream_cli` - `cargo test -p codex-core --test all integration_creates_and_checks_session_file` - `cargo test -p codex-exec --test all ephemeral` - `cargo test -p codex-exec --test all resume` - `cargo test -p codex-tui --test all resume_startup_does_not_consume_model_availability_nux_count` - `just bazel-lock-update` - `just bazel-lock-check` - `just fix -p codex-api -p codex-core -p codex-exec -p codex-tui` - `git diff --check`
This commit is contained in:
committed by
GitHub
Unverified
parent
913aad4d3c
commit
96833c5b15
Generated
-1
@@ -887,7 +887,6 @@
|
||||
"enum-as-inner_0.6.1": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
|
||||
"enumflags2_0.7.12": "{\"dependencies\":[{\"name\":\"enumflags2_derive\",\"req\":\"=0.7.12\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"std\":[]}}",
|
||||
"enumflags2_derive_0.7.12": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"derive\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
|
||||
"env-flags_0.1.1": "{\"dependencies\":[],\"features\":{}}",
|
||||
"env_filter_1.0.0": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.8\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}",
|
||||
"env_filter_1.0.1": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.29\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.12.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}",
|
||||
"env_home_0.1.0": "{\"dependencies\":[],\"features\":{}}",
|
||||
|
||||
Generated
+2
-7
@@ -2536,7 +2536,6 @@ dependencies = [
|
||||
"ctor 0.6.3",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"env-flags",
|
||||
"eventsource-stream",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
@@ -3791,6 +3790,7 @@ dependencies = [
|
||||
"codex-utils-string",
|
||||
"codex-windows-sandbox",
|
||||
"color-eyre",
|
||||
"core_test_support",
|
||||
"cpal",
|
||||
"crossterm",
|
||||
"derive_more 2.1.1",
|
||||
@@ -3841,6 +3841,7 @@ dependencies = [
|
||||
"which 8.0.0",
|
||||
"windows-sys 0.52.0",
|
||||
"winsplit",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5389,12 +5390,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env-flags"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbfd0e7fc632dec5e6c9396a27bc9f9975b4e039720e1fd3e34021d3ce28c415"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -280,7 +280,6 @@ dotenvy = "0.15.7"
|
||||
dunce = "1.0.4"
|
||||
ed25519-dalek = { version = "2.2.0", features = ["pkcs8"] }
|
||||
encoding_rs = "0.8.35"
|
||||
env-flags = "0.1.1"
|
||||
env_logger = "0.11.9"
|
||||
eventsource-stream = "0.2.3"
|
||||
flate2 = "1.1.8"
|
||||
|
||||
@@ -63,7 +63,6 @@ pub use crate::provider::Provider;
|
||||
pub use crate::provider::RetryConfig;
|
||||
pub use crate::provider::is_azure_responses_provider;
|
||||
pub use crate::requests::Compression;
|
||||
pub use crate::sse::stream_from_fixture;
|
||||
pub use crate::telemetry::SseTelemetry;
|
||||
pub use crate::telemetry::WebsocketTelemetry;
|
||||
pub use codex_protocol::protocol::RealtimeAudioFrame;
|
||||
|
||||
@@ -3,4 +3,3 @@ pub(crate) mod responses;
|
||||
pub(crate) use responses::ResponsesStreamEvent;
|
||||
pub(crate) use responses::process_responses_event;
|
||||
pub use responses::spawn_response_stream;
|
||||
pub use responses::stream_from_fixture;
|
||||
|
||||
@@ -5,24 +5,19 @@ use crate::rate_limits::parse_all_rate_limits;
|
||||
use crate::telemetry::SseTelemetry;
|
||||
use codex_client::ByteStream;
|
||||
use codex_client::StreamResponse;
|
||||
use codex_client::TransportError;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::ModelVerification;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use eventsource_stream::Eventsource;
|
||||
use futures::StreamExt;
|
||||
use futures::TryStreamExt;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::io::BufRead;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::Instant;
|
||||
use tokio::time::timeout;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tracing::debug;
|
||||
use tracing::trace;
|
||||
|
||||
@@ -31,35 +26,6 @@ const OPENAI_MODEL_HEADER: &str = "openai-model";
|
||||
const REQUEST_ID_HEADER: &str = "x-request-id";
|
||||
const TRUSTED_ACCESS_FOR_CYBER_VERIFICATION: &str = "trusted_access_for_cyber";
|
||||
|
||||
/// Streams SSE events from an on-disk fixture for tests.
|
||||
pub fn stream_from_fixture(
|
||||
path: impl AsRef<Path>,
|
||||
idle_timeout: Duration,
|
||||
) -> Result<ResponseStream, ApiError> {
|
||||
let file =
|
||||
std::fs::File::open(path.as_ref()).map_err(|err| ApiError::Stream(err.to_string()))?;
|
||||
let mut content = String::new();
|
||||
for line in std::io::BufReader::new(file).lines() {
|
||||
let line = line.map_err(|err| ApiError::Stream(err.to_string()))?;
|
||||
content.push_str(&line);
|
||||
content.push_str("\n\n");
|
||||
}
|
||||
|
||||
let reader = std::io::Cursor::new(content);
|
||||
let stream = ReaderStream::new(reader).map_err(|err| TransportError::Network(err.to_string()));
|
||||
let (tx_event, rx_event) = mpsc::channel::<Result<ResponseEvent, ApiError>>(1600);
|
||||
tokio::spawn(process_sse(
|
||||
Box::pin(stream),
|
||||
tx_event,
|
||||
idle_timeout,
|
||||
/*telemetry*/ None,
|
||||
));
|
||||
Ok(ResponseStream {
|
||||
rx_event,
|
||||
upstream_request_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn_response_stream(
|
||||
stream_response: StreamResponse,
|
||||
idle_timeout: Duration,
|
||||
@@ -593,8 +559,10 @@ mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use bytes::Bytes;
|
||||
use codex_client::StreamResponse;
|
||||
use codex_client::TransportError;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use futures::TryStreamExt;
|
||||
use futures::stream;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
@@ -603,6 +571,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_test::io::Builder as IoBuilder;
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
async fn collect_events(chunks: &[&[u8]]) -> Vec<Result<ResponseEvent, ApiError>> {
|
||||
let mut builder = IoBuilder::new();
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
filegroup(
|
||||
name = "model_availability_nux_fixtures",
|
||||
srcs = [
|
||||
"tests/cli_responses_fixture.sse",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
codex_rust_crate(
|
||||
name = "core",
|
||||
crate_name = "codex_core",
|
||||
|
||||
@@ -77,7 +77,6 @@ codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-
|
||||
csv = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
env-flags = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
|
||||
@@ -112,7 +112,6 @@ use crate::client_common::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::client_common::ResponseStream;
|
||||
use crate::feedback_tags;
|
||||
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||
use crate::util::emit_feedback_auth_recovery_tags;
|
||||
use codex_api::map_api_error;
|
||||
use codex_feedback::FeedbackRequestTags;
|
||||
@@ -772,7 +771,6 @@ impl ModelClient {
|
||||
pub fn responses_websocket_enabled(&self) -> bool {
|
||||
if !self.state.provider.info().supports_websockets
|
||||
|| self.state.disable_websockets.load(Ordering::Relaxed)
|
||||
|| (*CODEX_RS_SSE_FIXTURE).is_some()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1194,8 +1192,7 @@ impl ModelClientSession {
|
||||
|
||||
/// Streams a turn via the OpenAI Responses API.
|
||||
///
|
||||
/// Handles SSE fixtures, reasoning summaries, verbosity, and the
|
||||
/// `text` controls used for output schemas.
|
||||
/// Handles reasoning summaries, verbosity, and the `text` controls used for output schemas.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(
|
||||
name = "model_client.stream_responses_api",
|
||||
@@ -1221,21 +1218,6 @@ impl ModelClientSession {
|
||||
turn_metadata_header: Option<&str>,
|
||||
inference_trace: &InferenceTraceContext,
|
||||
) -> Result<ResponseStream> {
|
||||
if let Some(path) = &*CODEX_RS_SSE_FIXTURE {
|
||||
warn!(path, "Streaming from fixture");
|
||||
let stream = codex_api::stream_from_fixture(
|
||||
path,
|
||||
self.client.state.provider.info().stream_idle_timeout(),
|
||||
)
|
||||
.map_err(map_api_error)?;
|
||||
let (stream, _last_request_rx) = map_response_stream(
|
||||
stream,
|
||||
session_telemetry.clone(),
|
||||
InferenceTraceAttempt::disabled(),
|
||||
);
|
||||
return Ok(stream);
|
||||
}
|
||||
|
||||
let auth_manager = self.client.state.provider.auth_manager();
|
||||
let mut auth_recovery = auth_manager
|
||||
.as_ref()
|
||||
|
||||
@@ -7250,8 +7250,7 @@ model_verbosity = "high"
|
||||
/// 2. as part of a profile, where the `--profile` is specified via a CLI
|
||||
/// (or in the config file itself)
|
||||
/// 3. as an entry in `config.toml`, e.g. `model = "o3"`
|
||||
/// 4. the default value for a required field defined in code, e.g.,
|
||||
/// `crate::flags::OPENAI_DEFAULT_MODEL`
|
||||
/// 4. the default value for a required field defined in code.
|
||||
///
|
||||
/// Note that profiles are the recommended way to specify a group of
|
||||
/// configuration options together.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
use env_flags::env_flags;
|
||||
|
||||
env_flags! {
|
||||
/// Fixture path for offline tests (see client.rs).
|
||||
pub CODEX_RS_SSE_FIXTURE: Option<&str> = None;
|
||||
}
|
||||
@@ -35,7 +35,6 @@ mod environment_selection;
|
||||
pub mod exec;
|
||||
pub mod exec_env;
|
||||
mod exec_policy;
|
||||
mod flags;
|
||||
#[cfg(test)]
|
||||
mod git_info_tests;
|
||||
mod goals;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
event: response.created
|
||||
data: {"type":"response.created","response":{"id":"resp1"}}
|
||||
|
||||
event: response.output_item.done
|
||||
data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"fixture hello"}]}}
|
||||
|
||||
event: response.completed
|
||||
data: {"type":"response.completed","response":{"id":"resp1","output":[]}}
|
||||
@@ -4,9 +4,6 @@ codex_rust_crate(
|
||||
name = "common",
|
||||
crate_name = "core_test_support",
|
||||
crate_srcs = glob(["*.rs"]),
|
||||
lib_data_extra = [
|
||||
"//codex-rs/core:model_availability_nux_fixtures",
|
||||
],
|
||||
deps_extra = [
|
||||
"@crates//:similar",
|
||||
],
|
||||
|
||||
@@ -2,7 +2,6 @@ use assert_cmd::Command as AssertCommand;
|
||||
use codex_git_utils::collect_git_info;
|
||||
use codex_login::CODEX_API_KEY_ENV_VAR;
|
||||
use codex_protocol::protocol::GitInfo;
|
||||
use codex_utils_cargo_bin::find_resource;
|
||||
use core_test_support::fs_wait;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
@@ -16,9 +15,12 @@ fn repo_root() -> std::path::PathBuf {
|
||||
codex_utils_cargo_bin::repo_root().expect("failed to resolve repo root")
|
||||
}
|
||||
|
||||
fn cli_responses_fixture() -> std::path::PathBuf {
|
||||
#[expect(clippy::expect_used)]
|
||||
find_resource!("tests/cli_responses_fixture.sse").expect("failed to resolve fixture path")
|
||||
fn cli_sse_response() -> String {
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-fixture"),
|
||||
responses::ev_assistant_message("msg-fixture", "fixture hello"),
|
||||
responses::ev_completed("resp-fixture"),
|
||||
])
|
||||
}
|
||||
|
||||
/// Tests streaming the Responses API through the CLI using a mock server.
|
||||
@@ -262,37 +264,36 @@ async fn exec_cli_profile_applies_model_instructions_file() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Tests streaming responses through the CLI using a local SSE fixture file.
|
||||
/// This test:
|
||||
/// 1. Uses a pre-recorded SSE response fixture instead of a live server
|
||||
/// 2. Configures codex to read from this fixture via CODEX_RS_SSE_FIXTURE env var
|
||||
/// 3. Sends a "hello?" prompt and verifies the response
|
||||
/// 4. Ensures the fixture content is correctly streamed through the CLI
|
||||
/// Tests streaming responses through the CLI using a local Responses API server.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn responses_api_stream_cli() {
|
||||
skip_if_no_network!();
|
||||
|
||||
let fixture = cli_responses_fixture();
|
||||
let server = MockServer::start().await;
|
||||
let resp_mock = responses::mount_sse_once(&server, cli_sse_response()).await;
|
||||
let repo_root = repo_root();
|
||||
|
||||
let home = TempDir::new().unwrap();
|
||||
let bin = codex_utils_cargo_bin::cargo_bin("codex").unwrap();
|
||||
let mut cmd = AssertCommand::new(bin);
|
||||
cmd.timeout(Duration::from_secs(30));
|
||||
cmd.arg("exec")
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-c")
|
||||
.arg("openai_base_url=\"http://unused.local\"")
|
||||
.arg(format!("openai_base_url=\"{}/v1\"", server.uri()))
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
.arg("hello?");
|
||||
cmd.env("CODEX_HOME", home.path())
|
||||
.env("OPENAI_API_KEY", "dummy")
|
||||
.env("CODEX_RS_SSE_FIXTURE", fixture);
|
||||
.env("OPENAI_API_KEY", "dummy");
|
||||
|
||||
let output = cmd.output().unwrap();
|
||||
assert!(output.status.success());
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.contains("fixture hello"));
|
||||
|
||||
let request = resp_mock.single_request();
|
||||
assert_eq!(request.path(), "/v1/responses");
|
||||
}
|
||||
|
||||
/// End-to-end: create a session (writes rollout), verify the file, then resume and confirm append.
|
||||
@@ -308,23 +309,25 @@ async fn integration_creates_and_checks_session_file() -> anyhow::Result<()> {
|
||||
let marker = format!("integration-test-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
// 3. Use the same offline SSE fixture as responses_api_stream_cli so the test is hermetic.
|
||||
let fixture = cli_responses_fixture();
|
||||
// 3. Serve two hermetic SSE responses, one for the initial run and one for resume.
|
||||
let server = MockServer::start().await;
|
||||
let resp_mock =
|
||||
responses::mount_sse_sequence(&server, vec![cli_sse_response(), cli_sse_response()]).await;
|
||||
let repo_root = repo_root();
|
||||
|
||||
// 4. Run the codex CLI and invoke `exec`, which is what records a session.
|
||||
let bin = codex_utils_cargo_bin::cargo_bin("codex").unwrap();
|
||||
let mut cmd = AssertCommand::new(bin);
|
||||
cmd.timeout(Duration::from_secs(30));
|
||||
cmd.arg("exec")
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-c")
|
||||
.arg("openai_base_url=\"http://unused.local\"")
|
||||
.arg(format!("openai_base_url=\"{}/v1\"", server.uri()))
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
.arg(&prompt);
|
||||
cmd.env("CODEX_HOME", home.path())
|
||||
.env(CODEX_API_KEY_ENV_VAR, "dummy")
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture);
|
||||
.env(CODEX_API_KEY_ENV_VAR, "dummy");
|
||||
|
||||
let output = cmd.output().unwrap();
|
||||
assert!(
|
||||
@@ -436,21 +439,22 @@ async fn integration_creates_and_checks_session_file() -> anyhow::Result<()> {
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
let bin2 = codex_utils_cargo_bin::cargo_bin("codex").unwrap();
|
||||
let mut cmd2 = AssertCommand::new(bin2);
|
||||
cmd2.timeout(Duration::from_secs(30));
|
||||
cmd2.arg("exec")
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-c")
|
||||
.arg("openai_base_url=\"http://unused.local\"")
|
||||
.arg(format!("openai_base_url=\"{}/v1\"", server.uri()))
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
.arg(&prompt2)
|
||||
.arg("resume")
|
||||
.arg("--last");
|
||||
cmd2.env("CODEX_HOME", home.path())
|
||||
.env("OPENAI_API_KEY", "dummy")
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture);
|
||||
.env("OPENAI_API_KEY", "dummy");
|
||||
|
||||
let output2 = cmd2.output().unwrap();
|
||||
assert!(output2.status.success(), "resume codex-cli run failed");
|
||||
assert_eq!(resp_mock.requests().len(), 2);
|
||||
|
||||
// Find the new session file containing the resumed marker.
|
||||
let marker2_clone = marker2.clone();
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ allow = [
|
||||
# Used by: transitive only
|
||||
"ISC",
|
||||
# MIT - https://opensource.org/license/mit
|
||||
# Used by: allocative, ansi-to-tui, anyhow, arboard, assert_cmd, assert_matches, async-channel, async-stream, async-trait, axum, base64, bytes, chardetng, chrono, clap, clap_complete, color-eyre, crossterm, ctor, derive_more, diffy, dirs, dotenvy, encoding_rs, env-flags, env_logger, escargot, eventsource-stream, futures, http, ignore, image, indexmap, itertools, keyring, landlock, lazy_static, libc, log, lru, maplit, mime_guess, multimap, once_cell, openssl-sys, os_info, owo-colors, path-absolutize, pathdiff, portable-pty, predicates, pretty_assertions, pulldown-cmark, rand, ratatui, ratatui-macros, regex-lite, reqwest, rmcp, schemars, serde, serde_json, serde_with, serial_test, sha1, sha2, shlex, socket2, strum, strum_macros, sys-locale, tempfile, test-log, textwrap, thiserror, time, tiny_http, tokio, tokio-stream, tokio-test, tokio-util, toml, toml_edit, tonic, tracing, tracing-appender, tracing-subscriber, tracing-test, tree-sitter, tree-sitter-bash, tree-sitter-highlight, ts-rs, uds_windows, unicode-segmentation, unicode-width, url, urlencoding, uuid, vt100, walkdir, webbrowser, which, wildmatch, wiremock, zeroize
|
||||
# Used by: allocative, ansi-to-tui, anyhow, arboard, assert_cmd, assert_matches, async-channel, async-stream, async-trait, axum, base64, bytes, chardetng, chrono, clap, clap_complete, color-eyre, crossterm, ctor, derive_more, diffy, dirs, dotenvy, encoding_rs, env_logger, escargot, eventsource-stream, futures, http, ignore, image, indexmap, itertools, keyring, landlock, lazy_static, libc, log, lru, maplit, mime_guess, multimap, once_cell, openssl-sys, os_info, owo-colors, path-absolutize, pathdiff, portable-pty, predicates, pretty_assertions, pulldown-cmark, rand, ratatui, ratatui-macros, regex-lite, reqwest, rmcp, schemars, serde, serde_json, serde_with, serial_test, sha1, sha2, shlex, socket2, strum, strum_macros, sys-locale, tempfile, test-log, textwrap, thiserror, time, tiny_http, tokio, tokio-stream, tokio-test, tokio-util, toml, toml_edit, tonic, tracing, tracing-appender, tracing-subscriber, tracing-test, tree-sitter, tree-sitter-bash, tree-sitter-highlight, ts-rs, uds_windows, unicode-segmentation, unicode-width, url, urlencoding, uuid, vt100, walkdir, webbrowser, which, wildmatch, wiremock, zeroize
|
||||
"MIT",
|
||||
# MIT-0 - https://opensource.org/license/mit-0
|
||||
# Used by: dunce
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
event: response.created
|
||||
data: {"type":"response.created","response":{"id":"resp1"}}
|
||||
|
||||
event: response.output_item.done
|
||||
data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"fixture hello"}]}}
|
||||
|
||||
event: response.completed
|
||||
data: {"type":"response.completed","response":{"id":"resp1","output":[]}}
|
||||
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
#![cfg(not(target_os = "windows"))]
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use codex_utils_cargo_bin::find_resource;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::test_codex_exec::test_codex_exec;
|
||||
use walkdir::WalkDir;
|
||||
use wiremock::MockServer;
|
||||
|
||||
fn exec_sse_response() -> String {
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-ephemeral"),
|
||||
responses::ev_assistant_message("msg-ephemeral", "ephemeral response"),
|
||||
responses::ev_completed("resp-ephemeral"),
|
||||
])
|
||||
}
|
||||
|
||||
fn session_rollout_count(home_path: &std::path::Path) -> usize {
|
||||
let sessions_dir = home_path.join("sessions");
|
||||
@@ -19,13 +29,15 @@ fn session_rollout_count(home_path: &std::path::Path) -> usize {
|
||||
.count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persists_rollout_file_by_default() -> anyhow::Result<()> {
|
||||
let test = test_codex_exec();
|
||||
let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?;
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn persists_rollout_file_by_default() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
let test = test_codex_exec();
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = responses::mount_sse_once(&server, exec_sse_response()).await;
|
||||
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("default persistence behavior")
|
||||
.assert()
|
||||
@@ -35,13 +47,15 @@ fn persists_rollout_file_by_default() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> {
|
||||
let test = test_codex_exec();
|
||||
let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?;
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
let test = test_codex_exec();
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = responses::mount_sse_once(&server, exec_sse_response()).await;
|
||||
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("--ephemeral")
|
||||
.arg("ephemeral behavior")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
use anyhow::Context;
|
||||
use codex_utils_cargo_bin::find_resource;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::test_codex_exec::test_codex_exec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
@@ -8,6 +9,7 @@ use std::string::ToString;
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
use walkdir::WalkDir;
|
||||
use wiremock::MockServer;
|
||||
|
||||
/// Utility: scan the sessions dir for a rollout file that contains `marker`
|
||||
/// in any response_item.message.content entry. Returns the absolute path.
|
||||
@@ -104,26 +106,41 @@ fn last_user_image_count(path: &std::path::Path) -> usize {
|
||||
last_count
|
||||
}
|
||||
|
||||
fn exec_fixture() -> anyhow::Result<std::path::PathBuf> {
|
||||
Ok(find_resource!("tests/fixtures/cli_responses_fixture.sse")?)
|
||||
}
|
||||
|
||||
fn exec_repo_root() -> anyhow::Result<std::path::PathBuf> {
|
||||
Ok(codex_utils_cargo_bin::repo_root()?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
fn exec_sse_response(index: usize) -> String {
|
||||
let response_id = format!("resp-exec-{index}");
|
||||
let message_id = format!("msg-exec-{index}");
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created(&response_id),
|
||||
responses::ev_assistant_message(&message_id, "exec response"),
|
||||
responses::ev_completed(&response_id),
|
||||
])
|
||||
}
|
||||
|
||||
async fn mount_exec_responses(
|
||||
server: &MockServer,
|
||||
count: usize,
|
||||
) -> core_test_support::responses::ResponseMock {
|
||||
responses::mount_sse_sequence(server, (0..count).map(exec_sse_response).collect()).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
let repo_root = exec_repo_root()?;
|
||||
|
||||
// 1) First run: create a session with a unique marker in the content.
|
||||
let marker = format!("resume-last-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -140,8 +157,7 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
let marker2 = format!("resume-last-2-{}", Uuid::new_v4());
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -164,18 +180,20 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
let repo_root = exec_repo_root()?;
|
||||
|
||||
// 1) First run: create a session with a unique marker in the content.
|
||||
let marker = format!("resume-last-json-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -192,8 +210,7 @@ fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<(
|
||||
let marker2 = format!("resume-last-json-2-{}", Uuid::new_v4());
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -216,18 +233,20 @@ fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 5).await;
|
||||
|
||||
let dir_a = TempDir::new()?;
|
||||
let dir_b = TempDir::new()?;
|
||||
|
||||
let marker_a = format!("resume-cwd-a-{}", Uuid::new_v4());
|
||||
let prompt_a = format!("echo {marker_a}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(dir_a.path())
|
||||
@@ -237,8 +256,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
|
||||
let marker_b = format!("resume-cwd-b-{}", Uuid::new_v4());
|
||||
let prompt_b = format!("echo {marker_b}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(dir_b.path())
|
||||
@@ -260,8 +278,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
let session_id_b = extract_conversation_id(&path_b);
|
||||
let marker_b_touch = format!("resume-cwd-b-touch-{}", Uuid::new_v4());
|
||||
let prompt_b_touch = format!("echo {marker_b_touch}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(dir_b.path())
|
||||
@@ -278,8 +295,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
|
||||
let marker_b2 = format!("resume-cwd-b-2-{}", Uuid::new_v4());
|
||||
let prompt_b2 = format!("echo {marker_b2}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(dir_a.path())
|
||||
@@ -299,8 +315,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
|
||||
let marker_a2 = format!("resume-cwd-a-2-{}", Uuid::new_v4());
|
||||
let prompt_a2 = format!("echo {marker_a2}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(dir_a.path())
|
||||
@@ -323,24 +338,29 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
|
||||
// Seed a session.
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("echo seed-resume-session")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Resume while passing global flags after the subcommand to ensure clap accepts them.
|
||||
let base = format!("{}/v1", server.uri());
|
||||
let base_config = format!("openai_base_url={}", serde_json::to_string(&base)?);
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
.arg("resume")
|
||||
.arg("--last")
|
||||
.arg("--config")
|
||||
.arg(base_config)
|
||||
.arg("--json")
|
||||
.arg("--model")
|
||||
.arg("gpt-5.2-codex")
|
||||
@@ -355,18 +375,20 @@ fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
let repo_root = exec_repo_root()?;
|
||||
|
||||
// 1) First run: create a session
|
||||
let marker = format!("resume-by-id-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -387,8 +409,7 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
let marker2 = format!("resume-by-id-2-{}", Uuid::new_v4());
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -410,17 +431,19 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
let repo_root = exec_repo_root()?;
|
||||
|
||||
let marker = format!("resume-config-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("--sandbox")
|
||||
.arg("workspace-write")
|
||||
@@ -440,8 +463,7 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
|
||||
let output = test
|
||||
.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("--sandbox")
|
||||
.arg("workspace-write")
|
||||
@@ -484,17 +506,19 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let test = test_codex_exec();
|
||||
let fixture = exec_fixture()?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = mount_exec_responses(&server, /*count*/ 2).await;
|
||||
let repo_root = exec_repo_root()?;
|
||||
|
||||
let marker = format!("resume-image-{}", Uuid::new_v4());
|
||||
let prompt = format!("echo {marker}");
|
||||
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -516,8 +540,7 @@ fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> {
|
||||
|
||||
let marker2 = format!("resume-image-2-{}", Uuid::new_v4());
|
||||
let prompt2 = format!("echo {marker2}");
|
||||
test.cmd()
|
||||
.env("CODEX_RS_SSE_FIXTURE", &fixture)
|
||||
test.cmd_with_server(&server)
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
|
||||
@@ -18,7 +18,7 @@ codex_rust_crate(
|
||||
test_data_extra = glob([
|
||||
"src/**/*.rs",
|
||||
"src/**/snapshots/**",
|
||||
]) + ["//codex-rs/core:model_availability_nux_fixtures"],
|
||||
]),
|
||||
integration_compile_data_extra = ["src/test_backend.rs"],
|
||||
extra_binaries = [
|
||||
"//codex-rs/cli:codex",
|
||||
|
||||
@@ -152,6 +152,7 @@ arboard = { workspace = true }
|
||||
[dev-dependencies]
|
||||
codex-cli = { workspace = true }
|
||||
codex-mcp = { workspace = true }
|
||||
core_test_support = { workspace = true }
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
assert_matches = { workspace = true }
|
||||
@@ -162,3 +163,4 @@ rand = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
vt100 = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
wiremock = { workspace = true }
|
||||
|
||||
@@ -4,11 +4,14 @@ use std::time::Duration;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use serde_json::Value as JsonValue;
|
||||
use tempfile::tempdir;
|
||||
use tokio::select;
|
||||
use tokio::time::sleep;
|
||||
use tokio::time::timeout;
|
||||
use wiremock::MockServer;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_startup_does_not_consume_model_availability_nux_count() -> Result<()> {
|
||||
@@ -16,6 +19,7 @@ async fn resume_startup_does_not_consume_model_availability_nux_count() -> Resul
|
||||
if cfg!(windows) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let repo_root = codex_utils_cargo_bin::repo_root()?;
|
||||
let codex_home = tempdir()?;
|
||||
@@ -68,8 +72,14 @@ trust_level = "trusted"
|
||||
);
|
||||
std::fs::write(codex_home.path().join("config.toml"), config_contents)?;
|
||||
|
||||
let fixture_path =
|
||||
codex_utils_cargo_bin::find_resource!("../core/tests/cli_responses_fixture.sse")?;
|
||||
let server = MockServer::start().await;
|
||||
let sse = responses::sse(vec![
|
||||
responses::ev_response_created("resp-seed-session"),
|
||||
responses::ev_assistant_message("msg-seed-session", "seed session response"),
|
||||
responses::ev_completed("resp-seed-session"),
|
||||
]);
|
||||
let _response_mock = responses::mount_sse_once(&server, sse).await;
|
||||
let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri());
|
||||
let codex = if let Ok(path) = codex_utils_cargo_bin::cargo_bin("codex") {
|
||||
path
|
||||
} else {
|
||||
@@ -85,12 +95,13 @@ trust_level = "trusted"
|
||||
let exec_output = std::process::Command::new(&codex)
|
||||
.arg("exec")
|
||||
.arg("--skip-git-repo-check")
|
||||
.arg("-c")
|
||||
.arg(&openai_base_url_config)
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
.arg("seed session for resume")
|
||||
.env("CODEX_HOME", codex_home.path())
|
||||
.env("OPENAI_API_KEY", "dummy")
|
||||
.env("CODEX_RS_SSE_FIXTURE", fixture_path)
|
||||
.output()
|
||||
.context("failed to execute codex exec")?;
|
||||
anyhow::ensure!(
|
||||
@@ -114,6 +125,8 @@ trust_level = "trusted"
|
||||
repo_root.display().to_string(),
|
||||
"-c".to_string(),
|
||||
"analytics.enabled=false".to_string(),
|
||||
"-c".to_string(),
|
||||
openai_base_url_config,
|
||||
];
|
||||
|
||||
let spawned = codex_utils_pty::spawn_pty_process(
|
||||
|
||||
@@ -8,14 +8,18 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use tempfile::tempdir;
|
||||
use wiremock::MockServer;
|
||||
|
||||
#[test]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"]
|
||||
fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Result<()> {
|
||||
async fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Result<()> {
|
||||
if cfg!(windows) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_if_no_network!(Ok(()));
|
||||
if Command::new("tmux").arg("-V").output().is_err() {
|
||||
eprintln!("skipping resize smoke because tmux is unavailable");
|
||||
return Ok(());
|
||||
@@ -24,9 +28,9 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu
|
||||
let repo_root = codex_utils_cargo_bin::repo_root()?;
|
||||
let codex = codex_binary(&repo_root)?;
|
||||
let codex_home = tempdir()?;
|
||||
let fixture_dir = tempdir()?;
|
||||
let fixture = fixture_dir.path().join("resize-reflow.sse");
|
||||
write_fixture(&fixture)?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await;
|
||||
let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri());
|
||||
write_config(
|
||||
codex_home.path(),
|
||||
&repo_root,
|
||||
@@ -57,10 +61,11 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu
|
||||
.arg("env")
|
||||
.arg(format!("CODEX_HOME={}", codex_home.path().display()))
|
||||
.arg("OPENAI_API_KEY=dummy")
|
||||
.arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display()))
|
||||
.arg(codex)
|
||||
.arg("-c")
|
||||
.arg("analytics.enabled=false")
|
||||
.arg("-c")
|
||||
.arg(&openai_base_url_config)
|
||||
.arg("--no-alt-screen")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -162,29 +167,31 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"]
|
||||
fn tmux_repeated_resizes_do_not_push_composer_down() -> Result<()> {
|
||||
async fn tmux_repeated_resizes_do_not_push_composer_down() -> Result<()> {
|
||||
if cfg!(windows) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_if_no_network!(Ok(()));
|
||||
if Command::new("tmux").arg("-V").output().is_err() {
|
||||
eprintln!("skipping resize smoke because tmux is unavailable");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ false)?;
|
||||
run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ true)?;
|
||||
run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ false).await?;
|
||||
run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ true).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"]
|
||||
fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> {
|
||||
async fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> {
|
||||
if cfg!(windows) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_if_no_network!(Ok(()));
|
||||
if Command::new("tmux").arg("-V").output().is_err() {
|
||||
eprintln!("skipping resize smoke because tmux is unavailable");
|
||||
return Ok(());
|
||||
@@ -193,9 +200,9 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> {
|
||||
let repo_root = codex_utils_cargo_bin::repo_root()?;
|
||||
let codex = codex_binary(&repo_root)?;
|
||||
let codex_home = tempdir()?;
|
||||
let fixture_dir = tempdir()?;
|
||||
let fixture = fixture_dir.path().join("resize-reflow.sse");
|
||||
write_fixture(&fixture)?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await;
|
||||
let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri());
|
||||
write_config(
|
||||
codex_home.path(),
|
||||
&repo_root,
|
||||
@@ -226,10 +233,11 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> {
|
||||
.arg("env")
|
||||
.arg(format!("CODEX_HOME={}", codex_home.path().display()))
|
||||
.arg("OPENAI_API_KEY=dummy")
|
||||
.arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display()))
|
||||
.arg(codex)
|
||||
.arg("-c")
|
||||
.arg("analytics.enabled=false")
|
||||
.arg("-c")
|
||||
.arg(&openai_base_url_config)
|
||||
.arg("--no-alt-screen")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -312,13 +320,13 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()> {
|
||||
async fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()> {
|
||||
let repo_root = codex_utils_cargo_bin::repo_root()?;
|
||||
let codex = codex_binary(&repo_root)?;
|
||||
let codex_home = tempdir()?;
|
||||
let fixture_dir = tempdir()?;
|
||||
let fixture = fixture_dir.path().join("resize-reflow.sse");
|
||||
write_fixture(&fixture)?;
|
||||
let server = MockServer::start().await;
|
||||
let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await;
|
||||
let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri());
|
||||
write_config(
|
||||
codex_home.path(),
|
||||
&repo_root,
|
||||
@@ -354,10 +362,11 @@ fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()>
|
||||
.arg("env")
|
||||
.arg(format!("CODEX_HOME={}", codex_home.path().display()))
|
||||
.arg("OPENAI_API_KEY=dummy")
|
||||
.arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display()))
|
||||
.arg(codex)
|
||||
.arg("-c")
|
||||
.arg("analytics.enabled=false")
|
||||
.arg("-c")
|
||||
.arg(&openai_base_url_config)
|
||||
.arg("--no-alt-screen")
|
||||
.arg("-C")
|
||||
.arg(&repo_root)
|
||||
@@ -510,33 +519,13 @@ fn write_auth(codex_home: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_fixture(path: &Path) -> Result<()> {
|
||||
fn resize_reflow_sse() -> String {
|
||||
let text = "resize reflow sentinel says hi. This paragraph is intentionally long enough to exercise terminal wrapping, scrollback redraw, and pane resize behavior without requiring a live model response. It includes enough ordinary prose to wrap across several rows in a narrow tmux pane, then keep going so repeated split and restore cycles have visible history above the composer. If a resize path accidentally inserts blank rows or anchors the viewport lower on each pass, the composer row will drift after the pane returns to its original height.";
|
||||
let created = serde_json::json!({
|
||||
"type": "response.created",
|
||||
"response": { "id": "resp-resize-smoke" },
|
||||
});
|
||||
let done = serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{ "type": "output_text", "text": text }
|
||||
],
|
||||
},
|
||||
});
|
||||
let completed = serde_json::json!({
|
||||
"type": "response.completed",
|
||||
"response": { "id": "resp-resize-smoke", "output": [] },
|
||||
});
|
||||
let fixture = format!(
|
||||
"event: response.created\ndata: {created}\n\n\
|
||||
event: response.output_item.done\ndata: {done}\n\n\
|
||||
event: response.completed\ndata: {completed}\n\n"
|
||||
);
|
||||
std::fs::write(path, fixture)?;
|
||||
Ok(())
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-resize-smoke"),
|
||||
responses::ev_assistant_message("msg-resize-smoke", text),
|
||||
responses::ev_completed("resp-resize-smoke"),
|
||||
])
|
||||
}
|
||||
|
||||
fn wait_for_capture_contains(pane: &str, needle: &str, timeout: Duration) -> Result<String> {
|
||||
|
||||
Reference in New Issue
Block a user