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 <noreply@anthropic.com>
This commit is contained in:
parent
a1e74b59fa
commit
17c35f40a7
5 changed files with 71 additions and 1 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -262,6 +262,9 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
|||
[[package]]
|
||||
name = "mock-claude"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod hook;
|
||||
pub mod pty;
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ edition = "2021"
|
|||
[[bin]]
|
||||
name = "mock-claude"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
|
|
|||
|
|
@ -1 +1,17 @@
|
|||
fn main() {}
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let fifo_path = std::env::args()
|
||||
.nth(1)
|
||||
.expect("usage: mock-claude <fifo-path>");
|
||||
|
||||
// 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 });
|
||||
}
|
||||
|
|
|
|||
47
tests/pty_integration.rs
Normal file
47
tests/pty_integration.rs
Normal file
|
|
@ -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/<profile>/deps/`; other bins at `target/<profile>/`.
|
||||
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/<profile>/
|
||||
.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)",
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue