Skip to content
Open
Show file tree
Hide file tree
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
31 changes: 22 additions & 9 deletions src/cmds/system/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::core::filter::{self, FilterLevel, Language};
use crate::core::tracking;
use anyhow::{Context, Result};
use std::fs;
use std::io::{self, Write};
use std::path::Path;

pub fn run(
Expand Down Expand Up @@ -70,13 +71,14 @@ pub fn run(
} else {
filtered.clone()
};
print!("{}", rtk_output);
timer.track(
&format!("cat {}", file.display()),
"rtk read",
&content,
&rtk_output,
);
if write_stdout(&rtk_output)? {
timer.track(
&format!("cat {}", file.display()),
"rtk read",
&content,
&rtk_output,
);
}
Ok(())
}

Expand All @@ -87,7 +89,7 @@ pub fn run_stdin(
line_numbers: bool,
verbose: u8,
) -> Result<()> {
use std::io::{self, Read as IoRead};
use std::io::Read as IoRead;

let timer = tracking::TimedExecution::start();

Expand Down Expand Up @@ -134,12 +136,23 @@ pub fn run_stdin(
} else {
filtered.clone()
};
print!("{}", rtk_output);
if !write_stdout(&rtk_output)? {
return Ok(());
}

timer.track("cat - (stdin)", "rtk read -", &content, &rtk_output);
Ok(())
}

fn write_stdout(content: &str) -> Result<bool> {
let mut stdout = io::stdout().lock();
match stdout.write_all(content.as_bytes()) {
Ok(()) => Ok(true),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(false),
Err(e) => Err(e).context("failed writing read output"),
}
}

fn format_with_line_numbers(content: &str) -> String {
let lines: Vec<&str> = content.lines().collect();
let width = lines.len().to_string().len();
Expand Down
42 changes: 42 additions & 0 deletions tests/read_broken_pipe_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use rusqlite::Connection;
use std::fs;
use std::process::Command;

#[test]
fn read_piped_to_head_does_not_panic_or_record_partial_gain() {
let tmp = tempfile::tempdir().expect("tempdir");
let file = tmp.path().join("large.txt");
let db = tmp.path().join("history.db");
fs::write(&file, "abcdef0123456789\n".repeat(20_000)).expect("write test file");

let output = Command::new("sh")
.arg("-c")
.arg("\"$RTK_BIN\" read \"$RTK_FILE\" | head -c 10 >/dev/null")
.env("RTK_BIN", env!("CARGO_BIN_EXE_rtk"))
.env("RTK_FILE", &file)
.env("RTK_DB_PATH", &db)
.env("RTK_TELEMETRY_DISABLED", "1")
.output()
.expect("run piped rtk read");

assert!(
output.status.success(),
"pipeline should succeed: status={:?}, stderr={}",
output.status,
String::from_utf8_lossy(&output.stderr)
);

let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("panicked") && !stderr.contains("failed printing to stdout"),
"broken pipe should be handled without panic, got stderr: {stderr}"
);

if db.exists() {
let conn = Connection::open(&db).expect("open history db");
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM commands", [], |row| row.get(0))
.expect("count commands");
assert_eq!(count, 0, "partial read output must not be counted as gain");
}
}
Loading