Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,17 @@ fn validate_pnpm_filters(filters: &[String], command: &PnpmCommands) -> Option<S
}

fn main() {
// Reset SIGPIPE to default handler so writing to a closed pipe
// e.g `rtk git log | head` exits silently instead of panicking.
// Rust ignores SIGPIPE by default and with panic="abort" in the
// release profile that becomes SIGABRT + coredump.
#[cfg(unix)]
#[allow(unsafe_code)]
// nosemgrep: unsafe-block
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}

let code = match run_cli() {
Ok(code) => code,
Err(e) => {
Expand Down Expand Up @@ -3153,6 +3164,40 @@ mod tests {
}
}

#[test]
#[ignore] // Integration test: requires `cargo build` first
fn test_broken_pipe_does_not_crash() {
let bin_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("debug")
.join("rtk");
assert!(
bin_path.exists(),
"Debug binary not found at {:?} - run `cargo build` first",
bin_path
);

let mut child = std::process::Command::new(&bin_path)
.args(["git", "log", "--oneline", "-50"])
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn rtk");

// Read one byte then drop stdout to close the pipe.
let mut stdout = child.stdout.take().unwrap();
let mut buf = [0u8; 1];
let _ = std::io::Read::read(&mut stdout, &mut buf);

let status = child.wait().expect("Failed to wait for rtk");
let code = status.code().unwrap_or(-1);

assert_ne!(
code, 134,
"rtk crashed with SIGABRT (exit 134) on broken pipe - SIGPIPE handler missing"
);
}

#[test]
fn test_ultra_compact_long_form_still_works() {
let cli = Cli::try_parse_from(["rtk", "--ultra-compact", "git", "status"]).unwrap();
Expand Down
Loading