diff --git a/.gitignore b/.gitignore
index 85f028949e..59d45208ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,6 +130,11 @@ locale/*/*.flag
.libs/
svn_version.h
+## code coverage
+*.gcov
+*.gcno
+*.gcda
+
# list of executables
apps/1sec
apps/concat
diff --git a/.travis.yml b/.travis.yml
index 627bcc4259..7c15dcac4c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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:
diff --git a/3rdParty/buildGoogletestLinux.sh b/3rdParty/buildGoogletestLinux.sh
new file mode 100644
index 0000000000..ccd69d8177
--- /dev/null
+++ b/3rdParty/buildGoogletestLinux.sh
@@ -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 .
+
+# 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
diff --git a/3rdParty/buildLinuxDependencies.sh b/3rdParty/buildLinuxDependencies.sh
index 5115394e7c..baca1549db 100755
--- a/3rdParty/buildLinuxDependencies.sh
+++ b/3rdParty/buildLinuxDependencies.sh
@@ -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
diff --git a/Makefile.am b/Makefile.am
index 1627f1bd1c..a06839ba46 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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
diff --git a/configure.ac b/configure.ac
index bc8733d4b3..9b9c754656 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 245dd599b5..c9f29552c8 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -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"
diff --git a/m4/boinc_set_compile_flags.m4 b/m4/boinc_set_compile_flags.m4
index 1e2e0cef73..97f0677e06 100644
--- a/m4/boinc_set_compile_flags.m4
+++ b/m4/boinc_set_compile_flags.m4
@@ -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
+
])
diff --git a/sched/Makefile.am b/sched/Makefile.am
index c43dfd96d6..ba01358b48 100644
--- a/sched/Makefile.am
+++ b/sched/Makefile.am
@@ -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
diff --git a/tests/executeUnitTests.sh b/tests/executeUnitTests.sh
new file mode 100755
index 0000000000..555b31abde
--- /dev/null
+++ b/tests/executeUnitTests.sh
@@ -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 .
+
+# 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
diff --git a/tests/unit-tests/CMakeLists.txt b/tests/unit-tests/CMakeLists.txt
new file mode 100644
index 0000000000..766632a116
--- /dev/null
+++ b/tests/unit-tests/CMakeLists.txt
@@ -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)
diff --git a/tests/unit-tests/lib/CMakeLists.txt b/tests/unit-tests/lib/CMakeLists.txt
new file mode 100644
index 0000000000..d681555765
--- /dev/null
+++ b/tests/unit-tests/lib/CMakeLists.txt
@@ -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)
diff --git a/tests/unit-tests/lib/test_str_util.cpp b/tests/unit-tests/lib/test_str_util.cpp
new file mode 100644
index 0000000000..3069408576
--- /dev/null
+++ b/tests/unit-tests/lib/test_str_util.cpp
@@ -0,0 +1,96 @@
+#include "gtest/gtest.h"
+#include "common_defs.h"
+#include "str_util.h"
+#include
+#include
+
+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
diff --git a/tests/unit-tests/lib/test_url.cpp b/tests/unit-tests/lib/test_url.cpp
new file mode 100644
index 0000000000..43c87b4fb2
--- /dev/null
+++ b/tests/unit-tests/lib/test_url.cpp
@@ -0,0 +1,49 @@
+#include "gtest/gtest.h"
+#include "common_defs.h"
+#include "url.h"
+#include
+#include
+
+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
diff --git a/tests/unit-tests/sched/CMakeLists.txt b/tests/unit-tests/sched/CMakeLists.txt
new file mode 100644
index 0000000000..bb15e0376d
--- /dev/null
+++ b/tests/unit-tests/sched/CMakeLists.txt
@@ -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)
diff --git a/tests/unit-tests/sched/test_credit.cpp b/tests/unit-tests/sched/test_credit.cpp
new file mode 100644
index 0000000000..677cd71b24
--- /dev/null
+++ b/tests/unit-tests/sched/test_credit.cpp
@@ -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