2021-06-03 22:49:06 +00:00
// * 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.
2022-04-19 20:14:55 +00:00
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile logfile bogusfile siette ocho nueve diez
2021-10-01 12:17:57 +00:00
// spell-checker:ignore (libs) kqueue
2021-11-03 10:24:11 +00:00
// spell-checker:ignore (jargon) tailable untailable
2021-06-03 22:49:06 +00:00
2020-05-04 06:25:36 +00:00
extern crate tail ;
2015-12-12 21:24:48 +00:00
2020-05-25 17:05:26 +00:00
use crate ::common ::util ::* ;
2016-05-22 07:46:54 +00:00
use std ::char ::from_digit ;
2021-09-27 21:18:00 +00:00
use std ::io ::{ Read , Write } ;
2022-04-21 20:52:17 +00:00
#[ cfg(unix) ]
2021-09-27 21:18:00 +00:00
use std ::thread ::sleep ;
2022-04-21 20:52:17 +00:00
#[ cfg(unix) ]
2021-09-27 21:18:00 +00:00
use std ::time ::Duration ;
2015-12-12 21:24:48 +00:00
2021-10-24 20:35:00 +00:00
#[ cfg(target_os = " linux " ) ]
pub static BACKEND : & str = " inotify " ;
#[ cfg(all(unix, not(target_os = " linux " ))) ]
pub static BACKEND : & str = " kqueue " ;
2021-05-29 12:32:35 +00:00
static FOOBAR_TXT : & str = " foobar.txt " ;
static FOOBAR_2_TXT : & str = " foobar2.txt " ;
static FOOBAR_WITH_NULL_TXT : & str = " foobar_with_null.txt " ;
2022-04-21 20:52:17 +00:00
#[ cfg(unix) ]
2021-09-27 21:18:00 +00:00
static FOLLOW_NAME_TXT : & str = " follow_name.txt " ;
2022-04-21 20:52:17 +00:00
#[ cfg(unix) ]
2021-10-03 20:37:06 +00:00
static FOLLOW_NAME_SHORT_EXP : & str = " follow_name_short.expected " ;
2022-04-05 20:19:25 +00:00
#[ cfg(target_os = " linux " ) ]
2021-09-27 21:18:00 +00:00
static FOLLOW_NAME_EXP : & str = " follow_name.expected " ;
2015-12-12 21:24:48 +00:00
#[ test ]
fn test_stdin_default ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. pipe_in_fixture ( FOOBAR_TXT )
. run ( )
. stdout_is_fixture ( " foobar_stdin_default.expected " ) ;
2021-07-12 23:46:43 +00:00
}
#[ test ]
fn test_stdin_explicit ( ) {
new_ucmd! ( )
. pipe_in_fixture ( FOOBAR_TXT )
. arg ( " - " )
. run ( )
. stdout_is_fixture ( " foobar_stdin_default.expected " ) ;
2015-12-12 21:24:48 +00:00
}
#[ test ]
fn test_single_default ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( FOOBAR_TXT )
. run ( )
. stdout_is_fixture ( " foobar_single_default.expected " ) ;
2015-12-12 21:24:48 +00:00
}
2016-03-20 05:25:53 +00:00
2016-03-27 21:42:45 +00:00
#[ test ]
fn test_n_greater_than_number_of_lines ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( " -n " )
. arg ( " 99999999 " )
. arg ( FOOBAR_TXT )
. run ( )
2016-07-29 21:26:32 +00:00
. stdout_is_fixture ( FOOBAR_TXT ) ;
2016-03-27 21:42:45 +00:00
}
2016-04-02 10:32:33 +00:00
#[ test ]
fn test_null_default ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( " -z " )
. arg ( FOOBAR_WITH_NULL_TXT )
. run ( )
. stdout_is_fixture ( " foobar_with_null_default.expected " ) ;
2016-04-02 10:32:33 +00:00
}
2016-04-02 21:09:20 +00:00
#[ test ]
2021-10-24 20:35:00 +00:00
fn test_follow_single ( ) {
2016-08-23 11:52:43 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
2016-04-02 21:09:20 +00:00
let mut child = ucmd . arg ( " -f " ) . arg ( FOOBAR_TXT ) . run_no_wait ( ) ;
let expected = at . read ( " foobar_single_default.expected " ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
// We write in a temporary copy of foobar.txt
let expected = " line1 \n line2 \n " ;
at . append ( FOOBAR_TXT , expected ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
child . kill ( ) . unwrap ( ) ;
}
2022-01-25 02:18:59 +00:00
/// Test for following when bytes are written that are not valid UTF-8.
#[ test ]
fn test_follow_non_utf8_bytes ( ) {
// Tail the test file and start following it.
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
let mut child = ucmd . arg ( " -f " ) . arg ( FOOBAR_TXT ) . run_no_wait ( ) ;
let expected = at . read ( " foobar_single_default.expected " ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
// Now append some bytes that are not valid UTF-8.
//
// The binary integer "10000000" is *not* a valid UTF-8 encoding
// of a character: https://en.wikipedia.org/wiki/UTF-8#Encoding
//
// We also write the newline character because our implementation
// of `tail` is attempting to read a line of input, so the
// presence of a newline character will force the `follow()`
// function to conclude reading input bytes and start writing them
// to output. The newline character is not fundamental to this
// test, it is just a requirement of the current implementation.
let expected = [ 0b10000000 , b '\n' ] ;
at . append_bytes ( FOOBAR_TXT , & expected ) ;
let actual = read_size_bytes ( & mut child , expected . len ( ) ) ;
assert_eq! ( actual , expected . to_vec ( ) ) ;
child . kill ( ) . unwrap ( ) ;
}
2016-05-30 20:34:53 +00:00
#[ test ]
fn test_follow_multiple ( ) {
2016-08-23 11:52:43 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
2020-04-13 18:36:03 +00:00
let mut child = ucmd
. arg ( " -f " )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. run_no_wait ( ) ;
2016-05-30 20:34:53 +00:00
let expected = at . read ( " foobar_follow_multiple.expected " ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
let first_append = " trois \n " ;
at . append ( FOOBAR_2_TXT , first_append ) ;
assert_eq! ( read_size ( & mut child , first_append . len ( ) ) , first_append ) ;
2021-05-30 05:10:54 +00:00
let second_append = " twenty \n thirty \n " ;
2016-05-30 20:34:53 +00:00
let expected = at . read ( " foobar_follow_multiple_appended.expected " ) ;
at . append ( FOOBAR_TXT , second_append ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
child . kill ( ) . unwrap ( ) ;
}
2021-09-28 18:05:09 +00:00
#[ test ]
2021-10-24 20:35:00 +00:00
#[ cfg(unix) ]
2021-09-28 18:05:09 +00:00
fn test_follow_name_multiple ( ) {
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
let mut child = ucmd
. arg ( " --follow=name " )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. run_no_wait ( ) ;
let expected = at . read ( " foobar_follow_multiple.expected " ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
let first_append = " trois \n " ;
at . append ( FOOBAR_2_TXT , first_append ) ;
assert_eq! ( read_size ( & mut child , first_append . len ( ) ) , first_append ) ;
let second_append = " twenty \n thirty \n " ;
let expected = at . read ( " foobar_follow_multiple_appended.expected " ) ;
at . append ( FOOBAR_TXT , second_append ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
child . kill ( ) . unwrap ( ) ;
}
2016-05-31 20:40:06 +00:00
#[ test ]
fn test_follow_stdin ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( " -f " )
. pipe_in_fixture ( FOOBAR_TXT )
. run ( )
. stdout_is_fixture ( " follow_stdin.expected " ) ;
2016-05-31 20:40:06 +00:00
}
2021-05-30 05:10:54 +00:00
// FixME: test PASSES for usual windows builds, but fails for coverage testing builds (likely related to the specific RUSTFLAGS '-Zpanic_abort_tests -Cpanic=abort') This test also breaks tty settings under bash requiring a 'stty sane' or reset. // spell-checker:disable-line
2021-02-23 09:25:06 +00:00
#[ cfg(disable_until_fixed) ]
2016-12-20 01:47:40 +00:00
#[ test ]
fn test_follow_with_pid ( ) {
2020-08-10 05:15:26 +00:00
use std ::process ::{ Command , Stdio } ;
use std ::thread ::sleep ;
use std ::time ::Duration ;
2016-12-20 01:47:40 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
#[ cfg(unix) ]
let dummy_cmd = " sh " ;
#[ cfg(windows) ]
let dummy_cmd = " cmd " ;
2020-04-13 18:36:03 +00:00
let mut dummy = Command ::new ( dummy_cmd )
. stdout ( Stdio ::null ( ) )
. spawn ( )
. unwrap ( ) ;
2016-12-20 01:47:40 +00:00
let pid = dummy . id ( ) ;
2020-04-13 18:36:03 +00:00
let mut child = ucmd
. arg ( " -f " )
. arg ( format! ( " --pid= {} " , pid ) )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. run_no_wait ( ) ;
2016-12-20 01:47:40 +00:00
let expected = at . read ( " foobar_follow_multiple.expected " ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
let first_append = " trois \n " ;
at . append ( FOOBAR_2_TXT , first_append ) ;
assert_eq! ( read_size ( & mut child , first_append . len ( ) ) , first_append ) ;
2021-05-30 05:10:54 +00:00
let second_append = " twenty \n thirty \n " ;
2016-12-20 01:47:40 +00:00
let expected = at . read ( " foobar_follow_multiple_appended.expected " ) ;
at . append ( FOOBAR_TXT , second_append ) ;
assert_eq! ( read_size ( & mut child , expected . len ( ) ) , expected ) ;
// kill the dummy process and give tail time to notice this
dummy . kill ( ) . unwrap ( ) ;
let _ = dummy . wait ( ) ;
sleep ( Duration ::from_secs ( 1 ) ) ;
let third_append = " should \n be \n ignored \n " ;
at . append ( FOOBAR_TXT , third_append ) ;
assert_eq! ( read_size ( & mut child , 1 ) , " \u{0} " ) ;
// On Unix, trying to kill a process that's already dead is fine; on Windows it's an error.
#[ cfg(unix) ]
child . kill ( ) . unwrap ( ) ;
#[ cfg(windows) ]
assert_eq! ( child . kill ( ) . is_err ( ) , true ) ;
}
2016-03-26 19:36:15 +00:00
#[ test ]
fn test_single_big_args ( ) {
2021-05-29 12:32:35 +00:00
const FILE : & str = " single_big_args.txt " ;
const EXPECTED_FILE : & str = " single_big_args_expected.txt " ;
2016-03-26 19:36:15 +00:00
const LINES : usize = 1_000_000 ;
const N_ARG : usize = 100_000 ;
2016-03-20 05:25:53 +00:00
2016-08-23 11:52:43 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
2016-03-20 05:25:53 +00:00
2016-05-22 19:29:23 +00:00
let mut big_input = at . make_file ( FILE ) ;
2016-03-26 19:36:15 +00:00
for i in 0 .. LINES {
2022-01-30 10:04:36 +00:00
writeln! ( big_input , " Line {} " , i ) . expect ( " Could not write to FILE " ) ;
2016-03-20 05:25:53 +00:00
}
2016-03-26 19:36:15 +00:00
big_input . flush ( ) . expect ( " Could not flush FILE " ) ;
2016-03-20 05:25:53 +00:00
2016-05-22 19:29:23 +00:00
let mut big_expected = at . make_file ( EXPECTED_FILE ) ;
2016-03-26 19:36:15 +00:00
for i in ( LINES - N_ARG ) .. LINES {
2022-01-30 10:04:36 +00:00
writeln! ( big_expected , " Line {} " , i ) . expect ( " Could not write to EXPECTED_FILE " ) ;
2016-03-26 19:36:15 +00:00
}
big_expected . flush ( ) . expect ( " Could not flush EXPECTED_FILE " ) ;
2016-03-20 05:25:53 +00:00
2020-04-13 18:36:03 +00:00
ucmd . arg ( FILE )
. arg ( " -n " )
. arg ( format! ( " {} " , N_ARG ) )
. run ( )
. stdout_is ( at . read ( EXPECTED_FILE ) ) ;
2016-03-20 05:25:53 +00:00
}
2016-03-26 18:52:10 +00:00
#[ test ]
fn test_bytes_single ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( " -c " )
. arg ( " 10 " )
. arg ( FOOBAR_TXT )
. run ( )
2016-07-29 21:26:32 +00:00
. stdout_is_fixture ( " foobar_bytes_single.expected " ) ;
2016-03-26 18:52:10 +00:00
}
2016-03-26 18:56:00 +00:00
#[ test ]
fn test_bytes_stdin ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( " -c " )
. arg ( " 13 " )
. pipe_in_fixture ( FOOBAR_TXT )
. run ( )
. stdout_is_fixture ( " foobar_bytes_stdin.expected " ) ;
2016-03-26 18:56:00 +00:00
}
2016-03-27 21:33:55 +00:00
#[ test ]
fn test_bytes_big ( ) {
2021-05-29 12:32:35 +00:00
const FILE : & str = " test_bytes_big.txt " ;
const EXPECTED_FILE : & str = " test_bytes_big_expected.txt " ;
2016-03-27 21:33:55 +00:00
const BYTES : usize = 1_000_000 ;
const N_ARG : usize = 100_000 ;
2016-08-23 11:52:43 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
2016-03-27 21:33:55 +00:00
2016-05-22 19:29:23 +00:00
let mut big_input = at . make_file ( FILE ) ;
2016-03-27 21:33:55 +00:00
for i in 0 .. BYTES {
2016-05-22 07:46:54 +00:00
let digit = from_digit ( ( i % 10 ) as u32 , 10 ) . unwrap ( ) ;
2022-01-30 10:04:36 +00:00
write! ( big_input , " {} " , digit ) . expect ( " Could not write to FILE " ) ;
2016-03-27 21:33:55 +00:00
}
big_input . flush ( ) . expect ( " Could not flush FILE " ) ;
2016-05-22 19:29:23 +00:00
let mut big_expected = at . make_file ( EXPECTED_FILE ) ;
2016-03-27 21:33:55 +00:00
for i in ( BYTES - N_ARG ) .. BYTES {
2016-05-22 07:46:54 +00:00
let digit = from_digit ( ( i % 10 ) as u32 , 10 ) . unwrap ( ) ;
2022-01-30 10:04:36 +00:00
write! ( big_expected , " {} " , digit ) . expect ( " Could not write to EXPECTED_FILE " ) ;
2016-03-27 21:33:55 +00:00
}
big_expected . flush ( ) . expect ( " Could not flush EXPECTED_FILE " ) ;
2020-04-13 18:36:03 +00:00
let result = ucmd
. arg ( FILE )
. arg ( " -c " )
. arg ( format! ( " {} " , N_ARG ) )
2021-04-17 12:22:20 +00:00
. succeeds ( )
. stdout_move_str ( ) ;
2016-03-27 21:33:55 +00:00
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 ) ;
}
}
2016-04-02 21:16:11 +00:00
#[ test ]
fn test_lines_with_size_suffix ( ) {
2021-05-29 12:32:35 +00:00
const FILE : & str = " test_lines_with_size_suffix.txt " ;
const EXPECTED_FILE : & str = " test_lines_with_size_suffix_expected.txt " ;
2016-04-02 21:16:11 +00:00
const LINES : usize = 3_000 ;
const N_ARG : usize = 2 * 1024 ;
2016-08-23 11:52:43 +00:00
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
2016-04-02 21:16:11 +00:00
2016-05-22 19:29:23 +00:00
let mut big_input = at . make_file ( FILE ) ;
2016-04-02 21:16:11 +00:00
for i in 0 .. LINES {
2022-01-30 10:04:36 +00:00
writeln! ( big_input , " Line {} " , i ) . expect ( " Could not write to FILE " ) ;
2016-04-02 21:16:11 +00:00
}
big_input . flush ( ) . expect ( " Could not flush FILE " ) ;
2016-05-22 19:29:23 +00:00
let mut big_expected = at . make_file ( EXPECTED_FILE ) ;
2016-04-02 21:16:11 +00:00
for i in ( LINES - N_ARG ) .. LINES {
2022-01-30 10:04:36 +00:00
writeln! ( big_expected , " Line {} " , i ) . expect ( " Could not write to EXPECTED_FILE " ) ;
2016-04-02 21:16:11 +00:00
}
big_expected . flush ( ) . expect ( " Could not flush EXPECTED_FILE " ) ;
2020-04-13 18:36:03 +00:00
ucmd . arg ( FILE )
. arg ( " -n " )
. arg ( " 2K " )
. run ( )
. stdout_is_fixture ( EXPECTED_FILE ) ;
2016-04-02 21:16:11 +00:00
}
2017-10-05 19:25:21 +00:00
#[ test ]
fn test_multiple_input_files ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. run ( )
. stdout_is_fixture ( " foobar_follow_multiple.expected " ) ;
2017-10-05 19:25:21 +00:00
}
2021-09-16 19:40:15 +00:00
#[ test ]
fn test_multiple_input_files_missing ( ) {
new_ucmd! ( )
. arg ( FOOBAR_TXT )
. arg ( " missing1 " )
. arg ( FOOBAR_2_TXT )
. arg ( " missing2 " )
. run ( )
. stdout_is_fixture ( " foobar_follow_multiple.expected " )
. stderr_is (
2021-10-08 21:36:41 +00:00
" tail: cannot open 'missing1' for reading: No such file or directory \n \
tail : cannot open ' missing2 ' for reading : No such file or directory " ,
2021-09-16 19:40:15 +00:00
)
. code_is ( 1 ) ;
}
2021-10-24 20:35:00 +00:00
#[ test ]
fn test_follow_missing ( ) {
// Ensure that --follow=name does not imply --retry.
// Ensure that --follow={descriptor,name} (without --retry) does *not wait* for the
// file to appear.
for follow_mode in & [ " --follow=descriptor " , " --follow=name " ] {
new_ucmd! ( )
. arg ( follow_mode )
. arg ( " missing " )
. run ( )
. stderr_is (
" tail: cannot open 'missing' for reading: No such file or directory \n \
tail : no files remaining " ,
)
. code_is ( 1 ) ;
}
}
#[ test ]
fn test_follow_name_stdin ( ) {
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
at . touch ( " FILE1 " ) ;
at . touch ( " FILE2 " ) ;
ts . ucmd ( )
. arg ( " --follow=name " )
. arg ( " - " )
. run ( )
. stderr_is ( " tail: cannot follow '-' by name " )
. code_is ( 1 ) ;
ts . ucmd ( )
. arg ( " --follow=name " )
. arg ( " FILE1 " )
. arg ( " - " )
. arg ( " FILE2 " )
. run ( )
. stderr_is ( " tail: cannot follow '-' by name " )
. code_is ( 1 ) ;
}
2017-10-05 19:25:21 +00:00
#[ test ]
fn test_multiple_input_files_with_suppressed_headers ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. arg ( " -q " )
. run ( )
. stdout_is_fixture ( " foobar_multiple_quiet.expected " ) ;
2017-10-05 19:25:21 +00:00
}
#[ test ]
fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers ( ) {
2020-04-13 18:36:03 +00:00
new_ucmd! ( )
. arg ( FOOBAR_TXT )
. arg ( FOOBAR_2_TXT )
. arg ( " -v " )
2021-06-03 18:37:29 +00:00
. arg ( " -q " )
2020-04-13 18:36:03 +00:00
. run ( )
. stdout_is_fixture ( " foobar_multiple_quiet.expected " ) ;
2017-10-05 19:25:21 +00:00
}
2021-03-22 09:01:54 +00:00
2021-10-08 21:36:41 +00:00
#[ test ]
fn test_dir ( ) {
let ( at , mut ucmd ) = at_and_ucmd! ( ) ;
at . mkdir ( " DIR " ) ;
ucmd . arg ( " DIR " )
. run ( )
2021-10-24 20:35:00 +00:00
. stderr_is ( " tail: error reading 'DIR': Is a directory \n " )
2021-10-08 21:36:41 +00:00
. code_is ( 1 ) ;
}
2021-10-24 20:35:00 +00:00
#[ test ]
fn test_dir_follow ( ) {
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
at . mkdir ( " DIR " ) ;
for mode in & [ " --follow=descriptor " , " --follow=name " ] {
ts . ucmd ( )
. arg ( mode )
. arg ( " DIR " )
. run ( )
. stderr_is (
" tail: error reading 'DIR': Is a directory \n \
tail : DIR : cannot follow end of this type of file ; giving up on this name \ n \
tail : no files remaining \ n " ,
)
. code_is ( 1 ) ;
}
}
2021-10-24 20:03:00 +00:00
#[ test ]
fn test_dir_follow_retry ( ) {
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
at . mkdir ( " DIR " ) ;
ts . ucmd ( )
. arg ( " --follow=descriptor " )
. arg ( " --retry " )
. arg ( " DIR " )
. run ( )
. stderr_is (
" tail: warning: --retry only effective for the initial open \n \
tail : error reading ' DIR ' : Is a directory \ n \
tail : DIR : cannot follow end of this type of file \ n \
tail : no files remaining \ n " ,
)
. code_is ( 1 ) ;
}
2021-03-22 09:01:54 +00:00
#[ test ]
fn test_negative_indexing ( ) {
let positive_lines_index = new_ucmd! ( ) . arg ( " -n " ) . arg ( " 5 " ) . arg ( FOOBAR_TXT ) . run ( ) ;
let negative_lines_index = new_ucmd! ( ) . arg ( " -n " ) . arg ( " -5 " ) . arg ( FOOBAR_TXT ) . run ( ) ;
let positive_bytes_index = new_ucmd! ( ) . arg ( " -c " ) . arg ( " 20 " ) . arg ( FOOBAR_TXT ) . run ( ) ;
let negative_bytes_index = new_ucmd! ( ) . arg ( " -c " ) . arg ( " -20 " ) . arg ( FOOBAR_TXT ) . run ( ) ;
2021-04-17 12:22:20 +00:00
assert_eq! ( positive_lines_index . stdout ( ) , negative_lines_index . stdout ( ) ) ;
assert_eq! ( positive_bytes_index . stdout ( ) , negative_bytes_index . stdout ( ) ) ;
2021-03-22 09:01:54 +00:00
}
2021-04-22 15:10:08 +00:00
#[ test ]
fn test_sleep_interval ( ) {
2021-04-24 10:46:06 +00:00
new_ucmd! ( ) . arg ( " -s " ) . arg ( " 10 " ) . arg ( FOOBAR_TXT ) . succeeds ( ) ;
2021-09-16 15:14:23 +00:00
new_ucmd! ( ) . arg ( " -s " ) . arg ( " .1 " ) . arg ( FOOBAR_TXT ) . succeeds ( ) ;
new_ucmd! ( ) . arg ( " -s.1 " ) . arg ( FOOBAR_TXT ) . succeeds ( ) ;
new_ucmd! ( ) . arg ( " -s " ) . arg ( " -1 " ) . arg ( FOOBAR_TXT ) . fails ( ) ;
new_ucmd! ( )
. arg ( " -s " )
. arg ( " 1..1 " )
. arg ( FOOBAR_TXT )
. fails ( )
. stderr_contains ( " invalid number of seconds: '1..1' " )
. code_is ( 1 ) ;
2021-04-22 15:10:08 +00:00
}
2021-05-17 23:33:49 +00:00
/// Test for reading all but the first NUM bytes: `tail -c +3`.
#[ test ]
fn test_positive_bytes ( ) {
new_ucmd! ( )
. args ( & [ " -c " , " +3 " ] )
. pipe_in ( " abcde " )
. succeeds ( )
. stdout_is ( " cde " ) ;
}
/// Test for reading all bytes, specified by `tail -c +0`.
#[ test ]
fn test_positive_zero_bytes ( ) {
new_ucmd! ( )
. args ( & [ " -c " , " +0 " ] )
. pipe_in ( " abcde " )
. succeeds ( )
. stdout_is ( " abcde " ) ;
}
/// Test for reading all but the first NUM lines: `tail -n +3`.
#[ test ]
fn test_positive_lines ( ) {
new_ucmd! ( )
. args ( & [ " -n " , " +3 " ] )
. pipe_in ( " a \n b \n c \n d \n e \n " )
. succeeds ( )
. stdout_is ( " c \n d \n e \n " ) ;
}
2022-01-21 18:26:28 +00:00
/// Test for reading all but the first NUM lines of a file: `tail -n +3 infile`.
#[ test ]
fn test_positive_lines_file ( ) {
new_ucmd! ( )
. args ( & [ " -n " , " +7 " , " foobar.txt " ] )
. succeeds ( )
. stdout_is (
" siette
ocho
nueve
diez
once
" ,
) ;
}
/// Test for reading all but the first NUM bytes of a file: `tail -c +3 infile`.
#[ test ]
fn test_positive_bytes_file ( ) {
new_ucmd! ( )
. args ( & [ " -c " , " +42 " , " foobar.txt " ] )
. succeeds ( )
. stdout_is (
" ho
nueve
diez
once
" ,
) ;
}
2021-11-19 20:37:47 +00:00
/// Test for reading all but the first NUM lines: `tail -3`.
#[ test ]
fn test_obsolete_syntax_positive_lines ( ) {
new_ucmd! ( )
. args ( & [ " -3 " ] )
. pipe_in ( " a \n b \n c \n d \n e \n " )
. succeeds ( )
. stdout_is ( " c \n d \n e \n " ) ;
}
/// Test for reading all but the first NUM lines: `tail -n -10`.
#[ test ]
fn test_small_file ( ) {
new_ucmd! ( )
. args ( & [ " -n -10 " ] )
. pipe_in ( " a \n b \n c \n d \n e \n " )
. succeeds ( )
. stdout_is ( " a \n b \n c \n d \n e \n " ) ;
}
/// Test for reading all but the first NUM lines: `tail -10`.
#[ test ]
fn test_obsolete_syntax_small_file ( ) {
new_ucmd! ( )
. args ( & [ " -10 " ] )
. pipe_in ( " a \n b \n c \n d \n e \n " )
. succeeds ( )
. stdout_is ( " a \n b \n c \n d \n e \n " ) ;
}
2021-05-17 23:33:49 +00:00
/// Test for reading all lines, specified by `tail -n +0`.
#[ test ]
fn test_positive_zero_lines ( ) {
new_ucmd! ( )
. args ( & [ " -n " , " +0 " ] )
. pipe_in ( " a \n b \n c \n d \n e \n " )
. succeeds ( )
. stdout_is ( " a \n b \n c \n d \n e \n " ) ;
}
2021-06-01 10:17:11 +00:00
#[ test ]
2021-10-24 20:35:00 +00:00
fn test_invalid_num ( ) {
2021-06-01 10:17:11 +00:00
new_ucmd! ( )
. args ( & [ " -c " , " 1024R " , " emptyfile.txt " ] )
. fails ( )
2021-06-21 22:22:30 +00:00
. stderr_is ( " tail: invalid number of bytes: '1024R' " ) ;
2021-06-01 10:17:11 +00:00
new_ucmd! ( )
. args ( & [ " -n " , " 1024R " , " emptyfile.txt " ] )
. fails ( )
2021-06-21 22:22:30 +00:00
. stderr_is ( " tail: invalid number of lines: '1024R' " ) ;
2021-06-01 10:17:11 +00:00
#[ cfg(not(target_pointer_width = " 128 " )) ]
new_ucmd! ( )
. args ( & [ " -c " , " 1Y " , " emptyfile.txt " ] )
. fails ( )
2021-06-21 22:22:30 +00:00
. stderr_is ( " tail: invalid number of bytes: '1Y': Value too large for defined data type " ) ;
2021-06-01 10:17:11 +00:00
#[ cfg(not(target_pointer_width = " 128 " )) ]
new_ucmd! ( )
. args ( & [ " -n " , " 1Y " , " emptyfile.txt " ] )
. fails ( )
2021-06-21 22:22:30 +00:00
. stderr_is ( " tail: invalid number of lines: '1Y': Value too large for defined data type " ) ;
2021-06-03 18:37:29 +00:00
#[ cfg(target_pointer_width = " 32 " ) ]
{
let sizes = [ " 1000G " , " 10T " ] ;
for size in & sizes {
new_ucmd! ( )
. args ( & [ " -c " , size ] )
. fails ( )
. code_is ( 1 )
2022-02-22 10:09:22 +00:00
. stderr_only ( " tail: Insufficient addressable memory " ) ;
2021-06-03 18:37:29 +00:00
}
}
2022-02-03 12:17:53 +00:00
new_ucmd! ( )
. args ( & [ " -c " , " -³ " ] )
. fails ( )
. stderr_is ( " tail: invalid number of bytes: '³' " ) ;
2021-06-01 10:17:11 +00:00
}
#[ test ]
2021-10-24 20:35:00 +00:00
fn test_num_with_undocumented_sign_bytes ( ) {
2021-06-01 10:17:11 +00:00
// tail: '-' is not documented (8.32 man pages)
// head: '+' is not documented (8.32 man pages)
const ALPHABET : & str = " abcdefghijklmnopqrstuvwxyz " ;
new_ucmd! ( )
. args ( & [ " -c " , " 5 " ] )
. pipe_in ( ALPHABET )
. succeeds ( )
. stdout_is ( " vwxyz " ) ;
new_ucmd! ( )
. args ( & [ " -c " , " -5 " ] )
. pipe_in ( ALPHABET )
. succeeds ( )
. stdout_is ( " vwxyz " ) ;
new_ucmd! ( )
. args ( & [ " -c " , " +5 " ] )
. pipe_in ( ALPHABET )
. succeeds ( )
. stdout_is ( " efghijklmnopqrstuvwxyz " ) ;
}
2021-09-08 00:09:09 +00:00
#[ test ]
#[ cfg(unix) ]
2021-10-24 20:35:00 +00:00
fn test_bytes_for_funny_files ( ) {
2021-09-08 00:09:09 +00:00
// gnu/tests/tail-2/tail-c.sh
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
2022-04-02 08:47:37 +00:00
for file in [ " /proc/version " , " /sys/kernel/profiling " ] {
2021-09-08 00:09:09 +00:00
if ! at . file_exists ( file ) {
continue ;
}
let args = [ " --bytes " , " 1 " , file ] ;
let result = ts . ucmd ( ) . args ( & args ) . run ( ) ;
let exp_result = unwrap_or_return! ( expected_result ( & ts , & args ) ) ;
result
. stdout_is ( exp_result . stdout_str ( ) )
. stderr_is ( exp_result . stderr_str ( ) )
. code_is ( exp_result . code ( ) ) ;
}
}
2021-09-27 21:18:00 +00:00
2021-10-08 21:36:41 +00:00
#[ test ]
2021-10-24 20:03:00 +00:00
#[ cfg(unix) ]
fn test_retry1 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure --retry without --follow results in a warning.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let file_name = " FILE " ;
at . touch ( " FILE " ) ;
let result = ts . ucmd ( ) . arg ( file_name ) . arg ( " --retry " ) . run ( ) ;
result
. stderr_is ( " tail: warning: --retry ignored; --retry is useful only when following \n " )
. code_is ( 0 ) ;
}
#[ test ]
#[ cfg(unix) ]
fn test_retry2 ( ) {
// gnu/tests/tail-2/retry.sh
// The same as test_retry2 with a missing file: expect error message and exit 1.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let missing = " missing " ;
let result = ts . ucmd ( ) . arg ( missing ) . arg ( " --retry " ) . run ( ) ;
result
. stderr_is (
" tail: warning: --retry ignored; --retry is useful only when following \n \
tail : cannot open ' missing ' for reading : No such file or directory \ n " ,
)
. code_is ( 1 ) ;
}
2021-10-24 20:12:40 +00:00
#[ test ]
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_retry3 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure that `tail --retry --follow=name` waits for the file to appear.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let missing = " missing " ;
let expected_stderr = " tail: cannot open 'missing' for reading: No such file or directory \n \
tail : ' missing ' has appeared ; following new file \ n " ;
let expected_stdout = " X \n " ;
let delay = 1000 ;
let mut args = vec! [ " --follow=name " , " --retry " , missing , " --use-polling " ] ;
for _ in 0 .. 2 {
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . touch ( missing ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . truncate ( missing , " X \n " ) ;
sleep ( Duration ::from_millis ( 2 * delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
at . remove ( missing ) ;
args . pop ( ) ;
}
}
2021-10-24 20:13:58 +00:00
#[ test ]
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_retry4 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure that `tail --retry --follow=descriptor` waits for the file to appear.
// Ensure truncation is detected.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let missing = " missing " ;
let expected_stderr = " tail: warning: --retry only effective for the initial open \n \
tail : cannot open ' missing ' for reading : No such file or directory \ n \
tail : ' missing ' has appeared ; following new file \ n \
tail : missing : file truncated \ n " ;
let expected_stdout = " X1 \n X \n " ;
2022-04-30 10:02:42 +00:00
let delay = 100 ;
let mut args = vec! [ " -s.1 " , " --max-unchanged-stats=1 " , " --follow=descriptor " , " --retry " , missing , " ---disable-inotify " ] ;
2021-10-24 20:13:58 +00:00
for _ in 0 .. 2 {
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . touch ( missing ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . truncate ( missing , " X1 \n " ) ;
2022-04-30 10:02:42 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:13:58 +00:00
at . truncate ( missing , " X \n " ) ;
2022-04-30 10:02:42 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:13:58 +00:00
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
at . remove ( missing ) ;
args . pop ( ) ;
}
}
2021-10-24 20:15:14 +00:00
#[ test ]
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_retry5 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure that `tail --follow=descriptor --retry` exits when the file appears untailable.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let missing = " missing " ;
let expected_stderr = " tail: warning: --retry only effective for the initial open \n \
tail : cannot open ' missing ' for reading : No such file or directory \ n \
tail : ' missing ' has been replaced with an untailable file ; giving up on this name \ n \
tail : no files remaining \ n " ;
let delay = 1000 ;
let mut args = vec! [ " --follow=descriptor " , " --retry " , missing , " --use-polling " ] ;
for _ in 0 .. 2 {
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( missing ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert! ( buf_stdout . is_empty ( ) ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
at . rmdir ( missing ) ;
args . pop ( ) ;
}
}
2021-10-24 20:17:54 +00:00
#[ test ]
2021-11-03 10:24:11 +00:00
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
2021-10-24 20:17:54 +00:00
fn test_retry6 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure that --follow=descriptor (without --retry) does *not* try
// to open a file after an initial fail, even when there are other tailable files.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let missing = " missing " ;
let existing = " existing " ;
at . touch ( existing ) ;
let expected_stderr = " tail: cannot open 'missing' for reading: No such file or directory \n " ;
let expected_stdout = " ==> existing <== \n X \n " ;
let mut p = ts
. ucmd ( )
. arg ( " --follow=descriptor " )
. arg ( " missing " )
. arg ( " existing " )
. run_no_wait ( ) ;
let delay = 1000 ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . truncate ( missing , " Y \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . truncate ( existing , " X \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
}
2021-10-24 20:19:10 +00:00
#[ test ]
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_retry7 ( ) {
// gnu/tests/tail-2/retry.sh
// Ensure that `tail -F` retries when the file is initially untailable.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let untailable = " untailable " ;
let expected_stderr = " tail: error reading 'untailable': Is a directory \n \
tail : untailable : cannot follow end of this type of file \ n \
tail : ' untailable ' has appeared ; following new file \ n \
tail : ' untailable ' has become inaccessible : No such file or directory \ n \
tail : ' untailable ' has been replaced with an untailable file \ n \
tail : ' untailable ' has appeared ; following new file \ n " ;
let expected_stdout = " foo \n bar \n " ;
let delay = 1000 ;
2021-11-03 10:24:11 +00:00
let mut args = vec! [
" -s.1 " ,
" --max-unchanged-stats=1 " ,
" -F " ,
untailable ,
" --use-polling " ,
] ;
for _ in 0 .. 2 {
at . mkdir ( untailable ) ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
// tail: 'untailable' has become accessible
// or (The first is the common case, "has appeared" arises with slow rmdir):
// tail: 'untailable' has appeared; following new file
at . rmdir ( untailable ) ;
at . truncate ( untailable , " foo \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
// NOTE: GNU's `tail` only shows "become inaccessible"
// if there's a delay between rm and mkdir.
// tail: 'untailable' has become inaccessible: No such file or directory
at . remove ( untailable ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
// tail: 'untailable' has been replaced with an untailable file\n";
at . mkdir ( untailable ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
// full circle, back to the beginning
at . rmdir ( untailable ) ;
at . truncate ( untailable , " bar \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
p . kill ( ) . unwrap ( ) ;
2021-10-24 20:19:10 +00:00
2021-11-03 10:24:11 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
args . pop ( ) ;
at . remove ( untailable ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
}
2021-10-24 20:19:10 +00:00
}
2021-10-24 20:21:36 +00:00
#[ test ]
2022-04-22 10:09:39 +00:00
#[ cfg(all(unix, not(target_os = " android " ))) ] // FIXME: fix this test for Android
2021-10-24 20:21:36 +00:00
fn test_retry8 ( ) {
// Ensure that inotify will switch to polling mode if directory
// of the watched file was initially missing and later created.
// This is similar to test_retry9, but without:
// tail: directory containing watched file was removed\n\
// tail: inotify cannot be used, reverting to polling\n\
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let watched_file = std ::path ::Path ::new ( " watched_file " ) ;
let parent_dir = std ::path ::Path ::new ( " parent_dir " ) ;
let user_path = parent_dir . join ( watched_file ) ;
let parent_dir = parent_dir . to_str ( ) . unwrap ( ) ;
let user_path = user_path . to_str ( ) . unwrap ( ) ;
let expected_stderr = " \
tail : cannot open ' parent_dir / watched_file ' for reading : No such file or directory \ n \
tail : ' parent_dir / watched_file ' has appeared ; following new file \ n \
tail : ' parent_dir / watched_file ' has become inaccessible : No such file or directory \ n \
tail : ' parent_dir / watched_file ' has appeared ; following new file \ n " ;
let expected_stdout = " foo \n bar \n " ;
let delay = 1000 ;
let mut p = ts
. ucmd ( )
. arg ( " -F " )
. arg ( " -s.1 " )
. arg ( " --max-unchanged-stats=1 " )
. arg ( user_path )
. run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( parent_dir ) ;
at . append ( user_path , " foo \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . remove ( user_path ) ;
at . rmdir ( parent_dir ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( parent_dir ) ;
at . append ( user_path , " bar \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
}
#[ test ]
2022-04-22 10:09:39 +00:00
#[ cfg(all(unix, not(target_os = " android " ))) ] // FIXME: fix this test for Android
2021-10-24 20:21:36 +00:00
fn test_retry9 ( ) {
// gnu/tests/tail-2/inotify-dir-recreate.sh
// Ensure that inotify will switch to polling mode if directory
// of the watched file was removed and recreated.
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let watched_file = std ::path ::Path ::new ( " watched_file " ) ;
let parent_dir = std ::path ::Path ::new ( " parent_dir " ) ;
let user_path = parent_dir . join ( watched_file ) ;
let parent_dir = parent_dir . to_str ( ) . unwrap ( ) ;
let user_path = user_path . to_str ( ) . unwrap ( ) ;
2021-11-03 10:24:11 +00:00
let expected_stderr = format! (
" \
2021-10-24 20:21:36 +00:00
tail : ' parent_dir / watched_file ' has become inaccessible : No such file or directory \ n \
tail : directory containing watched file was removed \ n \
tail : { } cannot be used , reverting to polling \ n \
tail : ' parent_dir / watched_file ' has appeared ; following new file \ n \
tail : ' parent_dir / watched_file ' has become inaccessible : No such file or directory \ n \
tail : ' parent_dir / watched_file ' has appeared ; following new file \ n \
tail : ' parent_dir / watched_file ' has become inaccessible : No such file or directory \ n \
2021-10-24 20:35:00 +00:00
tail : ' parent_dir / watched_file ' has appeared ; following new file \ n " ,
BACKEND
) ;
2021-10-24 20:21:36 +00:00
let expected_stdout = " foo \n bar \n foo \n bar \n " ;
let delay = 1000 ;
at . mkdir ( parent_dir ) ;
at . truncate ( user_path , " foo \n " ) ;
let mut p = ts
. ucmd ( )
. arg ( " -F " )
. arg ( " -s.1 " )
. arg ( " --max-unchanged-stats=1 " )
. arg ( user_path )
. run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . remove ( user_path ) ;
at . rmdir ( parent_dir ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( parent_dir ) ;
at . truncate ( user_path , " bar \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . remove ( user_path ) ;
at . rmdir ( parent_dir ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( parent_dir ) ;
at . truncate ( user_path , " foo \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . remove ( user_path ) ;
at . rmdir ( parent_dir ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . mkdir ( parent_dir ) ;
at . truncate ( user_path , " bar \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
// println!("stdout:\n{}\nstderr:\n{}", buf_stdout, buf_stderr); // dbg
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
}
2021-10-24 20:28:30 +00:00
#[ test ]
2022-04-22 10:09:39 +00:00
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
2021-10-24 20:28:30 +00:00
fn test_follow_descriptor_vs_rename1 ( ) {
2021-10-08 21:36:41 +00:00
// gnu/tests/tail-2/descriptor-vs-rename.sh
2021-10-24 20:28:30 +00:00
// $ ((rm -f A && touch A && sleep 1 && echo -n "A\n" >> A && sleep 1 && \
// mv A B && sleep 1 && echo -n "B\n" >> B &)>/dev/null 2>&1 &) ; \
// sleep 1 && target/debug/tail --follow=descriptor A ---disable-inotify
// $ A
// $ B
2021-10-08 21:36:41 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let file_a = " FILE_A " ;
let file_b = " FILE_B " ;
2021-10-24 20:28:30 +00:00
let file_c = " FILE_C " ;
2021-10-08 21:36:41 +00:00
let mut args = vec! [
" --follow=descriptor " ,
" -s.1 " ,
" --max-unchanged-stats=1 " ,
file_a ,
2022-04-30 10:02:42 +00:00
" ---disable-inotify " ,
2021-10-08 21:36:41 +00:00
] ;
2021-10-24 20:28:30 +00:00
let delay = 500 ;
2022-04-22 10:09:39 +00:00
for _ in 0 .. 2 {
2021-10-08 21:36:41 +00:00
at . touch ( file_a ) ;
2021-10-24 20:28:30 +00:00
2021-10-08 21:36:41 +00:00
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:28:30 +00:00
at . append ( file_a , " A \n " ) ;
2021-10-08 21:36:41 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:28:30 +00:00
2021-10-08 21:36:41 +00:00
at . rename ( file_a , file_b ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:28:30 +00:00
at . append ( file_b , " B \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . rename ( file_b , file_c ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . append ( file_c , " C \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-08 21:36:41 +00:00
p . kill ( ) . unwrap ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:28:30 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , " A \n B \n C \n " ) ;
assert! ( buf_stderr . is_empty ( ) ) ;
2021-10-08 21:36:41 +00:00
2021-10-09 22:07:59 +00:00
args . pop ( ) ;
2021-10-08 21:36:41 +00:00
}
}
#[ test ]
2021-10-24 20:28:30 +00:00
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_follow_descriptor_vs_rename2 ( ) {
// Ensure the headers are correct for --verbose.
2021-10-08 21:36:41 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let file_a = " FILE_A " ;
let file_b = " FILE_B " ;
let file_c = " FILE_C " ;
let mut args = vec! [
" --follow=descriptor " ,
" -s.1 " ,
" --max-unchanged-stats=1 " ,
file_a ,
file_b ,
" --verbose " ,
2022-04-30 10:02:42 +00:00
" ---disable-inotify " ,
2021-10-08 21:36:41 +00:00
] ;
let delay = 100 ;
2022-04-22 10:09:39 +00:00
for _ in 0 .. 2 {
2021-10-08 21:36:41 +00:00
at . touch ( file_a ) ;
at . touch ( file_b ) ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . rename ( file_a , file_c ) ;
sleep ( Duration ::from_millis ( 1000 ) ) ;
at . append ( file_c , " x \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-24 20:28:30 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
2021-10-08 21:36:41 +00:00
assert_eq! (
buf_stdout ,
" ==> FILE_A <== \n \n ==> FILE_B <== \n \n ==> FILE_A <== \n x \n "
) ;
2021-10-24 20:28:30 +00:00
assert! ( buf_stderr . is_empty ( ) ) ;
2021-10-08 21:36:41 +00:00
2021-10-09 22:07:59 +00:00
args . pop ( ) ;
2021-10-08 21:36:41 +00:00
}
}
2021-09-27 21:18:00 +00:00
#[ test ]
2021-10-24 20:35:00 +00:00
#[ cfg(unix) ]
2021-10-03 20:37:06 +00:00
fn test_follow_name_remove ( ) {
// This test triggers a remove event while `tail --follow=name logfile` is running.
2021-10-24 20:35:00 +00:00
// ((sleep 2 && rm logfile &)>/dev/null 2>&1 &) ; tail --follow=name logfile
2021-09-27 21:18:00 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = FOLLOW_NAME_TXT ;
2021-10-09 22:07:59 +00:00
let source_copy = " source_copy " ;
at . copy ( source , source_copy ) ;
2021-09-27 21:18:00 +00:00
2021-10-03 20:37:06 +00:00
let expected_stdout = at . read ( FOLLOW_NAME_SHORT_EXP ) ;
2021-09-27 21:18:00 +00:00
let expected_stderr = format! (
2021-10-03 20:37:06 +00:00
" {}: {}: No such file or directory \n {0}: no files remaining \n " ,
2021-10-09 22:07:59 +00:00
ts . util_name , source_copy
2021-09-27 21:18:00 +00:00
) ;
2021-10-24 20:35:00 +00:00
let delay = 2000 ;
2021-10-09 22:07:59 +00:00
let mut args = vec! [ " --follow=name " , source_copy , " --use-polling " ] ;
2021-09-27 21:18:00 +00:00
2021-10-09 22:07:59 +00:00
for _ in 0 .. 2 {
at . copy ( source , source_copy ) ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
2021-09-27 21:18:00 +00:00
2021-10-09 22:07:59 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
at . remove ( source_copy ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-09-27 21:18:00 +00:00
2021-10-09 22:07:59 +00:00
p . kill ( ) . unwrap ( ) ;
2021-09-27 21:18:00 +00:00
2021-10-09 22:07:59 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
args . pop ( ) ;
}
2021-09-27 21:18:00 +00:00
}
2021-09-28 18:05:09 +00:00
#[ test ]
2021-10-24 20:31:14 +00:00
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
fn test_follow_name_truncate1 ( ) {
2021-09-28 18:05:09 +00:00
// This test triggers a truncate event while `tail --follow=name logfile` is running.
2021-10-24 20:31:14 +00:00
// $ cp logfile backup && head logfile > logfile && sleep 1 && cp backup logfile
2021-09-28 18:05:09 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = FOLLOW_NAME_TXT ;
2021-10-09 22:07:59 +00:00
let backup = " backup " ;
2021-09-28 18:05:09 +00:00
let expected_stdout = at . read ( FOLLOW_NAME_EXP ) ;
let expected_stderr = format! ( " {} : {} : file truncated \n " , ts . util_name , source ) ;
let args = [ " --follow=name " , source ] ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
2021-10-01 12:17:57 +00:00
let delay = 1000 ;
2021-09-28 18:05:09 +00:00
2021-10-09 22:07:59 +00:00
at . copy ( source , backup ) ;
2021-09-28 18:05:09 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-09 22:07:59 +00:00
at . touch ( source ) ; // trigger truncate
2021-09-28 18:05:09 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-09 22:07:59 +00:00
at . copy ( backup , source ) ;
2021-09-28 18:05:09 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
2021-10-09 22:07:59 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
2021-09-28 18:05:09 +00:00
assert_eq! ( buf_stdout , expected_stdout ) ;
2021-09-27 22:16:23 +00:00
assert_eq! ( buf_stderr , expected_stderr ) ;
}
2021-09-27 21:18:00 +00:00
#[ test ]
2021-10-24 20:31:14 +00:00
#[ cfg(unix) ]
fn test_follow_name_truncate2 ( ) {
// This test triggers a truncate event while `tail --follow=name logfile` is running.
// $ ((sleep 1 && echo -n "x\nx\nx\n" >> logfile && sleep 1 && \
// echo -n "x\n" > logfile &)>/dev/null 2>&1 &) ; tail --follow=name logfile
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = " logfile " ;
at . touch ( source ) ;
let expected_stdout = " x \n x \n x \n x \n " ;
let expected_stderr = format! ( " {} : {} : file truncated \n " , ts . util_name , source ) ;
let args = [ " --follow=name " , source ] ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
let delay = 1000 ;
at . append ( source , " x \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . append ( source , " x \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . append ( source , " x \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
at . truncate ( source , " x \n " ) ;
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
}
#[ test ]
2021-11-03 10:24:11 +00:00
#[ cfg(target_os = " linux " ) ] // FIXME: fix this test for BSD/macOS
2021-10-24 20:31:14 +00:00
fn test_follow_name_truncate3 ( ) {
// Opening an empty file in truncate mode should not trigger a truncate event while.
// $ rm -f logfile && touch logfile
// $ ((sleep 1 && echo -n "x\n" > logfile &)>/dev/null 2>&1 &) ; tail --follow=name logfile
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = " logfile " ;
at . touch ( source ) ;
let expected_stdout = " x \n " ;
let args = [ " --follow=name " , source ] ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
let delay = 1000 ;
sleep ( Duration ::from_millis ( delay ) ) ;
2021-11-03 10:24:11 +00:00
at . truncate ( source , " x \n " ) ;
2021-10-24 20:31:14 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert! ( buf_stderr . is_empty ( ) ) ;
}
2021-10-24 20:35:00 +00:00
#[ test ]
2022-04-22 10:09:39 +00:00
#[ cfg(all(unix, not(target_os = " android " ))) ] // FIXME: fix this test for Android
2021-10-03 20:37:06 +00:00
fn test_follow_name_move_create ( ) {
// This test triggers a move/create event while `tail --follow=name logfile` is running.
2021-10-24 20:35:00 +00:00
// ((sleep 2 && mv logfile backup && sleep 2 && cp backup logfile &)>/dev/null 2>&1 &) ; tail --follow=name logfile
2021-09-27 21:18:00 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = FOLLOW_NAME_TXT ;
2021-10-09 22:07:59 +00:00
let backup = " backup " ;
2021-09-27 21:18:00 +00:00
2021-10-01 12:17:57 +00:00
#[ cfg(target_os = " linux " ) ]
let expected_stdout = at . read ( FOLLOW_NAME_EXP ) ;
#[ cfg(target_os = " linux " ) ]
2021-09-27 21:18:00 +00:00
let expected_stderr = format! (
" {}: {}: No such file or directory \n {0}: '{1}' has appeared; following new file \n " ,
ts . util_name , source
) ;
2021-10-01 12:17:57 +00:00
// NOTE: We are less strict if not on Linux (inotify backend).
#[ cfg(not(target_os = " linux " )) ]
2021-10-03 20:37:06 +00:00
let expected_stdout = at . read ( FOLLOW_NAME_SHORT_EXP ) ;
2021-10-01 12:17:57 +00:00
#[ cfg(not(target_os = " linux " )) ]
let expected_stderr = format! ( " {} : {} : No such file or directory \n " , ts . util_name , source ) ;
2021-09-27 21:18:00 +00:00
let args = [ " --follow=name " , source ] ;
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
2021-10-24 20:35:00 +00:00
let delay = 2000 ;
2021-09-27 21:18:00 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-09 22:07:59 +00:00
at . rename ( source , backup ) ;
2021-09-27 21:18:00 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
2021-10-09 22:07:59 +00:00
at . copy ( backup , source ) ;
2021-09-27 21:18:00 +00:00
sleep ( Duration ::from_millis ( delay ) ) ;
p . kill ( ) . unwrap ( ) ;
2021-10-09 22:07:59 +00:00
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
2021-09-27 21:18:00 +00:00
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr ) ;
}
2021-09-27 22:16:23 +00:00
#[ test ]
2021-10-24 20:35:00 +00:00
#[ cfg(unix) ]
2021-10-09 22:07:59 +00:00
fn test_follow_name_move ( ) {
// This test triggers a move event while `tail --follow=name logfile` is running.
2021-10-24 20:35:00 +00:00
// ((sleep 2 && mv logfile backup &)>/dev/null 2>&1 &) ; tail --follow=name logfile
2021-09-27 22:16:23 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let at = & ts . fixtures ;
let source = FOLLOW_NAME_TXT ;
2021-10-09 22:07:59 +00:00
let backup = " backup " ;
2021-09-27 22:16:23 +00:00
2021-10-03 20:37:06 +00:00
let expected_stdout = at . read ( FOLLOW_NAME_SHORT_EXP ) ;
2021-10-09 22:07:59 +00:00
let expected_stderr = [
format! (
" {}: {}: No such file or directory \n {0}: no files remaining \n " ,
ts . util_name , source
) ,
format! ( " {} : {} : No such file or directory \n " , ts . util_name , source ) ,
] ;
2021-09-27 22:16:23 +00:00
2021-10-09 22:07:59 +00:00
let mut args = vec! [ " --follow=name " , source , " --use-polling " ] ;
2021-09-27 22:16:23 +00:00
2021-10-09 22:07:59 +00:00
#[ allow(clippy::needless_range_loop) ]
for i in 0 .. 2 {
let mut p = ts . ucmd ( ) . args ( & args ) . run_no_wait ( ) ;
2021-09-27 22:16:23 +00:00
2021-10-24 20:35:00 +00:00
sleep ( Duration ::from_millis ( 2000 ) ) ;
2021-10-09 22:07:59 +00:00
at . rename ( source , backup ) ;
2021-10-24 20:35:00 +00:00
sleep ( Duration ::from_millis ( 5000 ) ) ;
2021-09-27 22:16:23 +00:00
2021-10-09 22:07:59 +00:00
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , expected_stdout ) ;
assert_eq! ( buf_stderr , expected_stderr [ i ] ) ;
2021-09-27 22:16:23 +00:00
2021-10-09 22:07:59 +00:00
at . rename ( backup , source ) ;
args . pop ( ) ;
}
}
2022-04-21 20:52:17 +00:00
#[ test ]
#[ cfg(unix) ]
fn test_follow_inotify_only_regular ( ) {
// The GNU test inotify-only-regular.sh uses strace to ensure that `tail -f`
// doesn't make inotify syscalls and only uses inotify for regular files or fifos.
2022-04-22 10:09:39 +00:00
// We just check if tailing a character device has the same behavior as GNU's tail.
2022-04-21 20:52:17 +00:00
let ts = TestScenario ::new ( util_name! ( ) ) ;
let mut p = ts . ucmd ( ) . arg ( " -f " ) . arg ( " /dev/null " ) . run_no_wait ( ) ;
sleep ( Duration ::from_millis ( 200 ) ) ;
p . kill ( ) . unwrap ( ) ;
let ( buf_stdout , buf_stderr ) = take_stdout_stderr ( & mut p ) ;
assert_eq! ( buf_stdout , " " . to_string ( ) ) ;
assert_eq! ( buf_stderr , " " . to_string ( ) ) ;
}
2022-04-24 13:09:28 +00:00
#[ cfg(unix) ]
2021-10-09 22:07:59 +00:00
fn take_stdout_stderr ( p : & mut std ::process ::Child ) -> ( String , String ) {
2021-09-27 22:16:23 +00:00
let mut buf_stdout = String ::new ( ) ;
let mut p_stdout = p . stdout . take ( ) . unwrap ( ) ;
p_stdout . read_to_string ( & mut buf_stdout ) . unwrap ( ) ;
let mut buf_stderr = String ::new ( ) ;
let mut p_stderr = p . stderr . take ( ) . unwrap ( ) ;
p_stderr . read_to_string ( & mut buf_stderr ) . unwrap ( ) ;
2021-10-09 22:07:59 +00:00
( buf_stdout , buf_stderr )
2021-09-27 22:16:23 +00:00
}
2022-04-19 20:14:55 +00:00
2022-01-17 15:18:04 +00:00
#[ test ]
fn test_no_such_file ( ) {
new_ucmd! ( )
. arg ( " bogusfile " )
. fails ( )
. no_stdout ( )
. stderr_contains ( " cannot open 'bogusfile' for reading: No such file or directory " ) ;
}
2022-01-17 15:52:15 +00:00
#[ test ]
fn test_no_trailing_newline ( ) {
new_ucmd! ( ) . pipe_in ( " x " ) . succeeds ( ) . stdout_only ( " x " ) ;
}
2022-01-21 00:15:18 +00:00
#[ test ]
fn test_lines_zero_terminated ( ) {
new_ucmd! ( )
. args ( & [ " -z " , " -n " , " 2 " ] )
. pipe_in ( " a \0 b \0 c \0 d \0 e \0 " )
. succeeds ( )
. stdout_only ( " d \0 e \0 " ) ;
new_ucmd! ( )
. args ( & [ " -z " , " -n " , " +2 " ] )
. pipe_in ( " a \0 b \0 c \0 d \0 e \0 " )
. succeeds ( )
. stdout_only ( " b \0 c \0 d \0 e \0 " ) ;
}
2022-03-31 15:16:55 +00:00
#[ test ]
fn test_presume_input_pipe_default ( ) {
new_ucmd! ( )
. arg ( " ---presume-input-pipe " )
. pipe_in_fixture ( FOOBAR_TXT )
. run ( )
. stdout_is_fixture ( " foobar_stdin_default.expected " ) ;
}