From b064346eb31d1b935209f34ec4fe2c2ca0e2a718 Mon Sep 17 00:00:00 2001 From: Dan Fandrich Date: Sat, 10 Nov 2018 14:16:47 +0100 Subject: [PATCH] Add check-failmalloc.sh to test OOM conditions. This requires Failmalloc, a library that can be used along with glibc to cause malloc calls to fail in a defined way. Configure will search for libfailmalloc.so.0 in the usual places by default, or in a user-specified location. The tests are skipped if it's not available. Enable Failmalloc on the Travis coverage build. --- .travis.yml | 18 ++++++++++-- configure.ac | 4 +++ m4m/failmalloc.m4 | 44 ++++++++++++++++++++++++++++++ test/Makefile.am | 7 ++++- test/check-failmalloc.sh | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ test/check-vars.sh.in | 4 +++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 m4m/failmalloc.m4 create mode 100755 test/check-failmalloc.sh create mode 100644 test/check-vars.sh.in diff --git a/.travis.yml b/.travis.yml index cd4f25b..9623d1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,6 +79,9 @@ matrix: - libpopt-dev - subversion compiler: gcc + cache: + directories: + - $HOME/failmalloc env: - CONFIG=coverage @@ -94,6 +97,17 @@ install: ln -s "$TRAVIS_BUILD_DIR" src/libexif ./build-config.sh autoreconf -sivf + cd "$HOME" + # Failmalloc for improved test coverage + if [ ! -e "$HOME/failmalloc/lib/libfailmalloc.so.0" ] ; then + curl -fsSORL https://download.savannah.nongnu.org/releases/failmalloc/failmalloc-1.0.tar.gz + tar xaf failmalloc-1.0.tar.gz + cd failmalloc-1.0 + sed -i -e 's/\(__malloc_initialize_hook\)/volatile \1/' failmalloc.c + ./configure --prefix="$HOME/failmalloc" + make + make install + fi cd "$TRAVIS_BUILD_DIR" fi @@ -106,8 +120,8 @@ script: - if [ "$CONFIG" = "clang6" ] ; then CFLAGS='-Wall -Wextra -O3'; export CC=clang-6.0; fi - if [ "$CONFIG" = "gcc8" ] ; then CFLAGS='-Wall -Wextra -O3'; export export CC=gcc-8; fi - if [ "$CONFIG" = "sanitize" ] ; then CFLAGS='-g -Wall -Wextra -fsanitize=address -fsanitize=undefined'; export CC=clang-6.0; fi - - if [ "$CONFIG" = "coverage" ] ; then cd "$HOME"/libexif-testsuite; CFLAGS=--coverage; LDFLAGS=--coverage; fi - - ./configure --prefix="${HOME}"/install CFLAGS="$CFLAGS" ${LDFLAGS:+LDFLAGS=$LDFLAGS} || { tail -300 config.log; false; } + - if [ "$CONFIG" = "coverage" ] ; then cd "$HOME"/libexif-testsuite; CFLAGS=--coverage; CONFIGURE_OPTS="LDFLAGS=--coverage --with-failmalloc=$HOME/failmalloc/lib" ; fi + - ./configure --prefix="${HOME}"/install CFLAGS="$CFLAGS" $CONFIGURE_OPTS || { tail -300 config.log; false; } - make V=1 - make V=1 check || { tail -300 test*/test-suite.log; false; } - make V=1 install diff --git a/configure.ac b/configure.ac index aa82192..f39b4f3 100644 --- a/configure.ac +++ b/configure.ac @@ -129,6 +129,9 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([ ]) ]) +# Check whether libfailmalloc is available for tests +CHECK_FAILMALLOC + # doc support GP_CHECK_DOC_DIR GP_CHECK_DOXYGEN @@ -204,6 +207,7 @@ AC_CONFIG_FILES([ po/Makefile.in libexif.spec libexif/Makefile test/Makefile + test/check-vars.sh test/nls/Makefile m4m/Makefile doc/Makefile diff --git a/m4m/failmalloc.m4 b/m4m/failmalloc.m4 new file mode 100644 index 0000000..dff9599 --- /dev/null +++ b/m4m/failmalloc.m4 @@ -0,0 +1,44 @@ +dnl Search for libfailmalloc to use for testing +AC_DEFUN([CHECK_FAILMALLOC],[dnl + dnl Libtool sets the default library paths + AM_PROG_LIBTOOL + path_provided= + AC_ARG_WITH(failmalloc, [ --with-failmalloc=PATH use Failmalloc for tests], [ + if test x"$withval" = "x" -o x"$withval" = "xyes"; then + failmalloc_search_path="$sys_lib_search_path_spec" + elif test x"$withval" = "xno"; then + failmalloc_search_path="" + else + failmalloc_search_path="$withval" + path_provided=1 + fi + ], [failmalloc_search_path="$sys_lib_search_path_spec"] + ) + libfailmalloc_file=libfailmalloc.so.0 + FAILMALLOC_PATH= + dnl Check if the argument is a directory + for d in $failmalloc_search_path; do + AC_CHECK_FILE([$d/$libfailmalloc_file], [ + FAILMALLOC_PATH="$d/$libfailmalloc_file" + break + ], []) + done + if test -z "$FAILMALLOC_PATH" -a -n "$path_provided"; then + dnl Check if the argument is a file + AC_CHECK_FILE([$failmalloc_search_path], [FAILMALLOC_PATH="$failmalloc_search_path"], []) + fi + AC_MSG_CHECKING([for failmalloc]) + dnl Make sure AC_CHECK_FILE didn't find a directory by mistake + if test -n "$FAILMALLOC_PATH" -a -f "$FAILMALLOC_PATH"; then + AC_MSG_RESULT([yes]) + else + if test -n "$path_provided"; then + AC_MSG_ERROR([$libfailmalloc_file was not found at $failmalloc_search_path]) + else + AC_MSG_RESULT([no]) + fi + fi + AC_SUBST(FAILMALLOC_PATH) + AM_CONDITIONAL(USE_FAILMALLOC, [test "x$FAILMALLOC_PATH" != "x"]) +]) + diff --git a/test/Makefile.am b/test/Makefile.am index 21189da..4083e50 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -14,12 +14,17 @@ SUBDIRS = nls TESTS = test-mem test-value test-integers test-parse test-tagtable test-sorted \ test-fuzzer parse-regression.sh swap-byte-order.sh +if USE_FAILMALLOC +TESTS += check-failmalloc.sh +endif + check_PROGRAMS = test-mem test-mnote test-value test-integers test-parse \ test-tagtable test-sorted test-fuzzer LDADD = $(top_builddir)/libexif/libexif.la $(LTLIBINTL) -EXTRA_DIST = parse-regression.sh swap-byte-order.sh \ +EXTRA_DIST = check-vars.sh.in parse-regression.sh swap-byte-order.sh \ + check-failmalloc.sh \ testdata/canon_makernote_variant_1.jpg \ testdata/canon_makernote_variant_1.jpg.parsed \ testdata/fuji_makernote_variant_1.jpg \ diff --git a/test/check-failmalloc.sh b/test/check-failmalloc.sh new file mode 100755 index 0000000..602ce66 --- /dev/null +++ b/test/check-failmalloc.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# Use Failmalloc to test behaviour in the face of out-of-memory conditions. +# The test runs a binary multiple times while configuring Failmalloc to fail a +# different malloc() call each time, while looking for abnormal program exits +# due to segfaults. See https://www.nongnu.org/failmalloc/ +# +# Ideally, it would ensure that the test binary returns an error code on each +# failure, but this often doesn't happen. This is a problem that should be +# rectified, but the API doesn't allow returning an error code in many +# functions that could encounter a problem. The issue could be solve in more +# cases with more judicious use of log calls with EXIF_LOG_CODE_NO_MEMORY +# codes. +. ./check-vars.sh + +VERBOSE= +if [ "$1" = "-v" ] ; then + VERBOSE=1 +fi + +if [ x"$FAILMALLOC_PATH" = x ]; then + echo libfailmalloc is not available + echo SKIPPING + exit +fi + +BINARY_PREFIX=./ +if [ -e .libs/lt-test-value ]; then + # If libtool is in use, the normal "binary" is actually a shell script which + # would be interfered with by libfailmalloc. Instead, use the special lt- + # binary which should work properly. + BINARY_PREFIX=".libs/lt-" +fi + +# Usage: failmalloc_binary_test #iterations binary +# FIXME: auto-determine #iterations by comparing the output of each run +# with the output of a normal run, and exiting when that happens. +failmalloc_binary_test () { + binary="$BINARY_PREFIX$2" + iterations="$1" + shift + shift + echo Checking "$binary" for "$iterations" iterations + for n in $(seq "$iterations"); do + test "$VERBOSE" = 1 && { echo "$n"; set -x; } + FAILMALLOC_INTERVAL="$n" LD_PRELOAD="$FAILMALLOC_PATH" "$binary" "$@" >/dev/null + s=$? + test "$VERBOSE" = 1 && set +x; + if test "$s" -ge 128; then + # Such status codes only happen due to termination due to a signal + # like SIGSEGV. + echo "Abnormal binary exit status $s at malloc #$n on $binary" + echo FAILURE + exit 1 + fi + done +} + +# The number of iterations is determined empirically to be about twice as +# high as the maximum number of mallocs performed by the test program in order +# to avoid lowering code coverage in the case of future code changes that cause +# more allocations. + +failmalloc_binary_test 500 test-value +failmalloc_binary_test 300 test-mem +for f in $SRCDIR/testdata/*jpg; do + echo "Testing `basename "$f"`" + failmalloc_binary_test 500 test-parse "$f" + # N.B., test-parse --swap-byte-order doesn't test any new paths +done + +echo PASSED diff --git a/test/check-vars.sh.in b/test/check-vars.sh.in new file mode 100644 index 0000000..fa0b4c9 --- /dev/null +++ b/test/check-vars.sh.in @@ -0,0 +1,4 @@ +# Specifies autoconf variables for use by the test scripts + +SRCDIR=@srcdir@ +FAILMALLOC_PATH=@FAILMALLOC_PATH@ -- 2.7.4