From fcf48c444208c84985e7f41f816156b73331d910 Mon Sep 17 00:00:00 2001 From: Filip Bucek Date: Tue, 5 Nov 2019 16:27:07 +0100 Subject: [PATCH] adding code coverage --- .gitignore | 68 +++++++++ .gitlab-ci.yml | 41 +++--- scripts/build.sh | 302 ++++++++++++++++++++++++++++++++++++++++ scripts/codecoverage.sh | 125 +++++++++++++++++ 4 files changed, 520 insertions(+), 16 deletions(-) create mode 100644 .gitignore create mode 100755 scripts/build.sh create mode 100755 scripts/codecoverage.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e41db47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# project specific + +doc/xml +doc/html +doc/latex +doc/api.md + +build/ +install/ + + +# common +*~ +.DS_Store +.idea +lib/* + +# Python +__pycache__ + +# C++ objects and libs + +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Python +*.pyc + +# CMake + +*.txt.user +*.txt.user.* + +# Qt-es + +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +*.qm +moc_*.cpp +qrc_*.cpp +ui_*.h +Makefile* + +# QBS +default + +# QtCreator + +*.autosave + +#QtCtreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# XCode +xcuserdata/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 485372a..c8574cc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,7 +15,7 @@ build: # name of task - mkdir -p build - cd build - qmake ../core-sdk.pro - - make -j1 + - make -j4 cache: paths: - build/ @@ -24,28 +24,37 @@ cache: # ######## # Test ########## -test: # name of task - stage: test # in what stage it will run ( 1. build then test etc ) - tags: # Tags are used to choose correct gitlab-runner - - shell # spaceti-runner server has running runners and one has tag 'hw' - script: # run in bash +test: + stage: test + tags: + - shell + script: - cd build - make check test -valgrind: # name of task - stage: test # in what stage it will run ( 1. build then test etc ) - tags: # Tags are used to choose correct gitlab-runner - - shell # spaceti-runner server has running runners and one has tag 'hw' - script: # run in bash +valgrind: + stage: test + tags: + - shell + script: - cd build - valgrind make check test - # Quality of code cppcheck: - stage: test # in what stage it will run ( 1. build then test etc ) - tags: # Tags are used to choose correct gitlab-runner - - shell # spaceti-runner server has running runners and one has tag 'hw' - # allow_failure: true + stage: test + tags: + - shell + # Probably needed to allow failure + #allow_failure: true script: # run in bash - scripts/cppcheck.sh + + +codecoverage: + stage: test + tags: + - shell + script: + - scripts/codecoverage.sh + coverage: '/^\s+functions..:\s+(\d+\.\d+%).+$/' diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..7742500 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,302 @@ +#!/bin/bash + +# @see https://intoli.com/blog/exit-on-errors-in-bash-scripts/ +set -e + +trap 'LASTRES=$?; LAST=$BASH_COMMAND; if [[ LASTRES -ne 0 ]]; then msg_err "Command: \"$LAST\" exited with exit code: $LASTRES"; else msg_ok "finished";fi' EXIT + +SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +DIRNAME="${SRCDIR##*/}" + +main() { + setup + process_arguments "$@" +} + +function setup() { + setup_ascii + DEBUG="false" + CLEAN="false" + BUILDPATH=$SRCDIR"/../build" + CMAKE_INSTALL_PREFIX=$SRCDIR"/../build/bin" + + mkdir -p $BUILDPATH + export CTEST_OUTPUT_ON_FAILURE=1 + + GIT=`git -C "$SRCDIR" describe --tags --always` + GITSTATUS=`git status --porcelain` + GITMOD="" + if [ ! -z "$GITSTATUS" ]; then + GITMOD="-modified" + fi +} + +function process_arguments() { + # Help if no arguments + if [[ $# -eq 0 ]] ; then help ; exit 0 ; fi + + ## + # @see https://www.lifewire.com/pass-arguments-to-bash-script-2200571 + while getopts cdhxq:i: option; do + case "${option}" + in + c) CLEAN="true";; + d) DEBUG="true";; + q) QTVER=$OPTARG;; + i) CMAKE_INSTALL_PREFIX=$OPTARG;; + h) help;; + esac + done + shift $((OPTIND - 1)) # @see https://stackoverflow.com/a/52708357/1917249 + + + # Process arguments + if [ ! -z $QTVER ]; then QTDIR=${HOME}/Qt/$QTVER/clang_64; fi + export CMAKE_PREFIX_PATH=$QTDIR + + ## Processing rest of arguments + for arg in "$@"; do + case "$arg" in + check) check;; + build) build;; + test) test;; + testcc) testcc;; + opencc) opencc;; + deploy) deploy;; + cppcheck) cpp-check;; + help) help;; + esac + done +} + +function check() { + if [ -z $QTDIR ]; then fail "QTDIR not set"; else pass "QTDIR is set" ; fi + if [ ! -d $QTDIR ]; then fail "QTDIR does not exists"; else pass "QTDIR exists:" $QTDIR ; fi + + if [ -f "/usr/local/bin/cmake" ]; then pass "cmake installed"; else fail "cmake not installed: ${BLUE}brew install cmake${NC}"; fi + if [ -f "/usr/local/bin/ccache" ]; then pass "ccache installed"; else fail "ccache not installed: ${BLUE}brew install ccache${NC}"; fi + + echo "--------------------" + echo "Qt ver : $QTVER" + echo "Qt : $CMAKE_PREFIX_PATH" + echo "GIT : $GIT$GITMOD" + echo "Debug : $DEBUG" + echo "Clean : $CLEAN" + echo "Makeflags : $MAKEFLAGS" + echo "Source : $SRCDIR" + echo "Basename : $DIRNAME" + echo "BuildPath : $BUILDPATH" + echo "Install : $CMAKE_INSTALL_PREFIX" + echo "--------------------" +} + +function cpp-check() { + info "CppCheck" + #if ! cppcheck src --enable=all -i build --suppress='*:test' --error-exitcode=1; then msg_warn "CppCheck with some errors"; exit 0 fi + cppcheck src --enable=all -i build --suppress='*:test' --error-exitcode=1 +} + +function build() { + info "Building: configuration" + cd $BUILDPATH + mkdir -p $DIRNAME/release + cd $DIRNAME/release + if [ "$CLEAN" == "true" ]; then rm -f CMakeCache.txt; fi + + info "Building: cmake generation" + cmake $SRCDIR -DA_UNITTESTS=OFF -DCMAKE_INSTALL_PREFIX=$CMAKE_INSTALL_PREFIX + + info "Building: building"; + make install +} + +function test() { + info "Testing: configuration" + cd $BUILDPATH + mkdir -p $DIRNAME/test + cd $DIRNAME/test + if [ "$CLEAN" == "true" ]; then rm -f CMakeCache.txt; fi + info "Testing: cmake generation" + cmake $SRCDIR -DCMAKE_INSTALL_PREFIX=$CMAKE_INSTALL_PREFIX + info "Testing: building" + cmake --build . --target all + info "Testing: run" + ctest +} + +function testcc() { + info "Coverage: configuration" + PROJBUILD="$BUILDPATH"/$DIRNAME/testcc + cd $BUILDPATH + mkdir -p $DIRNAME/testcc + + echo $PWD + + cd $DIRNAME/testcc + if [ "$CLEAN" == "true" ]; then rm -f CMakeCache.txt; fi + info "Coverage: cmake generation" + cmake $SRCDIR -DA_COVERAGE=ON -DCMAKE_INSTALL_PREFIX=$CMAKE_INSTALL_PREFIX + + info "Coverage: Remove previous *.gcda files" + echo "Projbuild: $PROJBUILD" + find $PROJBUILD -name *.gcda -exec rm "{}" \; + + info "Coverage: building" + cmake --build . --target all + + info "Coverage: run" + ctest + + CCDIR="$BUILDPATH"/$DIRNAME/testcc-info + CODESOURCEDIR=$SRCDIR"/src" + HTML_RESULTS_DIR="$BUILDPATH"/$DIRNAME/testcc-html + COVERAGE_FILTER="*Qt*.framework* *Xcode.app* *.moc *moc_*.cpp */test/* *qrc_* ui_* */invsdk/* *qt.headers */build/* *main*.*" + + echo "x--------------------" + echo "Qt : $CMAKE_PREFIX_PATH" + echo "Source : $SRCDIR" + echo "Build : $BUILDPATH" + echo "Build proj : $PROJBUILD" + echo "Clean : $INVCLEAN" + echo "Makeflags : $MAKEFLAGS" + echo "CCDIR : $CCDIR" + echo "CODESOURCEDIR : $CODESOURCEDIR" + echo "HTML_RESULTS_DIR : $HTML_RESULTS_DIR" + echo "COVERAGE_FILTER : $COVERAGE_FILTER" + echo "--------------------" + + echo "[ Create dummy files to ensure not tested class are acounted ]" + find $PROJBUILD -name "*.gcno" -exec sh -c 'touch -a "${1%.gcno}.gcda"' _ {} \; + mkdir -p $CCDIR + + echo "[ Make dir 'codecoverage' clean ]" + rm -f $CCDIR/*.* + rm -rf $PROJBUILD/html + + + echo "[ Copy .gc?? file from $PROJBUILD into 'codecoverage' dir ]" + find $PROJBUILD -name *.gc?? -exec cp "{}" $CCDIR \; + + + ########################## + # RUN LCOV + ########################## + + # ${1} is the directory containing the .gcno files (%{PROJBUILD} in Qt Creator) + LCOV=lcov + GENHTML=genhtml + BROWSER="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + echo "[ Generating initial code coverage info report]" + ${LCOV} --base-directory ${CODESOURCEDIR} -d ${CCDIR} -c -o ${CCDIR}/coverage.info + + echo "[ Filtering code coverage info report ]" + ${LCOV} -r ${CCDIR}/coverage.info $COVERAGE_FILTER -o ${CCDIR}/coverage-filtered.info + + echo "[ Creating html output ]" + mkdir -p ${HTML_RESULTS_DIR} + "${GENHTML}" -o "${HTML_RESULTS_DIR}" "${CCDIR}/coverage-filtered.info" + + echo "[ Reseting coverage counts ]" + "${LCOV}" -d "${CCDIR}" -z +} + +function opencc() { + HTML_RESULTS_DIR="$BUILDPATH"/$DIRNAME/testcc-html + info "Open: code coverage index.html" + BROWSER="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + + open "${HTML_RESULTS_DIR}/index.html" & + #open -a "${BROWSER}" "${HTML_RESULTS_DIR}/index.html" & +} + + +function deploy() { + cd $SRCDIR + cd ../build/$DIRNAME + CURDIR=$PWD + APP_NAME=$DIRNAME + + INSTALL_ROOT="$CMAKE_INSTALL_PREFIX/$APP_NAME" + APP="$INSTALL_ROOT/$APP_NAME.app" + APPFW=$APP"/Contents/Frameworks" + APP_NEWPATH=$INSTALL_ROOT"/"$APP_NAME"-"$QTVER"-"$GIT$GITMOD".app" + APP_LATEST="$INSTALL_ROOT/$APP_NAME-$QTVER-latest.app" + QTDIR=$CMAKE_PREFIX_PATH + + echo "--------------------" + echo "INSTALL_ROOT : $INSTALL_ROOT" + echo "APP : $APP" + echo "APPFW : $APPFW" + echo "APP_NEWPATH : $APP_NEWPATH" + echo "APP_LATEST : $APP_LATEST" + echo "QTDIR : $QTDIR" + echo "--------------------" + + [ -d $APPFW ] || mkdir -p $APPFW + + echo "[ macdeployqt: deploying ]" + $QTDIR"/bin/macdeployqt" $APP -appstore-compliant + + # remove new path + if [ -d $APP_NEWPATH ]; then rm -rf $APP_NEWPATH; fi + + # remove applatest + if [ -d $APP_LATEST ]; then rm -rf $APP_LATEST; fi + + #copy app to new path + cp -r "$APP" "$APP_NEWPATH" + + # mv app to latest + mv "$APP" "$APP_LATEST" +} + +function help() { + info "Build script v1.0" + echo "check Check configuration" + echo "build Building" + echo "test Unittesting" + echo "testcc Code Coverage" + echo "opencc Open Code Coverage index.html"s +} + +# ################ +# Output funcitons +# ################ +function setup_ascii() { + RED='\033[0;31m' + GREEN='\033[0;32m' + BLUE='\033[0;34m' + ORANGE='\033[0;33m' + NC='\033[0m' # No Color + IN="[${BLUE} info ${NC}]" + ER="[${RED} ERR ${NC}]" + FAIL="[${RED}FAIL${NC}]" + OK="[${GREEN} OK ${NC}]" +} + +function msg_err() { + echo -e "[${RED} error ${NC}] $@" +} + +function msg_warn() { + echo -e "[${ORANGE} warrning ${NC}] $@" +} + +function msg_ok() { + echo -e "[${GREEN} ok ${NC}] $@" +} + +function info () { + echo -e "[${BLUE} $@ ${NC}]" +} + +function pass() { + echo -e "[${GREEN}PASS${NC}] $@" +} + +function fail() { + echo -e "[${RED}FAIL${NC}] $@" +} + +# Run main function +main "$@" diff --git a/scripts/codecoverage.sh b/scripts/codecoverage.sh new file mode 100755 index 0000000..27e8fda --- /dev/null +++ b/scripts/codecoverage.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +# @see https://intoli.com/blog/exit-on-errors-in-bash-scripts/ +set -e + +trap 'LASTRES=$?; LAST=$BASH_COMMAND; if [[ LASTRES -ne 0 ]]; then fail "Command: \"$LAST\" exited with exit code: $LASTRES"; elif [ $FAIL == 1 ]; then fail finished with error; exit 1; else pass "finished all";fi' EXIT + +SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +DIRNAME="${SRCDIR##*/}" + +BUILDPATH=$SRCDIR"/../build/testcc" +COVERAGE_DIR=$SRCDIR"/../build/testcc-info" +SOURCE_CODE=$SRCDIR"/.." +HTML_RESULTS_DIR=$SRCDIR"/../build/testcc-html" +COVERAGE_FILTER="*Qt*.framework* *Xcode.app* *.moc *moc_*.cpp */test/* *qrc_* ui_* */invsdk/* *qt.headers */build/* *main*.* *c++*" + + +main() { + setup + testcc +} + +function setup() { + mkdir -p $BUILDPATH + + GIT=`git -C "$SRCDIR" describe --tags --always` + GITSTATUS=`git status --porcelain` + GITMOD="" + if [ ! -z "$GITSTATUS" ]; then + GITMOD="-modified" + fi +} + + +function testcc() { + info "Creating: $BUILDPATH" + mkdir -p $BUILDPATH + cd $BUILDPATH + + info "Generate makefile with coverage option" + qmake $SOURCE_CODE/core-sdk.pro "QMAKE_CXXFLAGS += --coverage" "QMAKE_LFLAGS += --coverage" + + info "Remove previous *.gcda files" + find $BUILDPATH -name *.gcda -exec rm "{}" \; + + info "Build" + make -j4 # build + + info "Run unittests" + make check test + + + echo "--------------------" + echo "Source : $SRCDIR" + echo "Build : $BUILDPATH" + echo "Makeflags : $MAKEFLAGS" + echo "COVERAGE_DIR : $COVERAGE_DIR" + echo "SOURCE_CODE : $SOURCE_CODE" + echo "HTML_RESULTS_DIR : $HTML_RESULTS_DIR" + echo "COVERAGE_FILTER : $COVERAGE_FILTER" + echo "--------------------" + + #exit 0 + + info "Create dummy files to ensure not tested class are acounted" + find $BUILDPATH -name "*.gcno" -exec sh -c 'touch -a "${1%.gcno}.gcda"' _ {} \; + mkdir -p $COVERAGE_DIR + + info "Make dir 'codecoverage' clean" + rm -f $COVERAGE_DIR/*.* + rm -rf $HTML_RESULTS_DIR + + + info "Copy .gc?? file from $BUILDPATH into 'codecoverage' dir" + find $BUILDPATH -name *.gc?? -exec cp "{}" $COVERAGE_DIR \; + + + ########################## + # RUN LCOV + ########################## + + # ${1} is the directory containing the .gcno files (%{PROJBUILD} in Qt Creator) + LCOV=lcov + GENHTML=genhtml + BROWSER="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + info "Generating initial code coverage info report" + cd $BUILDPATH + ${LCOV} -d ${BUILDPATH} -c -o ${COVERAGE_DIR}/coverage.info + + info "Filtering code coverage info report" + ${LCOV} -r ${COVERAGE_DIR}/coverage.info $COVERAGE_FILTER -o ${COVERAGE_DIR}/coverage-filtered.info + + info "Creating html output" + mkdir -p ${HTML_RESULTS_DIR} + cd $BUILDPATH + "${GENHTML}" -o "${HTML_RESULTS_DIR}" "${COVERAGE_DIR}/coverage-filtered.info" + + info "Reseting coverage counts" + "${LCOV}" -d "${COVERAGE_DIR}" -z + + open "${HTML_RESULTS_DIR}/index.html" & +} + +# ################ +# Output funcitons +# ################ +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +function info () { + echo -e "[${BLUE} $@ ${NC}]" +} + +function pass() { + echo -e "[${GREEN}PASS${NC}] $@" +} + +function fail() { + FAIL=1; echo -e "[${RED}FAIL${NC}] $@" +} + +# Run main function +main "$@"