utils/tests.h
#ifndef WREPORT_TESTS_H
#define WREPORT_TESTS_H
#include <string>
#include <sstream>
#include <exception>
#include <functional>
#include <vector>
#include <cstdint>
namespace wreport {
namespace tests {
struct LocationInfo;
}
}
/*
* These global arguments will be shadowed by local variables in functions that
* implement tests.
*
* They are here to act as default root nodes to fulfill method signatures when
* tests are called from outside other tests.
*/
extern const wreport::tests::LocationInfo wreport_test_location_info;
namespace wreport {
namespace tests {
struct LocationInfo : public std::stringstream
{
LocationInfo() {}
std::ostream& operator()();
};
struct TestStackFrame
{
const char* file;
int line;
const char* call;
std::string local_info;
TestStackFrame(const char* file, int line, const char* call)
: file(file), line(line), call(call)
{
}
TestStackFrame(const char* file, int line, const char* call, const LocationInfo& local_info)
: file(file), line(line), call(call), local_info(local_info.str())
{
}
std::string format() const;
void format(std::ostream& out) const;
};
struct TestStack : public std::vector<TestStackFrame>
{
using vector::vector;
std::string backtrace() const;
void backtrace(std::ostream& out) const;
};
struct TestFailed : public std::exception
{
std::string message;
TestStack stack;
TestFailed(const std::exception& e);
template<typename ...Args>
TestFailed(const std::exception& e, Args&&... args)
: TestFailed(e)
{
add_stack_info(std::forward<Args>(args)...);
}
TestFailed(const std::string& message) : message(message) {}
template<typename ...Args>
TestFailed(const std::string& message, Args&&... args)
: TestFailed(message)
{
add_stack_info(std::forward<Args>(args)...);
}
const char* what() const noexcept override { return message.c_str(); }
template<typename ...Args>
void add_stack_info(Args&&... args) { stack.emplace_back(std::forward<Args>(args)...); }
};
struct TestSkipped : public std::exception
{
std::string reason;
TestSkipped();
TestSkipped(const std::string& reason);
};
#define WREPORT_TEST_INFO(name) \
wreport::tests::LocationInfo wreport_test_location_info; \
wreport::tests::LocationInfo& name = wreport_test_location_info
template<typename A>
void assert_true(const A& actual)
{
if (actual) return;
std::stringstream ss;
ss << "actual value " << actual << " is not true";
throw TestFailed(ss.str());
}
void assert_true(std::nullptr_t actual);
template<typename A>
void assert_false(const A& actual)
{
if (!actual) return;
std::stringstream ss;
ss << "actual value " << actual << " is not false";
throw TestFailed(ss.str());
}
void assert_false(std::nullptr_t actual);
template<typename LIST>
static inline void _format_list(std::ostream& o, const LIST& list) {
bool first = true;
o << "[";
for (const auto& v: list)
{
if (first)
first = false;
else
o << ", ";
o << v;
}
o << "]";
}
template<typename T>
void assert_equal(const std::vector<T>& actual, const std::vector<T>& expected)
{
if (actual == expected) return;
std::stringstream ss;
ss << "value ";
_format_list(ss, actual);
ss << " is different than the expected ";
_format_list(ss, expected);
throw TestFailed(ss.str());
}
template<typename T>
void assert_equal(const std::vector<T>& actual, const std::initializer_list<T>& expected)
{
if (actual == expected) return;
std::stringstream ss;
ss << "value ";
_format_list(ss, actual);
ss << " is different than the expected ";
_format_list(ss, expected);
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_equal(const A& actual, const E& expected)
{
if (actual == expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is different than the expected '" << expected << "'";
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_not_equal(const A& actual, const E& expected)
{
if (actual != expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is not different than the expected '" << expected << "'";
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_less(const A& actual, const E& expected)
{
if (actual < expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is not less than the expected '" << expected << "'";
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_less_equal(const A& actual, const E& expected)
{
if (actual <= expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is not less than or equals to the expected '" << expected << "'";
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_greater(const A& actual, const E& expected)
{
if (actual > expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is not greater than the expected '" << expected << "'";
throw TestFailed(ss.str());
}
template<typename A, typename E>
void assert_greater_equal(const A& actual, const E& expected)
{
if (actual >= expected) return;
std::stringstream ss;
ss << "value '" << actual << "' is not greater than or equals to the expected '" << expected << "'";
throw TestFailed(ss.str());
}
void assert_startswith(const std::string& actual, const std::string& expected);
void assert_endswith(const std::string& actual, const std::string& expected);
void assert_contains(const std::string& actual, const std::string& expected);
void assert_not_contains(const std::string& actual, const std::string& expected);
void assert_re_matches(const std::string& actual, const std::string& expected);
void assert_not_re_matches(const std::string& actual, const std::string& expected);
template<class A>
struct Actual
{
A _actual;
Actual(const A& actual) : _actual(actual) {}
~Actual() {}
void istrue() const { assert_true(_actual); }
void isfalse() const { assert_false(_actual); }
template<typename E> void operator==(const E& expected) const { assert_equal(_actual, expected); }
template<typename E> void operator!=(const E& expected) const { assert_not_equal(_actual, expected); }
template<typename E> void operator<(const E& expected) const { return assert_less(_actual, expected); }
template<typename E> void operator<=(const E& expected) const { return assert_less_equal(_actual, expected); }
template<typename E> void operator>(const E& expected) const { return assert_greater(_actual, expected); }
template<typename E> void operator>=(const E& expected) const { return assert_greater_equal(_actual, expected); }
};
struct ActualCString
{
const char* _actual;
ActualCString(const char* s) : _actual(s) {}
void istrue() const { return assert_true(_actual); }
void isfalse() const { return assert_false(_actual); }
void operator==(const char* expected) const;
void operator==(const std::string& expected) const;
void operator!=(const char* expected) const;
void operator!=(const std::string& expected) const;
void operator<(const std::string& expected) const;
void operator<=(const std::string& expected) const;
void operator>(const std::string& expected) const;
void operator>=(const std::string& expected) const;
void startswith(const std::string& expected) const;
void endswith(const std::string& expected) const;
void contains(const std::string& expected) const;
void not_contains(const std::string& expected) const;
void matches(const std::string& re) const;
void not_matches(const std::string& re) const;
};
struct ActualStdString : public Actual<std::string>
{
ActualStdString(const std::string& s) : Actual<std::string>(s) {}
using Actual<std::string>::operator==;
void operator==(const std::vector<uint8_t>& expected) const;
using Actual<std::string>::operator!=;
void operator!=(const std::vector<uint8_t>& expected) const;
void startswith(const std::string& expected) const;
void endswith(const std::string& expected) const;
void contains(const std::string& expected) const;
void not_contains(const std::string& expected) const;
void matches(const std::string& re) const;
void not_matches(const std::string& re) const;
};
struct ActualDouble : public Actual<double>
{
using Actual::Actual;
void almost_equal(double expected, unsigned places) const;
void not_almost_equal(double expected, unsigned places) const;
};
template<typename A>
inline Actual<A> actual(const A& actual) { return Actual<A>(actual); }
inline ActualCString actual(const char* actual) { return ActualCString(actual); }
inline ActualCString actual(char* actual) { return ActualCString(actual); }
inline ActualStdString actual(const std::string& actual) { return ActualStdString(actual); }
inline ActualStdString actual(const std::vector<uint8_t>& actual) { return ActualStdString(std::string(actual.begin(), actual.end())); }
inline ActualDouble actual(double actual) { return ActualDouble(actual); }
struct ActualFunction : public Actual<std::function<void()>>
{
using Actual::Actual;
void throws(const std::string& what_match) const;
};
inline ActualFunction actual_function(std::function<void()> actual) { return ActualFunction(actual); }
struct ActualFile : public Actual<std::string>
{
using Actual::Actual;
void exists() const;
void not_exists() const;
void startswith(const std::string& data) const;
void empty() const;
void not_empty() const;
void contents_equal(const std::string& data) const;
void contents_equal(const std::vector<uint8_t>& data) const;
void contents_equal(const std::initializer_list<std::string>& lines) const;
void contents_match(const std::string& data_re) const;
void contents_match(const std::initializer_list<std::string>& lines_re) const;
};
inline ActualFile actual_file(const std::string& pathname) { return ActualFile(pathname); }
#define wassert(...) \
do { try { \
__VA_ARGS__ ; \
} catch (wreport::tests::TestFailed& e) { \
e.add_stack_info(__FILE__, __LINE__, #__VA_ARGS__, wreport_test_location_info); \
throw; \
} catch (std::exception& e) { \
throw wreport::tests::TestFailed(e, __FILE__, __LINE__, #__VA_ARGS__, wreport_test_location_info); \
} } while(0)
#define wassert_true(...) wassert(actual(__VA_ARGS__).istrue())
#define wassert_false(...) wassert(actual(__VA_ARGS__).isfalse())
#define wassert_throws(exc, ...) \
[&]() { try { \
__VA_ARGS__ ; \
wfail_test(#__VA_ARGS__ " did not throw " #exc); \
} catch (TestFailed& e) { \
throw; \
} catch (exc& e) { \
return e; \
} catch (std::exception& e) { \
std::string msg(#__VA_ARGS__ " did not throw " #exc " but threw "); \
msg += typeid(e).name(); \
msg += " instead"; \
wfail_test(msg); \
} }()
#define wcallchecked(func) \
[&]() { try { \
return func; \
} catch (wreport::tests::TestFailed& e) { \
e.add_stack_info(__FILE__, __LINE__, #func, wreport_test_location_info); \
throw; \
} catch (std::exception& e) { \
throw wreport::tests::TestFailed(e, __FILE__, __LINE__, #func, wreport_test_location_info); \
} }()
#define wfail_test(msg) wassert(throw wreport::tests::TestFailed((msg)))
struct TestCase;
struct TestController;
struct TestRegistry;
struct TestCaseResult;
struct TestMethod;
struct TestMethodResult;
struct TestMethod
{
std::string name;
std::string doc;
std::function<void()> test_function;
TestMethod(const std::string& name)
: name(name) {}
TestMethod(const std::string& name, std::function<void()> test_function)
: name(name), test_function(test_function) {}
};
struct TestCase
{
std::string name;
std::vector<TestMethod> methods;
bool tests_registered = false;
TestCase(const std::string& name);
virtual ~TestCase() {}
void register_tests_once();
virtual void register_tests() = 0;
virtual void setup() {}
virtual void teardown() {}
virtual void method_setup(TestMethodResult&) {}
virtual void method_teardown(TestMethodResult&) {}
virtual TestCaseResult run_tests(TestController& controller);
virtual TestMethodResult run_test(TestController& controller, TestMethod& method);
TestMethod& add_method(const std::string& name)
{
methods.emplace_back(name);
return methods.back();
}
template<typename ...Args>
TestMethod& add_method(const std::string& name, std::function<void()> test_function)
{
methods.emplace_back(name, test_function);
return methods.back();
}
template<typename ...Args>
TestMethod& add_method(const std::string& name, const std::string& doc, std::function<void()> test_function)
{
methods.emplace_back(name, test_function);
methods.back().doc = doc;
return methods.back();
}
};
struct Fixture
{
// Called before each test
void test_setup() {}
// Called after each test
void test_teardown() {}
};
template<typename Fixture, typename... Args>
static inline Fixture* fixture_factory(Args... args)
{
return new Fixture(args...);
}
template<typename FIXTURE>
class FixtureTestCase : public TestCase
{
public:
typedef FIXTURE Fixture;
Fixture* fixture = nullptr;
std::function<Fixture*()> make_fixture;
template<typename... Args>
FixtureTestCase(const std::string& name, Args... args)
: TestCase(name)
{
make_fixture = std::bind(fixture_factory<FIXTURE, Args...>, args...);
}
void setup() override
{
TestCase::setup();
fixture = make_fixture();
}
void teardown() override
{
delete fixture;
fixture = 0;
TestCase::teardown();
}
void method_setup(TestMethodResult& mr) override
{
TestCase::method_setup(mr);
if (fixture) fixture->test_setup();
}
void method_teardown(TestMethodResult& mr) override
{
if (fixture) fixture->test_teardown();
TestCase::method_teardown(mr);
}
template<typename ...Args>
TestMethod& add_method(const std::string& name, std::function<void(FIXTURE&)> test_function)
{
return TestCase::add_method(name, [=]() { test_function(*fixture); });
}
template<typename ...Args>
TestMethod& add_method(const std::string& name, const std::string& doc, std::function<void(FIXTURE&)> test_function)
{
return TestCase::add_method(name, doc, [=]() { test_function(*fixture); });
}
};
}
}
#endif
wreport::tests::FixtureTestCase::setup
void setup() override
Set up the test case before it is run.
Definition utils/tests.h:668
wreport::tests::FixtureTestCase::add_method
TestMethod & add_method(const std::string &name, std::function< void(FIXTURE &)> test_function)
Register a new test method that takes a reference to the fixture as argument.
Definition utils/tests.h:698
wreport::tests::FixtureTestCase::method_teardown
void method_teardown(TestMethodResult &mr) override
Clean up after the test method is run.
Definition utils/tests.h:687
wreport::tests::FixtureTestCase::teardown
void teardown() override
Clean up after the test case is run.
Definition utils/tests.h:674
wreport::tests::FixtureTestCase::method_setup
void method_setup(TestMethodResult &mr) override
Set up before the test method is run.
Definition utils/tests.h:681
wreport::tests::FixtureTestCase::add_method
TestMethod & add_method(const std::string &name, const std::string &doc, std::function< void(FIXTURE &)> test_function)
Register a new test method that takes a reference to the fixture as argument, including documentation...
Definition utils/tests.h:708
wreport
String functions.
Definition benchmark.h:13
wreport::tests::ActualDouble
Definition utils/tests.h:374
wreport::tests::ActualFile
Definition utils/tests.h:399
wreport::tests::ActualFunction
Definition utils/tests.h:390
wreport::tests::ActualStdString
Definition utils/tests.h:358
wreport::tests::Actual
Definition utils/tests.h:319
wreport::tests::Fixture
Base class for test fixtures.
Definition utils/tests.h:635
wreport::tests::LocationInfo
Add information to the test backtrace for the tests run in the current scope.
Definition utils/tests.h:55
wreport::tests::LocationInfo::operator()
std::ostream & operator()()
Clear the current information and return the output stream to which new information can be sent.
wreport::tests::TestCaseResult
Result of running a whole test case.
Definition testrunner.h:97
wreport::tests::TestCase::run_tests
virtual TestCaseResult run_tests(TestController &controller)
Call setup(), run all the tests that have been registered, then call teardown().
wreport::tests::TestCase::register_tests
virtual void register_tests()=0
This will be called before running the test case, to populate it with its test methods.
wreport::tests::TestCase::methods
std::vector< TestMethod > methods
All registered test methods.
Definition utils/tests.h:525
wreport::tests::TestCase::setup
virtual void setup()
Set up the test case before it is run.
Definition utils/tests.h:551
wreport::tests::TestCase::method_teardown
virtual void method_teardown(TestMethodResult &)
Clean up after the test method is run.
Definition utils/tests.h:566
wreport::tests::TestCase::method_setup
virtual void method_setup(TestMethodResult &)
Set up before the test method is run.
Definition utils/tests.h:561
wreport::tests::TestCase::name
std::string name
Name of the test case.
Definition utils/tests.h:522
wreport::tests::TestCase::tests_registered
bool tests_registered
Set to true the first time register_tests_once is run.
Definition utils/tests.h:528
wreport::tests::TestCase::register_tests_once
void register_tests_once()
Idempotent wrapper for register_tests().
wreport::tests::TestCase::teardown
virtual void teardown()
Clean up after the test case is run.
Definition utils/tests.h:556
wreport::tests::TestCase::add_method
TestMethod & add_method(const std::string &name, const std::string &doc, std::function< void()> test_function)
Register a new test method, including documentation.
Definition utils/tests.h:615
wreport::tests::TestCase::run_test
virtual TestMethodResult run_test(TestController &controller, TestMethod &method)
Run a test method.
wreport::tests::TestCase::add_method
TestMethod & add_method(const std::string &name, std::function< void()> test_function)
Register a new test method.
Definition utils/tests.h:605
wreport::tests::TestCase::add_method
TestMethod & add_method(const std::string &name)
Register a new test method, with the actual test function to be added later.
Definition utils/tests.h:595
wreport::tests::TestController
Abstract interface for the objects that supervise test execution.
Definition testrunner.h:159
wreport::tests::TestFailed
Exception thrown when a test assertion fails, normally by Location::fail_test.
Definition utils/tests.h:104
wreport::tests::TestMethodResult
Result of running a test method.
Definition testrunner.h:27
wreport::tests::TestMethod
Test method information.
Definition utils/tests.h:493
wreport::tests::TestMethod::name
std::string name
Name of the test method.
Definition utils/tests.h:495
wreport::tests::TestMethod::test_function
std::function< void()> test_function
Main body of the test method.
Definition utils/tests.h:505
wreport::tests::TestMethod::doc
std::string doc
Documentation attached to this test method.
Definition utils/tests.h:498
wreport::tests::TestStack
Definition utils/tests.h:89
wreport::tests::TestStack::backtrace
std::string backtrace() const
Return the formatted backtrace for this location.
wreport::tests::TestStack::backtrace
void backtrace(std::ostream &out) const
Write the formatted backtrace for this location to out.