Skip to content

Commit 29d0543

Browse files
committed
fix(pty): Init tmp E2E directories as git repos for pty tests
The streaming cancellation E2E test was failing because the temp directory wasn't a git repository. This caused the ghost snapshot task to immediately fail and send a BackgroundEvent("Snapshots disabled...") that raced with and overwrote the "Working" status indicator within ~3ms. Changes: - Add `git_init` field to SessionConfig (default: true) - Run `git init` on temp directories during test setup by default - Add `without_git_init()` for tests that need non-git directories - Enable streaming cancellation test with proper git repo setup
1 parent 11156ef commit 29d0543

6 files changed

Lines changed: 135 additions & 45 deletions

File tree

codex-rs/tui-pty-e2e/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ impl TuiSession {
8181
let temp_dir = tempfile::tempdir()?;
8282
let hello_py = temp_dir.path().join("hello.py");
8383
std::fs::write(&hello_py, "print('Hello, World!')")?;
84+
85+
// Initialize as git repo if requested (prevents "Snapshots disabled" race)
86+
if config.git_init {
87+
std::process::Command::new("git")
88+
.args(["init"])
89+
.current_dir(temp_dir.path())
90+
.output()?;
91+
}
92+
8493
config.cwd = Some(temp_dir.path().to_path_buf());
8594
Self::spawn_with_config_and_tempdir(rows, cols, config, Some(temp_dir))
8695
} else {
@@ -433,6 +442,10 @@ pub struct SessionConfig {
433442
/// Custom config.toml content. If None, a default config will be generated.
434443
/// Set to Some("") to write an empty config file.
435444
pub config_toml: Option<String>,
445+
/// Initialize the temp directory as a git repository.
446+
/// This prevents the "Snapshots disabled" BackgroundEvent from overwriting
447+
/// the "Working" status indicator during streaming tests.
448+
pub git_init: bool,
436449
}
437450

438451
impl Default for SessionConfig {
@@ -452,6 +465,7 @@ impl SessionConfig {
452465
sandbox: Some(Sandbox::WorkspaceWrite),
453466
cwd: None,
454467
config_toml: None,
468+
git_init: true,
455469
}
456470
}
457471

@@ -503,6 +517,14 @@ impl SessionConfig {
503517
self.config_toml = Some(content.into());
504518
self
505519
}
520+
521+
/// Initialize the temp directory as a git repository.
522+
pub fn without_git_init(mut self) -> Self {
523+
// This prevents the "Snapshots disabled" BackgroundEvent from racing
524+
// with the "Working" status indicator during streaming tests.
525+
self.git_init = false;
526+
self
527+
}
506528
}
507529

508530
/// Get path to codex binary
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
actual screen state right after submission, with mock ACP:
2+
3+
```
4+
5+
› testing!!!
6+
7+
8+
• Working (1s • esc to interrupt)
9+
10+
11+
› Improve documentation in @filename
12+
13+
100% context left · ? for shortcuts
14+
```
15+
16+
then actual screen state after hitting escape:
17+
18+
```
19+
20+
› testing!!!
21+
22+
23+
■ Conversation interrupted - tell the model what to do differently. Something went wrong? Hit
24+
`/feedback` to report the issue.
25+
26+
27+
› Improve documentation in @filename
28+
29+
100% context left · ? for shortcuts
30+
```

codex-rs/tui-pty-e2e/tests/snapshots/prompt_flow__custom_response.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
source: tui-pty-e2e/tests/prompt_flow.rs
33
expression: normalize_for_snapshot(session.screen_contents())
44
---
5-
To get started, describe a task or try one of these commands:
6-
75
/init - create an AGENTS.md file with instructions for Codex
86
/status - show current session configuration
97
/approvals - choose what Codex can do without approval

codex-rs/tui-pty-e2e/tests/snapshots/prompt_flow__missing_model.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
source: tui-pty-e2e/tests/prompt_flow.rs
33
expression: normalize_for_snapshot(session.screen_contents())
44
---
5+
/init - create an AGENTS.md file with instructions for Codex
56
/status - show current session configuration
67
/approvals - choose what Codex can do without approval
78
/model - choose what model and reasoning effort to use
@@ -14,8 +15,6 @@ expression: normalize_for_snapshot(session.screen_contents())
1415
Fatal error: Model 'nonexistent' has wire_api=acp but is not registered in the
1516
ACP registry
1617

