From 99d6e72b896f394ae3b3466e2ac7e7640e7394a9 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 23 Oct 2016 13:54:53 -0700 Subject: [PATCH] Add gzip program tests --- tools/gzip_tests.sh | 412 ++++++++++++++++++++++++++++++++++++++++++++ tools/run_tests.sh | 35 +++- 2 files changed, 444 insertions(+), 3 deletions(-) create mode 100755 tools/gzip_tests.sh diff --git a/tools/gzip_tests.sh b/tools/gzip_tests.sh new file mode 100755 index 0000000..2c636f7 --- /dev/null +++ b/tools/gzip_tests.sh @@ -0,0 +1,412 @@ +#!/bin/bash +# +# Test script for libdeflate's gzip and gunzip programs. +# +# To run, you must set GZIP and GUNZIP in the environment to the absolute paths +# to the gzip and gunzip programs to test. All tests should pass regardless of +# whether the GNU versions or the libdeflate versions, or a combination, of +# these programs are used. +# +# The environmental variable SMOKEDATA must also be set to a file containing +# test data. +# + +set -eu -o pipefail + +export -n GZIP GUNZIP SMOKEDATA + +TMPDIR="$(mktemp -d)" +CURRENT_TEST= + +cleanup() { + if [ -n "$CURRENT_TEST" ]; then + echo "TEST FAILED: \"$CURRENT_TEST\"" + fi + rm -rf -- "$TMPDIR" +} + +trap cleanup EXIT + +SMOKEDATA="$(realpath "$SMOKEDATA")" +cd "$TMPDIR" + +begin_test() { + CURRENT_TEST="$1" + rm -rf -- "$TMPDIR"/* + cp "$SMOKEDATA" file +} + +gzip() { + $GZIP "$@" +} + +gunzip() { + $GUNZIP "$@" +} + +assert_status() { + local expected_status="$1" + local expected_msg="$2" + shift 2 + ( + set +e + eval "$*" 2>&1 >/dev/null + local actual_status=$? + if [ $actual_status -ne $expected_status ]; then + echo 1>&2 "Command '$*' exited with status" \ + "$actual_status but expected status" \ + "$expected_status" + exit 1 + fi + exit 0 + ) > command_output + if ! egrep -q "$expected_msg" command_output; then + echo 1>&2 "Expected output of command '$*' to match regex" \ + "'$expected_msg'" + echo 1>&2 "Actual output was:" + echo 1>&2 "---------------------------------------------------" + cat 1>&2 command_output + echo 1>&2 "---------------------------------------------------" + return 1 + fi +} + +assert_error() { + assert_status 1 "$@" +} + +assert_warning() { + assert_status 2 "$@" +} + +assert_skipped() { + assert_warning '\<(ignored|skipping|unchanged)\>' "$@" +} + + +begin_test 'Basic compression and decompression works' +cp file orig +gzip file +[ ! -e file -a -e file.gz ] +gunzip file.gz +[ -e file -a ! -e file.gz ] +cmp file orig + + +begin_test 'gzip -d is gunzip' +cp file orig +gzip file +gzip -d file.gz +cmp file orig + + +begin_test '-k (keep original file) works' +cp file orig +gzip -k file +cmp file orig +rm file +cp file.gz orig.gz +gunzip -k file.gz +cmp file.gz orig.gz + + +begin_test '-c (write to stdout) works' +cp file orig +gzip -k file +gzip -c file > 2.gz +cmp file orig +cmp file.gz 2.gz +gunzip -c 2.gz > file +cmp file.gz 2.gz +cmp file orig + + +begin_test 'Reading from stdin works' +gzip < file > 1.gz +gzip - < file > 2.gz +cat file | gzip > 3.gz +cat file | gzip - > 4.gz +cmp file <(gunzip < 1.gz) +cmp file <(gunzip - < 2.gz) +cmp file <(cat 3.gz | gunzip) +cmp file <(cat 4.gz | gunzip -) + + +begin_test '-n option is accepted' +gzip -n file +gunzip -n file.gz + + +begin_test 'can specify multiple options' +gzip -fk1 file +cmp <(gzip -c -1 file) file.gz +gunzip -kfd file.gz + + +begin_test 'Compression levels' +if [ "$GZIP" = /usr/bin/gzip ]; then + assert_error '\' gzip -10 + max_level=9 +else + for level in 13 99999 1a; do + assert_error '\' gzip -$level + done + max_level=12 +fi +for level in `seq 1 $max_level`; do + gzip -c -$level file > file$level + cmp file <(gunzip -c file$level) +done +rm file command_output +cmp <(ls -S) <(ls -v) # file,file{1..max_level} have decreasing size + + +begin_test 'Overwriting output file requires -f' +cp file orig +echo -n > file.gz +gzip -c file > 2.gz +assert_warning 'already exists' gzip file file +assert_warning 'already exists' gunzip file.gz c.gz +gzip file.gz 2>&1 >/dev/null | grep -q 'already has .gz suffix' +[ -e file.gz -a ! -e file.gz.gz ] +gzip -f file.gz +[ ! -e file.gz -a -e file.gz.gz ] +cmp file.gz.gz c.gz + + +begin_test 'Decompressing unsuffixed file only works with -c' +gzip file && mv file.gz file +assert_skipped gunzip file +assert_skipped gunzip -f file +gunzip -c file > orig +mv file file.gz && gunzip file.gz && cmp file orig + + +begin_test '... unless there is a corresponding suffixed file' +cp file orig +gzip file +[ ! -e file -a -e file.gz ] +gunzip -c file > tmp +cmp tmp orig +rm tmp +ln -s NONEXISTENT file +gunzip -c file > tmp +cmp tmp orig +rm tmp file +gunzip file +[ -e file -a ! -e file.gz ] +cmp file orig + + +begin_test 'Directory is skipped, even with -f' +mkdir dir +mkdir dir.gz +for opt in '' '-f' '-c'; do + assert_skipped gzip $opt dir +done +#assert_skipped gzip dir.gz # XXX: GNU gzip warns, libdeflate gzip no-ops +for opt in '' '-f' '-c'; do + for name in dir dir.gz; do + assert_skipped gunzip $opt $name + done +done + + +begin_test '(gzip) symlink is rejected without -f or -c' +ln -s file symlink1 +ln -s file symlink2 +assert_error 'Too many levels of symbolic links' gzip symlink1 +[ -e file -a -e symlink1 -a ! -e symlink1.gz ] +gzip -f symlink1 +[ -e file -a ! -e symlink1 -a -e symlink1.gz ] +gzip -c symlink2 > /dev/null + + +begin_test '(gunzip) symlink is rejected without -f or -c' +gzip file +ln -s file.gz symlink1.gz +ln -s file.gz symlink2.gz +assert_error 'Too many levels of symbolic links' gunzip symlink1 +[ -e file.gz -a -e symlink1.gz -a ! -e symlink1 ] +gunzip -f symlink1.gz +[ -e file.gz -a ! -e symlink1.gz -a -e symlink1 ] +gunzip -c symlink2.gz > /dev/null + + +begin_test 'FIFO is skipped, even with -f' +mkfifo foo +mkfifo foo.gz +assert_skipped gzip foo +assert_skipped gzip -f foo +#assert_skipped gzip -c foo # XXX: works with GNU gzip, not libdeflate's +assert_skipped gunzip foo.gz +assert_skipped gunzip -f foo.gz +#assert_skipped gunzip -c foo.gz # XXX: works with GNU gzip, not libdeflate's + + +begin_test '(gzip) overwriting symlink does not follow symlink' +echo 1 > 1 +echo 2 > 2 +gzip 1 +ln -s 1.gz 2.gz +gzip -f 2 +gunzip 1.gz +cmp <(echo 1) 1 + + +begin_test '(gunzip) overwriting symlink does not follow symlink' +echo 1 > 1 +echo 2 > 2 +gzip 2 +ln -s 1 2 +gunzip -f 2.gz +cmp <(echo 1) 1 +cmp <(echo 2) 2 + + +begin_test '(gzip) hard linked file skipped without -f or -c' +cp file orig +ln file link +[ $(stat -c %h file) -eq 2 ] +assert_skipped gzip file +gzip -c file > /dev/null +[ $(stat -c %h file) -eq 2 ] +gzip -f file +[ $(stat -c %h link) -eq 1 ] +[ $(stat -c %h file.gz) -eq 1 ] +cmp link orig +# XXX: GNU gzip skips hard linked files with -k, libdeflate's doesn't + + +begin_test '(gunzip) hard linked file skipped without -f or -c' +gzip file +ln file.gz link.gz +cp file.gz orig.gz +[ $(stat -c %h file.gz) -eq 2 ] +assert_skipped gunzip file.gz +gunzip -c file.gz > /dev/null +[ $(stat -c %h file.gz) -eq 2 ] +gunzip -f file +[ $(stat -c %h link.gz) -eq 1 ] +[ $(stat -c %h file) -eq 1 ] +cmp link.gz orig.gz + + +begin_test 'Multiple files' +cp file file2 +gzip file file2 +[ ! -e file -a ! -e file2 -a -e file.gz -a -e file2.gz ] +gunzip file.gz file2.gz +[ -e file -a -e file2 -a ! -e file.gz -a ! -e file2.gz ] + + +begin_test 'Multiple files, continue on warning' +mkdir 1 +cp file 2 +assert_skipped gzip 1 2 +[ ! -e 1.gz ] +cmp file <(gunzip -c 2.gz) +rmdir 1 +mkdir 1.gz +assert_skipped gunzip 1.gz 2.gz +[ ! -e 1 ] +cmp 2 file + + +begin_test 'Multiple files, continue on error' +cp file 1 +cp file 2 +chmod -r 1 +assert_error 'Permission denied' gzip 1 2 +[ ! -e 1.gz ] +cmp file <(gunzip -c 2.gz) +rm -f 1 +cp 2.gz 1.gz +chmod -r 1.gz +assert_error 'Permission denied' gunzip 1.gz 2.gz +[ ! -e 1 ] +cmp 2 file + + +begin_test 'Compressing empty file' +echo -n > empty +gzip empty +gunzip empty.gz +cmp /dev/null empty + + +begin_test 'Decompressing malformed file' +echo -n > foo.gz +assert_error '\<(not in gzip format|unexpected end of file)\>' \ + gunzip foo.gz +echo 1 > foo.gz +assert_error '\' gunzip foo.gz +echo abcdefgh > foo.gz +assert_error '\' gunzip foo.gz +xxd -r > foo.gz <<-EOF +00000000: 1f8b 0800 0000 0000 00ff 4b4c 4a4e 4924 ..........KLJNI$ +00000010: 1673 0100 6c5b a262 2e00 0000 .s..l[.b.... +EOF +assert_error '\<(not in gzip format|crc error)\>' gunzip foo.gz + + +for suf in .foo foo .blaaaaaaaaaaaaaaaargh; do + begin_test "Custom suffix: $suf" + gzip -S $suf file + [ ! -e file -a ! -e file.gz -a -e file$suf ] + assert_skipped gunzip file$suf + gunzip -S $suf file$suf + [ -e file -a ! -e file.gz -a ! -e file$suf ] +done +# DIFFERENCE: GNU gzip lower cases suffix, we don't + + +begin_test 'Empty suffix is rejected' +assert_error '\' gzip -S '""' file +assert_error '\' gunzip -S '""' file + + +begin_test 'Timestamps and mode are preserved' +chmod 777 file +orig_stat="$(stat -c '%a;%x;%y' file)" +gzip file +sleep 1 +gunzip file.gz +[ "$(stat -c '%a;%x;%y' file)" = "$orig_stat" ] + + +begin_test 'Help option' +gzip -h 2>&1 | grep -q 'Usage' +gunzip -h 2>&1 | grep -q 'Usage' + + +begin_test 'Incorrect usage' +for prog in gzip gunzip; do + for opt in '--invalid-option' '-0'; do + assert_error '\<(unrecognized|invalid) option\>' $prog $opt + done +done + + +begin_test 'Version information' +gzip -V | grep -q Copyright +gunzip -V | grep -q Copyright + +CURRENT_TEST= diff --git a/tools/run_tests.sh b/tools/run_tests.sh index b60248d..540b591 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -8,7 +8,7 @@ # exclude specific test groups. # -set -eu +set -eu -o pipefail cd "$(dirname "$0")/.." TESTGROUPS=(all) @@ -46,6 +46,8 @@ NDKDIR="${NDKDIR:=/opt/android-ndk}" FILES=("$SMOKEDATA" ./tools/exec_tests.sh benchmark test_checksums) EXEC_TESTS_CMD="WRAPPER= SMOKEDATA=\"$(basename $SMOKEDATA)\" sh exec_tests.sh" NPROC=$(grep -c processor /proc/cpuinfo) +VALGRIND="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" +SANITIZE_CFLAGS="-fsanitize=undefined -fno-sanitize-recover=undefined,integer" TMPFILE="$(mktemp)" trap "rm -f \"$TMPFILE\"" EXIT @@ -121,10 +123,10 @@ native_tests() { done log "Running tests with Valgrind" - WRAPPER="valgrind --error-exitcode=100 --quiet" native_build_and_test + WRAPPER="$VALGRIND" native_build_and_test log "Running tests with undefined behavior sanitizer" - WRAPPER= native_build_and_test CC=clang CFLAGS="-fsanitize=undefined" + WRAPPER= native_build_and_test CC=clang CFLAGS="$SANITIZE_CFLAGS" } ############################################################################### @@ -225,6 +227,32 @@ static_analysis_tests() { ############################################################################### +gzip_tests() { + test_group_included gzip || return 0 + + local gzip gunzip + run_cmd make -j$NPROC gzip gunzip + for gzip in "$PWD/gzip" /usr/bin/gzip; do + for gunzip in "$PWD/gunzip" /usr/bin/gunzip; do + log "Running gzip program tests with GZIP=$gzip," \ + "GUNZIP=$gunzip" + GZIP="$gzip" GUNZIP="$gunzip" SMOKEDATA="$SMOKEDATA" \ + ./tools/gzip_tests.sh + done + done + + log "Running gzip program tests with Valgrind" + GZIP="$VALGRIND $PWD/gzip" GUNZIP="$VALGRIND $PWD/gunzip" \ + SMOKEDATA="$SMOKEDATA" ./tools/gzip_tests.sh + + log "Running gzip program tests with undefined behavior sanitizer" + run_cmd make -j$NPROC CC=clang CFLAGS="$SANITIZE_CFLAGS" gzip gunzip + GZIP="$PWD/gzip" GUNZIP="$PWD/gunzip" \ + SMOKEDATA="$SMOKEDATA" ./tools/gzip_tests.sh +} + +############################################################################### + log "Starting libdeflate tests" log " TESTGROUPS=(${TESTGROUPS[@]})" log " SMOKEDATA=$SMOKEDATA" @@ -235,6 +263,7 @@ android_tests mips_tests windows_tests static_analysis_tests +gzip_tests if (( TESTS_SKIPPED )); then log "No tests failed, but some tests were skipped. See above."