mirror of
https://github.com/openai/codex.git
synced 2026-05-01 18:06:47 +00:00
chore: move codex-exec unit tests into sibling files (#16581)
## Why `codex-rs/exec/src/lib.rs` already keeps unit tests in a sibling `lib_tests.rs` module so the implementation stays top-heavy and easier to read. This applies that same layout to the rest of `codex-rs/exec/src` so each production file keeps its entry points and helpers ahead of test code. ## What - Move inline unit tests out of `cli.rs`, `main.rs`, `event_processor_with_human_output.rs`, and `event_processor_with_jsonl_output.rs` into sibling `*_tests.rs` files. - Keep test modules wired through `#[cfg(test)]` plus `#[path = "..."] mod tests;`, matching the `lib.rs` pattern. - Preserve the existing test coverage and assertions while making this a source-layout-only refactor. ## Verification - `cargo test -p codex-exec`
This commit is contained in:
402
codex-rs/exec/src/lib_tests.rs
Normal file
402
codex-rs/exec/src/lib_tests.rs
Normal file
@@ -0,0 +1,402 @@
|
||||
use super::*;
|
||||
use codex_otel::set_parent_from_w3c_trace_context;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use opentelemetry::trace::TraceContextExt;
|
||||
use opentelemetry::trace::TraceId;
|
||||
use opentelemetry::trace::TracerProvider as _;
|
||||
use opentelemetry_sdk::trace::SdkTracerProvider;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
|
||||
fn test_tracing_subscriber() -> impl tracing::Subscriber + Send + Sync {
|
||||
let provider = SdkTracerProvider::builder().build();
|
||||
let tracer = provider.tracer("codex-exec-tests");
|
||||
tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_defaults_analytics_to_enabled() {
|
||||
assert_eq!(DEFAULT_ANALYTICS_ENABLED, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_root_span_can_be_parented_from_trace_context() {
|
||||
let subscriber = test_tracing_subscriber();
|
||||
let _guard = tracing::subscriber::set_default(subscriber);
|
||||
|
||||
let parent = codex_protocol::protocol::W3cTraceContext {
|
||||
traceparent: Some("00-00000000000000000000000000000077-0000000000000088-01".into()),
|
||||
tracestate: Some("vendor=value".into()),
|
||||
};
|
||||
let exec_span = exec_root_span();
|
||||
assert!(set_parent_from_w3c_trace_context(&exec_span, &parent));
|
||||
|
||||
let trace_id = exec_span.context().span().span_context().trace_id();
|
||||
assert_eq!(
|
||||
trace_id,
|
||||
TraceId::from_hex("00000000000000000000000000000077").expect("trace id")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_uncommitted_review_request() {
|
||||
let args = ReviewArgs {
|
||||
uncommitted: true,
|
||||
base: None,
|
||||
commit: None,
|
||||
commit_title: None,
|
||||
prompt: None,
|
||||
};
|
||||
let request = build_review_request(&args).expect("builds uncommitted review request");
|
||||
|
||||
let expected = ReviewRequest {
|
||||
target: ReviewTarget::UncommittedChanges,
|
||||
user_facing_hint: None,
|
||||
};
|
||||
|
||||
assert_eq!(request, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_commit_review_request_with_title() {
|
||||
let args = ReviewArgs {
|
||||
uncommitted: false,
|
||||
base: None,
|
||||
commit: Some("123456789".to_string()),
|
||||
commit_title: Some("Add review command".to_string()),
|
||||
prompt: None,
|
||||
};
|
||||
let request = build_review_request(&args).expect("builds commit review request");
|
||||
|
||||
let expected = ReviewRequest {
|
||||
target: ReviewTarget::Commit {
|
||||
sha: "123456789".to_string(),
|
||||
title: Some("Add review command".to_string()),
|
||||
},
|
||||
user_facing_hint: None,
|
||||
};
|
||||
|
||||
assert_eq!(request, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_custom_review_request_trims_prompt() {
|
||||
let args = ReviewArgs {
|
||||
uncommitted: false,
|
||||
base: None,
|
||||
commit: None,
|
||||
commit_title: None,
|
||||
prompt: Some(" custom review instructions ".to_string()),
|
||||
};
|
||||
let request = build_review_request(&args).expect("builds custom review request");
|
||||
|
||||
let expected = ReviewRequest {
|
||||
target: ReviewTarget::Custom {
|
||||
instructions: "custom review instructions".to_string(),
|
||||
},
|
||||
user_facing_hint: None,
|
||||
};
|
||||
|
||||
assert_eq!(request, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_strips_utf8_bom() {
|
||||
let input = [0xEF, 0xBB, 0xBF, b'h', b'i', b'\n'];
|
||||
|
||||
let out = decode_prompt_bytes(&input).expect("decode utf-8 with BOM");
|
||||
|
||||
assert_eq!(out, "hi\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_decodes_utf16le_bom() {
|
||||
// UTF-16LE BOM + "hi\n"
|
||||
let input = [0xFF, 0xFE, b'h', 0x00, b'i', 0x00, b'\n', 0x00];
|
||||
|
||||
let out = decode_prompt_bytes(&input).expect("decode utf-16le with BOM");
|
||||
|
||||
assert_eq!(out, "hi\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_decodes_utf16be_bom() {
|
||||
// UTF-16BE BOM + "hi\n"
|
||||
let input = [0xFE, 0xFF, 0x00, b'h', 0x00, b'i', 0x00, b'\n'];
|
||||
|
||||
let out = decode_prompt_bytes(&input).expect("decode utf-16be with BOM");
|
||||
|
||||
assert_eq!(out, "hi\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_rejects_utf32le_bom() {
|
||||
// UTF-32LE BOM + "hi\n"
|
||||
let input = [
|
||||
0xFF, 0xFE, 0x00, 0x00, b'h', 0x00, 0x00, 0x00, b'i', 0x00, 0x00, 0x00, b'\n', 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
|
||||
let err = decode_prompt_bytes(&input).expect_err("utf-32le should be rejected");
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
PromptDecodeError::UnsupportedBom {
|
||||
encoding: "UTF-32LE"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_rejects_utf32be_bom() {
|
||||
// UTF-32BE BOM + "hi\n"
|
||||
let input = [
|
||||
0x00, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, b'h', 0x00, 0x00, 0x00, b'i', 0x00, 0x00, 0x00,
|
||||
b'\n',
|
||||
];
|
||||
|
||||
let err = decode_prompt_bytes(&input).expect_err("utf-32be should be rejected");
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
PromptDecodeError::UnsupportedBom {
|
||||
encoding: "UTF-32BE"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_prompt_bytes_rejects_invalid_utf8() {
|
||||
// Invalid UTF-8 sequence: 0xC3 0x28
|
||||
let input = [0xC3, 0x28];
|
||||
|
||||
let err = decode_prompt_bytes(&input).expect_err("invalid utf-8 should fail");
|
||||
|
||||
assert_eq!(err, PromptDecodeError::InvalidUtf8 { valid_up_to: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_with_stdin_context_wraps_stdin_block() {
|
||||
let combined = prompt_with_stdin_context("Summarize this concisely", "my output");
|
||||
|
||||
assert_eq!(
|
||||
combined,
|
||||
"Summarize this concisely\n\n<stdin>\nmy output\n</stdin>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_with_stdin_context_preserves_trailing_newline() {
|
||||
let combined = prompt_with_stdin_context("Summarize this concisely", "my output\n");
|
||||
|
||||
assert_eq!(
|
||||
combined,
|
||||
"Summarize this concisely\n\n<stdin>\nmy output\n</stdin>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lagged_event_warning_message_is_explicit() {
|
||||
assert_eq!(
|
||||
lagged_event_warning_message(/*skipped*/ 7),
|
||||
"in-process app-server event stream lagged; dropped 7 events".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_lookup_model_providers_filters_only_last_lookup() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let mut config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build default config");
|
||||
config.model_provider_id = "test-provider".to_string();
|
||||
|
||||
let last_args = crate::cli::ResumeArgs {
|
||||
session_id: None,
|
||||
last: true,
|
||||
all: false,
|
||||
images: vec![],
|
||||
prompt: None,
|
||||
};
|
||||
let named_args = crate::cli::ResumeArgs {
|
||||
session_id: Some("named-session".to_string()),
|
||||
last: false,
|
||||
all: false,
|
||||
images: vec![],
|
||||
prompt: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
resume_lookup_model_providers(&config, &last_args),
|
||||
Some(vec!["test-provider".to_string()])
|
||||
);
|
||||
assert_eq!(resume_lookup_model_providers(&config, &named_args), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_items_for_thread_returns_matching_turn_items() {
|
||||
let thread = AppServerThread {
|
||||
id: "thread-1".to_string(),
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
model_provider: "openai".to_string(),
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
status: codex_app_server_protocol::ThreadStatus::Idle,
|
||||
path: None,
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
cli_version: "0.0.0-test".to_string(),
|
||||
source: codex_app_server_protocol::SessionSource::Exec,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
name: None,
|
||||
turns: vec![
|
||||
codex_app_server_protocol::Turn {
|
||||
id: "turn-1".to_string(),
|
||||
items: vec![AppServerThreadItem::AgentMessage {
|
||||
id: "msg-1".to_string(),
|
||||
text: "hello".to_string(),
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
}],
|
||||
status: codex_app_server_protocol::TurnStatus::Completed,
|
||||
error: None,
|
||||
},
|
||||
codex_app_server_protocol::Turn {
|
||||
id: "turn-2".to_string(),
|
||||
items: vec![AppServerThreadItem::Plan {
|
||||
id: "plan-1".to_string(),
|
||||
text: "ship it".to_string(),
|
||||
}],
|
||||
status: codex_app_server_protocol::TurnStatus::Completed,
|
||||
error: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
turn_items_for_thread(&thread, "turn-1"),
|
||||
Some(vec![AppServerThreadItem::AgentMessage {
|
||||
id: "msg-1".to_string(),
|
||||
text: "hello".to_string(),
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
}])
|
||||
);
|
||||
assert_eq!(turn_items_for_thread(&thread, "missing-turn"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canceled_mcp_server_elicitation_response_uses_cancel_action() {
|
||||
let value = canceled_mcp_server_elicitation_response()
|
||||
.expect("mcp elicitation cancel response should serialize");
|
||||
let response: McpServerElicitationRequestResponse =
|
||||
serde_json::from_value(value).expect("cancel response should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Cancel,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_params_include_review_policy_when_review_policy_is_manual_only() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
..Default::default()
|
||||
})
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build config with manual-only review policy");
|
||||
|
||||
let params = thread_start_params_from_config(&config);
|
||||
|
||||
assert_eq!(
|
||||
params.approvals_reviewer,
|
||||
Some(codex_app_server_protocol::ApprovalsReviewer::User)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_params_include_review_policy_when_auto_review_is_enabled() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
approvals_reviewer: Some(ApprovalsReviewer::GuardianSubagent),
|
||||
..Default::default()
|
||||
})
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build config with guardian review policy");
|
||||
|
||||
let params = thread_start_params_from_config(&config);
|
||||
|
||||
assert_eq!(
|
||||
params.approvals_reviewer,
|
||||
Some(codex_app_server_protocol::ApprovalsReviewer::GuardianSubagent)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_configured_from_thread_response_uses_review_policy_from_response() {
|
||||
let response = ThreadStartResponse {
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
model_provider: "openai".to_string(),
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
status: codex_app_server_protocol::ThreadStatus::Idle,
|
||||
path: Some(PathBuf::from("/tmp/rollout.jsonl")),
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: codex_app_server_protocol::SessionSource::Cli,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
name: Some("thread".to_string()),
|
||||
turns: vec![],
|
||||
},
|
||||
model: "gpt-5.4".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
approval_policy: codex_app_server_protocol::AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::GuardianSubagent,
|
||||
sandbox: codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
read_only_access: codex_app_server_protocol::ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
},
|
||||
reasoning_effort: None,
|
||||
};
|
||||
|
||||
let event = session_configured_from_thread_start_response(&response)
|
||||
.expect("build bootstrap session configured event");
|
||||
|
||||
assert_eq!(
|
||||
event.approvals_reviewer,
|
||||
ApprovalsReviewer::GuardianSubagent
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user