17-
Snapshots disabled: current directory is not a Git repository. (0sesc to in
18-
1918

2019
› [DEFAULT_PROMPT]
2120

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: tui-pty-e2e/tests/streaming.rs
3+
expression: normalize_for_snapshot(session.screen_contents())
4+
---
5+
│ │
6+
model: mock-model /model to change
7+
directory: [TMP_DIR] │
8+
╰──────────────────────────────────────────╯
9+
10+
To get started, describe a task or try one of these commands:
11+
12+
/init - create an AGENTS.md file with instructions for Codex
13+
/status - show current session configuration
14+
/approvals - choose what Codex can do without approval
15+
/model - choose what model and reasoning effort to use
16+
/review - review any changes and find issues
17+
18+
19+
testing!!!
20+
21+
22+
Conversation interrupted - tell the model what to do differently. Something
23+
went wrong? Hit `/feedback` to report the issue.
24+
25+
26+
› [DEFAULT_PROMPT]
27+
28+
100% context left · ? for shortcuts
Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use insta::assert_snapshot;
2-
use std::time::Duration;
32
use tui_pty_e2e::normalize_for_snapshot;
43
use tui_pty_e2e::Key;
54
use tui_pty_e2e::SessionConfig;
@@ -10,16 +9,20 @@ use tui_pty_e2e::TIMEOUT_INPUT;
109
#[test]
1110
fn test_submit_text() {
1211
let config = SessionConfig::new().with_stream_until_cancel();
13-
1412
let mut session = TuiSession::spawn_with_config(24, 80, config).unwrap();
15-
session.wait_for_text("To get started", TIMEOUT).unwrap();
13+
14+
session
15+
.wait_for_text("›", TIMEOUT)
16+
.expect("Prompt did not appear");
17+
std::thread::sleep(TIMEOUT_INPUT);
1618

1719
// Submit prompt
1820
session.send_str("testing!!!").unwrap();
1921
session.wait_for_text("testing!!!", TIMEOUT).unwrap();
20-
std::thread::sleep(Duration::from_millis(100));
22+
std::thread::sleep(TIMEOUT_INPUT);
2123
session.send_key(Key::Enter).unwrap();
22-
std::thread::sleep(Duration::from_millis(100));
24+
25+
std::thread::sleep(TIMEOUT_INPUT);
2326
session.wait_for_text("? for shortcuts", TIMEOUT).unwrap();
2427
std::thread::sleep(TIMEOUT_INPUT);
2528

@@ -29,39 +32,49 @@ fn test_submit_text() {
2932
);
3033
}
3134

32-
// #[test]
33-
// fn test_escape_cancels_streaming() {
34-
// let config = SessionConfig::new().with_stream_until_cancel();
35-
//
36-
// let mut session = TuiSession::spawn_with_config(24, 80, config).unwrap();
37-
// session.wait_for_text("To get started", TIMEOUT).unwrap();
38-
//
39-
// // Submit prompt
40-
// session.send_str("testing!!!").unwrap();
41-
// session.wait_for_text("testing!!!", TIMEOUT).unwrap();
42-
// std::thread::sleep(Duration::from_millis(100));
43-
// session.send_key(Key::Enter).unwrap();
44-
// std::thread::sleep(Duration::from_millis(100));
45-
//
46-
// // Wait for streaming to start
47-
// session
48-
// .wait_for_text("Streaming...", TIMEOUT)
49-
// .expect("Streaming did not start");
50-
//
51-
// // Press Escape to cancel
52-
// session.send_key(Key::Escape).unwrap();
53-
//
54-
// // Verify cancellation completed
55-
// // (exact behavior depends on TUI implementation)
56-
// session
57-
// .wait_for(
58-
// |s| s.contains("Cancelled") || s.contains("Stopped"),
59-
// TIMEOUT,
60-
// )
61-
// .ok(); // May not show explicit message
62-
//
63-
// assert_snapshot!(
64-
// "cancelled_stream",
65-
// normalize_for_snapshot(session.screen_contents())
66-
// )
67-
// }
35+
#[test]
36+
fn test_escape_cancels_streaming() {
37+
// Use git_init to prevent "Snapshots disabled" from racing with "Working" status
38+
let config = SessionConfig::new().with_stream_until_cancel();
39+
let mut session = TuiSession::spawn_with_config(24, 80, config).unwrap();
40+
41+
// Wait for the prompt to appear (indicated by the chevron character)
42+
session
43+
.wait_for_text("›", TIMEOUT)
44+
.expect("Prompt did not appear");
45+
std::thread::sleep(TIMEOUT_INPUT);
46+
47+
// Submit prompt
48+
session.send_str("testing!!!").unwrap();
49+
session.wait_for_text("testing!!!", TIMEOUT).unwrap();
50+
std::thread::sleep(TIMEOUT_INPUT);
51+
session.send_key(Key::Enter).unwrap();
52+
std::thread::sleep(TIMEOUT_INPUT);
53+
54+
// Wait for streaming to start
55+
session
56+
.wait_for_text("Working", TIMEOUT)
57+
.expect("Streaming did not start");
58+
59+
// Press Escape to cancel doesn't work?
60+
session.send_key(Key::Escape).unwrap();
61+
std::thread::sleep(TIMEOUT_INPUT);
62+
// Press ctrl-c to cancel doesn't work?
63+
// session.send_key(Key::Ctrl('c')).unwrap();
64+
// std::thread::sleep(TIMEOUT_INPUT);
65+
66+
std::thread::sleep(TIMEOUT);
67+
// Verify cancellation completed
68+
// (exact behavior depends on TUI implementation)
69+
session
70+
.wait_for_text(
71+
"Conversation interrupted - tell the model what to do differently",
72+
TIMEOUT,
73+
)
74+
.expect("No interrupt reported");
75+
76+
assert_snapshot!(
77+
"cancelled_stream",
78+
normalize_for_snapshot(session.screen_contents())
79+
)
80+
}

0 commit comments

Comments
 (0)