add a unit tester

This commit is contained in:
方而静 2023-05-27 15:00:55 +08:00
parent f8902de5e6
commit 1204c58ff5
6 changed files with 221 additions and 4 deletions

View File

@ -53,7 +53,7 @@ BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 120
ColumnLimit: 99
QualifierAlignment: Left
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4

10
2d.hpp
View File

@ -21,8 +21,10 @@ Float dot(const Vec2& a, const Vec2& b) {
struct Circle {
Vec2 o;
Float r;
Circle() {}
Circle() = delete;
Circle(const Vec2& o, Float r): o(o), r(r) {}
bool inside(const Vec2& a) const {
return abs(o - a) <= r;
}
@ -32,8 +34,9 @@ template<int n>
struct Polygon {
std::array<Vec2, n> vertex;
Polygon() {}
Polygon() = delete;
Polygon(const std::array<Vec2, n>& v): vertex(v) {}
Vec2& operator[](int i) {
return vertex[i];
}
@ -73,7 +76,8 @@ bool intersect(const Polygon<n>& a, const Polygon<m>& b) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (cross(b[j] - a[i], a[(i + 1) % n] - a[i]) * cross(b[(j + 1) % m] - a[i], a[(i + 1) % n] - a[i]) <= 0
&& cross(a[i] - b[j], b[(j + 1) % m] - b[j]) * cross(a[(i + 1) % n] - b[j], b[(j + 1) % m] - b[j]) <= 0) {
&& cross(a[i] - b[j], b[(j + 1) % m] - b[j]) * cross(a[(i + 1) % n] - b[j], b[(j + 1) % m] - b[j])
<= 0) {
return true;
}
}

13
test/cases/2d.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "2d.hpp"
#include "u.hpp"
#include <functional>
namespace {
AddTestCase _("2d.hpp", [](TestCase &&t) {
t.expectEq<Float>([]{ return cross({1, 2}, {4, 1}); }, -7);
t.expectEq<Float>([]{ return dot({1, 2}, {4, 1}); }, 6);
});
}

13
test/cases/provider.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "u.hpp"
namespace {
AddTestCase _("tester", [](TestCase &&t) {
t.expectTrue([]{ return true; });
t.expectFalse([]{ return false; });
t.expectEq<int>([]{ return 42; }, 42);
t.expectEq<float>([] { return 10 + 1e-7; }, 10);
t.expectEq<double>([] { return 10 + 1e-10; }, 10);
});
}

6
test/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "u.hpp"
int main() {
TestSuit::getInstance()->evalTestCases();
return 0;
}

181
test/u.hpp Normal file
View File

@ -0,0 +1,181 @@
#ifndef HEADER_TEST_U_HPP
#define HEADER_TEST_U_HPP
#include <chrono>
#include <cstdio>
#include <exception>
#include <functional>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
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(const char* title): ok(0), fail(0), n(0) {
std::printf("Testing %s:\n", 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();
}
}
~TestCase() {
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 %dms.\n", time_used.count());
}
}
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;
std::putchar('.');
}
void onFail() noexcept {
fail += 1;
std::putchar('F');
}
void onError() noexcept {
fail += 1;
std::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;
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(const char* title, std::function<void(TestCase&&)> f) {
tc.emplace_back(title, f);
}
void evalTestCases() {
for (auto [title, func] : tc) {
func(TestCase(title));
std::putchar('\n');
}
tc.clear();
}
private:
static inline TestSuit* instance = nullptr;
TestSuit() {}
std::vector<std::pair<const char*, std::function<void(TestCase&&)>>> tc;
};
class AddTestCase {
AddTestCase() = delete;
AddTestCase(const AddTestCase&) = delete;
AddTestCase(AddTestCase&&) = delete;
public:
AddTestCase(const char* tilte, std::function<void(TestCase&&)> func) {
TestSuit::getInstance()->addTestCase(tilte, func);
}
};
#endif // HEADER_TEST_U_HPP