mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #854 from fitzgen/tests-for-tail-bytes
Miscellaneous `tail` related commits
This commit is contained in:
commit
c954b01aa3
3 changed files with 109 additions and 50 deletions
|
@ -272,6 +272,8 @@ fn follow<T: Read>(mut reader: BufReader<T>, settings: &Settings) {
|
||||||
fn backwards_thru_file<F>(file: &mut File, size: u64, buf: &mut Vec<u8>, should_stop: &mut F)
|
fn backwards_thru_file<F>(file: &mut File, size: u64, buf: &mut Vec<u8>, should_stop: &mut F)
|
||||||
where F: FnMut(u8) -> bool
|
where F: FnMut(u8) -> bool
|
||||||
{
|
{
|
||||||
|
assert!(buf.len() >= BLOCK_SIZE as usize);
|
||||||
|
|
||||||
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
|
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
|
||||||
|
|
||||||
for block_idx in 0..max_blocks_to_read {
|
for block_idx in 0..max_blocks_to_read {
|
||||||
|
@ -281,13 +283,6 @@ fn backwards_thru_file<F>(file: &mut File, size: u64, buf: &mut Vec<u8>, should_
|
||||||
BLOCK_SIZE
|
BLOCK_SIZE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure that the buffer is filled and zeroed, if needed.
|
|
||||||
if buf.len() < (block_size as usize) {
|
|
||||||
for _ in buf.len()..(block_size as usize) {
|
|
||||||
buf.push(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek backwards by the next block, read the full block into
|
// Seek backwards by the next block, read the full block into
|
||||||
// `buf`, and then seek back to the start of the block again.
|
// `buf`, and then seek back to the start of the block again.
|
||||||
let pos = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap();
|
let pos = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap();
|
||||||
|
@ -327,7 +322,7 @@ fn bounded_tail(mut file: File, settings: &Settings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(BLOCK_SIZE as usize);
|
let mut buf = vec![0; BLOCK_SIZE as usize];
|
||||||
|
|
||||||
// Find the position in the file to start printing from.
|
// Find the position in the file to start printing from.
|
||||||
match settings.mode {
|
match settings.mode {
|
||||||
|
@ -341,11 +336,8 @@ fn bounded_tail(mut file: File, settings: &Settings) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
FilterMode::Bytes(mut count) => {
|
FilterMode::Bytes(count) => {
|
||||||
backwards_thru_file(&mut file, size, &mut buf, &mut |_| {
|
file.seek(SeekFrom::End(-(count as i64))).unwrap();
|
||||||
count -= 1;
|
|
||||||
count == 0
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
38
tests/common/util.rs
Normal file → Executable file
38
tests/common/util.rs
Normal file → Executable file
|
@ -5,6 +5,7 @@ extern crate tempdir;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{Read, Write, Result};
|
use std::io::{Read, Write, Result};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::symlink as symlink_file;
|
use std::os::unix::fs::symlink as symlink_file;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -122,6 +123,40 @@ pub fn recursive_copy(src: &Path, dest: &Path) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A scoped, temporary file that is removed upon drop.
|
||||||
|
pub struct ScopedFile {
|
||||||
|
path: PathBuf,
|
||||||
|
file: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopedFile {
|
||||||
|
fn new(path: PathBuf, file: File) -> ScopedFile {
|
||||||
|
ScopedFile {
|
||||||
|
path: path,
|
||||||
|
file: file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ScopedFile {
|
||||||
|
type Target = File;
|
||||||
|
fn deref(&self) -> &File {
|
||||||
|
&self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for ScopedFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut File {
|
||||||
|
&mut self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScopedFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
fs::remove_file(&self.path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AtPath {
|
pub struct AtPath {
|
||||||
pub subdir: PathBuf,
|
pub subdir: PathBuf,
|
||||||
}
|
}
|
||||||
|
@ -185,6 +220,9 @@ impl AtPath {
|
||||||
Err(e) => panic!("{}", e),
|
Err(e) => panic!("{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn make_scoped_file(&self, name: &str) -> ScopedFile {
|
||||||
|
ScopedFile::new(self.plus(name), self.make_file(name))
|
||||||
|
}
|
||||||
pub fn touch(&self, file: &str) {
|
pub fn touch(&self, file: &str) {
|
||||||
log_info("touch", self.plus_as_string(file));
|
log_info("touch", self.plus_as_string(file));
|
||||||
File::create(&self.plus(file)).unwrap();
|
File::create(&self.plus(file)).unwrap();
|
||||||
|
|
|
@ -7,67 +7,96 @@ use common::util::*;
|
||||||
|
|
||||||
static UTIL_NAME: &'static str = "tail";
|
static UTIL_NAME: &'static str = "tail";
|
||||||
|
|
||||||
static INPUT: &'static str = "foobar.txt";
|
static FOOBAR_TXT: &'static str = "foobar.txt";
|
||||||
|
|
||||||
static BIG: &'static str = "big.txt";
|
|
||||||
|
|
||||||
static BIG_EXPECTED: &'static str = "big_single_big_args.expected";
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stdin_default() {
|
fn test_stdin_default() {
|
||||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
let result = ucmd.run_piped_stdin(at.read(INPUT));
|
let result = ucmd.run_piped_stdin(at.read(FOOBAR_TXT));
|
||||||
assert_eq!(result.stdout, at.read("foobar_stdin_default.expected"));
|
assert_eq!(result.stdout, at.read("foobar_stdin_default.expected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_default() {
|
fn test_single_default() {
|
||||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
let result = ucmd.arg(INPUT).run();
|
let result = ucmd.arg(FOOBAR_TXT).run();
|
||||||
assert_eq!(result.stdout, at.read("foobar_single_default.expected"));
|
assert_eq!(result.stdout, at.read("foobar_single_default.expected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const BIG_LINES: usize = 1_000_000;
|
#[test]
|
||||||
const BIG_N_ARG: usize = 100_000;
|
fn test_n_greater_than_number_of_lines() {
|
||||||
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
fn generate_big_test_files(at: &AtPath) {
|
let result = ucmd.arg("-n").arg("99999999").arg(FOOBAR_TXT).run();
|
||||||
let mut big_input = at.make_file(BIG);
|
assert_eq!(result.stdout, at.read(FOOBAR_TXT));
|
||||||
for i in 0..BIG_LINES {
|
|
||||||
write!(&mut big_input, "Line {}\n", i).expect("Could not write to BIG file");
|
|
||||||
}
|
|
||||||
big_input.flush().expect("Could not flush BIG file");
|
|
||||||
|
|
||||||
let mut big_expected = at.make_file(BIG_EXPECTED);
|
|
||||||
for i in (BIG_LINES - BIG_N_ARG)..BIG_LINES {
|
|
||||||
write!(&mut big_expected, "Line {}\n", i).expect("Could not write to BIG_EXPECTED file");
|
|
||||||
}
|
|
||||||
big_expected.flush().expect("Could not flush BIG_EXPECTED file");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cleanup_big_test_files(at: &AtPath) {
|
|
||||||
at.cleanup(BIG);
|
|
||||||
at.cleanup(BIG_EXPECTED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_big_args() {
|
fn test_single_big_args() {
|
||||||
|
const FILE: &'static str = "single_big_args.txt";
|
||||||
|
const EXPECTED_FILE: &'static str = "single_big_args_expected.txt";
|
||||||
|
const LINES: usize = 1_000_000;
|
||||||
|
const N_ARG: usize = 100_000;
|
||||||
|
|
||||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
generate_big_test_files(&at);
|
|
||||||
let result = ucmd.arg(BIG).arg("-n").arg(format!("{}", BIG_N_ARG)).run();
|
let mut big_input = at.make_scoped_file(FILE);
|
||||||
assert_eq!(result.stdout, at.read(BIG_EXPECTED));
|
for i in 0..LINES {
|
||||||
cleanup_big_test_files(&at);
|
write!(&mut big_input, "Line {}\n", i).expect("Could not write to FILE");
|
||||||
|
}
|
||||||
|
big_input.flush().expect("Could not flush FILE");
|
||||||
|
|
||||||
|
let mut big_expected = at.make_scoped_file(EXPECTED_FILE);
|
||||||
|
for i in (LINES - N_ARG)..LINES {
|
||||||
|
write!(&mut big_expected, "Line {}\n", i).expect("Could not write to EXPECTED_FILE");
|
||||||
|
}
|
||||||
|
big_expected.flush().expect("Could not flush EXPECTED_FILE");
|
||||||
|
|
||||||
|
let result = ucmd.arg(FILE).arg("-n").arg(format!("{}", N_ARG)).run();
|
||||||
|
assert_eq!(result.stdout, at.read(EXPECTED_FILE));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bytes_single() {
|
fn test_bytes_single() {
|
||||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
let result = ucmd.arg("-c").arg("10").arg(INPUT).run();
|
let result = ucmd.arg("-c").arg("10").arg(FOOBAR_TXT).run();
|
||||||
assert_eq!(result.stdout, at.read("foobar_bytes_single.expected"));
|
assert_eq!(result.stdout, at.read("foobar_bytes_single.expected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bytes_stdin() {
|
fn test_bytes_stdin() {
|
||||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
let result = ucmd.arg("-c").arg("13").run_piped_stdin(at.read(INPUT));
|
let result = ucmd.arg("-c").arg("13").run_piped_stdin(at.read(FOOBAR_TXT));
|
||||||
assert_eq!(result.stdout, at.read("foobar_bytes_stdin.expected"));
|
assert_eq!(result.stdout, at.read("foobar_bytes_stdin.expected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bytes_big() {
|
||||||
|
const FILE: &'static str = "test_bytes_big.txt";
|
||||||
|
const EXPECTED_FILE: &'static str = "test_bytes_big_expected.txt";
|
||||||
|
const BYTES: usize = 1_000_000;
|
||||||
|
const N_ARG: usize = 100_000;
|
||||||
|
|
||||||
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
|
||||||
|
let mut big_input = at.make_scoped_file(FILE);
|
||||||
|
for i in 0..BYTES {
|
||||||
|
let digit = std::char::from_digit((i % 10) as u32, 10).unwrap();
|
||||||
|
write!(&mut big_input, "{}", digit).expect("Could not write to FILE");
|
||||||
|
}
|
||||||
|
big_input.flush().expect("Could not flush FILE");
|
||||||
|
|
||||||
|
let mut big_expected = at.make_scoped_file(EXPECTED_FILE);
|
||||||
|
for i in (BYTES - N_ARG)..BYTES {
|
||||||
|
let digit = std::char::from_digit((i % 10) as u32, 10).unwrap();
|
||||||
|
write!(&mut big_expected, "{}", digit).expect("Could not write to EXPECTED_FILE");
|
||||||
|
}
|
||||||
|
big_expected.flush().expect("Could not flush EXPECTED_FILE");
|
||||||
|
|
||||||
|
let result = ucmd.arg(FILE).arg("-c").arg(format!("{}", N_ARG)).run().stdout;
|
||||||
|
let expected = at.read(EXPECTED_FILE);
|
||||||
|
|
||||||
|
assert_eq!(result.len(), expected.len());
|
||||||
|
for (actual_char, expected_char) in result.chars().zip(expected.chars()) {
|
||||||
|
assert_eq!(actual_char, expected_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue