#!/bin/sh
#
# A simple test suite for ccache.
#
# Copyright (C) 2002-2007 Andrew Tridgell
# Copyright (C) 2009-2016 Joel Rosdahl
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

unset CCACHE_BASEDIR
unset CCACHE_CC
unset CCACHE_COMPILERCHECK
unset CCACHE_COMPRESS
unset CCACHE_CPP2
unset CCACHE_DIR
unset CCACHE_DISABLE
unset CCACHE_EXTENSION
unset CCACHE_EXTRAFILES
unset CCACHE_HARDLINK
unset CCACHE_HASHDIR
unset CCACHE_LOGFILE
unset CCACHE_NLEVELS
unset CCACHE_NODIRECT
unset CCACHE_NOSTATS
unset CCACHE_PATH
unset CCACHE_PREFIX
unset CCACHE_READONLY
unset CCACHE_READONLY_DIRECT
unset CCACHE_RECACHE
unset CCACHE_SLOPPINESS
unset CCACHE_TEMPDIR
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"
    $CCACHE -s
    cd ..
    echo TEST FAILED
    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`
    done
    ) >> "$outfile"
}

getstat() {
    stat="$1"
    value=`$CCACHE -s | grep "$stat" | cut -c34-`
    echo $value
}

checkstat() {
    stat="$1"
    expected_value="$2"
    value=`getstat "$stat"`
    if [ "$expected_value" != "$value" ]; then
        test_failed "Expected \"$stat\" to be $expected_value, got $value"
    fi
}

compare_file() {
    cmp -s "$1" "$2"
    if [ $? -ne 0 ]; then
        test_failed "Files differ: $1 != $2"
    fi
}

checkfile() {
    if [ ! -f $1 ]; then
        test_failed "$1 not found"
    fi
    if [ "`cat $1`" != "$2" ]; then
        test_failed "Bad content of $1.\nExpected: $2\nActual: `cat $1`"
    fi
}

checkfilecount() {
    expected=$1
    pattern=$2
    dir=$3
    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
}

backdate() {
    touch -t 199901010000 "$@"
}

run_suite() {
    rm -rf $CCACHE_DIR
    CCACHE_NODIRECT=1
    export CCACHE_NODIRECT

    echo "starting testsuite $1"
    testsuite=$1

    ${1}_suite

    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"
    fi
}

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

    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_file 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_file reference_test1.o test1.o

    testname="debug"
    $CCACHE_COMPILE -c test1.c -g
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 2
    checkstat 'files in cache' 2

    testname="debug2"
    $CCACHE_COMPILE -c test1.c -g
    checkstat 'cache hit (preprocessed)' 2
    checkstat 'cache miss' 2

    testname="output"
    $CCACHE_COMPILE -c test1.c -o foo.o
    checkstat 'cache hit (preprocessed)' 3
    checkstat 'cache miss' 2
    compare_file reference_test1.o foo.o

    testname="link"
    $CCACHE_COMPILE test1.c -o test 2> /dev/null
    checkstat 'called for link' 1

    testname="linkobj"
    $CCACHE_COMPILE foo.o -o test 2> /dev/null
    checkstat 'called for link' 2

    testname="preprocessing"
    $CCACHE_COMPILE -E -c test1.c > /dev/null 2>&1
    checkstat 'called for preprocessing' 1

    testname="multiple"
    $CCACHE_COMPILE -c test1.c test2.c
    checkstat 'multiple source files' 1

    testname="find"
    $CCACHE blahblah -c test1.c 2> /dev/null
    checkstat "couldn't find the compiler" 1

    testname="bad"
    $CCACHE_COMPILE -c test1.c -I 2> /dev/null
    checkstat 'bad compiler arguments' 1

    testname="unsupported source language"
    ln -f test1.c test1.ccc
    $CCACHE_COMPILE -c test1.ccc 2> /dev/null
    checkstat 'unsupported source language' 1

    testname="unsupported"
    $CCACHE_COMPILE -M foo -c test1.c > /dev/null 2>&1
    checkstat 'unsupported compiler option' 1

    testname="stdout"
    $CCACHE echo foo -c test1.c > /dev/null
    checkstat 'compiler produced stdout' 1

    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

    testname="no-input"
    $CCACHE_COMPILE -c -O2 2> /dev/null
    checkstat 'no input file' 1

    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
    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

    testname="CCACHE_CPP2"
    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 4
    checkstat 'cache miss' 3
    CCACHE_DISABLE=1 $COMPILER -c test1.c -o reference_test1.o -O -O
    compare_file reference_test1.o test1.o

    CCACHE_CPP2=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 5
    checkstat 'cache miss' 3
    compare_file reference_test1.o test1.o

    testname="CCACHE_NOSTATS"
    CCACHE_NOSTATS=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 5
    checkstat 'cache miss' 3

    testname="CCACHE_RECACHE"
    CCACHE_RECACHE=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 5
    checkstat 'cache miss' 4
    compare_file reference_test1.o test1.o

    # strictly speaking should be 4 - RECACHE causes a double counting!
    checkstat 'files in cache' 4
    $CCACHE -c > /dev/null
    checkstat 'files in cache' 4

    testname="CCACHE_HASHDIR"
    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 5
    checkstat 'cache miss' 5
    compare_file reference_test1.o test1.o

    CCACHE_HASHDIR=1 $CCACHE_COMPILE -c test1.c -O -O
    checkstat 'cache hit (preprocessed)' 6
    checkstat 'cache miss' 5
    checkstat 'files in cache' 5
    compare_file reference_test1.o test1.o

    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' 6

    testname="CCACHE_UNIFY"
    CCACHE_UNIFY=1 $CCACHE_COMPILE -c test1.c
    checkstat 'cache hit (preprocessed)' 6
    checkstat 'cache miss' 7
    mv test1.c test1-saved.c
    echo '/* another comment */' > test1.c
    cat test1-saved.c >> test1.c
    CCACHE_UNIFY=1 $CCACHE_COMPILE -c test1.c
    mv test1-saved.c test1.c
    checkstat 'cache hit (preprocessed)' 7
    checkstat 'cache miss' 7
    CCACHE_DISABLE=1 $COMPILER -c test1.c -o reference_test1.o
    compare_file reference_test1.o test1.o

    testname="cache-size"
    for f in *.c; do
        $CCACHE_COMPILE -c $f
    done
    checkstat 'cache hit (preprocessed)' 8
    checkstat 'cache miss' 37
    checkstat 'files in cache' 37

    $CCACHE -C >/dev/null

    testname="cpp call"
    $CCACHE_COMPILE -c test1.c -E > test1.i
    checkstat 'cache hit (preprocessed)' 8
    checkstat 'cache miss' 37

    testname="direct .i compile"
    $CCACHE_COMPILE -c test1.c
    checkstat 'cache hit (preprocessed)' 8
    checkstat 'cache miss' 38

    $CCACHE_COMPILE -c test1.i
    checkstat 'cache hit (preprocessed)' 9
    checkstat 'cache miss' 38

    $CCACHE_COMPILE -c test1.i
    checkstat 'cache hit (preprocessed)' 10
    checkstat 'cache miss' 38

    testname="-x c"
    $CCACHE_COMPILE -x c -c test1.ccc
    checkstat 'cache hit (preprocessed)' 10
    checkstat 'cache miss' 39
    $CCACHE_COMPILE -x c -c test1.ccc
    checkstat 'cache hit (preprocessed)' 11
    checkstat 'cache miss' 39

    testname="-xc"
    $CCACHE_COMPILE -xc -c test1.ccc
    checkstat 'cache hit (preprocessed)' 12
    checkstat 'cache miss' 39

    testname="-x none"
    $CCACHE_COMPILE -x assembler -x none -c test1.c
    checkstat 'cache hit (preprocessed)' 13
    checkstat 'cache miss' 39

    testname="-x unknown"
    $CCACHE_COMPILE -x unknown -c test1.c 2>/dev/null
    checkstat 'cache hit (preprocessed)' 13
    checkstat 'cache miss' 39
    checkstat 'unsupported source language' 2

    testname="-D not hashed"
    $CCACHE_COMPILE -DNOT_AFFECTING=1 -c test1.c 2>/dev/null
    checkstat 'cache hit (preprocessed)' 14
    checkstat 'cache miss' 39

    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' 40
            CCACHE_CPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
            checkstat 'cache hit (preprocessed)' 15
            checkstat 'cache miss' 40
            $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
            checkstat 'cache hit (preprocessed)' 15
            checkstat 'cache miss' 41
            $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
            checkstat 'cache hit (preprocessed)' 16
            checkstat 'cache miss' 41
        fi
    fi

    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
touch override_path_compiler_executed
EOF
    chmod +x $override_path/cc
    CCACHE_PATH=$override_path $CCACHE cc -c test1.c
    if [ ! -f override_path_compiler_executed ]; then
        test_failed "CCACHE_PATH had no effect"
    fi

    testname="compilercheck=mtime"
    $CCACHE -Cz >/dev/null
    cat >compiler.sh <<EOF
#!/bin/sh
CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
export CCACHE_DISABLE
exec $COMPILER "\$@"
# A comment
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
    sed_in_place 's/comment/yoghurt/' compiler.sh # Don't change the size
    chmod +x compiler.sh
    backdate compiler.sh
    CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1
    touch compiler.sh
    CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 2

    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
    CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat '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

    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
    CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat '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

    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
    CCACHE_COMPILERCHECK=string:foo $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1
    CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 2
    CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 2
    checkstat 'cache miss' 2

    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
    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
    cat <<EOF >foobar.sh
#!/bin/sh
echo foo
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
    CCACHE_COMPILERCHECK='echo foo; echo bar' $CCACHE ./compiler.sh -c test1.c
    checkstat 'cache hit (preprocessed)' 2
    checkstat 'cache miss' 2

    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

    testname="recache should remove previous .stderr"
    $CCACHE -Cz >/dev/null
    $CCACHE_COMPILE -c test1.c
    checkstat 'cache hit (preprocessed)' 0
    checkstat '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
    CCACHE_RECACHE=1 $CCACHE_COMPILE -c test1.c
    num=`find $CCACHE_DIR -name '*.stderr' | wc -l`
    if [ $num -ne 0 ]; then
        test_failed "$num stderr files found, expected 0 (#2)"
    fi

    testname="no object file"
    $CCACHE -Cz >/dev/null
    cat <<'EOF' >test_no_obj.c
int test_no_obj;
EOF
    cat <<'EOF' >prefix-remove.sh
#!/bin/sh
"$@"
[ x$3 = x-o ] && rm $4
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

    testname="empty object file"
    cat <<'EOF' >test_empty_obj.c
int test_empty_obj;
EOF
    cat <<'EOF' >prefix-empty.sh
#!/bin/sh
"$@"
[ x$3 = x-o ] && cp /dev/null $4
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

    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. */
}
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

    $COMPILER -c -fprofile-generate test1.c 2>/dev/null
    if [ $? -eq 0 ] && [ $darwin_llvm -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

    ##################################################################
    # 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
    $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
    $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
    $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

    ##################################################################

    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
    fi

    ##################################################################

    rm -f test1.c
}

base_suite() {
    CCACHE_COMPILE="$CCACHE $COMPILER"
    base_tests
}

link_suite() {
    if [ `dirname $COMPILER` = . ]; then
        ln -s "$CCACHE" $COMPILER
        CCACHE_COMPILE="./$COMPILER"
        base_tests
        rm -f $COMPILER
    else
        echo "Compiler ($COMPILER) not taken from PATH -- not running link test"
    fi
}

hardlink_suite() {
    CCACHE_COMPILE="$CCACHE $COMPILER"
    CCACHE_HARDLINK=1
    export CCACHE_HARDLINK
    base_tests
    unset CCACHE_HARDLINK
}

cpp2_suite() {
    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
}

nlevels1_suite() {
    CCACHE_COMPILE="$CCACHE $COMPILER"
    CCACHE_NLEVELS=1
    export CCACHE_NLEVELS
    base_tests
    unset CCACHE_NLEVELS
}

direct_suite() {
    unset CCACHE_NODIRECT

    ##################################################################
    # Create some code to compile.
    cat <<EOF >test.c
/* test.c */
#include "test1.h"
#include "test2.h"
EOF
    cat <<EOF >test1.h
#include "test3.h"
int test1;
EOF
    cat <<EOF >test2.h
int test2;
EOF
    cat <<EOF >test3.h
int test3;
EOF
    cat <<EOF >code.c
/* code.c */
int test() {}
EOF
    backdate test1.h test2.h test3.h

    $COMPILER -c -Wp,-MD,expected.d test.c
    expected_d_content=`cat expected.d`

    $COMPILER -c -Wp,-MMD,expected_mmd.d test.c
    expected_mmd_d_content=`cat expected_mmd.d`

    ##################################################################
    # 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

    ##################################################################
    # 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

    ##################################################################
    # 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
    $CCACHE $COMPILER -c test.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 0

    ##################################################################
    # 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

    ##################################################################
    # 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

    $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

    mv test1.h test1.h.saved
    mv test3.h test3.h.saved
    cat <<EOF >test1.h
/* No more include of test3.h */
int test1;
EOF
    backdate test1.h

    $CCACHE $COMPILER -c test.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1

    $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

    rm -f other.d

    ##################################################################
    # 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
        $CCACHE $COMPILER -MD -c test.c -o test.dir/test$ext
        if [ ! -f $dep_file ]; then
            test_failed "$dep_file missing"
        fi
    done
    rm -rf test.dir
    checkstat 'files in cache' 12

    ##################################################################
    # 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"
    CCACHE_DISABLE=1 $COMPILER -c -Wp,-MD,other.d test.c -o reference_test.o
    compare_file 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_file reference_test.o test.o

    rm -f other.d

    $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_file reference_test.o test.o

    rm -f different_name.d

    ##################################################################
    # 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"
    CCACHE_DISABLE=1 $COMPILER -c -Wp,-MMD,other.d test.c -o reference_test.o
    compare_file 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_file reference_test.o test.o

    rm -f other.d

    $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_file reference_test.o test.o

    rm -f different_name.d

    ##################################################################
    # 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

    ##################################################################
    # 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"
    CCACHE_DISABLE=1 $COMPILER -c -MD test.c -o reference_test.o
    compare_file 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_file 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
    $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"

    rm -f 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
    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"
    CCACHE_DISABLE=1 $COMPILER -c -MD test.c -o reference_test.o
    compare_file 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_file 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_file 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_file reference_test.o test.o

    ##################################################################
    # Check that -MF works.
    testname="-MF"
    $CCACHE -C >/dev/null
    $CCACHE -z >/dev/null
    $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"
    CCACHE_DISABLE=1 $COMPILER -c -MD -MF other.d test.c -o reference_test.o
    compare_file 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_file 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_file 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_file 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

    $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_file reference_test.o test.o

    $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_file reference_test.o test.o

    find $CCACHE_DIR -name '*.d' -exec rm -f '{}' \;

    $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_file 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
#if FOO
/* Trigger preprocessor warning about extra token after #endif. */
#endif FOO
int stderr(void)
{
	/* 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

    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 $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
    $CCACHE $COMPILER -c empty.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    $CCACHE $COMPILER -c empty.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat '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
    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
    $CCACHE $COMPILER -c include_empty.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1

    ##################################################################
    # 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
    $CCACHE $COMPILER -c file.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    $CCACHE $COMPILER -c `pwd`/file.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 2

    testname="__FILE__ in include file"
    $CCACHE -Cz >/dev/null
    cat <<EOF >file.h
#define file __FILE__
int test;
EOF
    backdate file.h
    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
    $CCACHE $COMPILER -c file_h.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat '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)' 0
    checkstat 'cache miss' 2

    ##################################################################
    # 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
    cat <<EOF >file.h
#define file __FILE__
int test;
EOF
    backdate file.h
    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
    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
    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
    $CCACHE $COMPILER -c time.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1

    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
    $CCACHE $COMPILER -c time_h.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1

    ##################################################################
    # 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
    cat <<EOF >time.h
#define time __TIME__
int test;
EOF
    backdate time.h
    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
    cat <<EOF >new.c
#include "new.h"
EOF
    cat <<EOF >new.h
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
    $CCACHE $COMPILER -c new.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1

    ##################################################################
    # 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
    cat <<EOF >new.h
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

    ##################################################################
    # Check that environment variables that affect the preprocessor are taken
    # into account.
    testname="environment variables"
    $CCACHE -Cz >/dev/null
    rm -rf subdir1 subdir2
    mkdir subdir1 subdir2
    cat <<EOF >subdir1/foo.h
int foo;
EOF
    cat <<EOF >subdir2/foo.h
int foo;
EOF
    cat <<EOF >foo.c
#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
    CPATH=subdir1 $CCACHE $COMPILER -c foo.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    CPATH=subdir2 $CCACHE $COMPILER -c foo.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 2 # subdir2 is part of the preprocessor output
    CPATH=subdir2 $CCACHE $COMPILER -c foo.c
    checkstat 'cache hit (direct)' 2
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 2

    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
    $CCACHE $COMPILER -c comment.c
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat '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

    #################################################################
    # 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
        fi
    done
    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'`"
        if [ -n "$data" ]; then
            test_failed "$manifest contained troublesome file(s): $data"
        fi
    fi

    ##################################################################
    # Test --dump-manifest output.
    testname="--dump-manifest"
    $CCACHE -Cz >/dev/null
    $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 && \
       grep 'Hash: e94ceb9f1b196c387d098a5f1f4fe862' manifest.dump >/dev/null 2>&1 && \
       grep 'Hash: c2f5392dbc7e8ff6138d01608445240a' manifest.dump >/dev/null 2>&1; then
        : OK
    else
        test_failed "unexpected output of --dump-manifest"
    fi

    ##################################################################
    testname="argument-less -B and -L"
    $CCACHE -Cz > /dev/null
    cat <<EOF >test.c
