# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2016 Alan Somers
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

atf_test_case empty_r
empty_r_head()
{
	atf_set "descr" "Reverse an empty file"
}
empty_r_body()
{
	touch infile expectfile
	tail -r infile > outfile
	tail -r < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case file_r
file_r_head()
{
	atf_set "descr" "Reverse a file"
}
file_r_body()
{
	cat > infile <<HERE
This is the first line
This is the second line
This is the third line
HERE
	cat > expectfile << HERE
This is the third line
This is the second line
This is the first line
HERE
	tail -r infile > outfile
	tail -r < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case file_rn2
file_rn2_head()
{
	atf_set "descr" "Reverse the last two lines of a file"
}
file_rn2_body()
{
	cat > infile <<HERE
This is the first line
This is the second line
This is the third line
HERE
	cat > expectfile << HERE
This is the third line
This is the second line
HERE
	tail -rn2 infile > outfile
	tail -rn2 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

# Regression test for PR 222671
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=222671
atf_test_case pipe_leading_newline_r
pipe_leading_newline_r_head()
{
	atf_set "descr" "Reverse a pipe whose first character is a newline"
}
pipe_leading_newline_r_body()
{
	cat > expectfile << HERE
3
2
1

HERE
	printf '\n1\n2\n3\n' | tail -r > outfile
	printf '\n1\n2\n3\n' | tail -r > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case file_rc28
file_rc28_head()
{
	atf_set "descr" "Reverse a file and display the last 28 characters"
}
file_rc28_body()
{
	cat > infile <<HERE
This is the first line
This is the second line
This is the third line
HERE
	cat > expectfile << HERE
This is the third line
line
HERE
	tail -rc28 infile > outfile
	tail -rc28 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case file_rc28
file_rc28_head()
{
	atf_set "descr" "Reverse a file and display the last 28 characters"
}
file_rc28_body()
{
	cat > infile <<HERE
This is the first line
This is the second line
This is the third line
HERE
	cat > expectfile << HERE
This is the third line
line
HERE
	tail -rc28 infile > outfile
	tail -rc28 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case longfile_r
longfile_r_head()
{
	atf_set "descr" "Reverse a long file"
}
longfile_r_body()
{
	jot -w "%0511d" 1030 0 > infile
	jot -w "%0511d" 1030 1029 0 -1 > expectfile
	tail -r infile > outfile
	tail -r < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case longfile_r_enomem
longfile_r_enomem_head()
{
	atf_set "descr" "Reverse a file that's too long to store in RAM"
}
longfile_r_enomem_body()
{
	# When we reverse a file that's too long for RAM, tail should drop the
	# first part and just print what it can.  We'll check that the last
	# part is ok
	{
		ulimit -v 32768 || atf_skip "Can't adjust ulimit"
		jot -w "%01023d" 32768 0 | tail -r > outfile ;
	}
	if [ "$?" -ne 1 ]; then
		atf_skip "Didn't get ENOMEM.  Adjust test parameters"
	fi
	# We don't know how much of the input we dropped.  So just check that
	# the first ten lines of tail's output are the same as the last ten of
	# the input
	jot -w "%01023d" 10 32767 0 -1 > expectfile
	head -n 10 outfile > outtrunc
	diff expectfile outtrunc
	atf_check cmp expectfile outtrunc
}

atf_test_case longfile_r_longlines
longfile_r_longlines_head()
{
	atf_set "descr" "Reverse a long file with extremely long lines"
}
longfile_r_longlines_body()
{
	jot -s " " -w "%07d" 18000 0 > infile
	jot -s " " -w "%07d" 18000 18000 >> infile
	jot -s " " -w "%07d" 18000 36000 >> infile
	jot -s " " -w "%07d" 18000 36000 > expectfile
	jot -s " " -w "%07d" 18000 18000 >> expectfile
	jot -s " " -w "%07d" 18000 0 >> expectfile
	tail -r infile > outfile
	tail -r < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case longfile_rc135782
longfile_rc135782_head()
{
	atf_set "descr" "Reverse a long file and print the last 135,782 bytes"
}
longfile_rc135782_body()
{
	jot -w "%063d" 9000 0 > infile
	jot -w "%063d" 2121 8999 0 -1 > expectfile
	echo "0000000000000000000000000000000006878" >> expectfile
	tail -rc135782 infile > outfile
	tail -rc135782 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case longfile_rc145782_longlines
longfile_rc145782_longlines_head()
{
	atf_set "descr" "Reverse a long file with extremely long lines and print the last 145,782 bytes"
}
longfile_rc145782_longlines_body()
{
	jot -s " " -w "%07d" 18000 0 > infile
	jot -s " " -w "%07d" 18000 18000 >> infile
	jot -s " " -w "%07d" 18000 36000 >> infile
	jot -s " " -w "%07d" 18000 36000 > expectfile
	echo -n "35777 " >> expectfile
	jot -s " " -w "%07d" 222 35778 >> expectfile
	tail -rc145782 infile > outfile
	tail -rc145782 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case longfile_rn2500
longfile_rn2500_head()
{
	atf_set "descr" "Reverse a long file and print the last 2,500 lines"
}
longfile_rn2500_body()
{
	jot -w "%063d" 9000 0 > infile
	jot -w "%063d" 2500 8999 0 -1 > expectfile
	tail -rn2500 infile > outfile
	tail -rn2500 < infile > outpipe
	atf_check cmp expectfile outfile
	atf_check cmp expectfile outpipe
}

atf_test_case broken_pipe
broken_pipe_head()
{
	atf_set "descr" "Do not print bogus errno based output on short writes"
}
broken_pipe_body()
{
	atf_check -o save:ints seq -f '%128g' 1 1000
	atf_check -s ignore \
	    -e "inline:tail: stdout\nexit code: 1\n" \
	    -x '(tail -n 856 ints; echo exit code: $? >&2) | sleep 2'
}

atf_test_case stdin
stdin_head()
{
	atf_set "descr" "Check basic operations on standard input"
}
stdin_body()
{
	seq 1 5 > infile
	seq 1 5 > expectfile
	seq 5 1 > expectfile_r

	tail < infile > outfile
	tail -r < infile > outfile_r

	atf_check cmp expectfile outfile
	atf_check cmp expectfile_r outfile_r
}

atf_test_case follow
follow_head()
{
	atf_set "descr" "Basic regression test for -f"
}
follow_body()
{
	local pid

	seq 1 5 > expectfile
	seq 1 3 > infile
	tail -f infile > outfile &
	pid=$!
	sleep 0.1
	seq 4 5 >> infile
	sleep 0.1
	atf_check cmp expectfile outfile
	atf_check kill $pid
}

atf_test_case follow_stdin
follow_stdin_head()
{
	atf_set "descr" "Verify that -f works with files piped to standard input"
}
follow_stdin_body()
{
	local pid

	seq 1 5 > expectfile
	seq 1 3 > infile
	tail -f < infile > outfile &
	pid=$!
	sleep 0.1
	seq 4 5 >> infile
	sleep 0.1
	atf_check cmp expectfile outfile
	atf_check kill $pid
}

atf_test_case follow_create
follow_create_head()
{
	atf_set "descr" "Verify that -F works when a file is created"
}
follow_create_body()
{
	local pid

	rm -f infile
	tail -F infile > outfile &
	pid=$!
	sleep 0.1
	seq 1 5 >infile
	sleep 2
	atf_check cmp infile outfile
	atf_check kill $pid
}

atf_test_case follow_rename
follow_rename_head()
{
	atf_set "descr" "Verify that -F works when a file is replaced"
}
follow_rename_body()
{
	local pid

	seq 1 5 > expectfile
	seq 1 3 > infile
	tail -F infile > outfile &
	pid=$!
	sleep 0.1
	seq 4 5 > infile_new
	atf_check mv infile infile_old
	atf_check mv infile_new infile
	# tail -F polls for a new file every 1s.
	sleep 2
	atf_check cmp expectfile outfile
	atf_check kill $pid
}

atf_test_case silent_header
silent_header_head() {
	atf_set "descr" "Test tail(1)'s silent header feature"
}
silent_header_body() {
	jot 11 1 11 > file1
	jot 11 2 12 > file2
	jot 10 2 11 > expectfile
	jot 10 3 12 >> expectfile
	tail -q file1 file2 > outfile
	atf_check cmp outfile expectfile
}

atf_test_case verbose_header
verbose_header_head() {
	atf_set "descr" "Test tail(1)'s verbose header feature"
}
verbose_header_body() {
	jot 11 1 11 > file1
	echo '==> file1 <==' > expectfile
	jot 10 2 11 >> expectfile
	tail -v file1 > outfile
	atf_check cmp outfile expectfile
}

atf_test_case si_number
si_number_head() {
	atf_set "descr" "Test tail(1)'s SI number feature"
}
si_number_body() {
	jot -b aaaaaaa 129 > file1
	jot -b aaaaaaa 128 > expectfile
	tail -c 1k file1 > outfile
	atf_check cmp outfile expectfile
	jot 1025 1 1025 > file1
	jot 1024 2 1025 > expectfile
	tail -n 1k file1 > outfile
	atf_check cmp outfile expectfile
}

atf_test_case no_lf_at_eof
no_lf_at_eof_head()
{
	atf_set "descr" "File does not end in newline"
}
no_lf_at_eof_body()
{
	printf "a\nb\nc" >infile
	atf_check -o inline:"c" tail -1 infile
	atf_check -o inline:"b\nc" tail -2 infile
	atf_check -o inline:"a\nb\nc" tail -3 infile
	atf_check -o inline:"a\nb\nc" tail -4 infile
}

atf_test_case tail_b
tail_b_head()
{
	atf_set "descr" "Test -b option"
}
tail_b_body()
{
	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
	(jot -b b 256 ; jot -b c 256) >outfile
	# infile is 3 blocks long, outfile contains the last two
	atf_check -o file:outfile tail -b +2 infile # start at the 2nd block
	atf_check -o file:outfile tail -b -2 infile # 2 blocks from the end
	atf_check -o file:outfile tail -b  2 infile # 2 blocks from the end
}

atf_test_case tail_c
tail_c_head()
{
	atf_set "descr" "Test -c option"
}
tail_c_body()
{
	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
	(jot -b b 256 ; jot -b c 256) >outfile
	# infile is 1536 bytes long, outfile contains the last 1024
	atf_check -o file:outfile tail -c  +513 infile # start at the 513th byte
	atf_check -o file:outfile tail -c -1024 infile # 1024 bytes from the end
	atf_check -o file:outfile tail -c  1024 infile # 1024 bytes from the end
}

atf_test_case tail_n
tail_n_head()
{
	atf_set "descr" "Test -n option"
}
tail_n_body()
{
	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile
	(jot -b b 256 ; jot -b c 256) >outfile
	# infile is 768 lines long, outfile contains the last 512
	atf_check -o file:outfile tail -n +257 infile # start at the 257th line
	atf_check -o file:outfile tail -n -512 infile # 512 lines from the end
	atf_check -o file:outfile tail -n  512 infile # 512 lines from the end
}

atf_init_test_cases()
{
	atf_add_test_case empty_r
	atf_add_test_case file_r
	atf_add_test_case file_rc28
	atf_add_test_case file_rn2
	atf_add_test_case pipe_leading_newline_r
	# The longfile tests are designed to exercise behavior in r_buf(),
	# which operates on 128KB blocks
	atf_add_test_case longfile_r
	atf_add_test_case longfile_r_enomem
	atf_add_test_case longfile_r_longlines
	atf_add_test_case longfile_rc135782
	atf_add_test_case longfile_rc145782_longlines
	atf_add_test_case longfile_rn2500
	atf_add_test_case broken_pipe
	atf_add_test_case stdin
	atf_add_test_case follow
	atf_add_test_case follow_stdin
	atf_add_test_case follow_create
	atf_add_test_case follow_rename
	atf_add_test_case silent_header
	atf_add_test_case verbose_header
	atf_add_test_case si_number
	atf_add_test_case no_lf_at_eof
	atf_add_test_case tail_b
	atf_add_test_case tail_c
	atf_add_test_case tail_n
}
