1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

factor: prevent writing incomplete lines

This makes it possible to execute multiple `factor` instances that write
to the same output in parallel, without having them interfere.
This commit is contained in:
Michael Debertol 2021-08-09 16:03:23 +02:00
parent 966cf79747
commit 0edc9b01b9
2 changed files with 57 additions and 8 deletions

View file

@ -10,6 +10,7 @@
extern crate uucore;
use std::error::Error;
use std::fmt::Write as FmtWrite;
use std::io::{self, stdin, stdout, BufRead, Write};
mod factor;
@ -28,21 +29,29 @@ mod options {
pub static NUMBER: &str = "NUMBER";
}
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> {
num_str
.parse::<u64>()
.map_err(|e| e.into())
.and_then(|x| writeln!(w, "{}:{}", x, factor(x)).map_err(|e| e.into()))
fn print_factors_str(
num_str: &str,
w: &mut io::BufWriter<impl io::Write>,
factors_buffer: &mut String,
) -> Result<(), Box<dyn Error>> {
num_str.parse::<u64>().map_err(|e| e.into()).and_then(|x| {
factors_buffer.clear();
writeln!(factors_buffer, "{}:{}", x, factor(x))?;
w.write_all(factors_buffer.as_bytes())?;
Ok(())
})
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = uu_app().get_matches_from(args);
let stdout = stdout();
let mut w = io::BufWriter::new(stdout.lock());
// We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash.
let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock());
let mut factors_buffer = String::new();
if let Some(values) = matches.values_of(options::NUMBER) {
for number in values {
if let Err(e) = print_factors_str(number, &mut w) {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e);
}
}
@ -51,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for line in stdin.lock().lines() {
for number in line.unwrap().split_whitespace() {
if let Err(e) = print_factors_str(number, &mut w) {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e);
}
}

View file

@ -8,7 +8,10 @@
// spell-checker:ignore (methods) hexdigest
use tempfile::TempDir;
use crate::common::util::*;
use std::fs::OpenOptions;
use std::time::SystemTime;
#[path = "../../src/uu/factor/sieve.rs"]
@ -24,6 +27,43 @@ use self::sieve::Sieve;
const NUM_PRIMES: usize = 10000;
const NUM_TESTS: usize = 100;
#[test]
fn test_parallel() {
// factor should only flush the buffer at line breaks
let n_integers = 100_000;
let mut input_string = String::new();
for i in 0..=n_integers {
input_string.push_str(&(format!("{} ", i))[..]);
}
let tmp_dir = TempDir::new().unwrap();
let tmp_dir = AtPath::new(tmp_dir.path());
tmp_dir.touch("output");
let output = OpenOptions::new()
.append(true)
.open(tmp_dir.plus("output"))
.unwrap();
for mut child in (0..10)
.map(|_| {
new_ucmd!()
.set_stdout(output.try_clone().unwrap())
.pipe_in(input_string.clone())
.run_no_wait()
})
.collect::<Vec<_>>()
{
assert_eq!(child.wait().unwrap().code().unwrap(), 0);
}
let result = TestScenario::new(util_name!())
.ccmd("sort")
.arg(tmp_dir.plus("output"))
.succeeds();
let hash_check = sha1::Sha1::from(result.stdout()).hexdigest();
assert_eq!(hash_check, "cc743607c0ff300ff575d92f4ff0c87d5660c393");
}
#[test]
fn test_first_100000_integers() {
extern crate sha1;