mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-09-16 03:36:18 +00:00
tail: improve performance of piped stdin
Rewrite handling of stdin when it is piped and read input in chunks. Fixes https://github.com/uutils/coreutils/issues/3842
This commit is contained in:
parent
b39f5239e7
commit
2658f8ae5b
7 changed files with 1704 additions and 83 deletions
|
@ -3,7 +3,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile file siette ocho nueve diez
|
||||
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile file siette ocho nueve diez MULT
|
||||
// spell-checker:ignore (libs) kqueue
|
||||
// spell-checker:ignore (jargon) tailable untailable
|
||||
|
||||
|
@ -1090,18 +1090,6 @@ fn test_invalid_num() {
|
|||
.fails()
|
||||
.stderr_str()
|
||||
.starts_with("tail: invalid number of lines: '1Y': Value too large for defined data type");
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let sizes = ["1000G", "10T"];
|
||||
for size in &sizes {
|
||||
new_ucmd!()
|
||||
.args(&["-c", size])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_str()
|
||||
.starts_with("tail: Insufficient addressable memory");
|
||||
}
|
||||
}
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-³"])
|
||||
.fails()
|
||||
|
@ -2484,6 +2472,725 @@ fn test_illegal_seek() {
|
|||
assert_eq!(p.wait().unwrap().code().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_os = "android"), not(target_os = "windows")))] // FIXME: See https://github.com/uutils/coreutils/issues/3881
|
||||
mod pipe_tests {
|
||||
use super::*;
|
||||
use crate::common::random::*;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE;
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_value_is_higher_than_contained_lines() {
|
||||
let test_string = "a\nb\n";
|
||||
new_ucmd!()
|
||||
.args(&["-n", "3"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "999"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+3"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+999"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_negative_lines_option_given_no_newline_at_eof() {
|
||||
let test_string = "a\nb";
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "0"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("b");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a\nb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_positive_lines_option_given_no_newline_at_eof() {
|
||||
let test_string = "a\nb";
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+0"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a\nb");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+1"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a\nb");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_given_multibyte_utf8_characters() {
|
||||
// the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
|
||||
let test_string = "𝅘𝅥𝅮\n⏻\nƒ\na";
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+0"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("⏻\nƒ\na");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+3"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("ƒ\na");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+5"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-3"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("⏻\nƒ\na");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("ƒ\na");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-1"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-0"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size_no_newline_at_eof() {
|
||||
let total_lines = 1;
|
||||
let random_string = RandomString::generate_with_delimiter(
|
||||
Alphanumeric,
|
||||
b'\n',
|
||||
total_lines,
|
||||
false,
|
||||
CHUNK_BUFFER_SIZE,
|
||||
);
|
||||
let random_string = random_string.as_str();
|
||||
let lines = random_string.split_inclusive('\n');
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size() {
|
||||
let total_lines = 100;
|
||||
let random_string = RandomString::generate_with_delimiter(
|
||||
Alphanumeric,
|
||||
b'\n',
|
||||
total_lines,
|
||||
true,
|
||||
CHUNK_BUFFER_SIZE,
|
||||
);
|
||||
let random_string = random_string.as_str();
|
||||
let lines = random_string.split_inclusive('\n');
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
let expected = lines.clone().skip(total_lines - 1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-99"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-100"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_given_input_size_is_one_byte_greater_than_buffer_size() {
|
||||
let total_lines = 100;
|
||||
let random_string = RandomString::generate_with_delimiter(
|
||||
Alphanumeric,
|
||||
b'\n',
|
||||
total_lines,
|
||||
true,
|
||||
CHUNK_BUFFER_SIZE + 1,
|
||||
);
|
||||
let random_string = random_string.as_str();
|
||||
let lines = random_string.split_inclusive('\n');
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
let expected = lines.clone().skip(total_lines - 1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-99"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size() {
|
||||
let total_lines = 100;
|
||||
let random_string = RandomString::generate_with_delimiter(
|
||||
Alphanumeric,
|
||||
b'\n',
|
||||
total_lines,
|
||||
true,
|
||||
CHUNK_BUFFER_SIZE * 3 + 1,
|
||||
);
|
||||
let random_string = random_string.as_str();
|
||||
let lines = random_string.split_inclusive('\n');
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
let expected = lines.clone().skip(total_lines - 1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
let expected = lines.clone().skip(1).collect::<String>();
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-99"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-n", "-100"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_bytes_option_value_is_higher_than_contained_bytes() {
|
||||
let test_string = "a\nb";
|
||||
new_ucmd!()
|
||||
.args(&["-c", "4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "5"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "999"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+4"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+5"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+999"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_bytes_option_given_multibyte_utf8_characters() {
|
||||
// the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
|
||||
let test_string = "𝅘𝅥𝅮⏻ƒa";
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+0"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(&test_string.as_bytes()[1..]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+5"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("⏻ƒa");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+8"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("ƒa");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+10"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+11"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-1"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("a");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-2"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(&"ƒa".as_bytes()[1..]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-3"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("ƒa");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-6"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only("⏻ƒa");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-10"])
|
||||
.pipe_in(test_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(test_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_bytes_option_given_input_size_is_equal_to_buffer_size() {
|
||||
let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE);
|
||||
let random_string = random_string.as_str();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
let expected = &random_string.as_bytes()[1..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
let expected = &random_string.as_bytes()[1..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8191"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8192"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(random_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8193"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(random_string);
|
||||
|
||||
let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE - 1..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_bytes_option_given_input_size_is_one_byte_greater_than_buffer_size() {
|
||||
let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE + 1);
|
||||
let random_string = random_string.as_str();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
let expected = &random_string.as_bytes()[1..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+2"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-1"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[1..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8192"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8193"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size() {
|
||||
let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE * 3);
|
||||
let random_string = random_string.as_str();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-0"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
|
||||
let expected = &random_string.as_bytes()[8192..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+8193"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[8193..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+8194"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[16384..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+16385"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[16385..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+16386"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[16384..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8192"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[16383..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-8193"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[8192..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-16384"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
let expected = &random_string.as_bytes()[8191..];
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-16385"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only_bytes(expected);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-24576"])
|
||||
.pipe_in(random_string)
|
||||
.ignore_stdin_write_error()
|
||||
.succeeds()
|
||||
.stdout_only(random_string);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seek_bytes_backward_outside_file() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[macro_use]
|
||||
pub mod macros;
|
||||
pub mod random;
|
||||
pub mod util;
|
||||
|
|
314
tests/common/random.rs
Normal file
314
tests/common/random.rs
Normal file
|
@ -0,0 +1,314 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
/// Samples alphanumeric characters `[A-Za-z0-9]` including newline `\n`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use rand::{Rng, thread_rng};
|
||||
///
|
||||
/// let vec = thread_rng()
|
||||
/// .sample_iter(AlphanumericNewline)
|
||||
/// .take(10)
|
||||
/// .collect::<Vec<u8>>();
|
||||
/// println!("Random chars: {}", String::from_utf8(vec).unwrap());
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AlphanumericNewline;
|
||||
|
||||
impl AlphanumericNewline {
|
||||
/// The charset to act upon
|
||||
const CHARSET: &'static [u8] =
|
||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n";
|
||||
|
||||
/// Generate a random byte from [`Self::CHARSET`] and return it as `u8`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rng`: A [`rand::Rng`]
|
||||
///
|
||||
/// returns: u8
|
||||
fn random<R>(rng: &mut R) -> u8
|
||||
where
|
||||
R: Rng + ?Sized,
|
||||
{
|
||||
let idx = rng.gen_range(0..Self::CHARSET.len());
|
||||
Self::CHARSET[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<u8> for AlphanumericNewline {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
|
||||
Self::random(rng)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random string from a [`Distribution`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use crate::common::random::{AlphanumericNewline, RandomString};
|
||||
/// use rand::distributions::Alphanumeric;
|
||||
///
|
||||
/// // generates a 100 byte string with characters from AlphanumericNewline
|
||||
/// let random_string = RandomString::generate(&AlphanumericNewline, 100);
|
||||
/// assert_eq!(100, random_string.len());
|
||||
///
|
||||
/// // generates a 100 byte string with 10 newline characters not ending with a newline
|
||||
/// let string = RandomString::generate_with_delimiter(&Alphanumeric, b'\n', 10, false, 100);
|
||||
/// assert_eq!(100, random_string.len());
|
||||
/// ```
|
||||
pub struct RandomString;
|
||||
|
||||
impl RandomString {
|
||||
/// Generate a random string from the given [`Distribution`] with the given `length` in bytes.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `dist`: A u8 [`Distribution`]
|
||||
/// * `length`: the length of the resulting string in bytes
|
||||
///
|
||||
/// returns: String
|
||||
pub fn generate<D>(dist: D, length: usize) -> String
|
||||
where
|
||||
D: Distribution<u8>,
|
||||
{
|
||||
thread_rng()
|
||||
.sample_iter(dist)
|
||||
.take(length)
|
||||
.map(|b| b as char)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate a random string from the [`Distribution`] with the given `length` in bytes. The
|
||||
/// function takes a `delimiter`, which is randomly distributed in the string, such that exactly
|
||||
/// `num_delimiter` amount of `delimiter`s occur. If `end_with_delimiter` is set, then the
|
||||
/// string ends with the delimiter, else the string does not end with the delimiter.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `dist`: A `u8` [`Distribution`]
|
||||
/// * `delimiter`: A `u8` delimiter, which does not need to be included in the `Distribution`
|
||||
/// * `num_delimiter`: The number of `delimiter`s contained in the resulting string
|
||||
/// * `end_with_delimiter`: If the string shall end with the given delimiter
|
||||
/// * `length`: the length of the resulting string in bytes
|
||||
///
|
||||
/// returns: String
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use crate::common::random::{AlphanumericNewline, RandomString};
|
||||
///
|
||||
/// // generates a 100 byte string with 10 '\0' byte characters not ending with a '\0' byte
|
||||
/// let string = RandomString::generate_with_delimiter(&AlphanumericNewline, 0, 10, false, 100);
|
||||
/// assert_eq!(100, random_string.len());
|
||||
/// assert_eq!(
|
||||
/// 10,
|
||||
/// random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
/// );
|
||||
/// assert!(!random_string.as_bytes().ends_with(&[0]));
|
||||
/// ```
|
||||
pub fn generate_with_delimiter<D>(
|
||||
dist: D,
|
||||
delimiter: u8,
|
||||
num_delimiter: usize,
|
||||
end_with_delimiter: bool,
|
||||
length: usize,
|
||||
) -> String
|
||||
where
|
||||
D: Distribution<u8>,
|
||||
{
|
||||
if length == 0 {
|
||||
return String::from("");
|
||||
} else if length == 1 {
|
||||
return if num_delimiter > 0 {
|
||||
String::from(delimiter as char)
|
||||
} else {
|
||||
String::from(thread_rng().sample(&dist) as char)
|
||||
};
|
||||
}
|
||||
|
||||
let samples = length - 1;
|
||||
let mut result: Vec<u8> = thread_rng().sample_iter(&dist).take(samples).collect();
|
||||
|
||||
if num_delimiter == 0 {
|
||||
result.push(thread_rng().sample(&dist));
|
||||
return String::from_utf8(result).unwrap();
|
||||
}
|
||||
|
||||
let num_delimiter = if end_with_delimiter {
|
||||
num_delimiter - 1
|
||||
} else {
|
||||
num_delimiter
|
||||
};
|
||||
|
||||
let between = Uniform::new(0, samples);
|
||||
for _ in 0..num_delimiter {
|
||||
let mut pos = between.sample(&mut thread_rng());
|
||||
let turn = pos;
|
||||
while result[pos] == delimiter {
|
||||
pos += 1;
|
||||
if pos >= samples {
|
||||
pos = 0;
|
||||
}
|
||||
if pos == turn {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result[pos] = delimiter;
|
||||
}
|
||||
|
||||
if end_with_delimiter {
|
||||
result.push(delimiter);
|
||||
} else {
|
||||
result.push(thread_rng().sample(&dist));
|
||||
}
|
||||
|
||||
String::from_utf8(result).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
||||
#[test]
|
||||
fn test_random_string_generate() {
|
||||
let random_string = RandomString::generate(&AlphanumericNewline, 0);
|
||||
assert_eq!(0, random_string.len());
|
||||
|
||||
let random_string = RandomString::generate(&AlphanumericNewline, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
|
||||
let random_string = RandomString::generate(&AlphanumericNewline, 100);
|
||||
assert_eq!(100, random_string.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_string_generate_with_delimiter_when_length_is_zero() {
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 0, false, 0);
|
||||
assert_eq!(0, random_string.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_string_generate_with_delimiter_when_num_delimiter_is_greater_than_length() {
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 2, false, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
assert!(random_string.as_bytes().contains(&0));
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_string_generate_with_delimiter_should_end_with_delimiter() {
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, true, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, false, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, true, 2);
|
||||
assert_eq!(2, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 2, true, 2);
|
||||
assert_eq!(2, random_string.len());
|
||||
assert_eq!(
|
||||
2,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, true, 3);
|
||||
assert_eq!(3, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_string_generate_with_delimiter_should_not_end_with_delimiter() {
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 0, false, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
assert_eq!(
|
||||
0,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 0, true, 1);
|
||||
assert_eq!(1, random_string.len());
|
||||
assert_eq!(
|
||||
0,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, false, 2);
|
||||
assert_eq!(2, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(!random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 1, false, 3);
|
||||
assert_eq!(3, random_string.len());
|
||||
assert_eq!(
|
||||
1,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(!random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string = RandomString::generate_with_delimiter(&Alphanumeric, 0, 2, false, 3);
|
||||
assert_eq!(3, random_string.len());
|
||||
assert_eq!(
|
||||
2,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(!random_string.as_bytes().ends_with(&[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_with_delimiter_with_greater_length() {
|
||||
let random_string =
|
||||
RandomString::generate_with_delimiter(&Alphanumeric, 0, 100, false, 1000);
|
||||
assert_eq!(1000, random_string.len());
|
||||
assert_eq!(
|
||||
100,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(!random_string.as_bytes().ends_with(&[0]));
|
||||
|
||||
let random_string =
|
||||
RandomString::generate_with_delimiter(&Alphanumeric, 0, 100, true, 1000);
|
||||
assert_eq!(1000, random_string.len());
|
||||
assert_eq!(
|
||||
100,
|
||||
random_string.as_bytes().iter().filter(|p| **p == 0).count()
|
||||
);
|
||||
assert!(random_string.as_bytes().ends_with(&[0]));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue