#
# Copyright 2014, Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of Google Inc. nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
# OWNER 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.
#
#

# Helper function that is always used to create and fill stderr.txt for these
# tests.
_custom_create_file()
{
	# The first argument is a command.
	# The second is just a string.
	case "${1}" in
		creat) > stderr.txt ;;
		print) [ "${2}" ] && \
		    printf "%s\n" "${2}" >> stderr.txt ;;
	esac
}

# Helper function that create the file stderr.txt that contains the string
# passed in as the first argument.
create_stderr_file()
{
	_custom_create_file creat
	_custom_create_file print "${1}"
}

# Helper function that create the file stderr.txt that contains the expected
# truncate utility usage message.
create_stderr_usage_file()
{
	_custom_create_file creat
	_custom_create_file print "${1}"
	_custom_create_file print \
	    "usage: truncate [-c] -s [+|-|%|/]size[K|k|M|m|G|g|T|t] file ..."
	_custom_create_file print "       truncate [-c] -r rfile file ..."
	_custom_create_file print "       truncate [-c] -d [-o offset[K|k|M|m|G|g|T|t]] -l length[K|k|M|m|G|g|T|t] file ..."
}

atf_test_case illegal_option
illegal_option_head()
{
	atf_set "descr" "Verifies that truncate exits >0 when passed an" \
	    "invalid command line option"
}
illegal_option_body()
{
	create_stderr_usage_file 'truncate: illegal option -- 7'

	# We expect the error message, with no new files.
	atf_check -s not-exit:0 -e file:stderr.txt truncate -7 -s0 output.txt
	[ ! -e output.txt ] || atf_fail "output.txt should not exist"
}

atf_test_case illegal_size
illegal_size_head()
{
	atf_set "descr" "Verifies that truncate exits >0 when passed an" \
	    "invalid power of two convention"
}
illegal_size_body()
{
	create_stderr_file "truncate: invalid size argument \`+1L'"

	# We expect the error message, with no new files.
	atf_check -s not-exit:0 -e file:stderr.txt truncate -s+1L output.txt
	[ ! -e output.txt ] || atf_fail "output.txt should not exist"
}

atf_test_case too_large_size
too_large_size_head()
{
	atf_set "descr" "Verifies that truncate exits >0 when passed an" \
	    "a size that is INT64_MAX < size <= UINT64_MAX"
}
too_large_size_body()
{
	create_stderr_file "truncate: invalid size argument \`8388608t'"

	# We expect the error message, with no new files.
	atf_check -s not-exit:0 -e file:stderr.txt \
	    truncate -s8388608t output.txt
	[ ! -e output.txt ] || atf_fail "output.txt should not exist"
}

atf_test_case opt_c
opt_c_head()
{
	atf_set "descr" "Verifies that -c prevents creation of new files"
}
opt_c_body()
{
	# No new files and truncate returns 0 as if this is a success.
	atf_check truncate -c -s 0 doesnotexist.txt
	[ ! -e output.txt ] || atf_fail "doesnotexist.txt should not exist"
	> reference
	atf_check truncate -c -r reference doesnotexist.txt
	[ ! -e output.txt ] || atf_fail "doesnotexist.txt should not exist"

	create_stderr_file

	# The existing file will be altered by truncate.
	> exists.txt
	atf_check -e file:stderr.txt truncate -c -s1 exists.txt
	[ -s exists.txt ] || atf_fail "exists.txt be larger than zero bytes"
}

atf_test_case opt_rs
opt_rs_head()
{
	atf_set "descr" "Verifies that truncate command line flags" \
	    "-s and -r cannot be specifed together"
}
opt_rs_body()
{
	create_stderr_usage_file

	# Force an error due to the use of both -s and -r.
	> afile
	atf_check -s not-exit:0 -e file:stderr.txt truncate -s0 -r afile afile
}

atf_test_case no_files
no_files_head()
{
	atf_set "descr" "Verifies that truncate needs a list of files on" \
	    "the command line"
}
no_files_body()
{
	create_stderr_usage_file

	# A list of files must be present on the command line.
	atf_check -s not-exit:0 -e file:stderr.txt truncate -s1
}

