From 1204c58ff5746d1e58b564410c00aa1f74d22909 Mon Sep 17 00:00:00 2001 From: szdytom Date: Sat, 27 May 2023 15:00:55 +0800 Subject: [PATCH] add a unit tester --- .clang-format | 2 +- 2d.hpp | 10 ++- test/cases/2d.cpp | 13 +++ test/cases/provider.cpp | 13 +++ test/main.cpp | 6 ++ test/u.hpp | 181 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 test/cases/2d.cpp create mode 100644 test/cases/provider.cpp create mode 100644 test/main.cpp create mode 100644 test/u.hpp diff --git a/.clang-format b/.clang-format index 35b4843..f555efa 100644 --- a/.clang-format +++ b/.clang-format @@ -53,7 +53,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakConstructorInitializers: BeforeComma BreakStringLiterals: true -ColumnLimit: 120 +ColumnLimit: 99 QualifierAlignment: Left CompactNamespaces: false ConstructorInitializerIndentWidth: 4 diff --git a/2d.hpp b/2d.hpp index 9ec23ae..d884e2c 100644 --- a/2d.hpp +++ b/2d.hpp @@ -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 struct Polygon { std::array vertex; - Polygon() {} + Polygon() = delete; Polygon(const std::array& v): vertex(v) {} + Vec2& operator[](int i) { return vertex[i]; } @@ -73,7 +76,8 @@ bool intersect(const Polygon& a, const Polygon& 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; } } diff --git a/test/cases/2d.cpp b/test/cases/2d.cpp new file mode 100644 index 0000000..4c1d4e1 --- /dev/null +++ b/test/cases/2d.cpp @@ -0,0 +1,13 @@ +#include "2d.hpp" +#include "u.hpp" + +#include + +namespace { + +AddTestCase _("2d.hpp", [](TestCase &&t) { + t.expectEq([]{ return cross({1, 2}, {4, 1}); }, -7); + t.expectEq([]{ return dot({1, 2}, {4, 1}); }, 6); +}); + +} \ No newline at end of file diff --git a/test/cases/provider.cpp b/test/cases/provider.cpp new file mode 100644 index 0000000..1212828 --- /dev/null +++ b/test/cases/provider.cpp @@ -0,0 +1,13 @@ +#include "u.hpp" + +namespace { + +AddTestCase _("tester", [](TestCase &&t) { + t.expectTrue([]{ return true; }); + t.expectFalse([]{ return false; }); + t.expectEq([]{ return 42; }, 42); + t.expectEq([] { return 10 + 1e-7; }, 10); + t.expectEq([] { return 10 + 1e-10; }, 10); +}); + +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..790f508 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,6 @@ +#include "u.hpp" + +int main() { + TestSuit::getInstance()->evalTestCases(); + return 0; +} \ No newline at end of file diff --git a/test/u.hpp b/test/u.hpp new file mode 100644 index 0000000..cd71a4d --- /dev/null +++ b/test/u.hpp @@ -0,0 +1,181 @@ +#ifndef HEADER_TEST_U_HPP +#define HEADER_TEST_U_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +template +struct TestDefaultEps {}; + +template<> +struct TestDefaultEps { + static constexpr float value = 1e-6; +}; + +template<> +struct TestDefaultEps { + static constexpr double value = 1e-9; +}; + +template<> +struct TestDefaultEps { + 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 func) noexcept { + expectEq(func, true); + } + + void expectFalse(std::function func) noexcept { + expectEq(func, false); + } + + template> + typename std::enable_if::value>::type expectEq( + std::function 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 std::enable_if::value>::type expectEq(std::function 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 + std::optional runTask(std::function 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(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 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>> tc; +}; + +class AddTestCase { + AddTestCase() = delete; + AddTestCase(const AddTestCase&) = delete; + AddTestCase(AddTestCase&&) = delete; + +public: + AddTestCase(const char* tilte, std::function func) { + TestSuit::getInstance()->addTestCase(tilte, func); + } +}; + +#endif // HEADER_TEST_U_HPP