206 lines
3.9 KiB
C++
206 lines
3.9 KiB
C++
#ifndef HEADER_TEST_U_HPP
|
|
#define HEADER_TEST_U_HPP
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
#include <tuple>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
template<typename T>
|
|
struct TestDefaultEps {};
|
|
|
|
template<>
|
|
struct TestDefaultEps<float> {
|
|
static constexpr float value = 1e-6;
|
|
};
|
|
|
|
template<>
|
|
struct TestDefaultEps<double> {
|
|
static constexpr double value = 1e-9;
|
|
};
|
|
|
|
template<>
|
|
struct TestDefaultEps<long double> {
|
|
static constexpr long double value = 1e-12;
|
|
};
|
|
|
|
class TestCase {
|
|
public:
|
|
TestCase(int id, const char* title): ok(0), fail(0), n(0), ended(false), time_used(0) {
|
|
std::printf("[%02d]Testing %s:\n", id, title);
|
|
}
|
|
|
|
void expectTrue(std::function<bool()> func) noexcept {
|
|
expectEq<bool>(func, true);
|
|
}
|
|
|
|
void expectFalse(std::function<bool()> func) noexcept {
|
|
expectEq<bool>(func, false);
|
|
}
|
|
|
|
template<typename T, typename EPS = TestDefaultEps<T>>
|
|
typename std::enable_if<std::is_floating_point<T>::value>::type expectEq(
|
|
std::function<T()> func,
|
|
T&& answer) noexcept {
|
|
auto res = runTask(func);
|
|
if (!res.has_value()) {
|
|
return onError();
|
|
}
|
|
|
|
if (std::abs(res.value() - answer) < EPS::value) {
|
|
onPass();
|
|
} else {
|
|
onFail();
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
typename std::enable_if<!std::is_floating_point<T>::value>::type expectEq(
|
|
std::function<T()> func,
|
|
T&& answer) noexcept {
|
|
auto res = runTask(func);
|
|
if (!res.has_value()) {
|
|
return onError();
|
|
}
|
|
|
|
if (res.value() == answer) {
|
|
onPass();
|
|
} else {
|
|
onFail();
|
|
}
|
|
}
|
|
|
|
void end() noexcept {
|
|
if (ended) {
|
|
return;
|
|
}
|
|
ended = true;
|
|
|
|
std::putchar('\n');
|
|
for (int i = 0; i < 20; ++i) {
|
|
std::putchar('-');
|
|
}
|
|
std::putchar('\n');
|
|
if (fail) {
|
|
if (fail == 1) {
|
|
std::puts("1 test failed.");
|
|
} else {
|
|
std::printf("%d tests failed.\n", fail);
|
|
}
|
|
} else {
|
|
if (ok == 1) {
|
|
std::printf("OK. 1 test passed");
|
|
} else {
|
|
std::printf("OK. %d tests passed", ok);
|
|
}
|
|
std::printf(" in %lldms.\n", static_cast<long long>(time_used.count()));
|
|
}
|
|
}
|
|
|
|
bool hasFail() const noexcept {
|
|
return fail > 0;
|
|
}
|
|
|
|
private:
|
|
void putchar(char c) noexcept {
|
|
n += 1;
|
|
if (n > 1 && n % 15 == 1) {
|
|
std::putchar('\n');
|
|
}
|
|
std::putchar(c);
|
|
}
|
|
|
|
void onPass() noexcept {
|
|
ok += 1;
|
|
putchar('.');
|
|
}
|
|
|
|
void onFail() noexcept {
|
|
fail += 1;
|
|
putchar('F');
|
|
}
|
|
|
|
void onError() noexcept {
|
|
fail += 1;
|
|
putchar('E');
|
|
}
|
|
|
|
template<typename T>
|
|
std::optional<T> runTask(std::function<T()> func) noexcept {
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
try {
|
|
T res = func();
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto dt = end - start;
|
|
time_used += std::chrono::duration_cast<std::chrono::milliseconds>(dt);
|
|
return {res};
|
|
} catch (...) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
int ok, fail, n;
|
|
bool ended;
|
|
std::chrono::milliseconds time_used;
|
|
};
|
|
|
|
class TestSuit {
|
|
TestSuit(const TestSuit&) = delete;
|
|
TestSuit(TestSuit&&) = delete;
|
|
|
|
public:
|
|
static TestSuit* getInstance() {
|
|
if (!instance) {
|
|
instance = new TestSuit();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
void addTestCase(int id, const char* title, std::function<void(TestCase&)> f) {
|
|
tc.emplace_back(id, title, f);
|
|
}
|
|
|
|
int evalTestCases() {
|
|
std::sort(tc.begin(), tc.end(), [](const auto &x, const auto &y) {
|
|
return std::get<0>(x) < std::get<0>(y);
|
|
});
|
|
|
|
for (auto [id, title, func] : tc) {
|
|
TestCase t(id, title);
|
|
func(t);
|
|
t.end();
|
|
std::putchar('\n');
|
|
if (t.hasFail()) {
|
|
return 1;
|
|
}
|
|
}
|
|
tc.clear();
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
static inline TestSuit* instance = nullptr;
|
|
TestSuit() {}
|
|
|
|
std::vector<std::tuple<int, const char*, std::function<void(TestCase&)>>> tc;
|
|
};
|
|
|
|
class AddTestCase {
|
|
AddTestCase() = delete;
|
|
AddTestCase(const AddTestCase&) = delete;
|
|
AddTestCase(AddTestCase&&) = delete;
|
|
|
|
public:
|
|
AddTestCase(int id, const char* tilte, std::function<void(TestCase&)> func) {
|
|
TestSuit::getInstance()->addTestCase(id, tilte, func);
|
|
}
|
|
};
|
|
|
|
#endif // HEADER_TEST_U_HPP
|
|
|