#include <stdio.h>
int main(void)
{
#ifdef FOO
    puts("FOO");
#endif
    return 0;
}
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
}

basedir_suite() {
    ##################################################################
    # Create some code to compile.
    mkdir -p dir1/src dir1/include
    cat <<EOF >dir1/src/test.c
#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
    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

    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)' 0
    checkstat 'cache miss' 2
    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

    ln -s dir1 symlink_to_dir1
    cd symlink_to_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 ..

    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

    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 ..

    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 $COMPILER -I`pwd`/include -c src/test.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    cd ..

    ##################################################################
    # 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 `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
    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
    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
    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"
    for option in MF "MF " MQ "MQ " MT "MT "; do
        $CCACHE -Cz >/dev/null
        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
        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
        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
}

compression_suite() {
    ##################################################################
    # Create some code to compile.
    cat <<EOF >test.c
int test;
EOF

    ##################################################################
    # 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

    CCACHE_COMPRESS=1 $CCACHE $COMPILER -c test.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 1
    checkstat 'cache miss' 1

    $CCACHE $COMPILER -c test.c
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 2
    checkstat 'cache miss' 1
}

readonly_suite() {
    ##################################################################
    # Create some code to compile.
    echo "int test;" >test.c
    echo "int test2;" >test2.c

    # Cache a compilation.
    $CCACHE $COMPILER -c test.c -o test.o
    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
    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
    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
    status2=$?
    chmod -R a+w $CCACHE_DIR
    if [ $status1 -ne 0 ]; then
        test_failed "failure when compiling test.c readonly"
    fi
    if [ $status2 -ne 0 ]; then
        test_failed "failure when compiling test2.c readonly"
    fi
    if [ ! -f test.o ]; then
        test_failed "test.o missing"
    fi
    if [ ! -f test2.o ]; then
        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
    if [ $? -ne 0 ]; then
        test_failed "failure when compiling test2.c readonly"
    fi
    if [ ! -f test2.o ]; then
        test_failed "test2.o missing"
    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"
    fi

    # Check that readonly mode and direct mode works.
    unset CCACHE_NODIRECT
    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
    if [ $? -ne 0 ]; then
        test_failed "failure when compiling test2.c readonly"
    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"
    fi

    ##################################################################
}

