Содержимое файла testing.hpp
/* Simple C++ testing library.
* Copyright: 2014-2017, Dmitry Shachnev.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CXXUNIT_TESTING_HPP
#define CXXUNIT_TESTING_HPP
#include <cfenv>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <exception>
#include <iostream>
#include <string>
#include <vector>
#include "printing.hpp"
#ifdef __linux__
# include <csignal>
# include <execinfo.h>
#endif
#ifdef _MSC_VER
# define __PRETTY_FUNCTION__ __FUNCTION__
#endif
#define REGISTER_TEST(cls, name) \
static TestCaseProcessor _##cls({new cls(), name})
#define PRINT_ERROR() \
std::cerr << " " << E_ERROR("ERROR") << " in `" << __PRETTY_FUNCTION__ << "' at " << __FILE__ \
<< " line " << __LINE__ << ":" << std::endl;
#define ASSERT_TRUE(expression) \
do { \
auto _value = (expression); \
if (!do_assert(_value)) { \
PRINT_ERROR(); \
std::cerr << " Expression `" #expression "' is not true." << std::endl; \
std::cerr << " Value is " << repr(_value) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_FALSE(expression) \
do { \
auto _value = (expression); \
if (!do_assert(!_value)) { \
PRINT_ERROR(); \
std::cerr << " Expression `" #expression "' is not false." << std::endl; \
std::cerr << " Value is " << repr(_value) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_RELATION(e1, rel, e2) \
do { \
auto _v1 = (e1); \
auto _v2 = (e2); \
if (!do_assert(_v1 rel _v2)) { \
PRINT_ERROR(); \
std::cerr << " Expression `" #e1 " " #rel " " #e2 "' is not true." << std::endl; \
std::cerr << " " << #e1 " = " << repr(_v1) << ", " #e2 " = " << repr(_v2) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_EQUAL(e1, e2) \
do { \
auto _v1 = (e1); \
auto _v2 = (e2); \
if (!do_assert(_v1 == _v2)) { \
PRINT_ERROR(); \
std::cerr << " Expressions `" #e1 "' and `" #e2 "' are not equal." << std::endl; \
std::cerr << " " << #e1 " = " << repr(_v1) << ", " #e2 " = " << repr(_v2) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_ALMOST_EQUAL(e1, e2, precision) \
do { \
auto _v1 = (e1); \
auto _v2 = (e2); \
if (!do_assert(std::fabs(_v1 - _v2) < precision)) { \
PRINT_ERROR(); \
std::cerr << " Expressions `" #e1 "' and `" #e2 "' are not almost equal." << std::endl; \
std::cerr << " " << #e1 " = " << repr(_v1) << ", " #e2 " = " << repr(_v2) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_STRINGS_EQUAL(e1, e2) \
do { \
std::string _v1 = (e1); \
std::string _v2 = (e2); \
if (!do_assert(_v1 == _v2)) { \
PRINT_ERROR(); \
std::cerr << " Strings `" #e1 "' (1) and `" #e2 "' (2) are not equal." << std::endl; \
std::cerr << " (1): '" << _v1 << "'," << std::endl; \
std::cerr << " (2): '" << _v2 << "'" << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_FLOATS_EQUAL(e1, e2) \
do { \
auto _v1 = (e1); \
auto _v2 = (e2); \
if (!do_assert(compare_floats(_v1, _v2))) { \
PRINT_ERROR(); \
std::cerr << " Floating point numbers `" #e1 "' and `" #e2 "' are not equal." << std::endl; \
std::cerr << " " << #e1 " = " << repr(_v1) << ", " #e2 " = " << repr(_v2) << std::endl; \
handle_failfast(); \
} \
} while (0)
#define ASSERT_THROWS(exception_class, expression) \
do { \
bool caught = false; \
try { \
(void)(expression); \
} catch (const exception_class &_exc) { \
caught = true; \
break; \
} \
if (!do_assert(caught)) { \
PRINT_ERROR(); \
std::cerr << " Exception " << #exception_class << " not thrown." << std::endl; \
handle_failfast(); \
} \
} while(0)
template<typename T>
T repr(const T value) { return value; }
uint16_t repr(const uint8_t value) { return value; }
bool compare_floats(float v1, float v2) {
return (std::fabs(v1 - v2) * 100000.f <= std::min(fabs(v1), fabs(v2)));
}
bool compare_floats(double v1, double v2) {
return (std::fabs(v1 - v2) * 1000000000000. <= std::min(fabs(v1), fabs(v2)));
}
struct TestCase {
unsigned assertions_total;
unsigned assertions_successful;
bool failfast;
TestCase():
assertions_total(0),
assertions_successful(0),
failfast(false)
{}
bool do_assert(bool condition) {
++assertions_total;
if (condition) {
++assertions_successful;
}
return condition;
}
void handle_failfast() {
if (failfast) {
std::cerr << "Exiting immediately because failfast = true." << std::endl;
exit(1);
}
}
virtual void run() = 0;
virtual ~TestCase() {}
};
struct TestCaseInfo {
TestCase *test_case;
std::string name;
};
static std::vector<TestCaseInfo> storage;
struct TestCaseProcessor {
TestCaseProcessor(TestCaseInfo info) {
storage.push_back(info);
}
};
#ifdef __linux__
void signal_handler(int signum) {
std::cerr << E_ERROR("Signal occurred") << ": " << strsignal(signum) << std::endl;
/* Print the backtrace */
void *buffer[10];
size_t size = backtrace(buffer, 10);
backtrace_symbols_fd(buffer, size, STDERR_FILENO);
/* Now call the default handler */
signal(signum, SIG_DFL);
exit(128 + signum);
}
#endif
void print_help(const char *command_name) {
std::cout << "Usage: " << command_name << " [-f] [-n]" << std::endl;
std::cout << std::endl;
std::cout << " -f, --fail-fast: Exit after first failure" << std::endl;
std::cout << " -n, --no-catch: Do not catch C++ exceptions (useful for debugging)" << std::endl;
}
int main(int argc, char **argv) {
bool failfast = false;
bool nocatch = false;
for (int i = 1; i < argc; ++i) {
char *arg = argv[i];
if (!strcmp(arg, "--fail-fast") || !strcmp(arg, "-f")) {
failfast = true;
} else if (!strcmp(arg, "--no-catch") || !strcmp(arg, "-n")) {
nocatch = true;
} else {
print_help(argv[0]);
return 0;
}
}
#ifdef __USE_GNU
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
#endif
#ifdef __linux__
signal(SIGSEGV, signal_handler);
#endif
int result = 0;
for (const TestCaseInfo &info: storage) {
std::cout << " * " << info.name << std::endl;
TestCase *test_case = info.test_case;
test_case->failfast = failfast;
bool success = true;
if (nocatch) {
test_case->run();
} else {
try {
test_case->run();
} catch (const std::exception &e) {
success = false;
std::cerr << " " << E_ERROR("Exception occurred") << ": " << e.what() << std::endl;
}
}
if (test_case->assertions_successful < test_case->assertions_total) {
success = false;
}
if (success) {
std::cout << " Result: " << O_SUCCESS("SUCCESS")
<< " (" << test_case->assertions_total << " assertions passed)"
<< std::endl;
} else {
std::cout << " Result: " << O_ERROR("FAIL")
<< " (" << test_case->assertions_successful << " of "
<< test_case->assertions_total << " assertions passed)"
<< std::endl;
result = 1;
}
delete test_case;
}
return result;
}
#endif /* CXXUNIT_TESTING_HPP */