]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
test: Various improvements and cleanups that were long needed
authorJoel Rosdahl <joel@rosdahl.net>
Mon, 25 Jul 2016 15:58:37 +0000 (17:58 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 27 Jul 2016 18:00:07 +0000 (20:00 +0200)
* Made all test cases isolated, not reusing test state from previous
  tests.
* Introduced per-suite detection whether suite should be run or skipped.
* Only run base_tests with and without CCACHE_CPP2.
* Moved CCACHE_HARDLINK, CCACHE_NLEVELS, CCACHE_EXTRAFILES,
  CCACHE_IGNOREHEADERS, CCACHE_PREFIX, ccache symlinks and "buggy CPP"
  tests into base_tests instead of having test suites of their own.
* Split conditional tests (depending on compiler and/or OS) into
  separate test suites.
* Removed -fprofile-generate and -fprofile-use tests since they don't
  test any functionality.
* Made various cleanups that were long needed.

test.sh

diff --git a/test.sh b/test.sh
index f3759fe87ae59b7597ef97de2c213cad22a6debe..0f142619525af3510e7c0b9d6a9d6aa13af42248 100755 (executable)
--- a/test.sh
+++ b/test.sh
@@ -48,62 +48,84 @@ unset CCACHE_UMASK
 unset CCACHE_UNIFY
 unset GCC_COLORS
 
-# Many tests backdate files, which updates their ctimes.  In those tests, we
-# must ignore ctimes.  Might as well do so everywhere.
-default_sloppiness=include_file_ctime
-CCACHE_SLOPPINESS="$default_sloppiness"
-export CCACHE_SLOPPINESS
-
 test_failed() {
-    echo "SUITE: \"$testsuite\", TEST: \"$testname\" - $1"
+    echo
+    echo FAILED
+    echo
+    echo "Test suite:     $CURRENT_SUITE"
+    echo "Test case:      $CURRENT_TEST"
+    echo "Failure reason: $1"
+    echo
+    echo "ccache -s:"
     $CCACHE -s
-    cd ..
-    echo TEST FAILED
+    echo
     echo "Test data and log file have been left in $TESTDIR"
     exit 1
 }
 
-randcode() {
-    outfile="$1"
-    nlines=$2
-    i=0
-    (
-    while [ $i -lt $nlines ]; do
-        echo "int foo$nlines$i(int x) { return x; }"
-        i=`expr $i + 1`
+generate_code() {
+    local nlines=$1
+    local outfile=$2
+
+    rm -f $outfile
+    for i in $(seq $nlines); do
+        echo "int foo_$i(int x) { return x; }" >>$outfile
+    done
+}
+
+remove_cache() {
+    if [ -d $CCACHE_DIR ]; then
+        chmod -R +w $CCACHE_DIR
+        rm -rf $CCACHE_DIR
+    fi
+}
+
+clear_cache() {
+    $CCACHE -Cz >/dev/null
+}
+
+sed_in_place() {
+    local expr=$1
+    shift
+
+    for file in $*; do
+        sed "$expr" $file >$file.sed
+        mv $file.sed $file
     done
-    ) >> "$outfile"
 }
 
-getstat() {
-    stat="$1"
-    value=`$CCACHE -s | grep "$stat" | cut -c34-`
-    echo $value
+backdate() {
+    touch -t 199901010000 "$@"
 }
 
-checkstat() {
-    stat="$1"
-    expected_value="$2"
-    value=`getstat "$stat"`
+expect_stat() {
+    local stat="$1"
+    local expected_value="$2"
+    local value="$(echo $($CCACHE -s | fgrep "$stat" | cut -c34-))"
+
     if [ "$expected_value" != "$value" ]; then
-        test_failed "Expected \"$stat\" to be $expected_value, got $value"
+        test_failed "Expected \"$stat\" to be $expected_value, actual $value"
     fi
 }
 
-compare_file() {
-    cmp -s "$1" "$2"
-    if [ $? -ne 0 ]; then
-        test_failed "Files differ: $1 != $2"
+expect_equal_files() {
+    if [ ! -e "$1" ]; then
+        test_failed "compare_files: $1 missing"
+    fi
+    if [ ! -e "$2" ]; then
+        test_failed "compare_files: $2 missing"
+    fi
+    if ! cmp -s "$1" "$2"; then
+        test_failed "compare_files:: $1 and $2 differ"
     fi
 }
 
-compare_object() {
+expect_equal_object_files() {
     if [ $HOST_OS_LINUX -eq 1 ] && [ $COMPILER_TYPE_CLANG -eq 1 ]; then
-        if which eu-elfcmp >/dev/null 2>&1; then
-            eu-elfcmp -q "$1" "$2"
-        else
+        if ! which eu-elfcmp >/dev/null 2>&1; then
             test_failed "Please install elfutils to get eu-elfcmp"
         fi
+        eu-elfcmp -q "$1" "$2"
     else
         cmp -s "$1" "$2"
     fi
@@ -112,331 +134,565 @@ compare_object() {
     fi
 }
 
-checkfile() {
-    if [ ! -f $1 ]; then
-        test_failed "$1 not found"
+expect_file_content() {
+    local file="$1"
+    local content="$2"
+
+    if [ ! -f "$file" ]; then
+        test_failed "$file not found"
     fi
-    if [ "`cat $1`" != "$2" ]; then
-        test_failed "Bad content of $1.\nExpected: $2\nActual: `cat $1`"
+    if [ "$(cat $file)" != "$content" ]; then
+        test_failed "Bad content of $file.\nExpected: $content\nActual: $(cat $file)"
     fi
 }
 
-checkfilecount() {
-    expected=$1
-    pattern=$2
-    dir=$3
-    actual=`find $dir -name "$pattern" | wc -l`
+expect_file_count() {
+    local expected=$1
+    local pattern=$2
+    local dir=$3
+    local actual=`find $dir -name "$pattern" | wc -l`
     if [ $actual -ne $expected ]; then
         test_failed "Found $actual (expected $expected) $pattern files in $dir"
     fi
 }
 
-sed_in_place() {
-    expr=$1
-    shift
-    for file in $*; do
-        sed "$expr" > ${file}.sed < $file
-        mv ${file}.sed $file
-    done
-}
+run_suite() {
+    local name=$1
 
-backdate() {
-    touch -t 199901010000 "$@"
-}
+    CURRENT_SUITE=$name
 
-run_suite() {
-    rm -rf $CCACHE_DIR
     CCACHE_NODIRECT=1
     export CCACHE_NODIRECT
 
-    echo "starting testsuite $1"
-    testsuite=$1
+    cd $ABS_TESTDIR
+    rm -rf $ABS_TESTDIR/fixture
+    remove_cache
+
+    local skip_reason=""
+    if type SUITE_${name}_PREPARE >/dev/null 2>&1; then
+        mkdir $ABS_TESTDIR/fixture
+        cd $ABS_TESTDIR/fixture
+        skip_reason=$(SUITE_${name}_PREPARE)
+        cd $ABS_TESTDIR
+    fi
+    if [ -z "$skip_reason" ]; then
+        printf "Running test suite %s" $name
+        SUITE_${name}
+        echo
+    else
+        echo "Skipped test suite $name [$skip_reason]"
+    fi
+}
+
+TEST() {
+    CURRENT_TEST=$1
 
-    ${1}_suite
+    if $VERBOSE; then
+        printf "\n  %s" $CURRENT_TEST
+    else
+        printf .
+    fi
 
-    testname="the tmp directory should be empty"
-    if [ -d $CCACHE_DIR/tmp ] && [ "`find $CCACHE_DIR/tmp -type f | wc -l`" -gt 0 ]; then
-        test_failed "$CCACHE_DIR/tmp is not empty"
+    cd /
+    remove_cache
+    rm -rf $ABS_TESTDIR/run
+    if [ -d $ABS_TESTDIR/fixture ]; then
+        cp -a $ABS_TESTDIR/fixture $ABS_TESTDIR/run
+    else
+        mkdir $ABS_TESTDIR/run
     fi
+    cd $ABS_TESTDIR/run
 }
 
+# =============================================================================
+
 base_tests() {
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'files in cache' 0
-
-    j=1
-    rm -f *.c
-    while [ $j -lt 32 ]; do
-        randcode test$j.c $j
-        j=`expr $j + 1`
-    done
+    # -------------------------------------------------------------------------
+    TEST "Base case"
 
     CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
 
-    testname="BASIC"
     $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkstat 'files in cache' 1
-    compare_object reference_test1.o test1.o
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
 
-    testname="BASIC2"
     $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkstat 'files in cache' 1
-    compare_object reference_test1.o test1.o
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    # -------------------------------------------------------------------------
+    TEST "Debug option"
 
-    testname="debug"
     $CCACHE_COMPILE -c test1.c -g
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
-    checkstat 'files in cache' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
 
-    testname="debug2"
     $CCACHE_COMPILE -c test1.c -g
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c -g
+    expect_equal_object_files reference_test1.o reference_test1.o
+
+    # -------------------------------------------------------------------------
+    TEST "Output option"
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="output"
     $CCACHE_COMPILE -c test1.c -o foo.o
-    checkstat 'cache hit (preprocessed)' 3
-    checkstat 'cache miss' 2
-    compare_object reference_test1.o foo.o
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+    expect_equal_object_files reference_test1.o foo.o
+
+    # -------------------------------------------------------------------------
+    TEST "Called for link"
+
+    $CCACHE_COMPILE test1.c -o test 2>/dev/null
+    expect_stat 'called for link' 1
+
+    $CCACHE_COMPILE -c test1.c
+    $CCACHE_COMPILE test1.o -o test 2>/dev/null
+    expect_stat 'called for link' 2
 
-    testname="link"
-    $CCACHE_COMPILE test1.c -o test 2> /dev/null
-    checkstat 'called for link' 1
+    # -------------------------------------------------------------------------
+    TEST "No input file"
 
-    testname="linkobj"
-    $CCACHE_COMPILE foo.o -o test 2> /dev/null
-    checkstat 'called for link' 2
+    $CCACHE_COMPILE -c foo.c 2>/dev/null
+    expect_stat 'no input file' 1
 
-    testname="preprocessing"
-    $CCACHE_COMPILE -E -c test1.c > /dev/null 2>&1
-    checkstat 'called for preprocessing' 1
+    # -------------------------------------------------------------------------
+    TEST "Called for preprocessing"
 
-    testname="multiple"
+    $CCACHE_COMPILE -E -c test1.c >/dev/null 2>&1
+    expect_stat 'called for preprocessing' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Multiple source files"
+
+    touch test2.c
     $CCACHE_COMPILE -c test1.c test2.c
-    checkstat 'multiple source files' 1
+    expect_stat 'multiple source files' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Couldn't find the compiler"
+
+    $CCACHE blahblah -c test1.c 2>/dev/null
+    expect_stat "couldn't find the compiler" 1
 
-    testname="find"
-    $CCACHE blahblah -c test1.c 2> /dev/null
-    checkstat "couldn't find the compiler" 1
+    # -------------------------------------------------------------------------
+    TEST "Bad compiler arguments"
 
-    testname="bad"
-    $CCACHE_COMPILE -c test1.c -I 2> /dev/null
-    checkstat 'bad compiler arguments' 1
+    $CCACHE_COMPILE -c test1.c -I 2>/dev/null
+    expect_stat 'bad compiler arguments' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Unsupported source language"
 
-    testname="unsupported source language"
     ln -f test1.c test1.ccc
-    $CCACHE_COMPILE -c test1.ccc 2> /dev/null
-    checkstat 'unsupported source language' 1
+    $CCACHE_COMPILE -c test1.ccc 2>/dev/null
+    expect_stat 'unsupported source language' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Unsupported compiler option"
+
+    $CCACHE_COMPILE -M foo -c test1.c >/dev/null 2>&1
+    expect_stat 'unsupported compiler option' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Compiler produced stdout"
 
-    testname="unsupported"
-    $CCACHE_COMPILE -M foo -c test1.c > /dev/null 2>&1
-    checkstat 'unsupported compiler option' 1
+    $CCACHE echo foo -c test1.c >/dev/null
+    expect_stat 'compiler produced stdout' 1
 
-    testname="stdout"
-    $CCACHE echo foo -c test1.c > /dev/null
-    checkstat 'compiler produced stdout' 1
+    # -------------------------------------------------------------------------
+    TEST "Output to a non-regular file"
 
-    testname="non-regular"
     mkdir testd
-    $CCACHE_COMPILE -o testd -c test1.c > /dev/null 2>&1
-    rmdir testd > /dev/null 2>&1
-    checkstat 'output to a non-regular file' 1
+    $CCACHE_COMPILE -o testd -c test1.c >/dev/null 2>&1
+    rmdir testd >/dev/null 2>&1
+    expect_stat 'output to a non-regular file' 1
 
-    testname="no-input"
-    $CCACHE_COMPILE -c -O2 2> /dev/null
-    checkstat 'no input file' 1
+    # -------------------------------------------------------------------------
+    TEST "No input file"
+
+    $CCACHE_COMPILE -c -O2 2>/dev/null
+    expect_stat 'no input file' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_DISABLE"
 
-    testname="CCACHE_DISABLE"
-    mv $CCACHE_DIR $CCACHE_DIR.saved
     saved_config_path=$CCACHE_CONFIGPATH
     unset CCACHE_CONFIGPATH
-    CCACHE_DISABLE=1 $CCACHE_COMPILE -c test1.c 2> /dev/null
+    CCACHE_DISABLE=1 $CCACHE_COMPILE -c test1.c 2>/dev/null
     if [ -d $CCACHE_DIR ]; then
         test_failed "$CCACHE_DIR created despite CCACHE_DISABLE being set"
     fi
-    CCACHE_CONFIGPATH=$saved_config_path
-    mv $CCACHE_DIR.saved $CCACHE_DIR
-    checkstat 'cache hit (preprocessed)' 3
-    $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 4
+    export CCACHE_CONFIGPATH=$saved_config_path
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMMENTS"
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
 
-    testname="CCACHE_COMMENTS"
     mv test1.c test1-saved.c
-    echo '// initial comment' > test1.c
-    cat test1-saved.c >> test1.c
+    echo '// initial comment' >test1.c
+    cat test1-saved.c >>test1.c
     CCACHE_COMMENTS=1 $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 4
-    checkstat 'cache miss' 3
-    echo '// different comment' > test1.c
-    cat test1-saved.c >> test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    echo '// different comment' >test1.c
+    cat test1-saved.c >>test1.c
     CCACHE_COMMENTS=1 $CCACHE_COMPILE -c test1.c
     mv test1-saved.c test1.c
-    checkstat 'cache hit (preprocessed)' 4
-    checkstat 'cache miss' 4
-    CCACHE_DISABLE=1 $COMPILER -c test1.c -o reference_test1.o
-    compare_object reference_test1.o test1.o
-
-    testname="CCACHE_CPP2"
-    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c -O -O
-    checkstat 'cache hit (preprocessed)' 4
-    checkstat 'cache miss' 5
-    CCACHE_DISABLE=1 $COMPILER -c test1.c -o reference_test1.o -O -O
-    compare_object reference_test1.o test1.o
-
-    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c -O -O
-    checkstat 'cache hit (preprocessed)' 5
-    checkstat 'cache miss' 5
-    compare_object reference_test1.o test1.o
-
-    testname="CCACHE_NOSTATS"
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+    expect_equal_object_files reference_test1.o test1.o
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_CPP2"
+
+    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+    expect_equal_object_files reference_test1.o test1.o
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_NOSTATS"
+
     CCACHE_NOSTATS=1 $CCACHE_COMPILE -c test1.c -O -O
-    checkstat 'cache hit (preprocessed)' 5
-    checkstat 'cache miss' 5
-
-    testname="CCACHE_RECACHE"
-    CCACHE_RECACHE=1 $CCACHE_COMPILE -c test1.c -O -O
-    checkstat 'cache hit (preprocessed)' 5
-    checkstat 'cache miss' 6
-    compare_object reference_test1.o test1.o
-
-    # strictly speaking should be 4 - RECACHE causes a double counting!
-    checkstat 'files in cache' 6
-    $CCACHE -c > /dev/null
-    checkstat 'files in cache' 6
-
-    testname="CCACHE_HASHDIR"
-    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g -O -O
-    checkstat 'cache hit (preprocessed)' 5
-    checkstat 'cache miss' 7
-
-    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g -O -O
-    checkstat 'cache hit (preprocessed)' 6
-    checkstat 'cache miss' 7
-    checkstat 'files in cache' 7
-
-    testname="comments"
-    echo '// a silly comment' > test1-comment.c
-    cat test1.c >> test1-comment.c
-    $CCACHE_COMPILE -c test1-comment.c
-    rm -f test1-comment*
-    checkstat 'cache hit (preprocessed)' 6
-    checkstat 'cache miss' 8
-
-    testname="CCACHE_UNIFY"
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_RECACHE"
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_RECACHE=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+    expect_equal_object_files reference_test1.o test1.o
+
+    # CCACHE_RECACHE replace the object file, so the statistics counter will be
+    # off-by-one until next cleanup.
+    expect_stat 'files in cache' 2
+    $CCACHE -c >/dev/null
+    expect_stat 'files in cache' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_HASHDIR with -g"
+
+    mkdir dir1 dir2
+    cp test1.c dir1
+    cp test1.c dir2
+
+    cd dir1
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    cd ../dir2
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -g
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_HASHDIR without -g"
+
+    mkdir dir1 dir2
+    cp test1.c dir1
+    cp test1.c dir2
+
+    cd dir1
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    cd ../dir2
+    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_UNIFY"
+
+    echo '// a silly comment' >>test1.c
     CCACHE_UNIFY=1 $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 6
-    checkstat 'cache miss' 9
-    mv test1.c test1-saved.c
-    echo '// another comment' > test1.c
-    cat test1-saved.c >> test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    echo '// another silly comment' >>test1.c
     CCACHE_UNIFY=1 $CCACHE_COMPILE -c test1.c
-    mv test1-saved.c test1.c
-    checkstat 'cache hit (preprocessed)' 7
-    checkstat 'cache miss' 9
-    CCACHE_DISABLE=1 $COMPILER -c test1.c -o reference_test1.o
-    compare_object reference_test1.o test1.o
-
-    testname="cache-size"
-    for f in *.c; do
-        $CCACHE_COMPILE -c $f
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+    expect_equal_object_files reference_test1.o test1.o
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_NLEVELS"
+
+    CCACHE_NLEVELS=4 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+
+    CCACHE_NLEVELS=4 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+
+    # Directories in $CCACHE_DIR:
+    # - .
+    # - tmp
+    # - a
+    # - a/b
+    # - a/b/c
+    # - a/b/c/d
+    actual_dirs=$(find $CCACHE_DIR -type d | wc -l)
+    expected_dirs=6
+    if [ $actual_dirs -ne $expected_dirs ]; then
+        test_failed "Expected $expected_dirs directories, found $actual_dirs"
+    fi
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_EXTRAFILES"
+
+    echo a >a
+    echo b >b
+
+    $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
+    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 2
+
+    echo b2 >b
+
+    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 3
+
+    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 3
+    expect_stat 'cache miss' 3
+
+    CCACHE_EXTRAFILES="doesntexist" $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 3
+    expect_stat 'cache miss' 3
+    expect_stat 'error hashing extra file' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_PREFIX"
+
+    cat <<'EOF' >prefix-a
+#!/bin/sh
+echo a >>prefix.result
+exec "$@"
+EOF
+    cat <<'EOF' >prefix-b
+#!/bin/sh
+echo b >>prefix.result
+exec "$@"
+EOF
+    chmod +x prefix-a prefix-b
+    cat <<'EOF' >file.c
+int foo;
+EOF
+    PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_file_content prefix.result "a
+b"
+
+    PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_file_content prefix.result "a
+b"
+
+    rm -f prefix.result
+    PATH=.:$PATH CCACHE_PREFIX_CPP="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+    expect_file_content prefix.result "a
+b"
+
+    # -------------------------------------------------------------------------
+    TEST "Files in cache"
+
+    for i in $(seq 32); do
+        generate_code $i test$i.c
+        $CCACHE_COMPILE -c test$i.c
     done
-    checkstat 'cache hit (preprocessed)' 8
-    checkstat 'cache miss' 39
-    checkstat 'files in cache' 39
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 32
+    expect_stat 'files in cache' 32
 
-    $CCACHE -C >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Called for preprocessing"
+
+    $CCACHE_COMPILE -c test1.c -E >test1.i
+    expect_stat 'called for preprocessing' 1
 
-    testname="cpp call"
-    $CCACHE_COMPILE -c test1.c -E > test1.i
-    checkstat 'cache hit (preprocessed)' 8
-    checkstat 'cache miss' 39
+    # -------------------------------------------------------------------------
+    TEST "Direct .i compile"
 
-    testname="direct .i compile"
     $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 8
-    checkstat 'cache miss' 40
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
+    $COMPILER -c test1.c -E >test1.i
     $CCACHE_COMPILE -c test1.i
-    checkstat 'cache hit (preprocessed)' 9
-    checkstat 'cache miss' 40
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
 
-    $CCACHE_COMPILE -c test1.i
-    checkstat 'cache hit (preprocessed)' 10
-    checkstat 'cache miss' 40
+    # -------------------------------------------------------------------------
+    TEST "-x c"
+
+    ln -f test1.c test1.ccc
 
-    testname="-x c"
     $CCACHE_COMPILE -x c -c test1.ccc
-    checkstat 'cache hit (preprocessed)' 10
-    checkstat 'cache miss' 41
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -x c -c test1.ccc
-    checkstat 'cache hit (preprocessed)' 11
-    checkstat 'cache miss' 41
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "-xc"
+
+    ln -f test1.c test1.ccc
+
+    $CCACHE_COMPILE -xc -c test1.ccc
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="-xc"
     $CCACHE_COMPILE -xc -c test1.ccc
-    checkstat 'cache hit (preprocessed)' 12
-    checkstat 'cache miss' 41
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "-x none"
 
-    testname="-x none"
     $CCACHE_COMPILE -x assembler -x none -c test1.c
-    checkstat 'cache hit (preprocessed)' 13
-    checkstat 'cache miss' 41
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    $CCACHE_COMPILE -x assembler -x none -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "-x unknown"
 
-    testname="-x unknown"
     $CCACHE_COMPILE -x unknown -c test1.c 2>/dev/null
-    checkstat 'cache hit (preprocessed)' 13
-    checkstat 'cache miss' 41
-    checkstat 'unsupported source language' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'unsupported source language' 1
+
+    # -------------------------------------------------------------------------
+    TEST "-D not hashed"
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="-D not hashed"
     $CCACHE_COMPILE -DNOT_AFFECTING=1 -c test1.c
-    checkstat 'cache hit (preprocessed)' 14
-    checkstat 'cache miss' 41
-
-    if [ -x /usr/bin/printf ]; then
-        /usr/bin/printf '#include <wchar.h>\nwchar_t foo[] = L"\xbf";\n' >latin1.c
-        if CCACHE_DISABLE=1 $COMPILER -c -finput-charset=latin1 latin1.c >/dev/null 2>&1; then
-            testname="-finput-charset"
-            CCACHE_CPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
-            checkstat 'cache hit (preprocessed)' 14
-            checkstat 'cache miss' 42
-            CCACHE_CPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
-            checkstat 'cache hit (preprocessed)' 15
-            checkstat 'cache miss' 42
-            $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
-            checkstat 'cache hit (preprocessed)' 15
-            checkstat 'cache miss' 43
-            $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
-            checkstat 'cache hit (preprocessed)' 16
-            checkstat 'cache miss' 43
-        fi
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "-finput-charset"
+
+    unset CCACHE_CPP2
+
+    printf '#include <wchar.h>\nwchar_t foo[] = L"\xbf";\n' >latin1.c
+    if CCACHE_DISABLE=1 $COMPILER -c -finput-charset=latin1 latin1.c >/dev/null 2>&1; then
+        CCACHE_CPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
+
+        CCACHE_CPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
+        expect_stat 'cache hit (preprocessed)' 1
+        expect_stat 'cache miss' 1
+
+        $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
+        expect_stat 'cache hit (preprocessed)' 1
+        expect_stat 'cache miss' 2
+
+        $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
+        expect_stat 'cache hit (preprocessed)' 2
+        expect_stat 'cache miss' 2
     fi
 
-    testname="assembler"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "-S"
+
     $CCACHE_COMPILE -S test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -S test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -c test1.s
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
     $CCACHE_COMPILE -c test1.s
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_PATH"
 
-    testname="override path"
-    $CCACHE -Cz >/dev/null
     override_path=`pwd`/override_path
-    rm -rf $override_path
     mkdir $override_path
     cat >$override_path/cc <<EOF
 #!/bin/sh
@@ -448,8 +704,9 @@ EOF
         test_failed "CCACHE_PATH had no effect"
     fi
 
-    testname="compilercheck=mtime"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=mtime"
+
     cat >compiler.sh <<EOF
 #!/bin/sh
 CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
@@ -460,74 +717,117 @@ EOF
     chmod +x compiler.sh
     backdate compiler.sh
     CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     sed_in_place 's/comment/yoghurt/' compiler.sh # Don't change the size
     chmod +x compiler.sh
-    backdate compiler.sh
+    backdate compiler.sh # Don't change the timestamp
+
     CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     touch compiler.sh
     CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=content"
+
+    cat >compiler.sh <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+exec $COMPILER "\$@"
+EOF
+    chmod +x compiler.sh
 
-    testname="compilercheck=content"
-    $CCACHE -z >/dev/null
     CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    backdate compiler.sh
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
     echo "# Compiler upgrade" >>compiler.sh
-    backdate compiler.sh
+
     CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=none"
+
+    cat >compiler.sh <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+exec $COMPILER "\$@"
+EOF
+    chmod +x compiler.sh
 
-    testname="compilercheck=none"
-    $CCACHE -z >/dev/null
-    backdate compiler.sh
     CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     echo "# Compiler upgrade" >>compiler.sh
     CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=string"
+
+    cat >compiler.sh <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+exec $COMPILER "\$@"
+EOF
+    chmod +x compiler.sh
 
-    testname="compilercheck=string"
-    $CCACHE -z >/dev/null
-    backdate compiler.sh
     CCACHE_COMPILERCHECK=string:foo $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CCACHE_COMPILERCHECK=string:foo $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
     CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=command"
+
+    cat >compiler.sh <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+exec $COMPILER "\$@"
+EOF
+    chmod +x compiler.sh
 
-    testname="compilercheck=command"
-    $CCACHE -z >/dev/null
-    backdate compiler.sh
     CCACHE_COMPILERCHECK='echo %compiler%' $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     echo "# Compiler upgrade" >>compiler.sh
     CCACHE_COMPILERCHECK="echo ./compiler.sh" $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     cat <<EOF >foobar.sh
 #!/bin/sh
 echo foo
@@ -535,30 +835,42 @@ echo bar
 EOF
     chmod +x foobar.sh
     CCACHE_COMPILERCHECK='./foobar.sh' $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+
     CCACHE_COMPILERCHECK='echo foo; echo bar' $CCACHE ./compiler.sh -c test1.c
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERCHECK=unknown_command"
+
+    cat >compiler.sh <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+exec $COMPILER "\$@"
+EOF
+    chmod +x compiler.sh
 
-    testname="compilercheck=unknown_command"
-    $CCACHE -z >/dev/null
-    backdate compiler.sh
     CCACHE_COMPILERCHECK="unknown_command" $CCACHE ./compiler.sh -c test1.c 2>/dev/null
     if [ "$?" -eq 0 ]; then
         test_failed "Expected failure running unknown_command to verify compiler but was success"
     fi
-    checkstat 'compiler check failed' 1
+    expect_stat 'compiler check failed' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_RECACHE should remove previous .stderr"
 
-    testname="recache should remove previous .stderr"
-    $CCACHE -Cz >/dev/null
     $CCACHE_COMPILE -c test1.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     num=`find $CCACHE_DIR -name '*.stderr' | wc -l`
     if [ $num -ne 0 ]; then
         test_failed "$num stderr files found, expected 0 (#1)"
     fi
+
     obj_file=`find $CCACHE_DIR -name '*.o'`
     stderr_file=`echo $obj_file | sed 's/..$/.stderr/'`
     echo "Warning: foo" >$stderr_file
@@ -568,8 +880,9 @@ EOF
         test_failed "$num stderr files found, expected 0 (#2)"
     fi
 
-    testname="no object file"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "No object file"
+
     cat <<'EOF' >test_no_obj.c
 int test_no_obj;
 EOF
@@ -583,9 +896,11 @@ EOF
 EOF
     chmod +x prefix-remove.sh
     CCACHE_PREFIX=`pwd`/prefix-remove.sh $CCACHE_COMPILE -c test_no_obj.c
-    checkstat 'compiler produced no output' 1
+    expect_stat 'compiler produced no output' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Empty object file"
 
-    testname="empty object file"
     cat <<'EOF' >test_empty_obj.c
 int test_empty_obj;
 EOF
@@ -599,241 +914,431 @@ EOF
 EOF
     chmod +x prefix-empty.sh
     CCACHE_PREFIX=`pwd`/prefix-empty.sh $CCACHE_COMPILE -c test_empty_obj.c
-    checkstat 'compiler produced empty output' 1
+    expect_stat 'compiler produced empty output' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Caching stderr"
 
-    testname="stderr-files"
-    $CCACHE -Cz >/dev/null
-    num=`find $CCACHE_DIR -name '*.stderr' | wc -l`
-    if [ $num -ne 0 ]; then
-        test_failed "$num stderr files found, expected 0"
-    fi
     cat <<EOF >stderr.c
 int stderr(void)
 {
-       // Trigger warning by having no return statement.
+  // Trigger warning by having no return statement.
 }
 EOF
-    checkstat 'files in cache' 0
     $CCACHE_COMPILE -Wall -W -c stderr.c 2>/dev/null
     num=`find $CCACHE_DIR -name '*.stderr' | wc -l`
     if [ $num -ne 1 ]; then
         test_failed "$num stderr files found, expected 1"
     fi
-    checkstat 'files in cache' 2
-
-    testname="zero-stats"
-    $CCACHE -z > /dev/null
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'files in cache' 2
-
-    testname="clear"
-    $CCACHE -C > /dev/null
-    checkstat 'files in cache' 0
-
-    # the profile options do not seem to work correctly with clang or gcc-llvm
-    # on darwin machines
-    darwin_llvm=0
-    if [ $HOST_OS_APPLE -eq 1 ] && [ $COMPILER_USES_LLVM -eq 1 ]; then
-        darwin_llvm=1
-    fi
-    # ditto clang on linux
-    linux_clang=0
-    if [ $HOST_OS_LINUX -eq 1 ] && [ $COMPILER_TYPE_CLANG -eq 1 ]; then
-        linux_clang=1
-    fi
+    expect_stat 'files in cache' 2
 
-    $COMPILER -c -fprofile-generate test1.c 2>/dev/null
-    if [ $? -eq 0 ] && [ $darwin_llvm -eq 0 ] && [ $linux_clang -eq 0 ]; then
-        testname="profile-generate"
-        $CCACHE -Cz > /dev/null
-        $CCACHE_COMPILE -c -fprofile-generate test1.c
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 1
-        $CCACHE_COMPILE -c -fprofile-generate test1.c
-        checkstat 'cache hit (preprocessed)' 1
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 1
-
-        testname="profile-arcs"
-        $CCACHE_COMPILE -c -fprofile-arcs test1.c
-        checkstat 'cache hit (preprocessed)' 1
-        checkstat 'cache miss' 2
-        checkstat 'files in cache' 2
-        $CCACHE_COMPILE -c -fprofile-arcs test1.c
-        checkstat 'cache hit (preprocessed)' 2
-        checkstat 'cache miss' 2
-        checkstat 'files in cache' 2
-
-        testname="profile-use"
-        $CCACHE_COMPILE -c -fprofile-use test1.c 2>/dev/null
-        checkstat 'cache hit (preprocessed)' 2
-        checkstat 'cache miss' 3
-        $CCACHE_COMPILE -c -fprofile-use test1.c 2>/dev/null
-        checkstat 'cache hit (preprocessed)' 3
-        checkstat 'cache miss' 3
-    fi
+    # -------------------------------------------------------------------------
+    TEST "--zero-stats"
 
-    if [ $HOST_OS_APPLE -eq 1 ]; then
-        $CCACHE -Cz > /dev/null
-        testname="multiple-arch-options"
-        $CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c 2>/dev/null
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        $CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c 2>/dev/null
-        checkstat 'cache hit (preprocessed)' 1
-        checkstat 'cache miss' 1
-    fi
+    $CCACHE_COMPILE -c test1.c
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+
+    $CCACHE -z >/dev/null
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'files in cache' 1
+
+    # -------------------------------------------------------------------------
+    TEST "--clear"
+
+    $CCACHE_COMPILE -c test1.c
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+
+    $CCACHE -C >/dev/null
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 0
+
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-P"
 
-    ##################################################################
     # Check that -Wp,-P disables ccache. (-P removes preprocessor information
     # in such a way that the object file from compiling the preprocessed file
     # will not be equal to the object file produced when compiling without
     # ccache.)
-    testname="-Wp,-P"
-    $CCACHE -Cz >/dev/null
+
     $CCACHE_COMPILE -c -Wp,-P test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'unsupported compiler option' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'unsupported compiler option' 1
+
     $CCACHE_COMPILE -c -Wp,-P test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'unsupported compiler option' 2
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'unsupported compiler option' 2
+
     $CCACHE_COMPILE -c -Wp,-DFOO,-P,-DGOO test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'unsupported compiler option' 3
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'unsupported compiler option' 3
+
     $CCACHE_COMPILE -c -Wp,-DFOO,-P,-DGOO test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat 'unsupported compiler option' 4
-
-    ##################################################################
-    # Check that -Wp,-D works
-    testname="-Wp,-D"
-    $CCACHE -Cz >/dev/null
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'unsupported compiler option' 4
+
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-D"
+
     $CCACHE_COMPILE -c -Wp,-DFOO test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -c -DFOO test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -c -Wp,-DFOO,-P test1.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
 
-    ##################################################################
+    # -------------------------------------------------------------------------
+    TEST "Buggy GCC 6 cpp"
 
-    if [ $COMPILER_TYPE_CLANG -eq 1 ]; then
-        testname="serialize-diagnostics"
-        $CCACHE -Cz > /dev/null
-        $COMPILER -c --serialize-diagnostics expected.dia test1.c 2> /dev/null
-        # Run with CCACHE_CPP2 to ensure the same diagnostics output as above
-        CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c 2> /dev/null
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 2
-        compare_file expected.dia test.dia
-        rm -f test.dia
-        CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c 2> /dev/null
-        checkstat 'cache hit (preprocessed)' 1
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 2
-        compare_file expected.dia test.dia
-
-        rm -f test.dia
-        rm -f expected.dia
-
-        testname="serialize-diagnostics-compile-failed"
-        $CCACHE -Cz > /dev/null
-        echo "bad source" >error.c
-        $COMPILER -c --serialize-diagnostics expected.dia error.c 2> /dev/null
-        if [ $? -eq 0 ]; then
-            test_failed "expected an error compiling error.c"
-        fi
-        CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia error.c 2> /dev/null
-        checkstat 'compile failed' 1
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 0
-        checkstat 'files in cache' 0
-        compare_file expected.dia test.dia
-        rm -f test.dia
-        CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia error.c 2> /dev/null
-        checkstat 'compile failed' 2
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 0
-        checkstat 'files in cache' 0
-        compare_file expected.dia test.dia
+    cat >buggy-cpp <<EOF
+#!/bin/sh
+CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
+export CCACHE_DISABLE
+if echo "\$*" | grep -- -D >/dev/null; then
+  $COMPILER "\$@"
+else
+  # Mistreat the preprocessor output in the same way as GCC 6 does.
+  $COMPILER "\$@" |
+    sed -e '/^# 1 "<command-line>"\$/ a\\
+# 31 "<command-line>"' \\
+        -e 's/^# 1 "<command-line>" 2\$/# 32 "<command-line>" 2/'
+fi
+exit 0
+EOF
+    cat <<'EOF' >file.c
+int foo;
+EOF
+    chmod +x buggy-cpp
+
+    $CCACHE ./buggy-cpp -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    $CCACHE ./buggy-cpp -DNOT_AFFECTING=1 -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Symlink to source directory"
+
+    mkdir dir
+    cd dir
+    mkdir -p d1/d2
+    echo '#define A "OK"' >d1/h.h
+    cat <<EOF >d1/d2/c.c
+#include <stdio.h>
+#include "../h.h"
+int main() { printf("%s\n", A); }
+EOF
+    echo '#define A "BUG"' >h.h
+    ln -s d1/d2 d3
+
+    CCACHE_BASEDIR=/ $CCACHE $COMPILER -c $PWD/d3/c.c
+    $COMPILER c.o -o c
+    if [ "$(./c)" != OK ]; then
+        test_failed "Incorrect header file used"
     fi
 
-    ##################################################################
+    # -------------------------------------------------------------------------
+    TEST "Symlink to source file"
 
-    rm -f test1.c
-}
+    mkdir dir
+    cd dir
+    mkdir d
+    echo '#define A "BUG"' >d/h.h
+    cat <<EOF >d/c.c
+#include <stdio.h>
+#include "h.h"
+int main() { printf("%s\n", A); }
+EOF
+    echo '#define A "OK"' >h.h
+    ln -s d/c.c c.c
 
-base_suite() {
-    base_tests
+    CCACHE_BASEDIR=/ $CCACHE $COMPILER -c $PWD/c.c
+    $COMPILER c.o -o c
+    if [ "$(./c)" != OK ]; then
+        test_failed "Incorrect header file used"
+    fi
 }
 
-link_suite() {
-    compiler_binary=`echo $COMPILER | cut -d' ' -f1`
-    compiler_args=`echo $COMPILER | cut -s -d' ' -f2-`
-    if [ `dirname $compiler_binary` = . ]; then
-        ln -s "$CCACHE" $compiler_binary
-        CCACHE_COMPILE="./$compiler_binary $compiler_args"
-        base_tests
-        rm -f $compiler_binary
-    else
-        echo "Compiler ($compiler_binary) not taken from PATH -- not running link test"
-    fi
+# =============================================================================
+
+SUITE_base_PREPARE() {
+    generate_code 1 test1.c
 }
 
-hardlink_suite() {
-    CCACHE_COMPILE="$CCACHE $COMPILER"
-    CCACHE_HARDLINK=1
-    export CCACHE_HARDLINK
+SUITE_base() {
     base_tests
-    unset CCACHE_HARDLINK
 }
 
-cpp2_suite() {
+# =============================================================================
+
+SUITE_cpp2_PREPARE() {
+    generate_code 1 test1.c
+}
+
+SUITE_cpp2() {
     CCACHE_COMPILE="$CCACHE $COMPILER"
     CCACHE_CPP2=1
     export CCACHE_CPP2
+
     base_tests
+
     unset CCACHE_CPP2
 }
 
-nlevels4_suite() {
-    CCACHE_COMPILE="$CCACHE $COMPILER"
-    CCACHE_NLEVELS=4
-    export CCACHE_NLEVELS
-    base_tests
-    unset CCACHE_NLEVELS
+# =============================================================================
+
+SUITE_multi_arch_PREPARE() {
+    if [ $HOST_OS_APPLE -eq 0 ]; then
+        echo "multiple -arch options not supported on $(uname -s)"
+        return
+    fi
+
+    generate_code 1 test1.c
 }
 
-nlevels1_suite() {
-    CCACHE_COMPILE="$CCACHE $COMPILER"
-    CCACHE_NLEVELS=1
-    export CCACHE_NLEVELS
-    base_tests
-    unset CCACHE_NLEVELS
+SUITE_multi_arch() {
+    # -------------------------------------------------------------------------
+    TEST "cache hit"
+
+    $CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    $CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+}
+
+# =============================================================================
+
+SUITE_serialize_diagnostics_PREPARE() {
+    generate_code 1 test1.c
+    if CCACHE_DISABLE=1 $COMPILER -c --serialize-diagnostics \
+         test1.dia test1.c 2>/dev/null; then
+        rm test1.dia
+    else
+        echo "--serialize-diagnostics not supported by compiler"
+    fi
+}
+
+SUITE_serialize_diagnostics() {
+    # -------------------------------------------------------------------------
+    TEST "Compile OK"
+
+    CCACHE_DISABLE=1 $COMPILER -c --serialize-diagnostics expected.dia test1.c
+
+    # Run with CCACHE_CPP2 to ensure the same diagnostics output as above
+    CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2
+    expect_equal_files expected.dia test.dia
+
+    rm test.dia
+
+    CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2
+    expect_equal_files expected.dia test.dia
+
+    # -------------------------------------------------------------------------
+    TEST "Compile failed"
+
+    echo "bad source" >error.c
+    if CCACHE_DISABLE=1 $COMPILER -c --serialize-diagnostics expected.dia error.c 2>expected.stderr; then
+        test_failed "Expected an error compiling error.c"
+    fi
+
+    CCACHE_CPP2=1 $CCACHE_COMPILE -c --serialize-diagnostics test.dia error.c 2>test.stderr
+    expect_stat 'compile failed' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'files in cache' 0
+    expect_equal_files expected.dia test.dia
+    expect_equal_files expected.stderr test.stderr
+
+    # -------------------------------------------------------------------------
+    TEST "--serialize-diagnostics + CCACHE_BASEDIR"
+
+    mkdir -p dir1/src dir1/include
+    cat <<EOF >dir1/src/test.c
+#include <stdarg.h>
+#include <test.h>
+EOF
+    cat <<EOF >dir1/include/test.h
+int test;
+EOF
+    cp -r dir1 dir2
+    backdate dir1/include/test.h dir2/include/test.h
+
+    cat <<EOF >stderr.h
+int stderr(void)
+{
+  // Trigger warning by having no return statement.
+}
+EOF
+
+    unset CCACHE_NODIRECT
+
+    cd dir1
+    CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 4
+
+    cd ../dir2
+    CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 4
+}
+
+# =============================================================================
+
+SUITE_debug_prefix_map_PREPARE() {
+    if [ $COMPILER_TYPE_GCC -eq 1 -a $COMPILER_USES_MINGW -eq 0 ]; then
+        mkdir -p dir1/src dir1/include
+        cat <<EOF >dir1/src/test.c
+#include <stdarg.h>
+#include <test.h>
+EOF
+        cat <<EOF >dir1/include/test.h
+int test;
+EOF
+        cp -r dir1 dir2
+        backdate dir1/include/test.h dir2/include/test.h
+    else
+        echo "-fdebug-prefix-map not supported by compiler"
+    fi
 }
 
-direct_suite() {
+SUITE_debug_prefix_map() {
     unset CCACHE_NODIRECT
 
-    ##################################################################
-    # Create some code to compile.
+    # -------------------------------------------------------------------------
+    TEST "Mapping of debug info CWD"
+
+    cd dir1
+    CCACHE_HASHDIR=1 CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -I`pwd`/include -g -fdebug-prefix-map=`pwd`=dir -c `pwd`/src/test.c -o `pwd`/test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2
+    if grep -E "[^=]`pwd`[^=]" test.o >/dev/null 2>&1; then
+        test_failed "Source dir (`pwd`) found in test.o"
+    fi
+
+    cd ../dir2
+    CCACHE_HASHDIR=1 CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -I`pwd`/include -g -fdebug-prefix-map=`pwd`=dir -c `pwd`/src/test.c -o `pwd`/test.o
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2
+    if grep -E "[^=]`pwd`[^=]" test.o >/dev/null 2>&1; then
+        test_failed "Source dir (`pwd`) found in test.o"
+    fi
+}
+
+# =============================================================================
+
+SUITE_masquerading_PREPARE() {
+    local compiler_binary=$(echo $COMPILER | cut -d' ' -f1)
+    if [ "$(dirname $compiler_binary)" = . ]; then
+        ln -s "$CCACHE" $compiler_binary
+        local compiler_args=$(echo $COMPILER | cut -s -d' ' -f2-)
+        CCACHE_COMPILE="./$compiler_binary $compiler_args"
+        generate_code 1 test1.c
+    else
+        echo "compiler ($compiler_binary) not taken from PATH"
+    fi
+}
+
+SUITE_masquerading() {
+    # -------------------------------------------------------------------------
+    TEST "Masquerading via symlink"
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+}
+
+# =============================================================================
+
+SUITE_hardlink_PREPARE() {
+    generate_code 1 test1.c
+    if ! ln test1.c foo.c >/dev/null 2>&1; then
+        echo "file system doesn't support hardlinks"
+    fi
+    rm foo.c
+}
+
+SUITE_hardlink() {
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_HARDLINK"
+
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test1.o test1.c
+
+    $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    local obj_in_cache=$(find $CCACHE_DIR -name '*.o')
+    if [ ! $obj_in_cache -ef test1.o ]; then
+        test_failed "Object file not hard-linked to cached object file"
+    fi
+}
+
+# =============================================================================
+
+SUITE_direct_PREPARE() {
     cat <<EOF >test.c
 // test.c
 #include "test1.h"
@@ -848,86 +1353,100 @@ int test2;
 EOF
     cat <<EOF >test3.h
 int test3;
-EOF
-    cat <<EOF >code.c
-// code.c
-int test() { return 0; }
 EOF
     backdate test1.h test2.h test3.h
 
-    $COMPILER -c -Wp,-MD,expected.d test.c
-    expected_d_content=`cat expected.d`
+    CCACHE_DISABLE=1 $COMPILER -c -Wp,-MD,expected.d test.c
+    CCACHE_DISABLE=1 $COMPILER -c -Wp,-MMD,expected_mmd.d test.c
+    rm test.o
+}
+
+SUITE_direct() {
+    unset CCACHE_NODIRECT
+
+    # -------------------------------------------------------------------------
+    TEST "Base case"
 
-    $COMPILER -c -Wp,-MMD,expected_mmd.d test.c
-    expected_mmd_d_content=`cat expected_mmd.d`
+    CCACHE_DISABLE=1 $COMPILER -c -o reference_test.o test.c
 
-    ##################################################################
-    # First compilation is a miss.
-    testname="first compilation"
-    $CCACHE -z >/dev/null
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2 # .o + .manifest
+    expect_equal_object_files reference_test.o test.o
 
-    ##################################################################
-    # Another compilation should now generate a direct hit.
-    testname="direct hit"
-    $CCACHE -z >/dev/null
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2
+    expect_equal_object_files reference_test.o test.o
+
+    # -------------------------------------------------------------------------
+    TEST "Corrupt manifest file"
+
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    ##################################################################
-    # Check that corrupt manifest files are handled and rewritten.
-    testname="corrupt manifest file"
-    $CCACHE -z >/dev/null
     manifest_file=`find $CCACHE_DIR -name '*.manifest'`
     rm $manifest_file
     touch $manifest_file
+
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_NODIRECT"
+
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    ##################################################################
-    # Compiling with CCACHE_NODIRECT set should generate a preprocessed hit.
-    testname="preprocessed hit"
-    $CCACHE -z >/dev/null
     CCACHE_NODIRECT=1 $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Modified include file"
+
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    ##################################################################
-    # Test compilation of a modified include file.
-    testname="modified include file"
-    $CCACHE -z >/dev/null
     echo "int test3_2;" >>test3.h
     backdate test3.h
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    ##################################################################
-    # A removed but previously compiled header file should be handled
-    # gracefully.
-    testname="missing header file"
-    $CCACHE -z >/dev/null
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-    mv test1.h test1.h.saved
-    mv test3.h test3.h.saved
+    # -------------------------------------------------------------------------
+    TEST "Removed but previously compiled header file"
+
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    rm test3.h
     cat <<EOF >test1.h
 // No more include of test3.h
 int test1;
@@ -935,28 +1454,20 @@ EOF
     backdate test1.h
 
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    # Restore
-    mv test1.h.saved test1.h
-    mv test3.h.saved test3.h
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-    rm -f other.d
+    # -------------------------------------------------------------------------
+    TEST "Calculation of dependency file names"
 
-    ##################################################################
-    # Check calculation of dependency file names.
-    $CCACHE -Cz >/dev/null
-    checkstat 'files in cache' 0
     mkdir test.dir
     for ext in .obj "" . .foo.bar; do
-        testname="dependency file calculation from object file 'test$ext'"
         dep_file=test.dir/`echo test$ext | sed 's/\.[^.]*\$//'`.d
         $CCACHE $COMPILER -MD -c test.c -o test.dir/test$ext
         rm -f $dep_file
@@ -965,356 +1476,319 @@ EOF
             test_failed "$dep_file missing"
         fi
     done
-    rm -rf test.dir
-    checkstat 'files in cache' 12
+    expect_stat 'files in cache' 12
+
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-MD"
 
-    ##################################################################
-    # Check that -Wp,-MD,file.d works.
-    testname="-Wp,-MD"
-    $CCACHE -z >/dev/null
     $CCACHE $COMPILER -c -Wp,-MD,other.d test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected.d
+
     CCACHE_DISABLE=1 $COMPILER -c -Wp,-MD,other.d test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_equal_object_files reference_test.o test.o
 
     rm -f other.d
-
     $CCACHE $COMPILER -c -Wp,-MD,other.d test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
-    compare_object reference_test.o test.o
-
-    rm -f other.d
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     $CCACHE $COMPILER -c -Wp,-MD,different_name.d test.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile different_name.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files different_name.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
-    rm -f different_name.d
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-MMD"
 
-    ##################################################################
-    # Check that -Wp,-MMD,file.d works.
-    testname="-Wp,-MMD"
-    $CCACHE -C >/dev/null
-    $CCACHE -z >/dev/null
     $CCACHE $COMPILER -c -Wp,-MMD,other.d test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_mmd_d_content"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected_mmd.d
+
     CCACHE_DISABLE=1 $COMPILER -c -Wp,-MMD,other.d test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_equal_object_files reference_test.o test.o
 
     rm -f other.d
-
     $CCACHE $COMPILER -c -Wp,-MMD,other.d test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_mmd_d_content"
-    compare_object reference_test.o test.o
-
-    rm -f other.d
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected_mmd.d
+    expect_equal_object_files reference_test.o test.o
 
     $CCACHE $COMPILER -c -Wp,-MMD,different_name.d test.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile different_name.d "$expected_mmd_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files different_name.d expected_mmd.d
+    expect_equal_object_files reference_test.o test.o
 
-    rm -f different_name.d
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-D"
 
-    ##################################################################
-    # Check that -Wp,-D works
-    testname="-Wp,-D"
-    $CCACHE -Cz >/dev/null
     $CCACHE_COMPILE -c -Wp,-DFOO test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -c -DFOO test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE_COMPILE -c -Wp,-DFOO,-P test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "Multiple object entries in manifest"
 
-    ##################################################################
-    # Test some header modifications to get multiple objects in the manifest.
-    testname="several objects"
-    $CCACHE -z >/dev/null
     for i in 0 1 2 3 4; do
         echo "int test1_$i;" >>test1.h
         backdate test1.h
         $CCACHE $COMPILER -c test.c
         $CCACHE $COMPILER -c test.c
     done
-    checkstat 'cache hit (direct)' 5
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 5
+    expect_stat 'cache hit (direct)' 5
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 5
+
+    # -------------------------------------------------------------------------
+    TEST "-MD"
 
-    ##################################################################
-    # Check that -MD works.
-    testname="-MD"
-    $CCACHE -z >/dev/null
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+
     CCACHE_DISABLE=1 $COMPILER -c -MD test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_equal_object_files reference_test.o test.o
 
     rm -f test.d
-
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
-    compare_object reference_test.o test.o
-
-    ##################################################################
-    # Check that coverage works.
-    testname="coverage (empty)"
-    $CCACHE -z >/dev/null
-    $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="coverage (code)"
-    $CCACHE -z >/dev/null
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+    expect_equal_object_files reference_test.o test.o
+
+    # -------------------------------------------------------------------------
+    TEST "-ftest-coverage"
+
+    cat <<EOF >code.c
+int test() { return 0; }
+EOF
+
     $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage code.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    test -r code.gcno || test_failed "gcov"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    test -r code.gcno || test_failed "code.gcno missing"
 
-    rm -f code.gcno
+    rm code.gcno
 
     $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage code.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    test -r code.gcno || test_failed "gcov"
-
-    ##################################################################
-    # Check the scenario of running a ccache with direct mode on a cache
-    # built up by a ccache without direct mode support.
-    testname="direct mode on old cache"
-    $CCACHE -z >/dev/null
-    $CCACHE -C >/dev/null
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    test -r code.gcno || test_failed "code.gcno missing"
+
+    # -------------------------------------------------------------------------
+    TEST "Direct mode on cache created by ccache without direct mode support"
+
     CCACHE_NODIRECT=1 $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
     CCACHE_DISABLE=1 $COMPILER -c -MD test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_equal_object_files reference_test.o test.o
 
     rm -f test.d
 
     CCACHE_NODIRECT=1 $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     rm -f test.d
 
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     rm -f test.d
 
-    $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 1
-    checkfile test.d "$expected_d_content"
-    compare_object reference_test.o test.o
-
-    ##################################################################
-    # Check that -MF works.
-    testname="-MF"
-    $CCACHE -C >/dev/null
-    $CCACHE -z >/dev/null
+    $CCACHE $COMPILER -c -MD test.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+    expect_equal_object_files reference_test.o test.o
+
+    # -------------------------------------------------------------------------
+    TEST "-MF"
+
     $CCACHE $COMPILER -c -MD -MF other.d test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected.d
     CCACHE_DISABLE=1 $COMPILER -c -MD -MF other.d test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_equal_object_files reference_test.o test.o
 
     rm -f other.d
 
     $CCACHE $COMPILER -c -MD -MF other.d test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files other.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     $CCACHE $COMPILER -c -MD -MF different_name.d test.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile different_name.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files different_name.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     rm -f different_name.d
 
     $CCACHE $COMPILER -c -MD -MFthird_name.d test.c
-    checkstat 'cache hit (direct)' 3
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile third_name.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 3
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files third_name.d expected.d
+    expect_equal_object_files reference_test.o test.o
 
     rm -f third_name.d
 
-    ##################################################################
-    # Check that a missing .d file in the cache is handled correctly.
-    testname="missing dependency file"
-    $CCACHE -z >/dev/null
-    $CCACHE -C >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Missing .d file"
 
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
-    CCACHE_DISABLE=1 $COMPILER -c -MD test.c -o reference_test.o
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
-    compare_object reference_test.o test.o
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
 
-    find $CCACHE_DIR -name '*.d' -exec rm -f '{}' \;
+    find $CCACHE_DIR -name '*.d' -delete
 
     $CCACHE $COMPILER -c -MD test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkfile other.d "$expected_d_content"
-    compare_object reference_test.o test.o
-
-    ##################################################################
-    # Check that stderr from both the preprocessor and the compiler is emitted
-    # in direct mode too.
-    testname="cpp stderr"
-    $CCACHE -z >/dev/null
-    $CCACHE -C >/dev/null
-cat <<EOF >cpp-warning.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_equal_files test.d expected.d
+
+    # -------------------------------------------------------------------------
+    TEST "stderr from both preprocessor and compiler"
+
+    cat <<EOF >cpp-warning.c
 #if FOO
 // Trigger preprocessor warning about extra token after #endif.
 #endif FOO
 int stderr(void)
 {
-       // Trigger compiler warning by having no return statement.
+  // Trigger compiler warning by having no return statement.
 }
 EOF
     $CCACHE $COMPILER -Wall -W -c cpp-warning.c 2>stderr-orig.txt
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    CCACHE_NODIRECT=1
-    export CCACHE_NODIRECT
-    $CCACHE $COMPILER -Wall -W -c cpp-warning.c 2>stderr-cpp.txt
-    unset CCACHE_NODIRECT
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkfile stderr-cpp.txt "`cat stderr-orig.txt`"
+    CCACHE_NODIRECT=1 $CCACHE $COMPILER -Wall -W -c cpp-warning.c 2>stderr-cpp.txt
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_file_content stderr-cpp.txt "`cat stderr-orig.txt`"
 
     $CCACHE $COMPILER -Wall -W -c cpp-warning.c 2>stderr-mf.txt
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkfile stderr-mf.txt "`cat stderr-orig.txt`"
-
-    ##################################################################
-    # Check that it is possible to compile and cache an empty source code file.
-    testname="empty source file"
-    $CCACHE -Cz >/dev/null
-    cp /dev/null empty.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_file_content stderr-mf.txt "`cat stderr-orig.txt`"
+
+    # -------------------------------------------------------------------------
+    TEST "Empty source file"
+
+    touch empty.c
+
     $CCACHE $COMPILER -c empty.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c empty.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    ##################################################################
-    # Check that empty include files are handled as well.
-    testname="empty include file"
-    $CCACHE -Cz >/dev/null
-    cp /dev/null empty.h
+    # -------------------------------------------------------------------------
+    TEST "Empty include file"
+
+    touch empty.h
     cat <<EOF >include_empty.c
 #include "empty.h"
 EOF
     backdate empty.h
     $CCACHE $COMPILER -c include_empty.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     $CCACHE $COMPILER -c include_empty.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__FILE__ in source file disables direct mode"
 
-    ##################################################################
-    # Check that direct mode correctly detects file name/path changes.
-    testname="__FILE__ in source file"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >file.c
 #define file __FILE__
 int test;
 EOF
+
     $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c `pwd`/file.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__FILE__ in include file disables direct mode"
 
-    testname="__FILE__ in include file"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >file.h
 #define file __FILE__
 int test;
@@ -1323,43 +1797,50 @@ EOF
     cat <<EOF >file_h.c
 #include "file.h"
 EOF
+
     $CCACHE $COMPILER -c file_h.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c file_h.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     mv file_h.c file2_h.c
+
     $CCACHE $COMPILER -c `pwd`/file2_h.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__FILE__ in source file ignored if sloppy"
 
-    ##################################################################
-    # Check that direct mode ignores __FILE__ if sloppiness is specified.
-    testname="__FILE__ in source file, sloppy"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >file.c
 #define file __FILE__
 int test;
 EOF
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c `pwd`/file.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="__FILE__ in include file, sloppy"
-    $CCACHE -Cz >/dev/null
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c file.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c file.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c `pwd`/file.c
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__FILE__ in include file ignored if sloppy"
+
     cat <<EOF >file.h
 #define file __FILE__
 int test;
@@ -1368,75 +1849,86 @@ EOF
     cat <<EOF >file_h.c
 #include "file.h"
 EOF
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c file_h.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c file_h.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c file_h.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c file_h.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     mv file_h.c file2_h.c
-    CCACHE_SLOPPINESS="$default_sloppiness file_macro" $CCACHE $COMPILER -c `pwd`/file2_h.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    ##################################################################
-    # Check that we never get direct hits when __TIME__ is used.
-    testname="__TIME__ in source file"
-    $CCACHE -Cz >/dev/null
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE $COMPILER -c `pwd`/file2_h.c
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__TIME__ in source file disables direct mode"
+
     cat <<EOF >time.c
 #define time __TIME__
 int test;
 EOF
+
     $CCACHE $COMPILER -c time.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c time.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__TIME__ in include file disables direct mode"
 
-    testname="__TIME__ in include time"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >time.h
 #define time __TIME__
 int test;
 EOF
     backdate time.h
+
     cat <<EOF >time_h.c
 #include "time.h"
 EOF
+
     $CCACHE $COMPILER -c time_h.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c time_h.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__TIME__ in source file ignored if sloppy"
 
-    ##################################################################
-    # Check that direct mode ignores __TIME__ when sloppiness is specified.
-    testname="__TIME__ in source file, sloppy"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >time.c
 #define time __TIME__
 int test;
 EOF
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
 
-    testname="__TIME__ in include time, sloppy"
-    $CCACHE -Cz >/dev/null
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER -c time.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER -c time.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "__TIME__ in include file ignored if sloppy"
+
     cat <<EOF >time.h
 #define time __TIME__
 int test;
@@ -1445,19 +1937,19 @@ EOF
     cat <<EOF >time_h.c
 #include "time.h"
 EOF
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    ##################################################################
-    # Check that a too new include file turns off direct mode.
-    testname="too new include file"
-    $CCACHE -Cz >/dev/null
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER -c time_h.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER -c time_h.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Too new include file disables direct mode"
+
     cat <<EOF >new.c
 #include "new.h"
 EOF
@@ -1465,19 +1957,20 @@ EOF
 int test;
 EOF
     touch -t 203801010000 new.h
+
     $CCACHE $COMPILER -c new.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c new.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "New include file ignored if sloppy"
 
-    ##################################################################
-    # Check that include file mtime is ignored when sloppiness is specified.
-    testname="too new include file, sloppy"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >new.c
 #include "new.h"
 EOF
@@ -1485,20 +1978,22 @@ EOF
 int test;
 EOF
     touch -t 203801010000 new.h
-    CCACHE_SLOPPINESS="$default_sloppiness include_file_mtime" $CCACHE $COMPILER -c new.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness include_file_mtime" $CCACHE $COMPILER -c new.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    ##################################################################
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime" $CCACHE $COMPILER -c new.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime" $CCACHE $COMPILER -c new.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
     # Check that environment variables that affect the preprocessor are taken
     # into account.
-    testname="environment variables"
-    $CCACHE -Cz >/dev/null
+    TEST "CPATH included in hash"
+
     rm -rf subdir1 subdir2
     mkdir subdir1 subdir2
     cat <<EOF >subdir1/foo.h
@@ -1511,53 +2006,63 @@ EOF
 #include <foo.h>
 EOF
     backdate subdir1/foo.h subdir2/foo.h
+
     CPATH=subdir1 $CCACHE $COMPILER -c foo.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CPATH=subdir1 $CCACHE $COMPILER -c foo.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CPATH=subdir2 $CCACHE $COMPILER -c foo.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
     CPATH=subdir2 $CCACHE $COMPILER -c foo.c
-    checkstat 'cache hit (direct)' 2
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Comment in strings"
 
-    testname="comment in strings"
-    $CCACHE -Cz >/dev/null
     echo 'char *comment = " /* \\\\u" "foo" " */";' >comment.c
+
     $CCACHE $COMPILER -c comment.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     $CCACHE $COMPILER -c comment.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     echo 'char *comment = " /* \\\\u" "goo" " */";' >comment.c
+
     $CCACHE $COMPILER -c comment.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 2
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "#line directives with troublesome files"
 
-    #################################################################
-    # Check that strange "#line" directives are handled.
-    testname="#line directives with troublesome files"
-    $CCACHE -Cz >/dev/null
     cat <<EOF >strange.c
 int foo;
 EOF
     for x in stdout tty sda hda; do
         if [ -b /dev/$x ] || [ -c /dev/$x ]; then
-            echo "#line 1 \"/dev/$x\"" >> strange.c
+            echo "#line 1 \"/dev/$x\"" >>strange.c
         fi
     done
-    CCACHE_SLOPPINESS="$default_sloppiness include_file_mtime" $CCACHE $COMPILER -c strange.c
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime" $CCACHE $COMPILER -c strange.c
+
     manifest=`find $CCACHE_DIR -name '*.manifest'`
     if [ -n "$manifest" ]; then
         data="`$CCACHE --dump-manifest $manifest | egrep '/dev/(stdout|tty|sda|hda'`"
@@ -1566,25 +2071,25 @@ EOF
         fi
     fi
 
-    ##################################################################
-    # Test --dump-manifest output.
-    testname="--dump-manifest"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "--dump-manifest"
+
     $CCACHE $COMPILER test.c -c -o test.o
+
     manifest=`find $CCACHE_DIR -name '*.manifest'`
     $CCACHE --dump-manifest $manifest >manifest.dump
 
-    if grep 'Hash: e6b009695d072974f2c4d1dd7e7ed4fc' manifest.dump >/dev/null 2>&1 && \
+    if grep 'Hash: d4de2f956b4a386c6660990a7a1ab13f' manifest.dump >/dev/null 2>&1 && \
        grep 'Hash: e94ceb9f1b196c387d098a5f1f4fe862' manifest.dump >/dev/null 2>&1 && \
-       grep 'Hash: c2f5392dbc7e8ff6138d01608445240a' manifest.dump >/dev/null 2>&1; then
+       grep 'Hash: ba753bebf9b5eb99524bb7447095e2e6' manifest.dump >/dev/null 2>&1; then
         : OK
     else
-        test_failed "unexpected output of --dump-manifest"
+        test_failed "Unexpected output of --dump-manifest"
     fi
 
-    ##################################################################
-    testname="argument-less -B and -L"
-    $CCACHE -Cz > /dev/null
+    # -------------------------------------------------------------------------
+    TEST "Argument-less -B and -L"
+
     cat <<EOF >test.c
 #include <stdio.h>
 int main(void)
@@ -1596,17 +2101,37 @@ int main(void)
 }
 EOF
 
-    $CCACHE $COMPILER -A -L -DFOO -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache miss' 1
-    $CCACHE $COMPILER -A -L -DBAR -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache miss' 2
+    $CCACHE $COMPILER -B -L -DFOO -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache miss' 1
+
+    $CCACHE $COMPILER -B -L -DBAR -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_IGNOREHEADERS"
+
+    cat <<EOF >ignore.h
+// We don't want this header in the manifest.
+EOF
+    backdate ignore.h
+    cat <<EOF >ignore.c
+#include "ignore.h"
+int foo;
+EOF
+
+    CCACHE_IGNOREHEADERS="ignore.h" $CCACHE $COMPILER -c ignore.c
+    manifest=`find $CCACHE_DIR -name '*.manifest'`
+    data="`$CCACHE --dump-manifest $manifest | grep ignore.h`"
+    if [ -n "$data" ]; then
+        test_failed "$manifest contained ignored header: $data"
+    fi
 }
 
-basedir_suite() {
-    ##################################################################
-    # Create some code to compile.
+# =============================================================================
+
+SUITE_basedir_PREPARE() {
     mkdir -p dir1/src dir1/include
     cat <<EOF >dir1/src/test.c
 #include <stdarg.h>
@@ -1617,281 +2142,201 @@ int test;
 EOF
     cp -r dir1 dir2
     backdate dir1/include/test.h dir2/include/test.h
-
-    cat <<EOF >stderr.h
-int stderr(void)
-{
-       // Trigger warning by having no return statement.
 }
-EOF
-    cat <<EOF >stderr.c
-#include <stderr.h>
-EOF
-    backdate stderr.h
 
-    ##################################################################
-    # CCACHE_BASEDIR="" and using absolute include path will result in a cache
-    # miss.
-    testname="empty CCACHE_BASEDIR"
-    $CCACHE -z >/dev/null
+SUITE_basedir() {
+    unset CCACHE_NODIRECT
+
+    # -------------------------------------------------------------------------
+    TEST "Enabled CCACHE_BASEDIR"
 
     cd dir1
-    CCACHE_BASEDIR="" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    cd ..
-
-    cd dir2
-    CCACHE_BASEDIR="" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    cd ..
-
-    ##################################################################
-    # Setting CCACHE_BASEDIR will result in a cache hit because include paths
-    # in the preprocessed output are rewritten.
-    testname="set CCACHE_BASEDIR"
-    $CCACHE -z >/dev/null
-    $CCACHE -C >/dev/null
+    CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    ln -s dir1 symlink_to_dir1
-    cd symlink_to_dir1
+    cd ../dir2
     CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    cd ..
-
-    cd dir2
-    # The space after -I is there to test an extra code path.
-    CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I `pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    cd ..
-
-    ##################################################################
-    # Setting CCACHE_BASEDIR will result in a cache hit because -I arguments
-    # are rewritten, as are the paths stored in the manifest.
-    testname="set CCACHE_BASEDIR, direct lookup"
-    $CCACHE -z >/dev/null
-    $CCACHE -C >/dev/null
-    unset CCACHE_NODIRECT
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Disabled (default) CCACHE_BASEDIR"
 
     cd dir1
     CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    cd ..
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    cd dir2
-    CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    cd ..
-
-    ##################################################################
-    # CCACHE_BASEDIR="" is the default.
-    testname="default CCACHE_BASEDIR"
-    cd dir1
-    $CCACHE -z >/dev/null
+    # CCACHE_BASEDIR="" is the default:
     $CCACHE $COMPILER -I`pwd`/include -c src/test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 0
-    cd ..
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Path normalization"
 
-    ##################################################################
-    # Rewriting triggered by CCACHE_BASEDIR should handle paths with multiple
-    # slashes correctly.
-    testname="path normalization"
     cd dir1
-    $CCACHE -z >/dev/null
+    CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -c src/test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # Rewriting triggered by CCACHE_BASEDIR should handle paths with multiple
+    # slashes correctly:
     CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -I`pwd`//include -c `pwd`//src/test.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    cd ..
-
-    ##################################################################
-    # Check that rewriting triggered by CCACHE_BASEDIR also affects stderr.
-    testname="stderr"
-    $CCACHE -z >/dev/null
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Rewriting in stderr"
+
+    cat <<EOF >stderr.h
+int stderr(void)
+{
+  // Trigger warning by having no return statement.
+}
+EOF
+    backdate stderr.h
+    cat <<EOF >stderr.c
+#include <stderr.h>
+EOF
+
     CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -Wall -W -I`pwd` -c `pwd`/stderr.c -o `pwd`/stderr.o 2>stderr.txt
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if grep `pwd` stderr.txt >/dev/null 2>&1; then
         test_failed "Base dir (`pwd`) found in stderr:\n`cat stderr.txt`"
     fi
 
     CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -Wall -W -I`pwd` -c `pwd`/stderr.c -o `pwd`/stderr.o 2>stderr.txt
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if grep `pwd` stderr.txt >/dev/null 2>&1; then
         test_failed "Base dir (`pwd`) found in stderr:\n`cat stderr.txt`"
     fi
 
-    ##################################################################
-    # Check that -MF, -MQ and -MT arguments with absolute paths are rewritten
-    # to relative.
-    testname="-MF/-MQ/-MT with absolute paths"
+    # -------------------------------------------------------------------------
+    TEST "-MF/-MQ/-MT with absolute paths"
+
     for option in MF "MF " MQ "MQ " MT "MT "; do
-        $CCACHE -Cz >/dev/null
+        clear_cache
         cd dir1
         CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
-        checkstat 'cache hit (direct)' 0
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
+        expect_stat 'cache hit (direct)' 0
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
         cd ..
 
         cd dir2
         CCACHE_BASEDIR="`pwd`" $CCACHE $COMPILER -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
-        checkstat 'cache hit (direct)' 1
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
+        expect_stat 'cache hit (direct)' 1
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
         cd ..
     done
 
-    ##################################################################
-    # When BASEDIR is set to / check that -MF, -MQ and -MT arguments with absolute paths
-    # are rewritten to relative and the dependency file contains only relative# paths.
-    testname="-MF/-MQ/-MT with absolute paths and BASEDIR set to /"
+    # -------------------------------------------------------------------------
+    # When BASEDIR is set to /, check that -MF, -MQ and -MT arguments with
+    # absolute paths are rewritten to relative and that the dependency file
+    # only contains relative paths.
+    TEST "-MF/-MQ/-MT with absolute paths and BASEDIR set to /"
+
     for option in MF "MF " MQ "MQ " MT "MT "; do
-        $CCACHE -Cz >/dev/null
+        clear_cache
         cd dir1
         CCACHE_BASEDIR="/" $CCACHE $COMPILER -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
-        checkstat 'cache hit (direct)' 0
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        # Check that there is no absolute path in the dependency file
+        expect_stat 'cache hit (direct)' 0
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
+        # Check that there is no absolute path in the dependency file:
         while read line; do
             for file in $line; do
                 case $file in /*)
                     test_failed "Absolute file path '$file' found in dependency file '`pwd`/test.d'"
                 esac
             done
-        done < `pwd`/test.d
+        done <test.d
         cd ..
 
         cd dir2
         CCACHE_BASEDIR="/" $CCACHE $COMPILER -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
-        checkstat 'cache hit (direct)' 1
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
+        expect_stat 'cache hit (direct)' 1
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
         cd ..
     done
+}
 
-    ##################################################################
-    # Check that clang's --serialize-diagnostics arguments with absolute paths
-    # are rewritten to relative.
-    if [ $COMPILER_TYPE_CLANG -eq 1 ]; then
-        testname="serialize-diagnostics"
-        $CCACHE -Cz >/dev/null
-        cd dir1
-        CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
-        checkstat 'cache hit (direct)' 0
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 4
-        cd ..
-
-        cd dir2
-        CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
-        checkstat 'cache hit (direct)' 1
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 4
-        cd ..
-    fi
-
-    ##################################################################
-    # Check that gcc's -fdebug-prefix-map argument maps the debuginfo cwd.
-    if [ $COMPILER_TYPE_GCC -eq 1 -a $COMPILER_USES_MINGW -eq 0 ]; then
-        testname="debug-prefix-map"
-        $CCACHE -Cz >/dev/null
-        cd dir1
-        CCACHE_HASHDIR=1 CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -I`pwd`/include -g -fdebug-prefix-map=`pwd`=dir -c `pwd`/src/test.c -o `pwd`/test.o
-        checkstat 'cache hit (direct)' 0
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 2
-        if grep -E "[^=]`pwd`[^=]" test.o >/dev/null 2>&1; then
-            test_failed "Source dir (`pwd`) found in test.o"
-        fi
-        cd ..
+# =============================================================================
 
-        cd dir2
-        CCACHE_HASHDIR=1 CCACHE_BASEDIR=`pwd` $CCACHE $COMPILER -I`pwd`/include -g -fdebug-prefix-map=`pwd`=dir -c `pwd`/src/test.c -o `pwd`/test.o
-        checkstat 'cache hit (direct)' 1
-        checkstat 'cache hit (preprocessed)' 0
-        checkstat 'cache miss' 1
-        checkstat 'files in cache' 2
-        if grep -E "[^=]`pwd`[^=]" test.o >/dev/null 2>&1; then
-            test_failed "Source dir (`pwd`) found in test.o"
-        fi
-        cd ..
-    fi
+SUITE_compression_PREPARE() {
+    generate_code 1 test.c
 }
 
-compression_suite() {
-    ##################################################################
-    # Create some code to compile.
-    cat <<EOF >test.c
-int test;
-EOF
+SUITE_compression() {
+    # -------------------------------------------------------------------------
+    TEST "Hash sum equal for compressed and uncompressed files"
 
-    ##################################################################
-    # Check that compressed and uncompressed files get the same hash sum.
-    testname="compression hash sum"
     CCACHE_COMPRESS=1 $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
     CCACHE_COMPRESS=1 $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
 
     $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+}
+
+# =============================================================================
+
+SUITE_readonly_PREPARE() {
+    generate_code 1 test.c
+    generate_code 2 test2.c
 }
 
-readonly_suite() {
-    ##################################################################
-    # Create some code to compile.
-    echo "int test;" >test.c
-    echo "int test2;" >test2.c
+SUITE_readonly() {
+    # -------------------------------------------------------------------------
+    TEST "Cache hit"
 
     # Cache a compilation.
-    $CCACHE $COMPILER -c test.c -o test.o 2>/dev/null
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    # Make the cache readonly
-    # Check that readonly mode finds the result.
-    testname="cache hit"
-    rm -f test.o test2.o
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    rm test.o
+
+    # Make the cache read-only.
     chmod -R a-w $CCACHE_DIR
-    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp CCACHE_PREFIX=false $CCACHE $COMPILER -c test.c -o test.o >/dev/null 2>&1
+
+    # Check that read-only mode finds the cached result.
+    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp CCACHE_PREFIX=false $CCACHE $COMPILER -c test.c
     status1=$?
+
     # Check that fallback to the real compiler works for a cache miss.
-    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test2.c -o test2.o >/dev/null 2>&1
+    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test2.c
     status2=$?
-    chmod -R a+w $CCACHE_DIR
+
+    # Leave test dir a nice state after test failure.
+    chmod -R +w $CCACHE_DIR
+
     if [ $status1 -ne 0 ]; then
-        test_failed "failure when compiling test.c readonly"
+        test_failed "Failure when compiling test.c read-only"
     fi
     if [ $status2 -ne 0 ]; then
-        test_failed "failure when compiling test2.c readonly"
+        test_failed "Failure when compiling test2.c read-only"
     fi
     if [ ! -f test.o ]; then
         test_failed "test.o missing"
@@ -1900,191 +2345,140 @@ readonly_suite() {
         test_failed "test2.o missing"
     fi
 
-    # Check that readonly mode doesn't try to store new results.
-    testname="cache miss"
-    files_before=`find $CCACHE_DIR -type f | wc -l`
-    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test2.c -o test2.o
+    # -------------------------------------------------------------------------
+    TEST "Cache miss"
+
+    # Check that read-only mode doesn't try to store new results.
+    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test.c
     if [ $? -ne 0 ]; then
-        test_failed "failure when compiling test2.c readonly"
-    fi
-    if [ ! -f test2.o ]; then
-        test_failed "test2.o missing"
+        test_failed "Failure when compiling test2.c read-only"
     fi
-    files_after=`find $CCACHE_DIR -type f | wc -l`
-    if [ $files_before -ne $files_after ]; then
-        test_failed "readonly mode stored files in the cache"
+    if [ -d $CCACHE_DIR ]; then
+        test_failed "ccache dir was created"
     fi
 
-    # Check that readonly mode and direct mode works.
-    unset CCACHE_NODIRECT
+    # -------------------------------------------------------------------------
+    # Check that read-only mode and direct mode work together.
+    TEST "Cache hit, direct"
+
+    # Cache a compilation.
+    $CCACHE $COMPILER -c test.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    rm test.o
+
+    # Make the cache read-only.
+    chmod -R a-w $CCACHE_DIR
+
+    # Direct mode should work:
     files_before=`find $CCACHE_DIR -type f | wc -l`
-    CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test.c -o test.o
-    CCACHE_NODIRECT=1
-    export CCACHE_NODIRECT
+    CCACHE_DIRECT=1 CCACHE_READONLY=1 CCACHE_TEMPDIR=/tmp $CCACHE $COMPILER -c test.c
+    files_after=`find $CCACHE_DIR -type f | wc -l`
+
+    # Leave test dir a nice state after test failure.
+    chmod -R +w $CCACHE_DIR
+
     if [ $? -ne 0 ]; then
-        test_failed "failure when compiling test2.c readonly"
+        test_failed "Failure when compiling test.c read-only"
     fi
-    files_after=`find $CCACHE_DIR -type f | wc -l`
-    if [ $files_before -ne $files_after ]; then
-        test_failed "readonly mode + direct mode stored files in the cache"
+    if [ $files_after -ne $files_before ]; then
+        test_failed "Read-only mode + direct mode stored files in the cache"
     fi
+}
 
-    ##################################################################
+SUITE_readonly_direct_PREPARE() {
+    generate_code 1 test.c
 }
 
-readonly_direct_suite() {
+SUITE_readonly_direct() {
     unset CCACHE_NODIRECT
 
-    ##################################################################
-    # Create some code to compile.
-    echo "int test;" >test.c
+    # -------------------------------------------------------------------------
+    TEST "Direct hit"
 
-    # Cache a compilation.
-    testname="fill cache"
     $CCACHE $COMPILER -c test.c -o test.o
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    # Check that "readonly direct" mode gets a direct hit.
-    testname="direct hit"
     CCACHE_READONLY_DIRECT=1 $CCACHE $COMPILER -c test.c -o test.o
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    # Check that "readonly direct" mode doesn't get a preprocessed hit.
-    testname="preprocessed miss"
-    CCACHE_READONLY_DIRECT=1 $CCACHE $COMPILER -DFOO -c test.c -o test.o
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-}
-
-extrafiles_suite() {
-    ##################################################################
-    # Create some code to compile.
-    cat <<EOF >test.c
-int test;
-EOF
-    echo a >a
-    echo b >b
-
-    ##################################################################
-    # Test the CCACHE_EXTRAFILES feature.
-
-    testname="cache hit"
-    $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="cache miss"
-    $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="cache miss a b"
-    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
+    # -------------------------------------------------------------------------
+    TEST "Direct miss doesn't lead to preprocessed hit"
 
-    testname="cache hit a b"
-    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
+    $CCACHE $COMPILER -c test.c -o test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="cache miss a b2"
-    echo b2 >b
-    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 3
-
-    testname="cache hit a b2"
-    CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 3
-    checkstat 'cache miss' 3
-
-    testname="cache miss doesntexist"
-    CCACHE_EXTRAFILES="doesntexist" $CCACHE $COMPILER -c test.c
-    checkstat 'cache hit (preprocessed)' 3
-    checkstat 'cache miss' 3
-    checkstat 'error hashing extra file' 1
+    CCACHE_READONLY_DIRECT=1 $CCACHE $COMPILER -DFOO -c test.c -o test.o
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 }
 
-ignoreheaders_suite() {
-    unset CCACHE_NODIRECT
+# =============================================================================
 
-    #################################################################
-    # Check the ignore headers in the manifest feature
-    testname="ignore headers in manifest"
-    $CCACHE -Cz >/dev/null
-    cat <<EOF >ignore.h
-// We don't want this header in the manifest.
-EOF
-    backdate ignore.h
-    cat <<EOF >ignore.c
-#include "ignore.h"
-int foo;
-EOF
-    CCACHE_IGNOREHEADERS="ignore.h" $CCACHE $COMPILER -c ignore.c
-    manifest=`find $CCACHE_DIR -name '*.manifest'`
-    data="`$CCACHE --dump-manifest $manifest | grep 'ignore.h'`"
-    if [ -n "$data" ]; then
-        test_failed "$manifest contained ignored header(s): $data"
-    fi
-}
+prepare_cleanup_test_dir() {
+    local dir=$1
 
-prepare_cleanup_test() {
-    dir=$1
     rm -rf $dir
     mkdir -p $dir
-    i=0
-    while [ $i -lt 10 ]; do
+    for i in $(seq 0 9); do
         printf '%4017s' '' | tr ' ' 'A' >$dir/result$i-4017.o
         touch $dir/result$i-4017.stderr
         touch $dir/result$i-4017.d
         if [ $i -gt 5 ]; then
             backdate $dir/result$i-4017.stderr
         fi
-        i=`expr $i + 1`
     done
     # NUMFILES: 30, TOTALSIZE: 40 KiB, MAXFILES: 0, MAXSIZE: 0
     echo "0 0 0 0 0 0 0 0 0 0 0 30 40 0 0" >$dir/stats
 }
 
-cleanup_suite() {
-    testname="clear"
-    prepare_cleanup_test $CCACHE_DIR/a
-    $CCACHE -C >/dev/null
-    checkfilecount 0 '*.o' $CCACHE_DIR
-    checkfilecount 0 '*.d' $CCACHE_DIR
-    checkfilecount 0 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 0
-    checkstat 'cleanups performed' 1
+SUITE_cleanup() {
+    # -------------------------------------------------------------------------
+    TEST "Clear cache"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
 
-    testname="forced cleanup, no limits"
     $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
+    expect_file_count 0 '*.o' $CCACHE_DIR
+    expect_file_count 0 '*.d' $CCACHE_DIR
+    expect_file_count 0 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 0
+    expect_stat 'cleanups performed' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Forced cache cleanup, no limits"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
+
     $CCACHE -F 0 -M 0 >/dev/null
     $CCACHE -c >/dev/null
-    checkfilecount 10 '*.o' $CCACHE_DIR
-    checkfilecount 10 '*.d' $CCACHE_DIR
-    checkfilecount 10 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 30
-    checkstat 'cleanups performed' 0
+    expect_file_count 10 '*.o' $CCACHE_DIR
+    expect_file_count 10 '*.d' $CCACHE_DIR
+    expect_file_count 10 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 30
+    expect_stat 'cleanups performed' 0
+
+    # -------------------------------------------------------------------------
+    TEST "Forced cache cleanup, file limit"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
 
-    testname="forced cleanup, file limit"
-    $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
     # (9/10) * 30 * 16 = 432
     $CCACHE -F 432 -M 0 >/dev/null
     $CCACHE -c >/dev/null
     # floor(0.8 * 9) = 7
-    checkfilecount 7 '*.o' $CCACHE_DIR
-    checkfilecount 7 '*.d' $CCACHE_DIR
-    checkfilecount 7 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 21
-    checkstat 'cleanups performed' 1
+    expect_file_count 7 '*.o' $CCACHE_DIR
+    expect_file_count 7 '*.d' $CCACHE_DIR
+    expect_file_count 7 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 21
+    expect_stat 'cleanups performed' 1
     for i in 0 1 2 3 4 5 9; do
         file=$CCACHE_DIR/a/result$i-4017.o
         if [ ! -f $file ]; then
@@ -2098,23 +2492,27 @@ cleanup_suite() {
         fi
     done
 
-    # Warning: this test is known to fail on filesystems that have
-    # unusual block sizes, including ecryptfs.  The workaround is
-    # to place the test directory elsewhere:
+    # -------------------------------------------------------------------------
+    TEST "Forced cache cleanup, size limit"
+
+    # NOTE: This test is known to fail on filesystems that have unusual block
+    # sizes, including ecryptfs. The workaround is to place the test directory
+    # elsewhere:
+    #
     #     cd /tmp
     #     CCACHE=$DIR/ccache $DIR/test.sh
-    testname="forced cleanup, size limit"
-    $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
+
     # (4/10) * 10 * 4 * 16 = 256
     $CCACHE -F 0 -M 256K >/dev/null
     $CCACHE -c >/dev/null
     # floor(0.8 * 4) = 3
-    checkfilecount 3 '*.o' $CCACHE_DIR
-    checkfilecount 3 '*.d' $CCACHE_DIR
-    checkfilecount 3 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 9
-    checkstat 'cleanups performed' 1
+    expect_file_count 3 '*.o' $CCACHE_DIR
+    expect_file_count 3 '*.d' $CCACHE_DIR
+    expect_file_count 3 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 9
+    expect_stat 'cleanups performed' 1
     for i in 3 4 5; do
         file=$CCACHE_DIR/a/result$i-4017.o
         if [ ! -f $file ]; then
@@ -2128,38 +2526,43 @@ cleanup_suite() {
         fi
     done
 
-    testname="autocleanup"
-    $CCACHE -C >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Automatic cache cleanup"
+
     for x in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
-        prepare_cleanup_test $CCACHE_DIR/$x
+        prepare_cleanup_test_dir $CCACHE_DIR/$x
     done
+
     # (9/10) * 30 * 16 = 432
     $CCACHE -F 432 -M 0 >/dev/null
+    expect_file_count 160 '*.o' $CCACHE_DIR
+    expect_file_count 160 '*.d' $CCACHE_DIR
+    expect_file_count 160 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 480
+
     touch empty.c
-    checkfilecount 160 '*.o' $CCACHE_DIR
-    checkfilecount 160 '*.d' $CCACHE_DIR
-    checkfilecount 160 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 480
     $CCACHE $COMPILER -c empty.c -o empty.o
     # floor(0.8 * 9) = 7
-    checkfilecount 157 '*.o' $CCACHE_DIR
-    checkfilecount 156 '*.d' $CCACHE_DIR
-    checkfilecount 156 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 469
-    checkstat 'cleanups performed' 1
+    expect_file_count 157 '*.o' $CCACHE_DIR
+    expect_file_count 156 '*.d' $CCACHE_DIR
+    expect_file_count 156 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 469
+    expect_stat 'cleanups performed' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Cleanup of sibling files"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
 
-    testname="sibling cleanup"
-    $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
     # (9/10) * 30 * 16 = 432
     $CCACHE -F 432 -M 0 >/dev/null
     backdate $CCACHE_DIR/a/result2-4017.stderr
     $CCACHE -c >/dev/null
     # floor(0.8 * 9) = 7
-    checkfilecount 7 '*.o' $CCACHE_DIR
-    checkfilecount 7 '*.d' $CCACHE_DIR
-    checkfilecount 7 '*.stderr' $CCACHE_DIR
-    checkstat 'files in cache' 21
+    expect_file_count 7 '*.o' $CCACHE_DIR
+    expect_file_count 7 '*.d' $CCACHE_DIR
+    expect_file_count 7 '*.stderr' $CCACHE_DIR
+    expect_stat 'files in cache' 21
     for i in 0 1 3 4 5 8 9; do
         file=$CCACHE_DIR/a/result$i-4017.o
         if [ ! -f $file ]; then
@@ -2173,23 +2576,26 @@ cleanup_suite() {
         fi
     done
 
-    testname="new unknown file"
-    $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
+    # -------------------------------------------------------------------------
+    TEST "No cleanup of new unknown file"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
+
     touch $CCACHE_DIR/a/abcd.unknown
     $CCACHE -F 0 -M 0 -c >/dev/null # update counters
-    checkstat 'files in cache' 31
+    expect_stat 'files in cache' 31
     # (9/10) * 30 * 16 = 432
     $CCACHE -F 432 -M 0 >/dev/null
     $CCACHE -c >/dev/null
     if [ ! -f $CCACHE_DIR/a/abcd.unknown ]; then
         test_failed "$CCACHE_DIR/a/abcd.unknown removed"
     fi
-    checkstat 'files in cache' 19
+    expect_stat 'files in cache' 19
 
-    testname="old unknown file"
-    $CCACHE -C >/dev/null
-    prepare_cleanup_test $CCACHE_DIR/a
+    # -------------------------------------------------------------------------
+    TEST "Cleanup of old unknown file"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
     # (9/10) * 30 * 16 = 432
     $CCACHE -F 432 -M 0 >/dev/null
     touch $CCACHE_DIR/a/abcd.unknown
@@ -2199,30 +2605,35 @@ cleanup_suite() {
         test_failed "$CCACHE_DIR/a/abcd.unknown not removed"
     fi
 
-    testname="cleanup of tmp files"
-    $CCACHE -C >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Cleanup of tmp file"
+
+    mkdir -p $CCACHE_DIR/a
     touch $CCACHE_DIR/a/abcd.tmp.efgh
     $CCACHE -c >/dev/null # update counters
-    checkstat 'files in cache' 1
+    expect_stat 'files in cache' 1
     backdate $CCACHE_DIR/a/abcd.tmp.efgh
     $CCACHE -c >/dev/null
     if [ -f $CCACHE_DIR/a/abcd.tmp.efgh ]; then
         test_failed "$CCACHE_DIR/a/abcd.tmp.unknown not removed"
     fi
-    checkstat 'files in cache' 0
+    expect_stat 'files in cache' 0
+
+    # -------------------------------------------------------------------------
+    TEST "No cleanup of .nfs* files"
+
+    prepare_cleanup_test_dir $CCACHE_DIR/a
 
-    testname="ignore .nfs* files"
-    prepare_cleanup_test $CCACHE_DIR/a
     touch $CCACHE_DIR/a/.nfs0123456789
     $CCACHE -F 0 -M 0 >/dev/null
     $CCACHE -c >/dev/null
-    checkfilecount 1 '.nfs*' $CCACHE_DIR
-    checkstat 'files in cache' 30
+    expect_file_count 1 '.nfs*' $CCACHE_DIR
+    expect_stat 'files in cache' 30
 }
 
-pch_suite() {
-    unset CCACHE_NODIRECT
+# =============================================================================
 
+SUITE_pch_PREPARE() {
     cat <<EOF >pch.c
 #include "pch.h"
 int main()
@@ -2234,6 +2645,7 @@ EOF
     cat <<EOF >pch.h
 #include <stdlib.h>
 EOF
+    backdate pch.h
     cat <<EOF >pch2.c
 int main()
 {
@@ -2243,501 +2655,504 @@ int main()
 EOF
 
     if $COMPILER $SYSROOT -fpch-preprocess pch.h 2>/dev/null && [ -f pch.h.gch ] && $COMPILER $SYSROOT pch.c -o pch; then
-        rm pch.h.gch
+        rm pch.h.gch pch
     else
-        echo "Compiler (`$COMPILER --version | head -1`) doesn't support precompiled headers -- not running pch test"
+        echo "compiler ($($COMPILER --version | head -1)) doesn't support precompiled headers"
         return
     fi
+}
+
+SUITE_pch() {
+    unset CCACHE_NODIRECT
 
-    # clang and gcc handle precompiled headers similarly, but gcc is much more
-    # forgiving with precompiled headers. Both gcc and clang keep an absolute
-    # path reference to the original file except that clang uses that reference
-    # to validate the pch and gcc ignores the reference. Also, clang has an
-    # additional feature: pre-tokenized headers. For these reasons clang should
-    # be tested separately than gcc. clang can only use pch or pth headers on
-    # the command line and not as an #include statement inside a source file.
+    # Clang and GCC handle precompiled headers similarly, but GCC is much more
+    # forgiving with precompiled headers. Both GCC and Clang keep an absolute
+    # path reference to the original file except that Clang uses that reference
+    # to validate the pch and GCC ignores the reference. Also, Clang has an
+    # additional feature: pre-tokenized headers. For these reasons, Clang
+    # should be tested differently from GCC. Clang can only use pch or pth
+    # headers on the command line and not as an #include statement inside a
+    # source file.
 
     if [ $COMPILER_TYPE_CLANG -eq 1 ]; then
-        clang_pch_suite
+        pch_suite_clang
     else
-        gcc_pch_suite
+        pch_suite_gcc
     fi
 }
 
-gcc_pch_suite() {
-    ##################################################################
-    # Tests for creating a .gch without opt-in.
-
-    backdate pch.h
+pch_suite_gcc() {
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, -c, no -o, without opt-in"
 
-    testname="create .gch, -c, no -o, without opt-in"
-    $CCACHE -zC >/dev/null
     $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat "can't use precompiled header" 1
-
-    testname="create .gch, no -c, -o, without opt-in"
-    $CCACHE -Cz >/dev/null
-    $CCACHE $COMPILER pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat "can't use precompiled header" 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat "can't use precompiled header" 1
 
-    ##################################################################
-    # Tests for creating a .gch with opt-in.
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, no -c, -o, without opt-in"
 
-    backdate pch.h
+    $CCACHE $COMPILER pch.h -o pch.gch
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat "can't use precompiled header" 1
+
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, -c, no -o, with opt-in"
+
+    CCACHE_SLOPPINESS=pch_defines $CCACHE $COMPILER $SYSROOT -c pch.h
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    rm pch.h.gch
 
-    testname="create .gch, -c, no -o, with opt-in"
-    $CCACHE -zC >/dev/null
-    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    rm -f pch.h.gch
-    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    CCACHE_SLOPPINESS=pch_defines $CCACHE $COMPILER $SYSROOT -c pch.h
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if [ ! -f pch.h.gch ]; then
         test_failed "pch.h.gch missing"
     fi
 
-    testname="create .gch, no -c, -o, with opt-in"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, no -c, -o, with opt-in"
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if [ ! -f pch.gch ]; then
         test_failed "pch.gch missing"
     fi
-    rm pch.gch
 
-    ##################################################################
-    # Tests for using a .gch.
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, no -fpch-preprocess, #include"
 
-    rm -f pch.h
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
     backdate pch.h.gch
+    rm pch.h
 
-    testname="no -fpch-preprocess, #include"
-    $CCACHE -Cz >/dev/null
-    $CCACHE $COMPILER $SYSROOT -c pch.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
+    $CCACHE $COMPILER $SYSROOT -c pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
     # Preprocessor error because GCC can't find the real include file when
     # trying to preprocess:
-    checkstat 'preprocessor error' 1
+    expect_stat 'preprocessor error' 1
 
-    testname="no -fpch-preprocess, -include, no sloppiness"
-    $CCACHE -Cz >/dev/null
-    $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, no -fpch-preprocess, -include, no sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
     # Must enable sloppy time macros:
-    checkstat "can't use precompiled header" 1
+    expect_stat "can't use precompiled header" 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, no -fpch-preprocess, -include, sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, -fpch-preprocess, #include, no sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
 
-    testname="no -fpch-preprocess, -include"
-    $CCACHE -Cz >/dev/null
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="-fpch-preprocess, #include, no sloppiness"
-    $CCACHE -Cz >/dev/null
     $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
     # Must enable sloppy time macros:
-    checkstat "can't use precompiled header" 1
+    expect_stat "can't use precompiled header" 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, -fpch-preprocess, #include, sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, -fpch-preprocess, #include, file changed"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="-fpch-preprocess, #include, sloppiness"
-    $CCACHE -Cz >/dev/null
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="-fpch-preprocess, #include, file changed"
     echo "updated" >>pch.h.gch # GCC seems to cope with this...
     backdate pch.h.gch
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 2
 
-    testname="preprocessor mode"
-    $CCACHE -Cz >/dev/null
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-
-    testname="preprocessor mode, file changed"
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, preprocessor mode"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, preprocessor mode, file changed"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+    rm pch.h
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     echo "updated" >>pch.h.gch # GCC seems to cope with this...
     backdate pch.h.gch
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
-}
 
-clang_pch_suite() {
-    ##################################################################
-    # Tests for creating a .gch without opt-in.
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-    backdate pch.h
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+}
+
+pch_suite_clang() {
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, -c, no -o, without opt-in"
 
-    testname="create .gch, -c, no -o"
-    $CCACHE -zC >/dev/null
     $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat "can't use precompiled header" 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat "can't use precompiled header" 1
 
-    testname="create .gch, no -c, -o, without opt-in"
-    $CCACHE -Cz >/dev/null
-    $CCACHE $COMPILER pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
-    checkstat "can't use precompiled header" 1
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, no -c, -o, without opt-in"
 
-    ##################################################################
-    # Tests for creating a .gch with opt-in.
+    $CCACHE $COMPILER pch.h -o pch.gch
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat "can't use precompiled header" 1
 
-    backdate pch.h
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, -c, no -o, with opt-in"
 
-    testname="create .gch, -c, no -o, with opt-in"
-    $CCACHE -zC >/dev/null
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    rm -f pch.h.gch
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    rm pch.h.gch
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if [ ! -f pch.h.gch ]; then
         test_failed "pch.h.gch missing"
     fi
 
-    testname="create .gch, no -c, -o, with opt-in"
-    $CCACHE -Cz >/dev/null
+    # -------------------------------------------------------------------------
+    TEST "Create .gch, no -c, -o, with opt-in"
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if [ ! -f pch.gch ]; then
         test_failed "pch.gch missing"
     fi
-    rm pch.gch
 
-    ##################################################################
-    # Tests for using a .gch.
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, no -fpch-preprocess, -include, no sloppiness"
 
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
     backdate pch.h.gch
 
-    testname="gch, no -fpch-preprocess, -include, no sloppy time macros"
-    $CCACHE -Cz >/dev/null
     $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
     # Must enable sloppy time macros:
-    checkstat "can't use precompiled header" 1
+    expect_stat "can't use precompiled header" 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, no -fpch-preprocess, -include, sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, -fpch-preprocess, -include, file changed"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    testname="gch, no -fpch-preprocess, -include, sloppy time macros"
-    $CCACHE -Cz >/dev/null
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="gch, -fpch-preprocess, -include, file changed"
     echo "updated" >>pch.h.gch # clang seems to cope with this...
     backdate pch.h.gch
-    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 2
 
-    testname="gch, preprocessor mode"
-    $CCACHE -Cz >/dev/null
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-
-    testname="gch, preprocessor mode, file changed"
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, preprocessor mode"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .gch, preprocessor mode, file changed"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h
+    backdate pch.h.gch
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     echo "updated" >>pch.h.gch # clang seems to cope with this...
     backdate pch.h.gch
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
 
-    rm pch.h.gch
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-    ##################################################################
-    # Tests for creating a .pth.
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
 
-    backdate pch.h
+    # -------------------------------------------------------------------------
+    TEST "Create .pth, -c, -o"
 
-    testname="create .pth, -c, -o"
-    $CCACHE -zC >/dev/null
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     rm -f pch.h.pth
+
     CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
     if [ ! -f pch.h.pth ]; then
         test_failed "pch.h.pth missing"
     fi
 
-    ##################################################################
-    # Tests for using a .pth.
+    # -------------------------------------------------------------------------
+    TEST "Use .pth, no -fpch-preprocess, -include, no sloppiness"
 
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h -o pch.h.pth
     backdate pch.h.pth
 
-    testname="pth, no -fpch-preprocess, -include, no sloppy time macros"
-    $CCACHE -Cz >/dev/null
     $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 0
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
     # Must enable sloppy time macros:
-    checkstat "can't use precompiled header" 1
+    expect_stat "can't use precompiled header" 1
 
-    testname="pth, no -fpch-preprocess, -include, sloppy time macros"
-    $CCACHE -Cz >/dev/null
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-
-    testname="pth, -fpch-preprocess, -include, file changed"
-    echo "updated" >>pch.h.pth # clang seems to cope with this...
+    # -------------------------------------------------------------------------
+    TEST "Use .pth, no -fpch-preprocess, -include, sloppiness"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h -o pch.h.pth
     backdate pch.h.pth
-    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 1
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 2
 
-    testname="pth, preprocessor mode"
-    $CCACHE -Cz >/dev/null
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-
-    testname="pth, preprocessor mode, file changed"
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Use .pth, -fpch-preprocess, -include, file changed"
+
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h -o pch.h.pth
+    backdate pch.h.pth
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
     echo "updated" >>pch.h.pth # clang seems to cope with this...
     backdate pch.h.pth
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 2
-    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 2
-
-    rm pch.h.pth
-}
 
-upgrade_suite() {
-    testname="keep maxfiles and maxsize settings"
-    rm -rf $CCACHE_DIR $CCACHE_CONFIGPATH
-    mkdir -p $CCACHE_DIR/0
-    echo "0 0 0 0 0 0 0 0 0 0 0 0 0 2000 131072" >$CCACHE_DIR/0/stats
-    checkstat 'max files' 32000
-    checkstat 'max cache size' '2.1 GB'
-}
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-prefix_suite() {
-    testname="prefix"
-    $CCACHE -Cz >/dev/null
-    rm -f prefix.result
-    cat <<'EOF' >prefix-a
-#!/bin/sh
-echo a >>prefix.result
-exec "$@"
-EOF
-    cat <<'EOF' >prefix-b
-#!/bin/sh
-echo b >>prefix.result
-exec "$@"
-EOF
-    chmod +x prefix-a prefix-b
-    cat <<'EOF' >file.c
-int foo;
-EOF
-    PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    checkfile prefix.result "a
-b"
-    PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-    checkfile prefix.result "a
-b"
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
 
-    rm -f prefix.result
-    PATH=.:$PATH CCACHE_PREFIX_CPP="prefix-a prefix-b" $CCACHE $COMPILER -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 2
-    checkstat 'cache miss' 1
-    checkfile prefix.result "a
-b"
-}
+    # -------------------------------------------------------------------------
+    TEST "Use .pth, preprocessor mode"
 
-buggy_cpp_suite() {
-    testname="buggy_cpp"
-    $CCACHE -Cz >/dev/null
-    cat >buggy-cpp <<EOF
-#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
-if echo "\$*" | grep -- -D >/dev/null; then
-  $COMPILER "\$@"
-else
-  # mistreat the preprocessor output in the same way as gcc6 does
-  $COMPILER "\$@" |
-    sed -e '/^# 1 "<command-line>"\$/ a\\
-# 31 "<command-line>"' \\
-        -e 's/^# 1 "<command-line>" 2\$/# 32 "<command-line>" 2/'
-fi
-exit 0
-EOF
-    cat <<'EOF' >file.c
-int foo;
-EOF
-    chmod +x buggy-cpp
-    backdate buggy-cpp
-    $CCACHE ./buggy-cpp -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 0
-    checkstat 'cache miss' 1
-    $CCACHE ./buggy-cpp -DNOT_AFFECTING=1 -c file.c
-    checkstat 'cache hit (direct)' 0
-    checkstat 'cache hit (preprocessed)' 1
-    checkstat 'cache miss' 1
-}
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h -o pch.h.pth
+    backdate pch.h.pth
 
-symlinks_suite() {
-    ##################################################################
-    testname="symlink to source directory"
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    mkdir dir
-    cd dir
-    mkdir -p d1/d2
-    echo '#define A "OK"' >d1/h.h
-    cat <<EOF >d1/d2/c.c
-#include <stdio.h>
-#include "../h.h"
-int main() { printf("%s\n", A); }
-EOF
-    echo '#define A "BUG"' >h.h
-    ln -s d1/d2 d3
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
 
-    CCACHE_BASEDIR=/ $CCACHE $COMPILER -c $PWD/d3/c.c
-    $COMPILER -c $PWD/d3/c.c
-    $COMPILER c.o -o c
-    result=$(./c)
-    if [ "$result" != OK ]; then
-        test_failed "Incorrect header file used"
-    fi
+    # -------------------------------------------------------------------------
+    TEST "Use .pth, preprocessor mode, file changed"
 
-    cd ..
-    rm -rf dir
+    CCACHE_DISABLE=1 $COMPILER $SYSROOT -c pch.h -o pch.h.pth
+    backdate pch.h.pth
 
-    ##################################################################
-    testname="symlink to source file"
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
 
-    mkdir dir
-    cd dir
-    mkdir d
-    echo '#define A "BUG"' >d/h.h
-    cat <<EOF >d/c.c
-#include <stdio.h>
-#include "h.h"
-int main() { printf("%s\n", A); }
-EOF
-    echo '#define A "OK"' >h.h
-    ln -s d/c.c c.c
+    echo "updated" >>pch.h.pth # clang seems to cope with this...
+    backdate pch.h.pth
 
-    CCACHE_BASEDIR=/ $CCACHE $COMPILER -c $PWD/c.c
-    $COMPILER c.o -o c
-    result=$(./c)
-    if [ "$result" != OK ]; then
-        test_failed "Incorrect header file used"
-    fi
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 2
+
+    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 2
+}
 
-    cd ..
-    rm -rf dir
+# =============================================================================
+
+SUITE_upgrade() {
+    TEST "Keep maxfiles and maxsize settings"
+
+    rm $CCACHE_CONFIGPATH
+    mkdir -p $CCACHE_DIR/0
+    echo "0 0 0 0 0 0 0 0 0 0 0 0 0 2000 131072" >$CCACHE_DIR/0/stats
+    expect_stat 'max files' 32000
+    expect_stat 'max cache size' '2.1 GB'
 }
 
-######################################################################
+# =============================================================================
+
+# -----------------------------------------------------------------------------
 # main program
 
 if pwd | grep '[^A-Za-z0-9/.,=_%+-]' >/dev/null 2>&1; then
@@ -2748,7 +3163,6 @@ EOF
     exit 1
 fi
 
-suites="$*"
 if [ -n "$CC" ]; then
     COMPILER="$CC"
 else
@@ -2760,7 +3174,15 @@ fi
 
 CCACHE_COMPILE="$CCACHE $COMPILER"
 
-# save the type of compiler because some test may not work on all compilers
+export CCACHE_DETECT_SHEBANG
+CCACHE_DETECT_SHEBANG=1
+
+# Many tests backdate files, which updates their ctimes. In those tests, we
+# must ignore ctimes. Might as well do so everywhere.
+DEFAULT_SLOPPINESS=include_file_ctime
+CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS"
+export CCACHE_SLOPPINESS
+
 COMPILER_TYPE_CLANG=0
 COMPILER_TYPE_GCC=0
 
@@ -2769,6 +3191,7 @@ COMPILER_USES_MINGW=0
 
 HOST_OS_APPLE=0
 HOST_OS_LINUX=0
+HOST_OS_WINDOWS=0
 
 compiler_version="`$COMPILER --version 2>&1 | head -1`"
 case $compiler_version in
@@ -2788,16 +3211,15 @@ case $compiler_version in
     *llvm*|*LLVM*)
         COMPILER_USES_LLVM=1
         ;;
-esac
-
-case $compiler_version in
-    *mingw*)
+    *MINGW*|*mingw*)
         COMPILER_USES_MINGW=1
         ;;
 esac
 
-host_os="`uname -s`"
-case $host_os in
+case $(uname -s) in
+    *MINGW*|*mingw*)
+        HOST_OS_WINDOWS=1
+        ;;
     *Darwin*)
         HOST_OS_APPLE=1
         ;;
@@ -2806,19 +3228,11 @@ case $host_os in
         ;;
 esac
 
-TESTDIR=testdir.$$
-rm -rf $TESTDIR
-mkdir $TESTDIR
-cd $TESTDIR || exit 1
-
-CCACHE_DIR=`pwd`/.ccache
-export CCACHE_DIR
-CCACHE_LOGFILE=`pwd`/ccache.log
-export CCACHE_LOGFILE
-CCACHE_CONFIGPATH=`pwd`/ccache.conf
-export CCACHE_CONFIGPATH
-touch $CCACHE_CONFIGPATH
-
+if [ $HOST_OS_WINDOWS -eq 1 ]; then
+    PATH_DELIM=";"
+else
+    PATH_DELIM=":"
+fi
 
 if [ $HOST_OS_APPLE -eq 1 ]; then
     # Grab the developer directory from the environment or try xcode-select
@@ -2847,44 +3261,53 @@ fi
 
 # ---------------------------------------
 
+TESTDIR=testdir.$$
+ABS_TESTDIR=$PWD/$TESTDIR
+rm -rf $TESTDIR
+mkdir $TESTDIR
+cd $TESTDIR || exit 1
+
+CCACHE_DIR=`pwd`/.ccache
+export CCACHE_DIR
+CCACHE_LOGFILE=`pwd`/ccache.log
+export CCACHE_LOGFILE
+CCACHE_CONFIGPATH=`pwd`/ccache.conf
+export CCACHE_CONFIGPATH
+touch $CCACHE_CONFIGPATH
+
+# ---------------------------------------
+
 all_suites="
 base
-link          !win32
+cpp2
+multi_arch
+serialize_diagnostics
+debug_prefix_map
+masquerading
 hardlink
-nlevels4
-nlevels1
-basedir       !win32
 direct
+basedir
 compression
 readonly
 readonly_direct
-extrafiles
-ignoreheaders
 cleanup
 pch
-symlinks
 upgrade
-prefix
-buggy_cpp
 "
 
-case $host_os in
-    *MINGW*|*mingw*)
-        export CCACHE_DETECT_SHEBANG
-        CCACHE_DETECT_SHEBANG=1
-        PATH_DELIM=";"
-        all_suites="`echo "$all_suites" | grep -v '!win32'`"
-        ;;
-    *)
-        PATH_DELIM=":"
-        all_suites="`echo "$all_suites" | cut -d' ' -f1`"
-        ;;
-esac
+compiler_location=$(which $COMPILER)
+if [ "$compiler_location" = "$COMPILER" ]; then
+    echo "Compiler:         $COMPILER"
+else
+    echo "Compiler:         $COMPILER ($(which $COMPILER))"
+fi
+echo "Compiler version: $($COMPILER --version | head -n 1)"
+echo
 
-echo compiler: `which $COMPILER`
-echo version: `$COMPILER --version`
-echo test dir: $TESTDIR
+VERBOSE=false
+[ "$1" = "-v" ] && { VERBOSE=true; shift; }
 
+suites="$*"
 if [ -z "$suites" ]; then
     suites="$all_suites"
 fi
@@ -2893,9 +3316,7 @@ for suite in $suites; do
     run_suite $suite
 done
 
-# ---------------------------------------
-
-cd ..
-rm -rf $TESTDIR
-echo test done - OK
+cd /
+rm -rf $ABS_TESTDIR
+echo OK
 exit 0