readonly_direct_suite() {
    unset CCACHE_NODIRECT

    ##################################################################
    # Create some code to compile.
    echo "int test;" >test.c

    # 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

    # 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

    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

    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

    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
}

prepare_cleanup_test() {
    dir=$1
    rm -rf $dir
    mkdir -p $dir
    i=0
    while [ $i -lt 10 ]; 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

    testname="forced cleanup, no limits"
    $CCACHE -C >/dev/null
    prepare_cleanup_test $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

    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
    for i in 0 1 2 3 4 5 9; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ ! -f $file ]; then
            test_failed "File $file removed when it shouldn't"
        fi
    done
    for i in 6 7 8; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ -f $file ]; then
            test_failed "File $file not removed when it should"
        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:
    #     cd /tmp
    #     CCACHE=$DIR/ccache $DIR/test.sh
    testname="forced cleanup, size limit"
    $CCACHE -C >/dev/null
    prepare_cleanup_test $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
    for i in 3 4 5; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ ! -f $file ]; then
            test_failed "File $file removed when it shouldn't"
        fi
    done
    for i in 0 1 2 6 7 8 9; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ -f $file ]; then
            test_failed "File $file not removed when it should"
        fi
    done

    testname="autocleanup"
    $CCACHE -C >/dev/null
    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
    done
    # (9/10) * 30 * 16 = 432
    $CCACHE -F 432 -M 0 >/dev/null
    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

    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
    for i in 0 1 3 4 5 8 9; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ ! -f $file ]; then
            test_failed "File $file removed when it shouldn't"
        fi
    done
    for i in 2 6 7; do
        file=$CCACHE_DIR/a/result$i-4017.o
        if [ -f $file ]; then
            test_failed "File $file not removed when it should"
        fi
    done

    testname="new unknown file"
    $CCACHE -C >/dev/null
    prepare_cleanup_test $CCACHE_DIR/a
    touch $CCACHE_DIR/a/abcd.unknown
    $CCACHE -F 0 -M 0 -c >/dev/null # update counters
    checkstat '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

    testname="old unknown file"
    $CCACHE -C >/dev/null
    prepare_cleanup_test $CCACHE_DIR/a
    # (9/10) * 30 * 16 = 432
    $CCACHE -F 432 -M 0 >/dev/null
    touch $CCACHE_DIR/a/abcd.unknown
    backdate $CCACHE_DIR/a/abcd.unknown
    $CCACHE -c >/dev/null
    if [ -f $CCACHE_DIR/a/abcd.unknown ]; then
        test_failed "$CCACHE_DIR/a/abcd.unknown not removed"
    fi

    testname="cleanup of tmp files"
    $CCACHE -C >/dev/null
    touch $CCACHE_DIR/a/abcd.tmp.efgh
    $CCACHE -c >/dev/null # update counters
    checkstat '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

    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
}

