Tests: add unit testing framework using googletest

* enable coverage reports from gcc and disable optimizations via option to configure
* install googletest library into buildcache
* script to compile and run unit tests using cmake
* first set of unit tests originaly contributed by Keith Uplinger

There are several hardcoded paths and assumptions made in order to get this working on Travis CI. New tooling is using cmake for cross platform builds and as such is not easy to use with an autotools based system.
It's not ideal but better than nothing.
This commit is contained in:
Christian Beer 2019-04-20 12:06:24 +02:00
parent 99716be185
commit 6dbf55fd9a
16 changed files with 443 additions and 14 deletions

5
.gitignore vendored
View File

@ -130,6 +130,11 @@ locale/*/*.flag
.libs/
svn_version.h
## code coverage
*.gcov
*.gcno
*.gcda
# list of executables
apps/1sec
apps/concat

View File

@ -63,6 +63,7 @@ matrix:
before_install:
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then ( sudo apt-get -qq update ) fi
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then ( sudo apt-get install -y freeglut3-dev libxmu-dev libxi-dev libfcgi-dev libxss-dev libnotify-dev libxcb-util0-dev libsqlite3-dev libgtk2.0-dev libwebkitgtk-dev mingw-w64 binutils-mingw-w64-i686 binutils-mingw-w64-x86-64 gcc-mingw-w64 gcc-mingw-w64-i686 gcc-mingw-w64-x86-64 g++-mingw-w64 g++-mingw-w64-i686 g++-mingw-w64-x86-64 realpath p7zip-full ) fi
- if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BOINC_TYPE}" == "unit-test" ]]; then ( sudo pip install codecov ) fi
- if [[ "${BOINC_TYPE}" == "integration-test" ]]; then ( sudo apt-get install ansible/trusty-backports; sudo service mysql stop; ) fi
before_script:
@ -80,7 +81,7 @@ script:
- if [[ "${BOINC_TYPE}" == "apps-mingw" ]]; then ( cd lib && MINGW=x86_64-w64-mingw32 make -f Makefile.mingw wrapper ) fi
- if [[ "${BOINC_TYPE}" == "manager-osx" ]]; then ( ./3rdParty/buildMacDependencies.sh -q && ./mac_build/buildMacBOINC-CI.sh --no_shared_headers ) fi
- if [[ "${BOINC_TYPE}" == "manager-android" ]]; then ( cd android && ./buildAndroidBOINC-CI.sh --arch arm --silent && ./buildAndroidBOINC-CI.sh --arch arm64 --silent && ./buildAndroidBOINC-CI.sh --arch x86 --silent && ./buildAndroidBOINC-CI.sh --arch x86_64 --silent && cd BOINC && ./gradlew assemble -w && cd ../../ ) fi
- if [[ "${BOINC_TYPE}" == "unit-test" ]]; then ( /bin/true ) fi
- if [[ "${BOINC_TYPE}" == "unit-test" ]]; then ( ./3rdParty/buildLinuxDependencies.sh --gtest-only && ./configure --disable-client --disable-manager --enable-unit-tests CFLAGS="-g -O0" CXXFLAGS="-g -O0" && make && ./tests/executeUnitTests.sh --report-coverage ) fi
- if [[ "${BOINC_TYPE}" == "integration-test" ]]; then ( ./integration_test/executeTestSuite.sh ) fi
after_success:

67
3rdParty/buildGoogletestLinux.sh vendored Normal file
View File

@ -0,0 +1,67 @@
#!/bin/bash
# This file is part of BOINC.
# http://boinc.berkeley.edu
# Copyright (C) 2019 University of California
#
# BOINC is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# BOINC 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BOINC. If not, see <http://www.gnu.org/licenses/>.
# Script to build a googletest library for BOINC
# Usage:
# cd [path]/googletest-release-1.8.1/
# source path_to_this_script [--clean] [--prefix PATH]
#
# the --clean argument will force a full rebuild.
# if --prefix is given as absolute path the library is installed into there
doclean=""
debug_flag=""
lprefix=""
cmdline_prefix=""
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-clean|--clean)
doclean="yes"
;;
-prefix|--prefix)
lprefix="$2"
cmdline_prefix="--prefix=${lprefix}"
shift
;;
esac
shift # past argument or value
done
cd googletest
autoreconf -i -f
if [ $? -ne 0 ]; then cd ..; return 1; fi
./configure "${cmdline_prefix}"
if [ $? -ne 0 ]; then cd ..; return 1; fi
if [ "${doclean}" = "yes" ]; then
make clean
fi
make
if [ $? -ne 0 ]; then cd ..; return 1; fi
# make install is not supported so copy files manually
if [ "x${lprefix}" != "x" ]; then
mkdir -p ${lprefix}/lib ${lprefix}/include ${lprefix}/bin
(cp -r lib/.libs/*.a ${lprefix}/lib && cp -r include/gtest ${lprefix}/include && cp scripts/gtest-config ${lprefix}/bin)
if [ $? -ne 0 ]; then cd ..; return 1; fi
fi
cd ..
return 0

View File

@ -2,7 +2,7 @@
# This file is part of BOINC.
# http://boinc.berkeley.edu
# Copyright (C) 2017 University of California
# Copyright (C) 2019 University of California
#
# BOINC is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License
@ -51,6 +51,7 @@ ROOTDIR=$(pwd)
cache_dir=""
doclean=""
wxoption=""
gtest_only=""
build_config="Release"
while [[ $# -gt 0 ]]; do
key="$1"
@ -69,6 +70,9 @@ while [[ $# -gt 0 ]]; do
--disable-webview)
wxoption="--disable-webview ${wxoption} "
;;
--gtest-only)
gtest_only="yes"
;;
*)
echo "unrecognized option $key"
;;
@ -134,7 +138,11 @@ if [ "${doclean}" = "yes" ]; then
fi
#download_and_build $DIRNAME $FILENAME $DOWNLOADURL $BUILDSCRIPT
download_and_build "wxWidgets-3.0.2" "wxWidgets-3.0.2.tar.bz2" "https://sourceforge.net/projects/wxwindows/files/3.0.2/wxWidgets-3.0.2.tar.bz2" "${ROOTDIR}/3rdParty/buildWxLinux.sh ${wxoption}"
if [ "${gtest_only}" = "yes" ]; then
download_and_build "googletest-release-1.8.1" "release-1.8.1.tar.gz" "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" "${ROOTDIR}/3rdParty/buildGoogletestLinux.sh"
else
download_and_build "wxWidgets-3.0.2" "wxWidgets-3.0.2.tar.bz2" "https://sourceforge.net/projects/wxwindows/files/3.0.2/wxWidgets-3.0.2.tar.bz2" "${ROOTDIR}/3rdParty/buildWxLinux.sh ${wxoption}"
fi
# change back to root directory
cd ${ROOTDIR} || exit 1

View File

@ -71,7 +71,7 @@ svn_version.h: generate_svn_version.sh
# Add a stage target for staging a distribution
clean-generic:
rm -rf stage
rm -rf stage *.gcov
stage: all
if [ ! -d stage ] ; then mkdir stage ; fi

View File

@ -126,6 +126,12 @@ AC_ARG_ENABLE(apps,
[enable_apps=${enableval}],
[enable_apps=no])
AC_ARG_ENABLE(unit-tests,
AS_HELP_STRING([--enable-unit-tests],
[enable building the boinc unit tests]),
[enable_unit_tests=${enableval}],
[enable_unit_tests=no])
AC_ARG_ENABLE(pkg-libs,
AS_HELP_STRING([--enable-pkg-libs],
[Builds and installs components that would be present in a
@ -139,8 +145,9 @@ AC_ARG_ENABLE(pkg-libs,
enable_manager=no
enable_install_headers=no
enable_static=no
enable_boinczip=yes
enable_apps=no
enable_boinczip=yes
enable_apps=no
enable_unit_tests=no
],
[])
@ -156,8 +163,9 @@ AC_ARG_ENABLE(pkg-devel,
enable_client=no
enable_manager=no
enable_install_headers=yes
enable_boinczip=yes
enable_apps=no
enable_boinczip=yes
enable_apps=no
enable_unit_tests=no
],
[])
@ -173,8 +181,9 @@ AC_ARG_ENABLE(pkg-client,
enable_client=yes
enable_manager=no
enable_install_headers=no
enable_boinczip=no
enable_apps=no
enable_boinczip=no
enable_apps=no
enable_unit_tests=no
],
[])
@ -188,8 +197,9 @@ AC_ARG_ENABLE(pkg-manager,
enable_client=no
enable_manager=yes
enable_install_headers=no
enable_boinczip=no
enable_apps=no
enable_boinczip=no
enable_apps=no
enable_unit_tests=no
],
[])
@ -213,6 +223,9 @@ fi
if test x$enable_apps = xyes ; then
configured_to_build="${configured_to_build} apps"
fi
if test x$enable_unit_tests = xyes ; then
configured_to_build="${configured_to_build} unit-tests"
fi
if test -z "${configured_to_build}" ; then
AC_MSG_ERROR([
@ -1049,6 +1062,7 @@ AM_CONDITIONAL(ENABLE_MANAGER, [ test "x${ac_cv_have_wxwidgets}" = xyes -a "${en
AM_CONDITIONAL(ENABLE_LIBRARIES, [test "${enable_libraries}" = yes])
AM_CONDITIONAL(ENABLE_BOINCZIP, [test "${enable_boinczip}" = yes])
AM_CONDITIONAL(ENABLE_APPS, [test "${enable_apps}" = yes])
AM_CONDITIONAL(ENABLE_UNIT_TESTS, [test "${enable_unit_tests}" = yes])
AM_CONDITIONAL(ENABLE_BOINCCRYPT, [test "x${enable_server}" = xyes || test "x${enable_client}" = xyes ])
AM_CONDITIONAL(INSTALL_HEADERS, [test "${enable_install_headers}" = yes])
AM_CONDITIONAL(HAVE_CUDA_LIB, [test "${enable_client}" = yes -a -f ./coprocs/CUDA/posix/${boinc_platform}/libcudart.so])

View File

@ -244,7 +244,7 @@ $(LIBBOINC_FCGI_STATIC): libboinc_fcgi.la
$(LN) .libs/$(LIBBOINC_FCGI_STATIC) .
clean: clean-am
rm -f $(LIBBOINC_STATIC) $(LIBBOINC_CRYPT_STATIC) $(LIBBOINC_FCGI_STATIC)
rm -f $(LIBBOINC_STATIC) $(LIBBOINC_CRYPT_STATIC) $(LIBBOINC_FCGI_STATIC) *.gcno *.gcda *.gcov
endif
# end of "if ENABLE_LIBRARIES"

View File

@ -117,4 +117,11 @@ if test x${enable_generic_processor} = xyes ; then
;;
esac
fi
if test x${enable_unit_tests} = xyes ; then
BOINC_CHECK_CFLAG(--coverage)
BOINC_CHECK_CXXFLAG(--coverage)
LDFLAGS="$LDFLAGS --coverage"
fi
])

View File

@ -334,4 +334,4 @@ PHONY-start:
status stop: PHONY-start
@test -f $@ || @LN_S@ start $@ && test -f $@
CLEANFILES = status stop
CLEANFILES = status stop *.gcno *.gcda *.gcov

79
tests/executeUnitTests.sh Executable file
View File

@ -0,0 +1,79 @@
#!/bin/bash
# This file is part of BOINC.
# http://boinc.berkeley.edu
# Copyright (C) 2019 University of California
#
# BOINC is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# BOINC 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BOINC. If not, see <http://www.gnu.org/licenses/>.
# Script to execute googletest based unit tests for BOINC
# Usage:
# ./tests/executeUnitTests.sh [--report-coverage]
#
# if --report-coverage is given the test coverage will be reported to codecov.io
# check working directory because the script needs to be called like: ./tests/executeUnitTests.sh
if [ ! -d "tests" ]; then
echo "start this script in the source root directory"
exit 1
fi
ROOTDIR=$(pwd)
CI_RUN="${TRAVIS:-false}"
report=""
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--report-coverage)
report="yes"
;;
*)
echo "unrecognized option $key"
;;
esac
shift # past argument or value
done
command -v cmake >/dev/null 2>&1 || { echo >&2 "cmake is needed but not installed. Aborting."; exit 1; }
if [ "${report}" = "yes" ]; then
command -v gcov >/dev/null 2>&1 || { echo >&2 "gcov (lcov) is needed but not installed. Aborting."; exit 1; }
command -v codecov >/dev/null 2>&1 || { echo >&2 "codecov (pip install codecov) is needed but not installed. Aborting."; exit 1; }
fi
cd tests || exit 1
if [ -d "gtest" ]; then
rm -rf gtest
if [ $? -ne 0 ]; then cd ..; exit 1; fi
fi
mkdir gtest
if [ $? -ne 0 ]; then cd ..; exit 1; fi
cd gtest
cmake ../unit-tests
if [ $? -ne 0 ]; then cd ../..; exit 1; fi
make
if [ $? -ne 0 ]; then cd ../..; exit 1; fi
for T in lib sched; do
[ -d "${T}" ] && ./${T}/test_${T};
done
cd ../..
if [ "${report}" = "yes" ]; then
for T in lib sched; do
[ -d "${T}" ] && gcov -lp *.o >/dev/null;
done
codecov -X gcov
fi

View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 2.8)
project(BOINC_unit_testing)
enable_testing()
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_FLAGS "-g -Wall -Wextra -Werror --coverage")
find_package(Threads REQUIRED)
# There is no easy way to interface with the autotools driven build system in boinc
# so the paths below are hardcoded and are mainly suited for building on Travis CI
# TODO: make paths configurable and generate them from boinc make or switch boinc to cmake
include_directories(BEFORE "${PROJECT_SOURCE_DIR}/../../3rdParty/buildCache/linux/include")
include_directories("${PROJECT_SOURCE_DIR}/../..")
include_directories("${PROJECT_SOURCE_DIR}/../../sched")
include_directories("${PROJECT_SOURCE_DIR}/../../db")
include_directories("${PROJECT_SOURCE_DIR}/../../lib")
include_directories("${PROJECT_SOURCE_DIR}/../../tools")
include_directories("/usr/include/mariadb")
include_directories("/usr/include/mariadb/mysql")
find_library(GTEST_MAIN_LIB libgtest_main.a PATHS ${PROJECT_SOURCE_DIR}/../../3rdParty/buildCache/linux/lib NO_DEFAULT_PATH)
message(STATUS "gtest_main_lib: ${GTEST_MAIN_LIB}")
find_library(GTEST_LIB gtest PATHS ${PROJECT_SOURCE_DIR}/../../3rdParty/buildCache/linux/lib NO_DEFAULT_PATH)
message(STATUS "gtest_lib: ${GTEST_LIB}")
find_library(SCHED_LIB libsched.a PATHS ${PROJECT_SOURCE_DIR}/../../sched NO_DEFAULT_PATH)
find_library(BOINC_CRYPT_LIB libboinc_crypt.a PATHS ${PROJECT_SOURCE_DIR}/../../lib NO_DEFAULT_PATH)
find_library(BOINC_LIB libboinc.a PATHS ${PROJECT_SOURCE_DIR}/../../lib NO_DEFAULT_PATH)
find_library(MYSQL_LIB NAMES mariadb mysql)
message(STATUS "mysql_lib: ${MYSQL_LIB}")
add_subdirectory(lib)
add_subdirectory(sched)

View File

@ -0,0 +1,7 @@
file(GLOB SRCS *.cpp)
add_executable(test_lib ${SRCS})
TARGET_LINK_LIBRARIES(test_lib "${GTEST_LIB}" "${SCHED_LIB}" "${BOINC_CRYPT_LIB}" "${BOINC_LIB}" "${GTEST_MAIN_LIB}" pthread)
add_test(NAME test_lib COMMAND test_lib)

View File

@ -0,0 +1,96 @@
#include "gtest/gtest.h"
#include "common_defs.h"
#include "str_util.h"
#include <string>
#include <ios>
namespace test_str_util {
// The fixture for testing class Foo.
class test_str_util : public ::testing::Test {
protected:
// You can remove any or all of the following functions if its body
// is empty.
test_str_util() {
// You can do set-up work for each test here.
}
virtual ~test_str_util() {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
virtual void SetUp() {
// Code here will be called immediately after the constructor (right
// before each test).
}
virtual void TearDown() {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Objects declared here can be used by all tests in the test case for Foo.
};
// Tests that Foo does Xyz.
TEST_F(test_str_util, path_to_filename) {
std::string fname = "";
ASSERT_EQ(path_to_filename("/home/blah", fname), 0);
ASSERT_EQ(fname, "blah");
ASSERT_EQ(path_to_filename("hellokeith", fname), 0);
ASSERT_EQ(fname, "hellokeith");
fname = "";
ASSERT_EQ(path_to_filename("/home/blah/", fname), -2);
ASSERT_EQ(fname, "");
ASSERT_EQ(path_to_filename("", fname), -1);
ASSERT_EQ(fname, "");
}
TEST_F(test_str_util, nbytes_to_string) {
char buf[256];
nbytes_to_string(1024, 0, buf, sizeof (buf));
ASSERT_STREQ(buf, "1.00 KB");
nbytes_to_string(1024, 1024 * 1024, buf, sizeof (buf));
ASSERT_STREQ(buf, "0.00/1.00 MB");
nbytes_to_string(512, 1024, buf, sizeof (buf));
ASSERT_STREQ(buf, "0.50/1.00 KB");
nbytes_to_string(50000000000000, 0, buf, sizeof (buf));
ASSERT_STREQ(buf, "45.47 TB");
}
TEST_F(test_str_util, strip_whitespace) {
std::string tmp = " white space ";
strip_whitespace(tmp);
ASSERT_EQ(tmp, "white space");
tmp = "nospaces";
strip_whitespace(tmp);
ASSERT_EQ(tmp, "nospaces");
}
TEST_F(test_str_util, collapse_whitespace) {
std::string tmp = " white space ";
collapse_whitespace(tmp);
ASSERT_EQ(tmp, " white space ");
tmp = "nospaces";
collapse_whitespace(tmp);
ASSERT_EQ(tmp, "nospaces");
tmp = "inner spaces";
collapse_whitespace(tmp);
ASSERT_EQ(tmp, "inner spaces");
}
TEST_F(test_str_util, is_valid_filename) {
//char tmp = "filename.txt";
bool ret = is_valid_filename("filename.txt");
ASSERT_TRUE(ret);
ret = is_valid_filename("../filename.txt");
ASSERT_FALSE(ret);
}
} // namespace

View File

@ -0,0 +1,49 @@
#include "gtest/gtest.h"
#include "common_defs.h"
#include "url.h"
#include <string>
#include <ios>
namespace test_url {
// The fixture for testing class Foo.
class test_url : public ::testing::Test {
protected:
// You can remove any or all of the following functions if its body
// is empty.
test_url() {
// You can do set-up work for each test here.
}
virtual ~test_url() {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
virtual void SetUp() {
// Code here will be called immediately after the constructor (right
// before each test).
}
virtual void TearDown() {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Objects declared here can be used by all tests in the test case for Foo.
};
// Tests that Foo does Xyz.
TEST_F(test_url, is_https) {
ASSERT_EQ(is_https("hello"), false);
ASSERT_EQ(is_https("https://www.google.com"), true);
ASSERT_EQ(is_https("http://www.google.com"), false);
ASSERT_EQ(is_https("xhttps://www.google.com"), false);
}
} // namespace

View File

@ -0,0 +1,9 @@
file(GLOB SRCS *.cpp)
add_executable(test_sched ${SRCS})
TARGET_COMPILE_OPTIONS(test_sched PUBLIC ${MYSQL_CFLAGS})
TARGET_LINK_LIBRARIES(test_sched "${GTEST_LIB}" "${SCHED_LIB}" "${BOINC_CRYPT_LIB}" "${BOINC_LIB}" ${MYSQL_LIB} "${GTEST_MAIN_LIB}" pthread)
add_test(NAME test_sched COMMAND test_sched)

View File

@ -0,0 +1,50 @@
#include "gtest/gtest.h"
#include "common_defs.h"
#include "credit.h"
namespace test_credit {
// The fixture for testing class Foo.
class test_credit : public ::testing::Test {
protected:
// You can remove any or all of the following functions if its body
// is empty.
test_credit() {
// You can do set-up work for each test here.
}
virtual ~test_credit() {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
virtual void SetUp() {
// Code here will be called immediately after the constructor (right
// before each test).
}
virtual void TearDown() {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Objects declared here can be used by all tests in the test case for Foo.
};
// Tests that Foo does Xyz.
TEST_F(test_credit, fpops_to_credit) {
ASSERT_EQ(fpops_to_credit(1.0), COBBLESTONE_SCALE);
ASSERT_NE(fpops_to_credit(5.0), COBBLESTONE_SCALE);
ASSERT_EQ(fpops_to_credit(6000000.0), 6000000.0 * COBBLESTONE_SCALE);
}
TEST_F(test_credit, cpu_time_to_credit) {
ASSERT_EQ(cpu_time_to_credit(1.0, 1.0), COBBLESTONE_SCALE);
}
} // namespace