atf_test_case bad_refer
bad_refer_head()
{
	atf_set "descr" "Verifies that truncate detects a non-existent" \
	    "reference file"
}
bad_refer_body()
{
	create_stderr_file "truncate: afile: No such file or directory"

	# The reference file must exist before you try to use it.
	atf_check -s not-exit:0 -e file:stderr.txt truncate -r afile afile
	[ ! -e afile ] || atf_fail "afile should not exist"
}

atf_test_case bad_truncate
bad_truncate_head()
{
	atf_set "descr" "Verifies that truncate reports an error during" \
	    "truncation"
	atf_set "require.user" "unprivileged"
}
bad_truncate_body()
{
	create_stderr_file "truncate: exists.txt: Permission denied"

	# Trying to get the ftruncate() call to return -1.
	> exists.txt
	atf_check chmod 444 exists.txt

	atf_check -s not-exit:0 -e file:stderr.txt truncate -s1 exists.txt
}

atf_test_case new_absolute_grow
new_absolute_grow_head()
{
	atf_set "descr" "Verifies truncate can make and grow a new 1m file"
}
new_absolute_grow_body()
{
	create_stderr_file

	# Create a new file and grow it to 1024 bytes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s1k output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"

	create_stderr_file

	# Grow the existing file to 1M.  We are using absolute sizes.
	atf_check -s exit:0 -e file:stderr.txt truncate -c -s1M output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m"
}

atf_test_case new_absolute_shrink
new_absolute_shrink_head()
{
	atf_set "descr" "Verifies that truncate can make and" \
	    "shrink a new 1m file"
}
new_absolute_shrink_body()
{
	create_stderr_file

	# Create a new file and grow it to 1048576 bytes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s1M output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m"

	create_stderr_file

	# Shrink the existing file to 1k.  We are using absolute sizes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s1k output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"
}

atf_test_case new_relative_grow
new_relative_grow_head()
{
	atf_set "descr" "Verifies truncate can make and grow a new 1m file" \
	    "using relative sizes"
}
new_relative_grow_body()
{
	create_stderr_file

	# Create a new file and grow it to 1024 bytes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s+1k output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"

	create_stderr_file

	# Grow the existing file to 1M.  We are using relative sizes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s+1047552 output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m"
}

atf_test_case new_relative_shrink
new_relative_shrink_head()
{
	atf_set "descr" "Verifies truncate can make and shrink a new 1m file" \
	    "using relative sizes"
}
new_relative_shrink_body()
{
	create_stderr_file

	# Create a new file and grow it to 1049600 bytes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s+1049600 output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1049600 ] || atf_fail "expected file size of 1m"

	create_stderr_file

	# Shrink the existing file to 1k.  We are using relative sizes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s-1M output.txt
	atf_check -s exit:1 cmp -s output.txt /dev/zero
	eval $(stat -s output.txt)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"
}

