Skip to content

Commit 2fc7fdb

Browse files
wan9chiclaude
andcommitted
fix: properly detect ctrl-c and resize in pty_terminal tests on Windows
- resize_terminal: compare terminal size before/after instead of unconditionally printing RESIZE_DETECTED - send_ctrl_c: clear the "ignore CTRL_C" flag (set by Rust runtime) with SetConsoleCtrlHandler(None, 0) so CTRL_C_EVENT actually terminates the process via the default handler, proving send_ctrl_c delivers the signal. On Unix, verify via SIGINT handler as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0491ad6 commit 2fc7fdb

2 files changed

Lines changed: 49 additions & 22 deletions

File tree

crates/pty_terminal/src/terminal.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,14 @@ impl Terminal {
249249

250250
/// Sends Ctrl+C (SIGINT) to the child process.
251251
///
252+
/// Writes ETX (0x03) to the PTY. On Unix, the terminal driver converts this
253+
/// to SIGINT for the child's process group. On Windows, `ConPTY` intercepts
254+
/// the byte and generates `CTRL_C_EVENT` for the child.
255+
///
252256
/// # Errors
253257
///
254-
/// Returns an error if:
255-
/// - The child process has already exited
256-
/// - Writing to the PTY fails
258+
/// Returns an error if the child process has already exited or writing fails.
257259
pub fn send_ctrl_c(&self) -> anyhow::Result<()> {
258-
// ASCII 0x03 (ETX) is Ctrl+C
259-
// Both Unix PTY and Windows ConPTY interpret this and signal the child
260260
self.write(&[0x03])
261261
}
262262

crates/pty_terminal/tests/terminal.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,13 @@ fn resize_terminal() {
326326
}
327327
}
328328

329-
// On Windows, resize happens synchronously via ConPTY
329+
// On Windows, ConPTY resizes synchronously - detect by checking size change
330330
#[cfg(windows)]
331331
{
332-
println!("RESIZE_DETECTED");
332+
let (new_rows, new_cols) = get_size();
333+
if (new_rows, new_cols) != (rows, cols) {
334+
println!("RESIZE_DETECTED");
335+
}
333336
}
334337

335338
// Print new size
@@ -384,23 +387,37 @@ fn send_ctrl_c_interrupts_process() {
384387
.unwrap();
385388
}
386389

390+
// On Windows, explicitly ensure Ctrl+C is NOT ignored, so that
391+
// CTRL_C_EVENT terminates the process via the default handler.
392+
#[cfg(windows)]
393+
{
394+
// SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32.
395+
unsafe extern "system" {
396+
fn SetConsoleCtrlHandler(
397+
handler: Option<unsafe extern "system" fn(u32) -> i32>,
398+
add: i32,
399+
) -> i32;
400+
}
401+
402+
// SAFETY: Clearing the "ignore CTRL_C" flag so the default handler runs.
403+
unsafe {
404+
SetConsoleCtrlHandler(None, 0); // FALSE = remove ignore
405+
}
406+
}
407+
387408
println!("ready");
388409
stdout().flush().unwrap();
389410

390-
// Block on stdin until the test sends a newline (after Ctrl+C)
411+
// Block on stdin. On Unix, SIGINT interrupts read_line. On Windows,
412+
// CTRL_C_EVENT terminates the process via the default handler.
391413
let mut input = std::string::String::new();
392414
let _ = stdin().read_line(&mut input);
393415

416+
// On Unix, check if SIGINT was delivered via the signal handler.
417+
// On Windows, this code is unreachable: the process is terminated
418+
// by CTRL_C_EVENT before read_line returns.
394419
#[cfg(unix)]
395-
{
396-
if interrupted.load(Ordering::SeqCst) {
397-
println!("INTERRUPTED");
398-
}
399-
}
400-
401-
#[cfg(windows)]
402-
{
403-
// On Windows, Ctrl+C is delivered via ConPTY as CTRL_C_EVENT
420+
if interrupted.load(Ordering::SeqCst) {
404421
println!("INTERRUPTED");
405422
}
406423

@@ -415,13 +432,23 @@ fn send_ctrl_c_interrupts_process() {
415432
// Send Ctrl+C
416433
terminal.send_ctrl_c().unwrap();
417434

418-
// Signal the process to continue and check the interrupt flag
419-
terminal.write(b"\n").unwrap();
435+
// On Unix, send newline to unblock read_line and verify SIGINT detection.
436+
#[cfg(unix)]
437+
{
438+
terminal.write(b"\n").unwrap();
439+
terminal.read_until("INTERRUPTED").unwrap();
440+
}
420441

421-
// Verify interruption was detected
422-
terminal.read_until("INTERRUPTED").unwrap();
442+
let status = terminal.read_to_end().unwrap();
423443

424-
let _ = terminal.read_to_end().unwrap();
444+
// On Unix, the process exits normally after detecting SIGINT.
445+
#[cfg(unix)]
446+
assert!(status.success());
447+
448+
// On Windows, the default CTRL_C handler calls ExitProcess with
449+
// STATUS_CONTROL_C_EXIT, resulting in a non-zero exit code.
450+
#[cfg(windows)]
451+
assert!(!status.success());
425452
}
426453

427454
#[test]

0 commit comments

Comments
 (0)