From b6f9a597bde0b2ff9f73ae99bde05867a60b6e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20Gr=C3=A4ser?= <graeser@dune-project.org> Date: Thu, 19 Nov 2015 09:56:16 +0100 Subject: [PATCH] [concept][test] Add test for concept checking facility This also adds two test utilities from dune-functions that are both used in this test. * TestSuite: A helper class to organize checks in tests in a unified way and more readable way. * CollectorStream: A simple stream that allows to collect data and forward it to its creator using a callback. Used by testsuite.hh Since can also be used for tests in other modules they are installed in dune/common/test. However, CollectorStream is not strictly related to tests and may also be moved directly to dune/common/. --- dune/common/test/CMakeLists.txt | 9 ++ dune/common/test/collectorstream.hh | 81 +++++++++++ dune/common/test/concept.cc | 200 +++++++++++++++++++++++++++ dune/common/test/testsuite.hh | 206 ++++++++++++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 dune/common/test/collectorstream.hh create mode 100644 dune/common/test/concept.cc create mode 100644 dune/common/test/testsuite.hh diff --git a/dune/common/test/CMakeLists.txt b/dune/common/test/CMakeLists.txt index d17644325..a8a204696 100644 --- a/dune/common/test/CMakeLists.txt +++ b/dune/common/test/CMakeLists.txt @@ -25,6 +25,9 @@ dune_add_test(NAME check_fvector_size_fail2 dune_add_test(SOURCES classnametest.cc LINK_LIBRARIES dunecommon) +dune_add_test(SOURCES concept.cc + LINK_LIBRARIES dunecommon) + dune_add_test(SOURCES conversiontest.cc) dune_add_test(SOURCES diagonalmatrixtest.cc @@ -149,3 +152,9 @@ if(${LAPACK_FOUND}) LINK_LIBRARIES dunecommon SKIP_ON_77) endif() + +install( + FILES + testsuite.hh + collectorstream.hh + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dune/common/test) diff --git a/dune/common/test/collectorstream.hh b/dune/common/test/collectorstream.hh new file mode 100644 index 000000000..4c31a57c3 --- /dev/null +++ b/dune/common/test/collectorstream.hh @@ -0,0 +1,81 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +#ifndef DUNE_COMMON_TEST_COLLECTORSTREAM_HH +#define DUNE_COMMON_TEST_COLLECTORSTREAM_HH + +#include <sstream> +#include <string> +#include <functional> + + +#include <dune/common/typeutilities.hh> + + +namespace Dune { + + + +/** + * \brief Data collector stream + * + * A class derived from std::ostringstream that allows to + * collect data via a temporary returned object. To facilitate + * this it stores a callback that is used to pass the collected + * data to its creator on destruction. + * + * In order to avoid passing the same data twice, copy construction + * is forbidden and only move construction is allowed. + */ +class CollectorStream : public std::ostringstream +{ +public: + + /** + * \brief Create from callback + * + * \tparam CallBack Type of callback. Must be convertible to std::function<void(std::string)> + * \param callBack A copy of this function will be stored and called on destruction. + */ + template<class CallBack, + Dune::disableCopyMove<CollectorStream, CallBack> = 0> + CollectorStream(CallBack&& callBack) : + callBack_(callBack) + {} + + CollectorStream(const CollectorStream& other) = delete; + + /** + * \brief Move constructor + * + * This will take over the data and callback from the + * moved from CollectorStream and disable the callback + * in the latter. + */ + CollectorStream(CollectorStream&& other) : + callBack_(other.callBack_) + { + (*this) << other.str(); + other.callBack_ = [](std::string){}; + } + + /** + * \brief Destructor + * + * This calls the callback function given on creation + * passing all collected data as a single string argument. + */ + ~CollectorStream() + { + callBack_(this->str()); + } + +private: + std::function<void(std::string)> callBack_; +}; + + + +} // namespace Dune + + +#endif // DUNE_COMMON_TEST_COLLECTORSTREAM_HH diff --git a/dune/common/test/concept.cc b/dune/common/test/concept.cc new file mode 100644 index 000000000..5fc00707a --- /dev/null +++ b/dune/common/test/concept.cc @@ -0,0 +1,200 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +#include <config.h> + +#include <iostream> + +#include <dune/common/parallel/mpihelper.hh> +#include <dune/common/typelist.hh> + +#include <dune/common/concept.hh> +#include <dune/common/test/testsuite.hh> + + + +struct HasFoo +{ + template<class T> + auto require(const T& t) -> decltype( + t.foo() + ); +}; + +struct HasBar +{ + template<class T> + auto require(const T& t) -> decltype( + t.bar() + ); +}; + +struct HasFooAndBar1 : Dune::Concept::Refines<HasFoo> +{ + template<class T> + auto require(const T& t) -> decltype( + t.bar() + ); +}; + +struct HasFooAndBar2 : Dune::Concept::Refines<HasBar> +{ + template<class T> + auto require(const T& t) -> decltype( + t.foo() + ); +}; + +struct HasFooAndBar3 +{ + template<class T> + auto require(const T& t) -> decltype( + t.foo(), + t.bar() + ); +}; + +struct HasFooAndBar4 : Dune::Concept::Refines<HasFoo, HasBar> +{ + template<class T> + auto require(const T& t) -> decltype( + 0 + ); +}; + +struct HasFooAndBar5 +{ + template<class T> + auto require(const T& t) -> decltype( + 0 + ); + using BaseConceptList = Dune::TypeList<HasFoo, HasBar>; +}; + + + + +template<class T> +struct Foo +{ + T foo() const + { return T(); } +}; + +template<class T> +struct Bar +{ + T bar() const + { return T(); } +}; + +template<class T> +struct FooBar +{ + T foo() const + { return T(); } + + T bar() const + { return T(); } +}; + + + + +int main ( int argc, char **argv ) +try +{ + using namespace Dune; + + MPIHelper::instance(argc, argv); + + TestSuite test; + + test.check(models<HasFoo, Foo<int>>()) + << "models<HasFoo, Foo<int>>() gives wrong result"; + + test.check(not models<HasFoo, Bar<int>>()) + << "models<HasFoo, Bar<int>>() gives wrong result"; + + test.check(models<HasFoo, FooBar<int>>()) + << "models<HasFoo, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasBar, Foo<int>>()) + << "models<HasBar, Foo<int>>() gives wrong result"; + + test.check(models<HasBar, Bar<int>>()) + << "models<HasBar, Bar<int>>() gives wrong result"; + + test.check(models<HasBar, FooBar<int>>()) + << "models<HasBar, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasFooAndBar1, Foo<int>>()) + << "models<HasFooAndBar1, Foo<int>>() gives wrong result"; + + test.check(not models<HasFooAndBar1, Bar<int>>()) + << "models<HasFooAndBar1, Bar<int>>() gives wrong result"; + + test.check(models<HasFooAndBar1, FooBar<int>>()) + << "models<HasFooAndBar1, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasFooAndBar2, Foo<int>>()) + << "models<HasFooAndBar2, Foo<int>>() gives wrong result"; + + test.check(not models<HasFooAndBar2, Bar<int>>()) + << "models<HasFooAndBar2, Bar<int>>() gives wrong result"; + + test.check(models<HasFooAndBar2, FooBar<int>>()) + << "models<HasFooAndBar2, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasFooAndBar3, Foo<int>>()) + << "models<HasFooAndBar3, Foo<int>>() gives wrong result"; + + test.check(not models<HasFooAndBar3, Bar<int>>()) + << "models<HasFooAndBar3, Bar<int>>() gives wrong result"; + + test.check(models<HasFooAndBar3, FooBar<int>>()) + << "models<HasFooAndBar3, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasFooAndBar4, Foo<int>>()) + << "models<HasFooAndBar4, Foo<int>>() gives wrong result"; + + test.check(not models<HasFooAndBar4, Bar<int>>()) + << "models<HasFooAndBar4, Bar<int>>() gives wrong result"; + + test.check(models<HasFooAndBar4, FooBar<int>>()) + << "models<HasFooAndBar4, FooBar<int>>() gives wrong result"; + + + + test.check(not models<HasFooAndBar5, Foo<int>>()) + << "models<HasFooAndBar5, Foo<int>>() gives wrong result"; + + test.check(not models<HasFooAndBar5, Bar<int>>()) + << "models<HasFooAndBar5, Bar<int>>() gives wrong result"; + + test.check(models<HasFooAndBar5, FooBar<int>>()) + << "models<HasFooAndBar5, FooBar<int>>() gives wrong result"; + + + + return test.exit(); +} +catch( Dune::Exception &e ) +{ + std::cerr << "Dune reported error: " << e << std::endl; + return 1; +} +catch(...) +{ + std::cerr << "Unknown exception thrown!" << std::endl; + return 1; +} diff --git a/dune/common/test/testsuite.hh b/dune/common/test/testsuite.hh new file mode 100644 index 000000000..ff1952365 --- /dev/null +++ b/dune/common/test/testsuite.hh @@ -0,0 +1,206 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +#ifndef DUNE_COMMON_TEST_TESTSUITE_HH +#define DUNE_COMMON_TEST_TESTSUITE_HH + +#include <iostream> +#include <sstream> +#include <string> + +#include <dune/common/exceptions.hh> +#include <dune/common/test/collectorstream.hh> + + + +namespace Dune { + + + + /** + * \brief A Simple helper class to organize your test suite + * + * Usage: Construct a TestSuite and call check() or require() + * with the condition to check and probably a name for this check. + * These methods return a stream such that you can pipe in an + * explanantion accomponied by respective data to give a reason + * for a test failure. + */ + class TestSuite + { + public: + enum ThrowPolicy + { + AlwaysThrow, + ThrowOnRequired + }; + + /** + * \brief Create TestSuite + * + * \param name A name to identify this TestSuite. Defaults to "". + * \param policy If AlwaysThrow any failing check will throw, otherwise only required checks will do. + */ + TestSuite(ThrowPolicy policy, std::string name="") : + name_(name), + checks_(0), + failedChecks_(0), + throwPolicy_(policy==AlwaysThrow) + {} + + /** + * \brief Create TestSuite + * + * \param name A name to identify this TestSuite. Defaults to "". + * \param policy If AlwaysThrow any failing check will throw, otherwise only required checks will do. Defaults to ThrowOnRequired + */ + TestSuite(std::string name="", ThrowPolicy policy=ThrowOnRequired) : + name_(name), + checks_(0), + failedChecks_(0), + throwPolicy_(policy==AlwaysThrow) + {} + + /** + * \brief Check condition + * + * This will throw an exception if the check fails and if the AlwaysThrow policy was used on creation. + * + * \param conditon Checks if this is true and increases the failure counter if not. + * \param name A name to identify this check. Defaults to "" + * \returns A CollectorStream that can be used to create a diagnostic message to be printed on failure. + */ + CollectorStream check(bool condition, std::string name="") + { + ++checks_; + if (not condition) + ++failedChecks_; + + return CollectorStream([=](std::string reason) { + if (not condition) + this->announceCheckResult(throwPolicy_, "CHECK ", name, reason); + }); + } + + /** + * \brief Check a required condition condition + * + * This will always throw an exception if the check fails. + * + * \param conditon Checks if this is true and increases the failure counter if not. + * \param name A name to identify this check. Defaults to "" + * \returns A CollectorStream that can be used to create a diagnostic message to be printed on failure. + */ + CollectorStream require(bool condition, std::string name="") + { + ++checks_; + if (not condition) + ++failedChecks_; + + return CollectorStream([=](std::string reason) { + if (not condition) + this->announceCheckResult(true, "REQUIRED CHECK", name, reason); + }); + } + + /** + * \brief Collect data from a sub-TestSuite + * + * This will incorporate the accumulated results of the sub-TestSuite + * into this one. If the sub-TestSuite failed, i.e., contained failed + * checks, a summary will be printed. + */ + void subTest(const TestSuite& subTest) + { + checks_ += subTest.checks_; + failedChecks_ += subTest.failedChecks_; + + if (not subTest) + announceCheckResult(throwPolicy_, "SUBTEST", subTest.name(), std::to_string(subTest.failedChecks_)+"/"+std::to_string(subTest.checks_) + " checks failed in this subtest."); + } + + /** + * \brief Check if this TestSuite failed + * + * \returns False if any of the executed tests failed, otherwise true. + */ + operator const bool () const + { + return (failedChecks_==0); + } + + /** + * \brief Query name + * + * \returns Name of this TestSuite + */ + std::string name() const + { + return name_; + } + + /** + * \brief Print a summary of this TestSuite + * + * \returns False if any of the executed tests failed, otherwise true. + */ + bool report() const + { + if (failedChecks_>0) + std::cout << composeMessage("TEST ", name(), std::to_string(failedChecks_)+"/"+std::to_string(checks_) + " checks failed in this test.") << std::endl; + return (failedChecks_==0); + } + + /** + * \brief Exit the test. + * + * This wil print a summary of the test and return an integer + * to be used on program exit. + * + * \returns 1 if any of the executed tests failed, otherwise 0. + */ + int exit() const + { + return (report() ? 0: 1); + } + + protected: + + // Compose a diagnostic message + static std::string composeMessage(std::string type, std::string name, std::string reason) + { + std::ostringstream s; + s << type << " FAILED"; + if (name!="") + s << "(" << name << ")"; + s << ": "; + if (reason!="") + s << reason; + return s.str(); + } + + // Announce check results. To be called on failed checks + static void announceCheckResult(bool throwException, std::string type, std::string name, std::string reason) + { + std::string message = composeMessage(type, name, reason); + std::cout << message << std::endl; + if (throwException) + { + Dune::Exception ex; + ex.message(message); + throw ex; + } + } + + std::string name_; + std::size_t checks_; + std::size_t failedChecks_; + bool throwPolicy_; + }; + + + +} // namespace Dune + + + +#endif // DUNE_COMMON_TEST_TESTSUITE_HH -- GitLab