diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 6f324929cc3..f992d17182d 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -836,6 +836,21 @@ fn open_input_file(in_file_name: Option<&OsStr>) -> UResult> { }) } +fn stdbuf_writer(writer: W) -> Box { + match std::env::var_os("_STDBUF_O") { + Some(v) if v == "L" => Box::new(std::io::LineWriter::new(writer)), + Some(v) => { + // TODO: verify parsing error + let capacity = v + .to_str() + .and_then(|s| s.parse().ok()) + .unwrap_or(OUTPUT_BUFFER_CAPACITY); + Box::new(BufWriter::with_capacity(capacity, writer)) + } + None => Box::new(BufWriter::with_capacity(OUTPUT_BUFFER_CAPACITY, writer)), + } +} + // None or "-" means stdout. fn open_output_file(out_file_name: Option<&OsStr>) -> UResult> { Ok(match out_file_name { @@ -843,11 +858,8 @@ fn open_output_file(out_file_name: Option<&OsStr>) -> UResult> { let out_file = File::create(path).map_err_context( || translate!("uniq-error-could-not-open", "path" => path.maybe_quote()), )?; - Box::new(BufWriter::with_capacity(OUTPUT_BUFFER_CAPACITY, out_file)) + stdbuf_writer(out_file) } - _ => Box::new(BufWriter::with_capacity( - OUTPUT_BUFFER_CAPACITY, - stdout().lock(), - )), + _ => stdbuf_writer(stdout().lock()), }) } diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 8e940a743cd..466b9d9c98f 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -2,11 +2,20 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore (vars) STDBUF // spell-checker:ignore nabcd badoption schar +use rstest::rstest; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::process::{Command, Stdio}; +use std::thread; +use std::time::Duration; use uucore::posix::OBSOLETE; use uutests::at_and_ucmd; use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; static INPUT: &str = "sorted.txt"; static OUTPUT: &str = "sorted-output.txt"; @@ -1199,7 +1208,59 @@ fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") .args(&["-z"]) - .set_stdout(std::fs::File::create("/dev/full").unwrap()) + .set_stdout(File::create("/dev/full").unwrap()) .fails() .stderr_is("uniq: write error: No space left on device\n"); } + +#[cfg(unix)] +#[rstest] +#[case(None, &["1\n", "2\n", "3\n"], "1\n2\n3\n")] +#[case(Some("L"), &["1\n", "2\n", "3\n"], "1\n")] +#[case(Some("0"), &["1\n", "2\n", "3\n"], "1\n")] +// TODO: comment out because this is not super reliable i believe +#[case(Some("128"), &["000000000000000000000000000000000000000000000000000000000000001\n", +"000000000000000000000000000000000000000000000000000000000000002\n", +"000000000000000000000000000000000000000000000000000000000000003\n", +"000000000000000000000000000000000000000000000000000000000000004\n", +"000000000000000000000000000000000000000000000000000000000000005\n"], +"000000000000000000000000000000000000000000000000000000000000001\n000000000000000000000000000000000000000000000000000000000000002\n")] +fn test_uniq_stdbuf_output_mode( + #[case] stdbuf_o: Option<&str>, + #[case] input: &[&str], + #[case] expected_read: &str, +) { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let fifo = at.plus("fifo"); + let out = at.plus("out"); + at.mkfifo("fifo"); + let mut dd = Command::new("dd") + .args(["count=1", &format!("if={}", fifo.display())]) + .stdout(File::create(&out).unwrap()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + let mut cmd = Command::new(&ts.bin_path); + if let Some(mode) = stdbuf_o { + cmd.env("_STDBUF_O", mode); + } + let mut uniq = cmd + .arg(util_name!()) + .stdin(Stdio::piped()) + .stdout(Stdio::from( + OpenOptions::new().write(true).open(&fifo).unwrap(), + )) + .spawn() + .unwrap(); + { + let mut stdin = uniq.stdin.take().unwrap(); + for line in input { + stdin.write_all(line.as_bytes()).unwrap(); + thread::sleep(Duration::from_millis(100)); + } + } + let _ = dd.wait(); + let _ = uniq.wait(); + assert_eq!(std::fs::read_to_string(&out).unwrap(), expected_read); +}