#!/bin/bash # # kpatch integration test framework # # Copyright (C) 2014 Josh Poimboeuf # # 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 2 # 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. # # # This is a basic integration test framework for kpatch, which tests building, # loading, and unloading patches, as well as any other related custom tests. # # This script looks for test input files in the current directory. It expects # certain file naming conventions: # # - foo.patch: patch that should build successfully # # - foo-SLOW.patch: patch that should be skipped in the quick test # # - bar-FAIL.patch: patch that should fail to build # # - foo-LOADED.test: executable which tests whether the foo.patch module is # loaded. It will be used to test that loading/unloading the patch module # works as expected. # # Any other *.test files will be executed after all the patch modules have been # built from the *.patch files. They can be used for more custom tests above # and beyond the simple loading and unloading tests. SCRIPTDIR="$(readlink -f $(dirname $(type -p $0)))" ROOTDIR="$(readlink -f $SCRIPTDIR/../..)" # TODO: option to use system-installed binaries instead KPATCH="sudo $ROOTDIR/kpatch/kpatch" RMMOD="sudo rmmod" KPATCHBUILD="$ROOTDIR"/kpatch-build/kpatch-build ERROR=0 LOG=test.log rm -f $LOG usage() { echo "usage: $0 [options]" >&2 echo " -h, --help Show this help message" >&2 echo " -c, --cached Don't rebuild patch modules" >&2 echo " -q, --quick Just combine all patches into one module for testing" >&2 } options=$(getopt -o hcq -l "help,cached,quick" -- "$@") || exit 1 eval set -- "$options" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; -c|--cached) SKIPBUILD=1 ;; -q|--quick) QUICK=1 ;; esac shift done error() { echo "ERROR: $@" |tee -a $LOG >&2 ERROR=$((ERROR + 1)) } log() { echo "$@" |tee -a $LOG } unload_all() { for i in `lsmod |egrep '^kpatch' |awk '{print $1}'`; do if [[ $i != kpatch ]]; then $KPATCH unload $i >> $LOG 2>&1 || error "\"kpatch unload $i\" failed" fi done if lsmod |egrep -q '^kpatch'; then $RMMOD kpatch >> $LOG 2>&1 || error "\"rmmod kpatch\" failed" fi } build_module() { file=$1 prefix=${file%%.patch} module=kpatch-$prefix.ko [[ $prefix = COMBINED ]] && return if [[ $prefix =~ -FAIL ]]; then shouldfail=1 else shouldfail=0 fi if [[ $SKIPBUILD -eq 1 ]]; then skip=0 [[ $shouldfail -eq 1 ]] && skip=1 [[ -e $module ]] && skip=1 [[ $skip -eq 1 ]] && log "skipping build: $prefix" && return fi log "build: $prefix" if ! $KPATCHBUILD $file >> $LOG 2>&1; then [[ $shouldfail -eq 0 ]] && error "$prefix: build failed" else [[ $shouldfail -eq 1 ]] && error "$prefix: build succeeded when it should have failed" fi } run_load_test() { file=$1 prefix=${file%%.patch} module=kpatch-$prefix.ko testprog=$prefix-LOADED.test [[ $prefix = COMBINED ]] && return [[ $prefix =~ -FAIL ]] && return if [[ ! -e $module ]]; then log "can't find $module, skipping" return fi if [[ -e $testprog ]]; then log "load test: $prefix" else log "load test: $prefix (no test prog)" fi if ! $KPATCH replace $module >> $LOG 2>&1; then error "$prefix: kpatch replace failed" return fi if [[ -e $testprog ]] && ! ./$testprog >> $LOG 2>&1; then error "$prefix: $testprog failed after kpatch replace" return fi if ! $KPATCH unload $module >> $LOG 2>&1; then error "$prefix: kpatch unload failed" return fi if [[ -e $testprog ]] && ./$testprog >> $LOG 2>&1; then error "$prefix: $testprog succeeded after kpatch unload" return fi if ! $KPATCH load $module >> $LOG 2>&1; then error "$prefix: kpatch load failed" return fi if [[ -e $testprog ]] && ! ./$testprog >> $LOG 2>&1; then error "$prefix: $testprog failed after kpatch load" return fi } run_custom_test() { testprog=$1 prefix=${file%%.test} [[ $testprog = *-LOADED.test ]] && return log "custom test: $prefix" if ! ./$testprog >> $LOG 2>&1; then error "$prefix: test failed" fi } build_combined_module() { if [[ $SKIPBUILD -eq 1 ]] && [[ -e kpatch-COMBINED.ko ]]; then log "skipping build: combined" return fi if ! which combinediff > /dev/null; then log "patchutils not installed, skipping combined module build" error "PLEASE INSTALL PATCHUTILS" return fi rm -f COMBINED.patch first=1 for file in *.patch; do prefix=${file%%.patch} [[ $prefix =~ -FAIL ]] && continue [[ $prefix =~ -SLOW ]] && continue log "combine: $prefix" if [[ $first -eq 1 ]]; then cp -f $file COMBINED.patch first=0 continue fi combinediff COMBINED.patch $file > TMP.patch mv -f TMP.patch COMBINED.patch done log "build: combined module" if ! $KPATCHBUILD COMBINED.patch >> $LOG 2>&1; then error "combined build failed" fi } run_combined_test() { if [[ ! -e kpatch-COMBINED.ko ]]; then log "can't find kpatch-COMBINED.ko, skipping" return fi log "load test: combined module" if ! $KPATCH replace kpatch-COMBINED.ko >> $LOG 2>&1; then error "combined: kpatch replace failed" return fi for testprog in *.test; do [[ $testprog != *-LOADED.test ]] && continue if ! ./$testprog >> $LOG 2>&1; then error "combined: $testprog failed" fi done } echo "clearing printk buffer" sudo dmesg -C cd "$SCRIPTDIR" if [[ $QUICK != 1 ]]; then for file in *.patch; do build_module $file done fi build_combined_module unload_all if [[ $QUICK != 1 ]]; then for file in *.patch; do run_load_test $file done fi run_combined_test if [[ $QUICK != 1 ]]; then for file in *.test; do unload_all run_custom_test $file done fi unload_all dmesg |grep -q "Call Trace" && error "kernel error detected in printk buffer" if [[ $ERROR -gt 0 ]]; then log "$ERROR errors encountered" echo "see test.log for more information" else log "SUCCESS" fi exit $ERROR