Skip to content
Snippets Groups Projects
Commit b6f9a597 authored by Carsten Gräser's avatar Carsten Gräser
Browse files

[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/.
parent b42d77ab
No related branches found
No related tags found
No related merge requests found
......@@ -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)
// -*- 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
// -*- 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;
}
// -*- 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment