From cced165fbd37146182438685107f5261af2d89be Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Tue, 2 Sep 2014 07:17:55 +0400 Subject: [PATCH] Update testing.h --- tests/testing.h | 372 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 282 insertions(+), 90 deletions(-) diff --git a/tests/testing.h b/tests/testing.h index 308202c..bfb586b 100644 --- a/tests/testing.h +++ b/tests/testing.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2013 Dmitry Marakasov + * Copyright (c) 2011-2014 Dmitry Marakasov * All rights reserved. * * See https://github.com/AMDmi3/testing.h for updates, bug reports, @@ -32,109 +32,301 @@ #include #include +#include +#include +#include -// Define NO_TEST_COLOR before including testing.h to disable colors -#ifndef NO_TEST_COLOR -# define TEST_COLOR +// +// Helper class for literal quoting / extra processing +// +// You may instance this template for printing your own classes +// +template +std::string QuoteLiteral(T value) { + std::stringstream ss; + ss << value; + return ss.str(); +} + +template<> +std::string QuoteLiteral(std::string value) { + return std::string("\"") + value + std::string("\""); +} + +template<> +std::string QuoteLiteral(const char* value) { + return std::string("\"") + value + std::string("\""); +} + +// +// Test registry +// +class Tester { +private: + int num_tests_; // Total numer of tests registered + int num_failures_; // Number of tests failed + bool use_color_; // Whether to use color + + bool unexpected_exception_; // Whether unexpected exception was encountered + + int permanent_flags_; // Flags which affect each test invocation + +public: + // Test results + enum Result { + PASS, + FAIL, + WARN, + }; + + // Flags to control test bahavior + enum Flags { + NON_FATAL = 0x01, + HIDE_MESSAGE = 0x02, + }; + + // Styles for coloring + enum Styles { + PASS_BANNER, + FAIL_BANNER, + WARN_BANNER, + LITERAL, + EXPRESSION, + DESCRIPTION, + }; + + // Dummy type for preprocessor magic to happen + struct DummyArgument { + }; + +private: + // + // Apply named style to a string + // + std::string Stylify(Styles style, const std::string& str) const { + if (!use_color_) + return str; + + bool bright = false; + int color = 0; + + switch (style) { + case PASS_BANNER: bright = false; color = 2; break; + case WARN_BANNER: bright = true; color = 3; break; + case FAIL_BANNER: bright = true; color = 1; break; + case LITERAL: bright = true; color = 5; break; + case EXPRESSION: bright = true; color = 7; break; + case DESCRIPTION: bright = true; color = 4; break; + default: + return str; + } + return std::string("\033[") + (bright ? "1" : "0") + ";" + std::to_string(30 + color) + "m" + str + "\033[0m"; + } + + // + // Main test handling routine + // + // This method handles test counters and formats messages + // + void RegisterTest(bool passed, int flags, const std::string& message, const std::string& description) { + num_tests_++; + + if (passed) { + std::cerr << Stylify(PASS_BANNER, "PASS: "); + } else if ((flags | permanent_flags_) & NON_FATAL) { + std::cerr << Stylify(WARN_BANNER, "WARN: "); + } else { + std::cerr << Stylify(FAIL_BANNER, "FAIL: "); + num_failures_++; + } + + if ((flags | permanent_flags_) & HIDE_MESSAGE && !description.empty()) { + std::cerr << description; + } else { + std::cerr << message; + if (!description.empty()) + std::cerr << " " << Stylify(DESCRIPTION, std::string("// ") + description); + } + std::cerr << std::endl; + } + +public: + Tester() : num_tests_(0), num_failures_(0), use_color_(true), unexpected_exception_(false), permanent_flags_(0) { +#ifdef TESTING_NO_COLOR + use_color_ = false; #endif - -#define TEST_ESCAPE "\033" - -#ifdef TEST_COLOR -# define PASSED TEST_ESCAPE "[0;32mPASSED:" TEST_ESCAPE "[0m " -# define FAILED TEST_ESCAPE "[1;31mFAILED:" TEST_ESCAPE "[0m " -#else -# define PASSED "PASSED: " -# define FAILED "FAILED: " -#endif - -// Test begin/end -#define BEGIN_TEST(...) int main(__VA_ARGS__) { int num_failing_tests_ = 0; - -#define END_TEST() if (num_failing_tests_ > 0) std::cerr << num_failing_tests_ << " failures" << std::endl; return num_failing_tests_; } - -// Equality checks -#define EXPECT_TRUE(expr) { \ - if (!(expr)) { \ - std::cerr << FAILED #expr << std::endl; \ - ++num_failing_tests_; \ - } else { \ - std::cerr << PASSED #expr << std::endl; \ - } \ } -#define EXPECT_STRING(expr, expected) { \ - std::string result = (expr); \ - if (result != expected) { \ - std::cerr << FAILED #expr " returned \"" << result << "\", while expected \"" << expected << "\"" << std::endl; \ - ++num_failing_tests_; \ - } else { \ - std::cerr << PASSED #expr " == \"" << expected << "\"" << std::endl; \ - } \ + void EnableColor(bool enable) { + use_color_ = enable; } -#define EXPECT_EQUAL(type, expr, expected) { \ - type result = (expr); \ - if (result != expected) { \ - std::cerr << FAILED #expr " returned " << result << ", while expected " << expected << std::endl; \ - ++num_failing_tests_; \ - } else { \ - std::cerr << PASSED #expr " == " << expected << std::endl; \ - } \ + void EnableFlags(int flags) { + permanent_flags_ |= flags; } -// Range checks -#define EXPECT_VALUE_IN_RANGE(type, expr, from, to) { \ - type result = (expr); \ - if (from <= result && result <= to) { \ - std::cerr << PASSED #expr " returned " << result << ", which is in range [" << from << ", " << to << "] as expected" << std::endl; \ - } else { \ - std::cerr << FAILED #expr " returned " << result << ", which is out of expected range [" << from << ", " << to << "]" << std::endl; \ - ++num_failing_tests_; \ - } \ + void DisableFlags(int flags) { + permanent_flags_ &= ~flags; } -// Shortcuts for above; feel free to ask to add more -#define EXPECT_INT(expr, expected) EXPECT_EQUAL(int, expr, expected) -#define EXPECT_FLOAT_IN_RANGE(expr, from, to) EXPECT_VALUE_IN_RANGE(float, expr, from, to) + void ResetFlags() { + permanent_flags_ = 0; + } -// Exception checks -#define EXPECT_EXCEPTION(expr, exception) { \ - bool correct_catch = false; \ - try { \ - expr; \ - } catch (exception &e) { \ - correct_catch = true; \ + // ExpectTrue + void ExpectTrue(const std::string& expression_str, bool sample, const std::string& description, int flags, DummyArgument) { + RegisterTest(sample, flags, Stylify(EXPRESSION, expression_str), description); + } + + // ExpectEqual + template + void ExpectEqual(const std::string& expression_str, const T1& sample, const T2& expected, const std::string& description, int flags, DummyArgument) { + std::string message; + + if (sample == expected) + message = Stylify(EXPRESSION, expression_str) + " returned " + Stylify(LITERAL, QuoteLiteral(sample)) + " as expected"; + else + message = Stylify(EXPRESSION, expression_str) + " returned " + Stylify(LITERAL, QuoteLiteral(sample)) + " while expected " + Stylify(LITERAL, QuoteLiteral(expected)); + + RegisterTest(sample == expected, flags, message, description); + } + + // ExpectException + template + void ExpectException(const std::string& expression_str, std::function&& func, const std::string& exception_str, const std::string& description, int flags, DummyArgument) { + bool as_expected = false; + bool thrown = false; + bool std_exception = false; + + std::string what; + + try { + func(); + } catch (E& e) { + as_expected = true; + thrown = true; + } catch (std::exception& e) { + thrown = true; + std_exception = true; + what = e.what(); + } catch (...) { + thrown = true; + } + + std::string message; + + if (thrown && as_expected) + message = Stylify(EXPRESSION, expression_str) + " has thrown " + Stylify(EXPRESSION, exception_str) + " as expected"; + else if (thrown && std_exception) + message = Stylify(EXPRESSION, expression_str) + " has thrown unexpected exception derived from std::exception (what(): " + Stylify(LITERAL, what) + "), while expected " + Stylify(EXPRESSION, exception_str); + else if (thrown) + message = Stylify(EXPRESSION, expression_str) + " has thrown unexpected exception not derived from std::exception, while expected " + Stylify(EXPRESSION, exception_str); + else + message = Stylify(EXPRESSION, expression_str) + " hasn't thrown expected exception " + Stylify(EXPRESSION, exception_str); + + RegisterTest(thrown && as_expected, flags, message, description); + } + + // ExpectTrue overloads + void ExpectTrue(const std::string& expression_str, bool sample, DummyArgument dummy) { + ExpectTrue(expression_str, sample, "", 0, dummy); + } + + void ExpectTrue(const std::string& expression_str, bool sample, const std::string& description, DummyArgument dummy) { + ExpectTrue(expression_str, sample, description, 0, dummy); + } + + void ExpectTrue(const std::string& expression_str, bool sample, int flags, DummyArgument dummy) { + ExpectTrue(expression_str, sample, "", flags, dummy); + } + + // ExpectEqual overloads + template + void ExpectEqual(const std::string& expression_str, const T1& sample, const T2& expected, DummyArgument dummy) { + return ExpectEqual(expression_str, sample, expected, "", 0, dummy); + } + + template + void ExpectEqual(const std::string& expression_str, const T1& sample, const T2& expected, const std::string& description, DummyArgument dummy) { + return ExpectEqual(expression_str, sample, expected, description, 0, dummy); + } + + template + void ExpectEqual(const std::string& expression_str, const T1& sample, const T2& expected, int flags, DummyArgument dummy) { + return ExpectEqual(expression_str, sample, expected, "", flags, dummy); + } + + // ExpectException overloads + template + void ExpectException(const std::string& expression_str, std::function&& func, const std::string& exception_str, DummyArgument dummy) { + return ExpectException(expression_str, std::move(func), exception_str, "", 0, dummy); + } + + template + void ExpectException(const std::string& expression_str, std::function&& func, const std::string& exception_str, const std::string& description, DummyArgument dummy) { + return ExpectException(expression_str, std::move(func), exception_str, description, 0, dummy); + } + + template + void ExpectException(const std::string& expression_str, std::function&& func, const std::string& exception_str, int flags, DummyArgument dummy) { + return ExpectException(expression_str, std::move(func), exception_str, "", flags, dummy); + } + + void RegisterUnexpectedException() { + unexpected_exception_ = true; + } + + void PrintSummary() { + std::cerr << num_failures_ << " failures out of " << num_tests_ << " tests"; + if (unexpected_exception_) + std::cerr << ", and was terminated prematurely due to unexpected exception"; + std::cerr << "\n"; + } + + int GetExitCode() { + return num_failures_ || unexpected_exception_; + } +}; + +// core macros +#define BEGIN_TEST(...) \ + int main(__VA_ARGS__) { \ + Tester tester_; \ + try { + +#define HANDLE_EXCEPTION(exception) \ + } catch(exception) { \ + tester_.RegisterUnexpectedException(); + +#define END_TEST() \ + } catch(std::exception& e) { \ + tester_.RegisterUnexpectedException(); \ + std::cerr << "unexpected exception was thrown during the test: " << e.what() << std::endl; \ } catch (...) { \ + tester_.RegisterUnexpectedException(); \ + std::cerr << "unexpected exception was thrown during the test" << std::endl; \ } \ - if (correct_catch) { \ - std::cerr << PASSED #expr " has thrown " #exception << " as expected " << std::endl; \ - } else { \ - std::cerr << FAILED #expr " hasn't thrown expected " #exception << std::endl; \ - ++num_failing_tests_; \ - } \ + tester_.PrintSummary(); \ + return tester_.GetExitCode(); \ } -#define EXPECT_NO_EXCEPTION(expr) { \ - bool had_exception = false; \ - const char* what = NULL; \ - try { \ - expr; \ - } catch (std::exception& e) { \ - had_exception = true; \ - what = e.what(); \ - } catch (...) { \ - had_exception = true; \ - } \ - if (had_exception && what) { \ - std::cerr << FAILED #expr << " has thrown unexpected exception derived from std::exception, what() returned \"" << what << "\"" << std::endl; \ - ++num_failing_tests_; \ - } else if (had_exception) { \ - std::cerr << FAILED #expr << " has thrown unexpected exception not derived from std::exception" << std::endl; \ - ++num_failing_tests_; \ - } else { \ - std::cerr << PASSED #expr << " hasn't thrown any exceptions as expected" << std::endl; \ - } \ - } +// wrappers to allow true variable number of arguments +#define METHOD_WRAPPER(method, expr, ...) tester_.method(#expr, expr, __VA_ARGS__) +#define METHOD_WRAPPER_LAMBDA(method, expr, ...) tester_.method(#expr, [&](){return expr;}, __VA_ARGS__) +#define METHOD_WRAPPER_EXCEPTION(expr, exception, ...) tester_.ExpectException(#expr, [&](){expr;}, #exception, __VA_ARGS__) + +// checks +#define EXPECT_TRUE(...) do { METHOD_WRAPPER(ExpectTrue, __VA_ARGS__, Tester::DummyArgument()); } while(0) +#define EXPECT_EQUAL(...) do { METHOD_WRAPPER(ExpectEqual, __VA_ARGS__, Tester::DummyArgument()); } while(0) +#define EXPECT_EXCEPTION(...) do { METHOD_WRAPPER_EXCEPTION(__VA_ARGS__, Tester::DummyArgument()); } while(0) + +// functions +#define ENABLE_FLAGS(flags) do { tester_.EnableFlags(flags); } while(0) +#define DISABLE_FLAGS(flags) do { tester_.DisableFlags(flags); } while(0) +#define RESET_FLAGS() do { tester_.ResetFlags(); } while(0) + +// flags +#define NON_FATAL Tester::NON_FATAL +#define HIDE_MESSAGE Tester::HIDE_MESSAGE #endif // TESTING_H_INCLUDED