#!/bin/bash # SPDX-License-Identifier: MIT # Copyright 2020 Google LLC # # Use of this source code is governed by an MIT-style # license that can be found in the LICENSE file or at # https://opensource.org/licenses/MIT. # # # Test script for fsverity-utils. Runs 'make check' in lots of configurations, # runs static analysis, and does a few other tests. # # Note: for more test coverage, in addition to running this script, also build # fsverity-utils into a kvm-xfstests test appliance and run # 'kvm-xfstests -c ext4,f2fs -g verity' set -e -u -o pipefail cd "$(dirname "$0")/.." log() { echo "[$(date)] $*" 1>&2 } fail() { echo "FAIL: $*" 1>&2 exit 1 } TMPDIR=$(mktemp -d -t libfsverity_test.XXXXXXXXX) trap 'rm -r "$TMPDIR"' EXIT # Both stdout and stderr go to log file. # Only stderr goes to terminal. echo "Starting fsverity-utils tests. See run-tests.log for full output." rm -f run-tests.log exec >> run-tests.log exec 2> >(tee -ia run-tests.log 1>&2) MAKE="make -j$(getconf _NPROCESSORS_ONLN)" TEST_FUNCS=() static_linking_test() { log "Build and test with statically linking" $MAKE CFLAGS="-Werror" if ldd fsverity | grep libfsverity.so; then fail "fsverity binary should be statically linked to libfsverity by default" fi ./fsverity --version log "Check that all global symbols are prefixed with \"libfsverity_\"" if nm libfsverity.a | grep ' T ' | grep -v " libfsverity_"; then fail "Some global symbols are not prefixed with \"libfsverity_\"" fi } TEST_FUNCS+=(static_linking_test) dynamic_linking_test() { log "Build and test with dynamic linking" $MAKE CFLAGS="-Werror" USE_SHARED_LIB=1 check if ! ldd fsverity | grep libfsverity.so; then fail "fsverity binary should be dynamically linked to libfsverity when USE_SHARED_LIB=1" fi log "Check that all exported symbols are prefixed with \"libfsverity_\"" if nm libfsverity.so | grep ' T ' | grep -v " libfsverity_"; then fail "Some exported symbols are not prefixed with \"libfsverity_\"" fi } TEST_FUNCS+=(dynamic_linking_test) cplusplus_test() { $MAKE CFLAGS="-Werror" libfsverity.so log "Test using libfsverity from C++ program" cat > "$TMPDIR/test.cc" < #include int main() { std::cout << libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256) << std::endl; } EOF c++ -Wall -Werror "$TMPDIR/test.cc" -Iinclude -L. -lfsverity -o "$TMPDIR/test" [ "$(LD_LIBRARY_PATH=. "$TMPDIR/test")" = "32" ] rm "${TMPDIR:?}"/* } TEST_FUNCS+=(cplusplus_test) untracked_files_test() { log "Check that build doesn't produce untracked files" $MAKE CFLAGS="-Werror" all test_programs if git status --short | grep -q '^??'; then git status fail "Build produced untracked files (check 'git status'). Missing gitignore entry?" fi } TEST_FUNCS+=(untracked_files_test) uninstall_test() { log "Test that 'make uninstall' uninstalls all files" make DESTDIR="$TMPDIR" install if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" = 0 ]; then fail "'make install' didn't install any files" fi make DESTDIR="$TMPDIR" uninstall if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" != 0 ]; then fail "'make uninstall' didn't uninstall all files" fi rm -r "${TMPDIR:?}"/* } TEST_FUNCS+=(uninstall_test) dash_test() { log "Build, install, and uninstall with dash" make clean SHELL=/bin/dash make DESTDIR="$TMPDIR" SHELL=/bin/dash install make DESTDIR="$TMPDIR" SHELL=/bin/dash uninstall } TEST_FUNCS+=(dash_test) license_test() { log "Check that all files have license and copyright info" list="$TMPDIR/filelist" filter_license_info() { # files to exclude from license and copyright info checks grep -E -v '(\.gitignore|LICENSE|.*\.md|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)' } git grep -L 'SPDX-License-Identifier: MIT' \ | filter_license_info > "$list" || true if [ -s "$list" ]; then fail "The following files are missing an appropriate SPDX license identifier: $(<"$list")" fi # For now some people still prefer a free-form license statement, not just SPDX. git grep -L 'Use of this source code is governed by an MIT-style' \ | filter_license_info > "$list" || true if [ -s "$list" ]; then fail "The following files are missing an appropriate license statement: $(<"$list")" fi git grep -L '\' | filter_license_info > "$list" || true if [ -s "$list" ]; then fail "The following files are missing a copyright statement: $(<"$list")" fi rm "$list" } TEST_FUNCS+=(license_test) gcc_test() { log "Build and test with gcc (-O2)" $MAKE CC=gcc CFLAGS="-O2 -Werror" check log "Build and test with gcc (-O3)" $MAKE CC=gcc CFLAGS="-O3 -Werror" check } TEST_FUNCS+=(gcc_test) clang_test() { log "Build and test with clang (-O2)" $MAKE CC=clang CFLAGS="-O2 -Werror" check log "Build and test with clang (-O3)" $MAKE CC=clang CFLAGS="-O3 -Werror" check } TEST_FUNCS+=(clang_test) 32bit_test() { log "Build and test with gcc (32-bit)" $MAKE CC=gcc CFLAGS="-O2 -Werror -m32" check } TEST_FUNCS+=(32bit_test) sanitizers_test() { log "Build and test with clang + UBSAN" $MAKE CC=clang \ CFLAGS="-O2 -Werror -fsanitize=undefined -fno-sanitize-recover=undefined" \ check log "Build and test with clang + ASAN" $MAKE CC=clang \ CFLAGS="-O2 -Werror -fsanitize=address -fno-sanitize-recover=address" \ check log "Build and test with clang + unsigned integer overflow sanitizer" $MAKE CC=clang \ CFLAGS="-O2 -Werror -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" \ check log "Build and test with clang + CFI" $MAKE CC=clang CFLAGS="-O2 -Werror -fsanitize=cfi -flto -fvisibility=hidden" \ AR=llvm-ar check } TEST_FUNCS+=(sanitizers_test) valgrind_test() { log "Build and test with valgrind" $MAKE TEST_WRAPPER_PROG="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" \ CFLAGS="-O2 -Werror" check } TEST_FUNCS+=(valgrind_test) boringssl_test() { log "Build and test using BoringSSL instead of OpenSSL" log "-> Building BoringSSL" $MAKE boringssl log "-> Building fsverity-utils linked to BoringSSL" $MAKE CFLAGS="-O2 -Werror" LDFLAGS="-Lboringssl/build/crypto" \ CPPFLAGS="-Iboringssl/include" LDLIBS="-lcrypto -lpthread" check } TEST_FUNCS+=(boringssl_test) openssl1_test() { log "Build and test using OpenSSL 1.0" $MAKE CFLAGS="-O2 -Werror" LDFLAGS="-L/usr/lib/openssl-1.0" \ CPPFLAGS="-I/usr/include/openssl-1.0" check } TEST_FUNCS+=(openssl1_test) openssl3_test() { log "Build and test using OpenSSL 3.0" OSSL3=$HOME/src/openssl/inst/usr/local LD_LIBRARY_PATH="$OSSL3/lib64" $MAKE CFLAGS="-O2 -Werror" \ LDFLAGS="-L$OSSL3/lib64" CPPFLAGS="-I$OSSL3/include" check } TEST_FUNCS+=(openssl3_test) unsigned_char_test() { log "Build and test using -funsigned-char" $MAKE CFLAGS="-O2 -Werror -funsigned-char" check } TEST_FUNCS+=(unsigned_char_test) signed_char_test() { log "Build and test using -fsigned-char" $MAKE CFLAGS="-O2 -Werror -fsigned-char" check } TEST_FUNCS+=(signed_char_test) windows_build_test() { log "Cross-compile for Windows (32-bit)" $MAKE CC=i686-w64-mingw32-gcc CFLAGS="-O2 -Werror" log "Cross-compile for Windows (64-bit)" $MAKE CC=x86_64-w64-mingw32-gcc CFLAGS="-O2 -Werror" } TEST_FUNCS+=(windows_build_test) sparse_test() { log "Run sparse" ./scripts/run-sparse.sh } TEST_FUNCS+=(sparse_test) clang_analyzer_test() { log "Run clang static analyzer" scan-build --status-bugs make CFLAGS="-O2 -Werror" all test_programs } TEST_FUNCS+=(clang_analyzer_test) shellcheck_test() { log "Run shellcheck" shellcheck scripts/*.sh 1>&2 } TEST_FUNCS+=(shellcheck_test) test_exists() { local tst=$1 local func for func in "${TEST_FUNCS[@]}"; do if [ "${tst}_test" = "$func" ]; then return 0 fi done return 1 } if [[ $# == 0 ]]; then for func in "${TEST_FUNCS[@]}"; do eval "$func" done else for tst; do if ! test_exists "$tst"; then echo 1>&2 "Unknown test: $tst" exit 2 fi done for tst; do eval "${tst}_test" done fi log "All tests passed!"