atf_test_case cannot_open
cannot_open_head()
{
	atf_set "descr" "Verifies truncate handles open failures correctly" \
	    "in a list of files"
	atf_set "require.user" "unprivileged"
}
cannot_open_body()
{
	# Create three files -- the middle file cannot allow writes.
	> before
	> 0000
	> after
	atf_check chmod 0000 0000

	create_stderr_file "truncate: 0000: Permission denied"

	# Create a new file and grow it to 1024 bytes.
	atf_check -s not-exit:0 -e file:stderr.txt \
	truncate -c -s1k before 0000 after
	eval $(stat -s before)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"
	eval $(stat -s after)
	[ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k"
	eval $(stat -s 0000)
	[ ${st_size} -eq 0 ] || atf_fail "expected file size of zero"
}

atf_test_case reference
reference_head()
{
	atf_set "descr" "Verifies that truncate can use a reference file"
}
reference_body()
{
	# Create a 4 byte reference file.
	printf "123\n" > reference
	eval $(stat -s reference)
	[ ${st_size} -eq 4 ] || atf_fail "reference file should be 4 bytes"

	create_stderr_file

	# Create a new file and grow it to 4 bytes.
	atf_check -e file:stderr.txt truncate -r reference afile
	eval $(stat -s afile)
	[ ${st_size} -eq 4 ] || atf_fail "new file should also be 4 bytes"
}

atf_test_case new_zero
new_zero_head()
{
	atf_set "descr" "Verifies truncate can make and grow zero byte file"
}
new_zero_body()
{
	create_stderr_file

	# Create a new file and grow it to zero bytes.
	atf_check -s exit:0 -e file:stderr.txt truncate -s0 output.txt
	eval $(stat -s output.txt)
	[ ${st_size} -eq 0 ] || atf_fail "expected file size of zero"

	# Pretend to grow the file.
	atf_check -s exit:0 -e file:stderr.txt truncate -s+0 output.txt
	eval $(stat -s output.txt)
	[ ${st_size} -eq 0 ] || atf_fail "expected file size of zero"
}

atf_test_case negative
negative_head()
{
	atf_set "descr" "Verifies truncate treats negative sizes as zero"
}
negative_body()
{
	# Create a 5 byte file.
	printf "abcd\n" > afile
	eval $(stat -s afile)
	[ ${st_size} -eq 5 ] || atf_fail "afile file should be 5 bytes"

	create_stderr_file

	# Create a new file and do a 100 byte negative relative shrink.
	atf_check -e file:stderr.txt truncate -s-100 afile
	eval $(stat -s afile)
	[ ${st_size} -eq 0 ] || atf_fail "new file should now be zero bytes"
}

atf_test_case roundup
roundup_head()
{
	atf_set "descr" "Verifies truncate round up"
}
roundup_body()
{
	# Create a 5 byte file.
	printf "abcd\n" > afile
	eval $(stat -s afile)
	[ ${st_size} -eq 5 ] || atf_fail "afile file should be 5 bytes"

	create_stderr_file

	# Create a new file and do a 100 byte roundup.
	atf_check -e file:stderr.txt truncate -s%100 afile
	eval $(stat -s afile)
	[ ${st_size} -eq 100 ] || atf_fail "new file should now be 100 bytes"
}

atf_test_case rounddown
rounddown_head()
{
	atf_set "descr" "Verifies truncate round down"
}
rounddown_body()
{
	# Create a 5 byte file.
	printf "abcd\n" > afile
	eval $(stat -s afile)
	[ ${st_size} -eq 5 ] || atf_fail "afile file should be 5 bytes"

	create_stderr_file

	# Create a new file and do a 2 byte roundup.
	atf_check -e file:stderr.txt truncate -s/2 afile
	eval $(stat -s afile)
	[ ${st_size} -eq 4 ] || atf_fail "new file should now be 4 bytes"
}

atf_test_case rounddown_zero
rounddown_zero_head()
{
	atf_set "descr" "Verifies truncate round down to zero"
}
rounddown_zero_body()
{
	# Create a 5 byte file.
	printf "abcd\n" > afile
	eval $(stat -s afile)
	[ ${st_size} -eq 5 ] || atf_fail "afile file should be 5 bytes"

	create_stderr_file

	# Create a new file and do a 10 byte roundup.
	atf_check -e file:stderr.txt truncate -s/10 afile
	eval $(stat -s afile)
	[ ${st_size} -eq 0 ] || atf_fail "new file should now be 0 bytes"
}

atf_init_test_cases()
{
	atf_add_test_case illegal_option
	atf_add_test_case illegal_size
	atf_add_test_case too_large_size
	atf_add_test_case opt_c
	atf_add_test_case opt_rs
	atf_add_test_case no_files
	atf_add_test_case bad_refer
	atf_add_test_case bad_truncate
	atf_add_test_case cannot_open
	atf_add_test_case new_absolute_grow
	atf_add_test_case new_absolute_shrink
	atf_add_test_case new_relative_grow
	atf_add_test_case new_relative_shrink
	atf_add_test_case reference
	atf_add_test_case new_zero
	atf_add_test_case negative
	atf_add_test_case roundup
	atf_add_test_case rounddown
	atf_add_test_case rounddown_zero
}