pch_suite() {
    unset CCACHE_NODIRECT

    cat <<EOF >pch.c
#include "pch.h"
int main()
{
  void *p = NULL;
  return 0;
}
EOF
    cat <<EOF >pch.h
#include <stdlib.h>
EOF
    cat <<EOF >pch2.c
int main()
{
  void *p = NULL;
  return 0;
}
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
    else
        echo "Compiler (`$COMPILER --version | head -1`) doesn't support precompiled headers -- not running pch test"
        return
    fi

    # 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.

    if [ $COMPILER_TYPE_CLANG -eq 1 ]; then
        clang_pch_suite
    else
        gcc_pch_suite
    fi
}

gcc_pch_suite() {
    ##################################################################
    # Tests for creating a .gch without opt-in.

    backdate pch.h

    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

    ##################################################################
    # Tests for creating a .gch with opt-in.

    backdate pch.h

    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
    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
    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
    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
    if [ ! -f pch.gch ]; then
        test_failed "pch.gch missing"
    fi
    rm pch.gch

    ##################################################################
    # Tests for using a .gch.

    rm -f pch.h
    backdate pch.h.gch

    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
    # Preprocessor error because GCC can't find the real include file when
    # trying to preprocess:
    checkstat '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
    # Must enable sloppy time macros:
    checkstat "can't use precompiled header" 1

    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
    # Must enable sloppy time macros:
    checkstat "can't use precompiled header" 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"
    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.

    backdate pch.h

    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' 1
    rm -f pch.h.gch
    $CCACHE $COMPILER $SYSROOT -c pch.h
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    if [ ! -f pch.h.gch ]; then
        test_failed "pch.h.gch missing"
    fi

    testname="create .gch, no -c, -o"
    $CCACHE -Cz >/dev/null
    $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    if [ ! -f pch.gch ]; then
        test_failed "pch.gch missing"
    fi
    rm pch.gch

    ##################################################################
    # Tests for using a .gch.

    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
    # Must enable sloppy time macros:
    checkstat "can't use precompiled header" 1

    testname="gch, no -fpch-preprocess, -include, sloppy time macros"
    $CCACHE -Cz >/dev/null
    CCACHE_SLOPPINESS=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=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=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=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"
    echo "updated" >>pch.h.gch # clang seems to cope with this...
    backdate pch.h.gch
    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=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=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

    ##################################################################
    # Tests for creating a .pth.

    backdate pch.h

    testname="create .pth, -c, -o"
    $CCACHE -zC >/dev/null
    $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
    checkstat 'cache hit (direct)' 0
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    rm -f pch.h.pth
    $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
    checkstat 'cache hit (direct)' 1
    checkstat 'cache hit (preprocessed)' 0
    checkstat 'cache miss' 1
    if [ ! -f pch.h.pth ]; then
        test_failed "pch.h.pth missing"
    fi

    ##################################################################
    # Tests for using a .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
    # Must enable sloppy time macros:
    checkstat "can't use precompiled header" 1

    testname="pth, no -fpch-preprocess, -include, sloppy time macros"
    $CCACHE -Cz >/dev/null
    CCACHE_SLOPPINESS=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=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...
    backdate pch.h.pth
    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="pth, preprocessor mode"
    $CCACHE -Cz >/dev/null
    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=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=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"
    echo "updated" >>pch.h.pth # clang seems to cope with this...
    backdate pch.h.pth
    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=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=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'
}

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"
}

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
}

