Files
codex/prs/bolinfest/study/PR-1545-study.md
2025-09-02 15:17:45 -07:00

3.7 KiB
Raw Blame History

DOs

  • Put tests first: Keep helper fns after tests to foreground intent.
// Tests first
/// Sends a prompt and asserts the streamed message.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn full_conversation_turn_integration() { /* ... */ }

// Helpers after tests
fn write_config(codex_home: &Path, server: &MockServer) { /* ... */ }
  • Use readable multi-line config strings: Start with a leading newline; inline variables with format!.
fs::write(
    codex_home.join("config.toml"),
    format!(r#"
model_provider = "mock"
model = "test-model"

[model_providers.mock]
name = "mock"
base_url = "{}/v1"
env_key = "PATH"
wire_api = "responses"
"#, server.uri()),
)?;
  • Consider indoc for long blocks: Keep indentation tidy without left margin loss.
use indoc::formatdoc;

fs::write(
    codex_home.join("config.toml"),
    formatdoc!(r#"
        model_provider = "mock"
        model = "test-model"

        [model_providers.mock]
        name = "mock"
        base_url = "{base}/v1"
        env_key = "PATH"
        wire_api = "responses"
    "#, base = server.uri()),
)?;
  • Prefer template + replace for SSE: Avoid brace-escaping hell in format! strings.
fn sse_message(text: &str) -> String {
    const TEMPLATE: &str = r#"event: response.output_item.done
data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"TEXT_PLACEHOLDER"}]}}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp1","output":[]}}

"#;
    TEMPLATE.replace("TEXT_PLACEHOLDER", text)
}
  • Name things precisely: Use codex_home for the temp config directory.
fn write_config(codex_home: &Path, server: &MockServer) {
    // ...
}
  • Assert on the final message via file: Use --output-last-message and compare file contents.
let codex_home = TempDir::new().unwrap();
let sandbox = TempDir::new().unwrap();
write_config(codex_home.path(), &server);

let last = sandbox.path().join("last_message.txt");

let mut cmd = assert_cmd::Command::cargo_bin("codex").unwrap();
cmd.env("CODEX_HOME", codex_home.path())
    .current_dir(sandbox.path())
    .arg("exec")
    .arg("--skip-git-repo-check")
    .arg("--output-last-message")
    .arg(&last)
    .arg("Hello");

cmd.assert().success().stdout(predicates::str::contains("Hello, world."));
assert_eq!(fs::read_to_string(&last).unwrap().trim(), "Hello, world.");
  • Document each test: Add a one-liner docstring explaining the behavior under test.
/// Simulates a shell tool call then verifies the assistant's follow-up.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn tool_invocation_flow() { /* ... */ }

DON'Ts

  • Dont put helpers above tests: It hides the tests purpose and increases scrolling.
// Anti-pattern: helpers first
fn write_config(...) { /* ... */ }
#[tokio::test] async fn test_case() { /* ... */ }
  • Dont hand-escape JSON braces inside format!: Hard to read and easy to break.
// Anti-pattern: unreadable escaping
format!("data: {{\"text\":\"{text}\"}}");
  • Dont use vague names like dir or home: Be explicit about the directorys role.
// Anti-pattern: vague
fn write_config(dir: &Path, server: &MockServer) { /* ... */ }
  • Dont assert only via stdout: Stream formatting can change; assert the canonical last message file.
// Anti-pattern: only stdout check
cmd.assert().stdout(predicates::str::contains("done"));
  • Dont omit test docstrings: Future readers shouldnt have to infer the intent.
// Anti-pattern: no docstring
#[tokio::test] async fn full_conversation_turn_integration() { /* ... */ }