From 17c35f40a72e76330c0de536ce9cc2b9b81abc48 Mon Sep 17 00:00:00 2001 From: jedarden Date: Mon, 8 Jun 2026 08:56:36 -0400 Subject: [PATCH] Add mock-claude fixture, test_pty_spawns_tty integration test, and hook module export - test-fixtures/mock-claude: implement mock binary that writes 'stop' to FIFO then checks isatty(stdin), exiting 0 on TTY - tests/pty_integration.rs: test_pty_spawns_tty uses HookInstaller + PtySpawner to verify controlling TTY is established - src/lib.rs: expose hook module publicly - Cargo.lock: add libc dependency for mock-claude Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 3 ++ src/lib.rs | 1 + test-fixtures/mock-claude/Cargo.toml | 3 ++ test-fixtures/mock-claude/src/main.rs | 18 +++++++++- tests/pty_integration.rs | 47 +++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/pty_integration.rs diff --git a/Cargo.lock b/Cargo.lock index 5753b69..549c9a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,9 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mock-claude" version = "0.1.0" +dependencies = [ + "libc", +] [[package]] name = "nix" diff --git a/src/lib.rs b/src/lib.rs index c884660..73505c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod cli; pub mod config; pub mod error; +pub mod hook; pub mod pty; diff --git a/test-fixtures/mock-claude/Cargo.toml b/test-fixtures/mock-claude/Cargo.toml index 371fb90..a6a96b8 100644 --- a/test-fixtures/mock-claude/Cargo.toml +++ b/test-fixtures/mock-claude/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [[bin]] name = "mock-claude" path = "src/main.rs" + +[dependencies] +libc = "0.2" diff --git a/test-fixtures/mock-claude/src/main.rs b/test-fixtures/mock-claude/src/main.rs index f328e4d..3c7a2c6 100644 --- a/test-fixtures/mock-claude/src/main.rs +++ b/test-fixtures/mock-claude/src/main.rs @@ -1 +1,17 @@ -fn main() {} +use std::io::Write; + +fn main() { + let fifo_path = std::env::args() + .nth(1) + .expect("usage: mock-claude "); + + // Simulate the stop hook by writing "stop" to the FIFO. + // O_WRONLY on a FIFO blocks until a reader opens the other end. + if let Ok(mut file) = std::fs::OpenOptions::new().write(true).open(&fifo_path) { + let _ = file.write_all(b"stop\n"); + } + + // Exit 0 if stdin is a controlling TTY (login_tty succeeded), 1 otherwise. + let has_tty = unsafe { libc::isatty(libc::STDIN_FILENO) } == 1; + std::process::exit(if has_tty { 0 } else { 1 }); +} diff --git a/tests/pty_integration.rs b/tests/pty_integration.rs new file mode 100644 index 0000000..648c5b5 --- /dev/null +++ b/tests/pty_integration.rs @@ -0,0 +1,47 @@ +use claude_print::hook::HookInstaller; +use claude_print::pty::PtySpawner; +use std::ffi::CString; +use std::io::Read; + +/// Locate the mock-claude binary compiled alongside the test binary. +/// Test binaries live at `target//deps/`; other bins at `target//`. +fn mock_claude_bin() -> std::path::PathBuf { + let exe = std::env::current_exe().expect("current_exe"); + let profile_dir = exe + .parent() // deps/ + .and_then(|p| p.parent()) // target// + .expect("unexpected test binary path"); + profile_dir.join("mock-claude") +} + +#[test] +fn test_pty_spawns_tty() { + let installer = HookInstaller::new().expect("HookInstaller::new"); + let fifo_path = installer.fifo_path.clone(); + + // Read from the FIFO in a background thread so mock-claude's write doesn't block. + let reader = std::thread::spawn(move || { + let mut file = std::fs::File::open(&fifo_path).expect("open stop.fifo"); + let mut buf = String::new(); + file.read_to_string(&mut buf).ok(); + buf + }); + + let bin = mock_claude_bin(); + let cmd = CString::new(bin.to_str().expect("binary path is utf-8")).expect("CString"); + let fifo_arg = + CString::new(installer.fifo_path.to_str().expect("fifo path is utf-8")).expect("CString"); + + let spawner = PtySpawner::spawn(&cmd, &[fifo_arg]).expect("PtySpawner::spawn"); + let exit_code = spawner.relay().expect("relay"); + + let fifo_content = reader.join().expect("reader thread"); + assert!( + fifo_content.contains("stop"), + "expected 'stop' in FIFO content, got: {fifo_content:?}", + ); + assert_eq!( + exit_code, 0, + "mock-claude should detect a controlling TTY (isatty(0) returned false)", + ); +}