symlinks_suite() {
    ##################################################################
    testname="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 $PWD/d3/c.c
    $COMPILER c.o -o c
    result=$(./c)
    if [ "$result" != OK ]; then
        test_failed "Incorrect header file used"
    fi

    cd ..
    rm -rf dir

    ##################################################################
    testname="symlink to source file"

    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

    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

    cd ..
    rm -rf dir
}

######################################################################
# main program

if pwd | grep '[^A-Za-z0-9/.,=_%+-]' >/dev/null 2>&1; then
    cat <<EOF
Error: The test suite doesn't work in directories with whitespace or other
funny characters in the name. Sorry.
EOF
    exit 1
fi

suites="$*"
if [ -n "$CC" ]; then
    COMPILER="$CC"
else
    COMPILER=gcc
fi
if [ -z "$CCACHE" ]; then
    CCACHE=`pwd`/ccache
fi

# save the type of compiler because some test may not work on all compilers
COMPILER_TYPE_CLANG=0
COMPILER_TYPE_GCC=0

COMPILER_USES_LLVM=0
HOST_OS_APPLE=0

compiler_version="`$COMPILER --version 2>&1 | head -1`"
case $compiler_version in
    *gcc*|*g++*|2.95*)
        COMPILER_TYPE_GCC=1
        ;;
    *clang*)
        COMPILER_TYPE_CLANG=1
        ;;
    *)
        echo "WARNING: Compiler $COMPILER not supported (version: $compiler_version) -- not running tests" >&2
        exit 0
        ;;
