From 540f839cb5868dac3eb409f509b85995826cf559 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Sat, 6 Jun 2020 19:44:36 +0200 Subject: Add unit-test framework. --- .gitignore | 17 ++-- Makefile.am | 4 +- configure.ac | 14 ++-- test/Makefile.am | 25 ++++-- test/dgtest.cc | 37 ++++++++ test/dgunit.h | 241 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/logintest.cc | 46 ++++++++++ test/proto.cc | 44 ---------- test/scopedfile.cc | 70 ++++++++++++++++ test/scopedfile.h | 42 ++++++++++ tools/add_file | 25 +----- 11 files changed, 473 insertions(+), 92 deletions(-) create mode 100644 test/dgtest.cc create mode 100644 test/dgunit.h create mode 100644 test/logintest.cc delete mode 100644 test/proto.cc create mode 100644 test/scopedfile.cc create mode 100644 test/scopedfile.h diff --git a/.gitignore b/.gitignore index bb8afab..6f306be 100644 --- a/.gitignore +++ b/.gitignore @@ -7,16 +7,19 @@ config.h.in config.log config.status configure +compile +config.h.in~ depcomp install-sh ltmain.sh missing -src/.deps/ -src/Makefile -src/Makefile.am.test -src/Makefile.in +.deps/ +Makefile +Makefile.in src/muniad -src/*.o +*.o +*.trs stamp-h1 -tools/Makefile -tools/Makefile.in +test/*test +test/*.xml +test/*.log diff --git a/Makefile.am b/Makefile.am index 06d6b0e..905feab 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = gnu -SUBDIRS = src tools -DISTDIRS = src tools +SUBDIRS = src test tools +DISTDIRS = src test tools diff --git a/configure.ac b/configure.ac index 4747405..3137788 100644 --- a/configure.ac +++ b/configure.ac @@ -32,12 +32,13 @@ dnl Check for getopt dnl ====================== AC_HAVE_HEADERS(getopt.h) -#AC_ARG_WITH(test, [ --with-test Build unit tests]) -#if test x$with_test == xyes; then -# AC_MSG_WARN([*** Building unittests!]) -# AM_PATH_CPPUNIT(1.9.6) -# AC_OUTPUT(test/Makefile) -#fi +dnl ====================== +dnl Compile unit tests +dnl ====================== +AC_ARG_WITH([test], + AS_HELP_STRING([--with-test], [Build unit tests])) + +AM_CONDITIONAL([ENABLE_TESTS], [test "x$with_test" = "xyes"]) dnl ====================== dnl Check for eXpat library @@ -72,5 +73,6 @@ PKG_CHECK_MODULES(LIBWEBSOCKETS, libwebsockets >= 2.1) AC_OUTPUT( Makefile src/Makefile + test/Makefile tools/Makefile) diff --git a/test/Makefile.am b/test/Makefile.am index 8cf4ce5..34b750d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,13 +1,20 @@ # Rules for the test code (use `make check` to execute) -TESTS = proto +if ENABLE_TESTS + +TESTS = logintest + +EXTRA_DIST = \ + dgunit.h \ + scopedfile.h check_PROGRAMS = $(TESTS) -proto_CXXFLAGS = -DOUTPUT=\"proto\" $(CPPUNIT_CFLAGS) \ - -I$(top_srcdir)/src -I$(top_srcdir)/hugin -DDISABLE_HUGIN -proto_CFLAGS = -DDISABLE_HUGIN -proto_LDFLAGS = $(CPPUNIT_LIBS) -proto_SOURCES = \ - $(top_srcdir)/hugin/hugin.c \ - test.cc \ - proto.cc +logintest_CXXFLAGS = -DOUTPUT=\"logintest\" \ + $(DEBUG_FLAGS) \ + -I$(top_srcdir)/src +logintest_LDFLAGS = +logintest_SOURCES = \ + logintest.cc \ + dgtest.cc + +endif diff --git a/test/dgtest.cc b/test/dgtest.cc new file mode 100644 index 0000000..21e1543 --- /dev/null +++ b/test/dgtest.cc @@ -0,0 +1,37 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * dgtest.cc + * + * Sat Jun 16 15:48:36 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo 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. + * + * DrumGizmo 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 DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#define DGUNIT_MAIN +#include "dgunit.h" + +#include + +int main(int argc, char* argv[]) +{ + std::ofstream xmlfile; + xmlfile.open("result_" OUTPUT ".xml"); + return DGUnit::runTests(xmlfile); +} diff --git a/test/dgunit.h b/test/dgunit.h new file mode 100644 index 0000000..6717310 --- /dev/null +++ b/test/dgunit.h @@ -0,0 +1,241 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * dgunit.h + * + * Sat Jun 16 15:44:06 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo 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. + * + * DrumGizmo 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 DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DGUnit +{ +public: + DGUnit() + { + if(DGUnit::suite_list == nullptr) + { + DGUnit::suite_list = this; + return; + } + + auto unit = DGUnit::suite_list; + while(unit->next_unit) + { + } + unit->next_unit = this; + } + + //! Overload to prepare stuff for each of the tests. + virtual void setup() {} + + //! Overload to tear down stuff for each of the tests. + virtual void teardown() {} + + struct test_result + { + std::string func; + std::string file; + std::size_t line; + std::string msg; + int id; + }; + + //! Run test + //! \param test_suite the name of a test suite or null for all. + //! \param test_name the name of a test name inside a test suite. Only valid + //! if test_suite is non-null. nullptr for all tests. + static int runTests(std::ofstream& out) + { + std::size_t test_num{0}; + std::size_t failed{0}; + + std::list failed_tests; + std::list successful_tests; + + for(auto suite = DGUnit::suite_list; suite; suite = suite->next_unit) + { + for(auto test : suite->tests) + { + ++test_num; + try + { + suite->setup(); + test.first(); + suite->teardown(); + } + catch(test_result& result) + { + std::cout << "F"; + fflush(stdout); + result.id = test_num; + result.func = test.second; + failed_tests.push_back(result); + ++failed; + continue; + } + catch(...) + { + break; // Uncaught exception. Do not proceed with this test. + } + std::cout << "."; + fflush(stdout); + test_result result{test.second}; + result.id = test_num; + successful_tests.push_back(result); + } + } + + out << "\n"; + out << "\n"; + out << " \n"; + for(auto test : failed_tests) + { + out << " \n"; + out << " " << sanitize(test.func) << "\n"; + out << " Assertion\n"; + out << " \n"; + out << " " << sanitize(test.file) << "\n"; + out << " " << test.line << "\n"; + out << " \n"; + out << " " << sanitize(test.msg) << "\n"; + out << " \n"; + } + out << " \n"; + out << " \n"; + for(auto test : successful_tests) + { + out << " \n"; + out << " " << sanitize(test.func) << "\n"; + out << " \n"; + + } + out << " \n"; + out << " \n"; + out << " " << (successful_tests.size() + failed_tests.size()) << "\n"; + out << " " << failed_tests.size() << "\n"; + out << " 0\n"; + out << " " << failed_tests.size() << "\n"; + out << " \n"; + out << "\n"; + + return failed == 0 ? 0 : 1; + } + +protected: + template + void registerTest(O* obj, const F& fn, const char* name) + { + tests.emplace_back(std::make_pair(std::bind(fn, obj), name)); + } + #define DGUNIT_TEST(func) \ + registerTest(this, &func, #func) + + void dg_assert(bool value, const char* expr, + const char* file, std::size_t line) + { + if(!value) + { + std::stringstream ss; + ss << "assertion failed\n" + "- Expression: " << expr << "\n"; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define DGUNIT_ASSERT(value) \ + dg_assert(value, #value, __FILE__, __LINE__) + + void assert_equal(double expected, double value, + const char* file, std::size_t line) + { + if(std::fabs(expected - value) > 0.0000001) + { + std::stringstream ss; + ss << "equality assertion failed\n" + "- Expected: " << expected << "\n" + "- Actual : " << value << "\n"; + throw test_result{"", file, line, ss.str()}; + } + } + template + void assert_equal(T expected, T value, + const char* file, std::size_t line) + { + if(expected != value) + { + std::stringstream ss; + ss << "equality assertion failed\n" + "- Expected: " << expected << "\n" + "- Actual : " << value << "\n"; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define DGUNIT_ASSERT_EQUAL(expected, value) \ + assert_equal(expected, value, __FILE__, __LINE__) + +private: + static std::string sanitize(const std::string& input) + { + std::string output; + for(auto c : input) + { + switch(c) + { + case '"': + output += """; + break; + case '&': + output += "&"; + break; + case '<': + output += "<"; + break; + case '>': + output += ">"; + break; + default: + output += c; + break; + } + } + return output; + } + + static DGUnit* suite_list; + DGUnit* next_unit{nullptr}; + std::vector, const char*>> tests; +}; + +#ifdef DGUNIT_MAIN +DGUnit* DGUnit::suite_list{nullptr}; +#endif diff --git a/test/logintest.cc b/test/logintest.cc new file mode 100644 index 0000000..f4dc192 --- /dev/null +++ b/test/logintest.cc @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * logintest.cc + * + * Sat Jun 6 19:32:12 CEST 2020 + * Copyright 2020 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Munia. + * + * Munia 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. + * + * Munia 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 Munia; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "dgunit.h" + +class LoginTest + : public DGUnit +{ +public: + LoginTest() + { + DGUNIT_TEST(LoginTest::test); + } + + void test() + { + DGUNIT_ASSERT_EQUAL(42, 42); + } +}; + +// Registers the fixture into the 'registry' +static LoginTest test; diff --git a/test/proto.cc b/test/proto.cc deleted file mode 100644 index 0cc8da9..0000000 --- a/test/proto.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/*************************************************************************** - * engine.cc - * - * Fri Nov 29 18:09:02 CET 2013 - * Copyright 2013 Bent Bisballe Nyeng - * deva@aasimon.org - ****************************************************************************/ - -/* - * This file is part of DrumGizmo. - * - * DrumGizmo 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. - * - * DrumGizmo 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 DrumGizmo; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ -#include - -class test_proto_class : public CppUnit::TestFixture -{ - CPPUNIT_TEST_SUITE(test_proto_class); - CPPUNIT_TEST(test_proto); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() {} - void tearDown() {} - void test_proto() { - } -}; - -// Registers the fixture into the 'registry' -CPPUNIT_TEST_SUITE_REGISTRATION(test_proto_class); - diff --git a/test/scopedfile.cc b/test/scopedfile.cc new file mode 100644 index 0000000..b03a2bc --- /dev/null +++ b/test/scopedfile.cc @@ -0,0 +1,70 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * scopedfile.cc + * + * Wed Jun 6 15:15:31 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo 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. + * + * DrumGizmo 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 DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "scopedfile.h" + +#include +#include + +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +struct Pimpl +{ + std::string filename; + int fd; +}; + +ScopedFile::ScopedFile(const std::string& data) + : pimpl(std::make_unique()) +{ +#ifndef _WIN32 + char templ[] = "/tmp/dg-scoped-file-XXXXXX"; // buffer for filename + pimpl->fd = mkstemp(templ); +#else + char templ[] = "dg-scoped-file-XXXXXX"; // buffer for filename + _mktemp_s(templ); + pimpl->fd = open(templ); +#endif + pimpl->filename = templ; + auto sz = write(pimpl->fd, data.data(), data.size()); + (void)sz; + close(pimpl->fd); +} + +ScopedFile::~ScopedFile() +{ + unlink(pimpl->filename.data()); +} + +std::string ScopedFile::filename() const +{ + return pimpl->filename; +} diff --git a/test/scopedfile.h b/test/scopedfile.h new file mode 100644 index 0000000..d66eac4 --- /dev/null +++ b/test/scopedfile.h @@ -0,0 +1,42 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * scopedfile.h + * + * Wed Jun 6 15:15:31 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo 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. + * + * DrumGizmo 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 DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include +#include + +class ScopedFile +{ +public: + ScopedFile(const std::string& data); + ~ScopedFile(); + + std::string filename() const; + +private: + std::unique_ptr pimpl; +}; diff --git a/tools/add_file b/tools/add_file index d9deb50..e86c117 100755 --- a/tools/add_file +++ b/tools/add_file @@ -62,34 +62,11 @@ function ccfile() { echo -n $hf >> $1; echo '"' >> $1; echo '' >> $1; - - local hn=`echo $1 | cut -d'.' -f1 | tr 'a-z.' 'A-Z_'` - echo "#ifdef TEST_${hn}" >> $1; - echo "//Additional dependency files" >> $1; - echo "//deps:" >> $1; - echo "//Required cflags (autoconf vars may be used)" >> $1; - echo "//cflags:" >> $1; - echo "//Required link options (autoconf vars may be used)" >> $1; - echo "//libs:" >> $1; - echo "#include \"test.h\"" >> $1; - echo "" >> $1; - echo "TEST_BEGIN;" >> $1; - echo "" >> $1; - echo "// TODO: Put some testcode here (see test.h for usable macros)." >> $1; - echo "TEST_TRUE(false, \"No tests yet!\");" >> $1; - echo "" >> $1; - echo "TEST_END;" >> $1; - echo "" >> $1; - echo "#endif/*TEST_${hn}*/" >> $1; } function hfile() { allfile $1; - local hn=`echo $1 | tr 'a-z.' 'A-Z_'` - local pr=`echo $PROJECT | tr 'a-z.' 'A-Z_'` - echo "#ifndef __${pr}_${hn}__" >> $1; - echo "#define __${pr}_${hn}__" >> $1; - echo "#endif/*__${pr}_${hn}__*/" >> $1; + echo "#pragma once" >> $1; } if [ "$#" = "1" ]; then -- cgit v1.2.3