summaryrefslogtreecommitdiff
path: root/ept/utils/tests.h
diff options
context:
space:
mode:
authorEnrico Zini <enrico@enricozini.org>2015-09-10 19:52:45 +0200
committerEnrico Zini <enrico@enricozini.org>2015-09-10 19:52:45 +0200
commitc5dd516802af50f8e369e9f520b88dcf28dc77df (patch)
treeb7df739f0cb01abf87bd6cae9cd55bb26920fc94 /ept/utils/tests.h
parent74e4ebe74b307d65d9064357cbf80855afbe059b (diff)
parentea85d58bc9d619e1511b40a16a630c5da9e60a42 (diff)
downloadlibept-c5dd516802af50f8e369e9f520b88dcf28dc77df.tar.gz
Merge branch 'notagcoll': removed dependencies on tagcoll and wibble.
Diffstat (limited to 'ept/utils/tests.h')
-rw-r--r--ept/utils/tests.h804
1 files changed, 804 insertions, 0 deletions
diff --git a/ept/utils/tests.h b/ept/utils/tests.h
new file mode 100644
index 0000000..3b00a14
--- /dev/null
+++ b/ept/utils/tests.h
@@ -0,0 +1,804 @@
+#ifndef EPT_TESTS_H
+#define EPT_TESTS_H
+
+/**
+ * @author Enrico Zini <enrico@enricozini.org>, Peter Rockai (mornfall) <me@mornfall.net>
+ * @brief Utility functions for the unit tests
+ *
+ * Copyright (C) 2006--2007 Peter Rockai (mornfall) <me@mornfall.net>
+ * Copyright (C) 2003--2013 Enrico Zini <enrico@debian.org>
+ */
+
+#include <string>
+#include <sstream>
+#include <exception>
+#include <functional>
+#include <vector>
+
+namespace ept {
+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 ept::tests::LocationInfo ept_test_location_info;
+
+namespace ept {
+namespace tests {
+
+/**
+ * Add information to the test backtrace for the tests run in the current
+ * scope.
+ *
+ * Example usage:
+ * \code
+ * test_function(...)
+ * {
+ * EPT_TEST_INFO(info);
+ * for (unsigned i = 0; i < 10; ++i)
+ * {
+ * info() << "Iteration #" << i;
+ * ...
+ * }
+ * }
+ * \endcode
+ */
+struct LocationInfo : public std::stringstream
+{
+ LocationInfo() {}
+
+ /**
+ * Clear the current information and return the output stream to which new
+ * information can be sent
+ */
+ std::ostream& operator()();
+};
+
+/// Information about one stack frame in the test execution stack
+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;
+
+ /// Return the formatted backtrace for this location
+ std::string backtrace() const;
+
+ /// Write the formatted backtrace for this location to \a out
+ void backtrace(std::ostream& out) const;
+};
+
+/**
+ * Exception raised when a test assertion fails, normally by
+ * Location::fail_test
+ */
+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)...); }
+};
+
+/**
+ * Use this to declare a local variable with the given name that will be
+ * picked up by tests as extra local info
+ */
+#define EPT_TEST_INFO(name) \
+ ept::tests::LocationInfo ept_test_location_info; \
+ ept::tests::LocationInfo& name = ept_test_location_info
+
+
+/// Test function that ensures that the actual value is true
+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);
+
+/// Test function that ensures that the actual value is false
+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);
+
+/**
+ * Test function that ensures that the actual value is the same as a reference
+ * one
+ */
+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());
+}
+
+/**
+ * Test function that ensures that the actual value is different than a
+ * reference one
+ */
+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());
+}
+
+/// Ensure that the actual value is less than the reference value
+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());
+}
+
+/// Ensure that the actual value is less or equal than the reference value
+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());
+}
+
+/// Ensure that the actual value is greater than the reference value
+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());
+}
+
+/// Ensure that the actual value is greather or equal than the reference value
+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());
+}
+
+/// Ensure that the string \a actual starts with \a expected
+void assert_startswith(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual ends with \a expected
+void assert_endswith(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual contains \a expected
+void assert_contains(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual does not contain \a expected
+void assert_not_contains(const std::string& actual, const std::string& expected);
+
+/**
+ * Ensure that the string \a actual matches the extended regular expression
+ * \a expected.
+ *
+ * The syntax is that of extended regular expression (see man regex(7) ).
+ */
+void assert_re_matches(const std::string& actual, const std::string& expected);
+
+/**
+ * Ensure that the string \a actual does not match the extended regular
+ * expression \a expected.
+ *
+ * The syntax is that of extended regular expression (see man regex(7) ).
+ */
+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) {}
+
+ 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 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); }
+
+
+/**
+ * Run the given command, raising TestFailed with the appropriate backtrace
+ * information if it threw an exception.
+ *
+ * If the command raises TestFailed, it adds the current stack to its stack
+ * information.
+ */
+#define wassert(...) \
+ do { try { \
+ __VA_ARGS__ ; \
+ } catch (TestFailed& e) { \
+ e.add_stack_info(__FILE__, __LINE__, #__VA_ARGS__, ept_test_location_info); \
+ throw; \
+ } catch (std::exception& e) { \
+ throw TestFailed(e, __FILE__, __LINE__, #__VA_ARGS__, ept_test_location_info); \
+ } } while(0)
+
+/// Shortcut to check that a given expression returns true
+#define wassert_true(...) wassert(actual(__VA_ARGS__).istrue())
+
+/// Shortcut to check that a given expression returns false
+#define wassert_false(...) wassert(actual(__VA_ARGS__).isfalse())
+
+/**
+ * Call a function returning its result, and raising TestFailed with the
+ * appropriate backtrace information if it threw an exception.
+ *
+ * If the function raises TestFailed, it adds the current stack to its stack
+ * information.
+ */
+#define wcallchecked(func) \
+ [&]() { try { \
+ return func; \
+ } catch (TestFailed& e) { \
+ e.add_stack_info(__FILE__, __LINE__, #func, ept_test_location_info); \
+ throw; \
+ } catch (std::exception& e) { \
+ throw TestFailed(e, __FILE__, __LINE__, #func, ept_test_location_info); \
+ } }()
+
+
+struct TestCase;
+
+/**
+ * Result of running a test method.
+ */
+struct TestMethodResult
+{
+ /// Name of the test case
+ std::string test_case;
+
+ /// Name of the test method
+ std::string test_method;
+
+ /// If non-empty, the test failed with this error
+ std::string error_message;
+
+ /// Stack frame of where the error happened
+ TestStack error_stack;
+
+ /// If non-empty, the test raised an exception and this is its type ID
+ std::string exception_typeid;
+
+ /// True if the test has been skipped
+ bool skipped = false;
+
+
+ TestMethodResult(const std::string& test_case, const std::string& test_method)
+ : test_case(test_case), test_method(test_method) {}
+
+ void set_failed(TestFailed& e)
+ {
+ error_message = e.what();
+ error_stack = e.stack;
+ if (error_message.empty())
+ error_message = "test failed with an empty error message";
+ }
+
+ void set_exception(std::exception& e)
+ {
+ error_message = e.what();
+ if (error_message.empty())
+ error_message = "test threw an exception with an empty error message";
+ exception_typeid = typeid(e).name();
+ }
+
+ void set_unknown_exception()
+ {
+ error_message = "unknown exception caught";
+ }
+
+ void set_setup_exception(std::exception& e)
+ {
+ error_message = "[setup failed: ";
+ error_message += e.what();
+ error_message += "]";
+ }
+
+ void set_teardown_exception(std::exception& e)
+ {
+ error_message = "[teardown failed: ";
+ error_message += e.what();
+ error_message += "]";
+ }
+
+ bool is_success() const
+ {
+ return error_message.empty();
+ }
+};
+
+/**
+ * Result of running a whole test case
+ */
+struct TestCaseResult
+{
+ /// Name of the test case
+ std::string test_case;
+ /// Outcome of all the methods that have been run
+ std::vector<TestMethodResult> methods;
+ /// Set to a non-empty string if the setup method of the test case failed
+ std::string fail_setup;
+ /// Set to a non-empty string if the teardown method of the test case
+ /// failed
+ std::string fail_teardown;
+ /// Set to true if this test case has been skipped
+ bool skipped = false;
+
+ TestCaseResult(const std::string& test_case) : test_case(test_case) {}
+
+ void set_setup_failed()
+ {
+ fail_setup = "test case setup method threw an unknown exception";
+ }
+
+ void set_setup_failed(std::exception& e)
+ {
+ fail_setup = "test case setup method threw an exception: ";
+ fail_setup += e.what();
+ }
+
+ void set_teardown_failed()
+ {
+ fail_teardown = "test case teardown method threw an unknown exception";
+ }
+
+ void set_teardown_failed(std::exception& e)
+ {
+ fail_teardown = "test case teardown method threw an exception: ";
+ fail_teardown += e.what();
+ }
+
+ void add_test_method(TestMethodResult&& e)
+ {
+ methods.emplace_back(std::move(e));
+ }
+
+ bool is_success() const
+ {
+ if (!fail_setup.empty() || !fail_teardown.empty()) return false;
+ for (const auto& m: methods)
+ if (!m.is_success())
+ return false;
+ return true;
+ }
+};
+
+struct TestCase;
+struct TestCaseResult;
+struct TestMethod;
+struct TestMethodResult;
+
+/**
+ * Abstract interface for the objects that supervise test execution.
+ *
+ * This can be used for printing progress, or to skip test methods or test
+ * cases.
+ */
+struct TestController
+{
+ virtual ~TestController() {}
+
+ /**
+ * Called before running a test case.
+ *
+ * @returns true if the test case should be run, false if it should be skipped
+ */
+ virtual bool test_case_begin(const TestCase& test_case, const TestCaseResult& test_case_result) { return true; }
+
+ /**
+ * Called after running a test case.
+ */
+ virtual void test_case_end(const TestCase& test_case, const TestCaseResult& test_case_result) {}
+
+ /**
+ * Called before running a test method.
+ *
+ * @returns true if the test method should be run, false if it should be skipped
+ */
+ virtual bool test_method_begin(const TestMethod& test_method, const TestMethodResult& test_method_result) { return true; }
+
+ /**
+ * Called after running a test method.
+ */
+ virtual void test_method_end(const TestMethod& test_method, const TestMethodResult& test_method_result) {}
+};
+
+/**
+ * Simple default implementation of TestController.
+ *
+ * It does progress printing to stdout and basic glob-based test method
+ * filtering.
+ */
+struct SimpleTestController : public TestController
+{
+ /// Any method not matching this glob expression will not be run
+ std::string whitelist;
+
+ /// Any method matching this glob expression will not be run
+ std::string blacklist;
+
+ bool test_case_begin(const TestCase& test_case, const TestCaseResult& test_case_result) override;
+ void test_case_end(const TestCase& test_case, const TestCaseResult& test_case_result) override;
+ bool test_method_begin(const TestMethod& test_method, const TestMethodResult& test_method_result) override;
+ void test_method_end(const TestMethod& test_method, const TestMethodResult& test_method_result) override;
+
+ bool test_method_should_run(const std::string& fullname) const;
+};
+
+
+/**
+ * Test registry.
+ *
+ * It collects information about all known test cases and takes care of running
+ * them.
+ */
+struct TestRegistry
+{
+ /// All known test cases
+ std::vector<TestCase*> entries;
+
+ /**
+ * Register a new test case.
+ *
+ * No memory management is done: test_case needs to exist for the whole
+ * lifetime of TestRegistry.
+ */
+ void register_test_case(TestCase& test_case);
+
+ /**
+ * Run all the registered tests using the given controller
+ */
+ std::vector<TestCaseResult> run_tests(TestController& controller);
+
+ /// Get the singleton instance of TestRegistry
+ static TestRegistry& get();
+};
+
+/**
+ * Test method information
+ */
+struct TestMethod
+{
+ /// Name of the test method
+ std::string name;
+
+ /// Main body of the test method
+ std::function<void()> test_function;
+
+ TestMethod(const std::string& name, std::function<void()> test_function)
+ : name(name), test_function(test_function) {}
+};
+
+
+/**
+ * Test case collecting several test methods, and self-registering with the
+ * singleton instance of TestRegistry.
+ */
+struct TestCase
+{
+ /// Name of the test case
+ std::string name;
+
+ /// All registered test methods
+ std::vector<TestMethod> methods;
+
+ TestCase(const std::string& name)
+ : name(name)
+ {
+ TestRegistry::get().register_test_case(*this);
+ }
+ virtual ~TestCase() {}
+
+ /**
+ * This will be called before running the test case, to populate it with
+ * its test methods.
+ *
+ * This needs to be reimplemented with a function that will mostly be a
+ * sequence of calls to add_method().
+ */
+ virtual void register_tests() = 0;
+
+ /**
+ * Set up the test case before it is run.
+ */
+ virtual void setup() {}
+
+ /**
+ * Clean up after the test case is run
+ */
+ virtual void teardown() {}
+
+ /**
+ * Set up before the test method is run
+ */
+ virtual void method_setup(TestMethodResult&) {}
+
+ /**
+ * Clean up after the test method is run
+ */
+ virtual void method_teardown(TestMethodResult&) {}
+
+ /**
+ * Call setup(), run all the tests that have been registered, then
+ * call teardown().
+ *
+ * Exceptions in setup() and teardown() are caught and reported in
+ * TestCaseResult. Test are run using run_test().
+ */
+ virtual TestCaseResult run_tests(TestController& controller);
+
+ /**
+ * Run a test method.
+ *
+ * Call method_setup(), run all the tests that have been registered, then
+ * call method_teardown().
+ *
+ * Exceptions thrown by the test method are caught and reported in
+ * TestMethodResult.
+ *
+ * Exceptions in method_setup() and method_teardown() are caught and
+ * reported in TestMethodResult.
+ */
+ virtual TestMethodResult run_test(TestController& controller, TestMethod& method);
+
+ /**
+ * Register a new test method
+ */
+ template<typename ...Args>
+ void add_method(const std::string& name, std::function<void()> test_function)
+ {
+ methods.emplace_back(name, test_function);
+ }
+
+ /**
+ * Register a new test method
+ */
+ template<typename ...Args>
+ void add_method(const std::string& name, std::function<void()> test_function, Args&&... args)
+ {
+ methods.emplace_back(name, test_function, std::forward<Args>(args)...);
+ }
+
+ /**
+ * Register a new test metheod, with arguments.
+ *
+ * Any extra arguments to the function will be passed to the test method.
+ */
+ template<typename FUNC, typename ...Args>
+ void add_method(const std::string& name, FUNC test_function, Args&&... args)
+ {
+ methods.emplace_back(name, [test_function, args...]() { test_function(args...); });
+ }
+};
+
+
+/**
+ * Base class for test fixtures.
+ *
+ * A fixture will have a constructor and a destructor to do setup/teardown, and
+ * a reset() function to be called inbetween tests.
+ *
+ * Fixtures do not need to descend from Fixture: this implementation is
+ * provided as a default for tests that do not need one, or as a base for
+ * fixtures that do not need reset().
+ */
+struct Fixture
+{
+ virtual ~Fixture() {}
+
+ // Called before each test
+ virtual void test_setup() {}
+
+ // Called after each test
+ virtual void test_teardown() {}
+};
+
+/**
+ * Test case that includes a fixture
+ */
+template<typename FIXTURE>
+struct FixtureTestCase : public TestCase
+{
+ typedef FIXTURE Fixture;
+
+ Fixture* fixture = 0;
+ std::function<Fixture*()> make_fixture;
+
+ template<typename... Args>
+ FixtureTestCase(const std::string& name, Args... args)
+ : TestCase(name)
+ {
+ make_fixture = [=]() { return new Fixture(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);
+ }
+
+ /**
+ * Add a method that takes a reference to the fixture as argument.
+ *
+ * Any extra arguments to the function will be passed to the test method
+ * after the fixture.
+ */
+ template<typename FUNC, typename ...Args>
+ void add_method(const std::string& name, FUNC test_function, Args&&... args)
+ {
+ methods.emplace_back(name, [this, test_function, args...] { test_function(*fixture, args...); });
+ }
+};
+
+#if 0
+ struct Test
+ {
+ std::string name;
+ std::function<void()> test_func;
+ };
+
+ /// Add tests to the test case
+ virtual void add_tests() {}
+#endif
+
+
+}
+}
+
+#endif