esac

case $compiler_version in
    *llvm*|*LLVM*)
        COMPILER_USES_LLVM=1
        ;;
esac

host_os="`uname -s`"
case $host_os in
    *Darwin*)
        HOST_OS_APPLE=1
        ;;
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_APPLE -eq 1 ]; then
    # Grab the developer directory from the environment or try xcode-select
    if [ "$XCODE_DEVELOPER_DIR" = "" ]; then
      XCODE_DEVELOPER_DIR=`xcode-select --print-path`
      if [ "$XCODE_DEVELOPER_DIR" = "" ]; then
        echo "Error: XCODE_DEVELOPER_DIR environment variable not set and xcode-select path not set"
        exit 1
      fi
    fi

    # Choose the latest SDK if an SDK root is not set
    MAC_PLATFORM_DIR=$XCODE_DEVELOPER_DIR/Platforms/MacOSX.platform
    if [ "$SDKROOT" = "" ]; then
        SDKROOT="`eval ls -f -1 -d \"$MAC_PLATFORM_DIR/Developer/SDKs/\"*.sdk | tail -1`"
        if [ "$SDKROOT" = "" ]; then
            echo "Error: Cannot find a valid SDK root directory"
            exit 1
        fi
    fi

    SYSROOT="-isysroot `echo \"$SDKROOT\" | sed 's/ /\\ /g'`"
else
    SYSROOT=
fi

# ---------------------------------------

all_suites="
base
link          !win32
hardlink
nlevels4
nlevels1
basedir       !win32
direct
compression
readonly
readonly_direct
extrafiles
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

echo compiler: `which $COMPILER`
echo version: `$COMPILER --version`
echo test dir: $TESTDIR

if [ -z "$suites" ]; then
    suites="$all_suites"
fi

for suite in $suites; do
    run_suite $suite
done

# ---------------------------------------

cd ..
rm -rf $TESTDIR
echo test done - OK
exit 0
