From 60e0e213a0ab785add0a7ab62e8b2b98acdc8faf Mon Sep 17 00:00:00 2001 From: szdytom Date: Wed, 6 Aug 2025 11:28:11 +0800 Subject: [PATCH] refactor: tests with Catch2, consolidate test cases and improve structure Signed-off-by: szdytom --- third_party/CMakeLists.txt | 1 + third_party/catch2.cmake | 12 ++ util/test/CMakeLists.txt | 20 ++-- util/test/small_map.cpp | 77 ------------- util/test/test_small_map.cpp | 103 +++++++++++++++++ util/test/test_tile_geometry.cpp | 182 +++++++++++++++++-------------- util/test/test_vec2.cpp | 118 +++++++++++++------- 7 files changed, 306 insertions(+), 207 deletions(-) create mode 100644 third_party/catch2.cmake delete mode 100644 util/test/small_map.cpp create mode 100644 util/test/test_small_map.cpp diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index b80d958..b5e5b28 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.27) set(ISTD_THIRD_PARTY_LIBS entt asio + catch2 ) foreach(lib IN LISTS ISTD_THIRD_PARTY_LIBS) diff --git a/third_party/catch2.cmake b/third_party/catch2.cmake new file mode 100644 index 0000000..e374917 --- /dev/null +++ b/third_party/catch2.cmake @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.27) +cmake_policy(VERSION 3.27) +include(FetchContent) + +# Catch2 third-party library setup +message(STATUS "Downloading Catch2...") +FetchContent_Declare( + Catch2 + URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.9.0.zip" +) +FetchContent_MakeAvailable(Catch2) +message(STATUS "Catch2 ready") diff --git a/util/test/CMakeLists.txt b/util/test/CMakeLists.txt index 394a031..d9183bc 100644 --- a/util/test/CMakeLists.txt +++ b/util/test/CMakeLists.txt @@ -3,13 +3,15 @@ cmake_minimum_required(VERSION 3.27) include(CTest) enable_testing() -function(declare_istd_util_test name src) - add_executable(${name} ${src}) - target_link_libraries(${name} PRIVATE istd_util) - target_compile_features(${name} PRIVATE cxx_std_23) - add_test(NAME ${name} COMMAND ${name}) -endfunction() +# Create a unified test executable from multiple source files +add_executable(istd_util_tests + test_small_map.cpp + test_vec2.cpp + test_tile_geometry.cpp +) -declare_istd_util_test(test_small_map small_map.cpp) -declare_istd_util_test(test_vec2 test_vec2.cpp) -declare_istd_util_test(test_tile_geometry test_tile_geometry.cpp) +target_link_libraries(istd_util_tests PRIVATE istd_util Catch2::Catch2WithMain) +target_compile_features(istd_util_tests PRIVATE cxx_std_23) + +# Add the test to CTest +add_test(NAME istd_util_tests COMMAND istd_util_tests) diff --git a/util/test/small_map.cpp b/util/test/small_map.cpp deleted file mode 100644 index 3ab36aa..0000000 --- a/util/test/small_map.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "istd_util/small_map.h" -#include - -int main() { - using namespace istd; - // Test insert and size - SmallMap map; - assert(map.empty()); - map.insert(1, 10); - map.insert(2, 20); - map.insert(3, 30); - assert(map.size() == 3); - assert(!map.empty()); - - // Test operator[] - assert(map[1] == 10); - assert(map[2] == 20); - assert(map[3] == 30); - - // Test erase - map.erase(2); - assert(map.size() == 2); - assert(map[1] == 10); - assert(map[3] == 30); - - // Test clear - map.clear(); - assert(map.empty()); - - // Test duplicate insert throws - SmallMap map2; - map2.insert(5, 50); - bool thrown = false; - try { - map2.insert(5, 60); - } catch (const std::invalid_argument &) { - thrown = true; - } - assert(thrown); - - // Test out_of_range on operator[] - thrown = false; - try { - (void)map2[99]; - } catch (const std::out_of_range &) { - thrown = true; - } - assert(thrown); - - // Test erase throws on missing key - thrown = false; - try { - map2.erase(99); - } catch (const std::out_of_range &) { - thrown = true; - } - assert(thrown); - - // Test iterator - map2.insert(6, 60); - map2.insert(7, 70); - int sum = 0; - for (auto it = map2.begin(); it != map2.end(); ++it) { - sum += it->value; - } - assert(sum == 180); // 50 + 60 + 70 - - // Test const iterators - const auto &cmap = map2; - sum = 0; - for (auto it = cmap.cbegin(); it != cmap.cend(); ++it) { - sum += it->value; - } - assert(sum == 180); - - return 0; -} diff --git a/util/test/test_small_map.cpp b/util/test/test_small_map.cpp new file mode 100644 index 0000000..39c9b47 --- /dev/null +++ b/util/test/test_small_map.cpp @@ -0,0 +1,103 @@ +#include "istd_util/small_map.h" +#include +#include + +using namespace istd; + +TEST_CASE("SmallMap basic operations", "[small_map]") { + SECTION("insert and size") { + SmallMap map; + REQUIRE(map.empty()); + + map.insert(1, 10); + map.insert(2, 20); + map.insert(3, 30); + + REQUIRE(map.size() == 3); + REQUIRE_FALSE(map.empty()); + } + + SECTION("operator[] access") { + SmallMap map; + map.insert(1, 10); + map.insert(2, 20); + map.insert(3, 30); + + REQUIRE(map[1] == 10); + REQUIRE(map[2] == 20); + REQUIRE(map[3] == 30); + } + + SECTION("erase operation") { + SmallMap map; + map.insert(1, 10); + map.insert(2, 20); + map.insert(3, 30); + + map.erase(2); + REQUIRE(map.size() == 2); + REQUIRE(map[1] == 10); + REQUIRE(map[3] == 30); + } + + SECTION("clear operation") { + SmallMap map; + map.insert(1, 10); + map.insert(2, 20); + + map.clear(); + REQUIRE(map.empty()); + } +} + +TEST_CASE("SmallMap exception handling", "[small_map]") { + SECTION("duplicate insert throws") { + SmallMap map; + map.insert(5, 50); + + REQUIRE_THROWS_AS(map.insert(5, 60), std::invalid_argument); + } + + SECTION("out_of_range on operator[]") { + SmallMap map; + map.insert(5, 50); + + REQUIRE_THROWS_AS(map[99], std::out_of_range); + } + + SECTION("erase throws on missing key") { + SmallMap map; + map.insert(5, 50); + + REQUIRE_THROWS_AS(map.erase(99), std::out_of_range); + } +} + +TEST_CASE("SmallMap iterators", "[small_map]") { + SECTION("iterator traversal") { + SmallMap map; + map.insert(5, 50); + map.insert(6, 60); + map.insert(7, 70); + + int sum = 0; + for (auto it = map.begin(); it != map.end(); ++it) { + sum += it->value; + } + REQUIRE(sum == 180); // 50 + 60 + 70 + } + + SECTION("const iterators") { + SmallMap map; + map.insert(5, 50); + map.insert(6, 60); + map.insert(7, 70); + + const auto &cmap = map; + int sum = 0; + for (auto it = cmap.cbegin(); it != cmap.cend(); ++it) { + sum += it->value; + } + REQUIRE(sum == 180); + } +} diff --git a/util/test/test_tile_geometry.cpp b/util/test/test_tile_geometry.cpp index b6066c7..5d3e077 100644 --- a/util/test/test_tile_geometry.cpp +++ b/util/test/test_tile_geometry.cpp @@ -1,98 +1,112 @@ #include "istd_util/tile_geometry.h" -#include +#include +#include #include #include + using namespace istd; +using Catch::Approx; -int main() { - // Test a simple horizontal segment - Vec2 p1(0.5f, 1.2f); - Vec2 p2(0.5f, 4.8f); - std::vector> result; - for (auto [i, j] : tiles_on_segment(p1, p2)) { - result.emplace_back(i, j); - } - // Should traverse columns 1 to 4, row 0 - assert(result.size() == 4); - assert(result[0] == std::make_tuple(0, 1)); - assert(result[1] == std::make_tuple(0, 2)); - assert(result[2] == std::make_tuple(0, 3)); - assert(result[3] == std::make_tuple(0, 4)); +TEST_CASE("tiles_on_segment function", "[tile_geometry]") { + SECTION("horizontal segment") { + Vec2 p1(0.5f, 1.2f); + Vec2 p2(0.5f, 4.8f); + std::vector> result; + for (auto [i, j] : tiles_on_segment(p1, p2)) { + result.emplace_back(i, j); + } - // Test a diagonal segment - p1 = Vec2(1.1f, 1.1f); - p2 = Vec2(3.9f, 3.9f); - result.clear(); - for (auto [i, j] : tiles_on_segment(p1, p2)) { - result.emplace_back(i, j); - } - // Should traverse (1,1), (2,2), (3,3) - assert(result.size() == 3); - assert(result[0] == std::make_tuple(1, 1)); - assert(result[1] == std::make_tuple(2, 2)); - assert(result[2] == std::make_tuple(3, 3)); - - // Test vertical segment - p1 = Vec2(2.2f, 0.5f); - p2 = Vec2(5.7f, 0.5f); - result.clear(); - for (auto [i, j] : tiles_on_segment(p1, p2)) { - result.emplace_back(i, j); - } - // Should traverse rows 2 to 5, column 0 - assert(result.size() == 4); - assert(result[0] == std::make_tuple(2, 0)); - assert(result[1] == std::make_tuple(3, 0)); - assert(result[2] == std::make_tuple(4, 0)); - assert(result[3] == std::make_tuple(5, 0)); - - // Test single tile - p1 = Vec2(7.3f, 8.9f); - p2 = Vec2(7.7f, 8.1f); - result.clear(); - for (auto [i, j] : tiles_on_segment(p1, p2)) { - result.emplace_back(i, j); - } - assert(result.size() == 1); - assert(result[0] == std::make_tuple(7, 8)); - - // Test tile_segment_intersection: horizontal segment - p1 = Vec2(0.5f, 1.2f); - p2 = Vec2(0.5f, 4.8f); - { - Vec2 inter = tile_segment_intersection(p1, p2, {0, 2}); - assert(inter.is_valid()); - assert(std::abs(inter.x - 0.5f) < 1e-6); - assert(inter.y >= 2.0f && inter.y <= 3.0f); + // Should traverse columns 1 to 4, row 0 + REQUIRE(result.size() == 4); + REQUIRE(result[0] == std::make_tuple(0, 1)); + REQUIRE(result[1] == std::make_tuple(0, 2)); + REQUIRE(result[2] == std::make_tuple(0, 3)); + REQUIRE(result[3] == std::make_tuple(0, 4)); } - // Test tile_segment_intersection: diagonal segment - p1 = Vec2(1.1f, 1.1f); - p2 = Vec2(3.9f, 3.9f); - { - Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); - assert(inter.is_valid()); - assert(inter.x >= 2.0f && inter.x <= 3.0f); - assert(inter.y >= 2.0f && inter.y <= 3.0f); + SECTION("diagonal segment") { + Vec2 p1(1.1f, 1.1f); + Vec2 p2(3.9f, 3.9f); + std::vector> result; + for (auto [i, j] : tiles_on_segment(p1, p2)) { + result.emplace_back(i, j); + } + + // Should traverse (1,1), (2,2), (3,3) + REQUIRE(result.size() == 3); + REQUIRE(result[0] == std::make_tuple(1, 1)); + REQUIRE(result[1] == std::make_tuple(2, 2)); + REQUIRE(result[2] == std::make_tuple(3, 3)); } - // Test tile_segment_intersection: no intersection - p1 = Vec2(0.0f, 0.0f); - p2 = Vec2(0.5f, 0.5f); - { - Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); - assert(!inter.is_valid()); + SECTION("vertical segment") { + Vec2 p1(2.2f, 0.5f); + Vec2 p2(5.7f, 0.5f); + std::vector> result; + for (auto [i, j] : tiles_on_segment(p1, p2)) { + result.emplace_back(i, j); + } + + // Should traverse rows 2 to 5, column 0 + REQUIRE(result.size() == 4); + REQUIRE(result[0] == std::make_tuple(2, 0)); + REQUIRE(result[1] == std::make_tuple(3, 0)); + REQUIRE(result[2] == std::make_tuple(4, 0)); + REQUIRE(result[3] == std::make_tuple(5, 0)); } - // Test tile_segment_intersection: segment starts inside tile - p1 = Vec2(2.2f, 2.2f); - p2 = Vec2(5.0f, 5.0f); - { - Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); - assert(inter.is_valid()); - assert(std::abs(inter.x - 2.2f) < 1e-6); - assert(std::abs(inter.y - 2.2f) < 1e-6); - } + SECTION("single tile") { + Vec2 p1(7.3f, 8.9f); + Vec2 p2(7.7f, 8.1f); + std::vector> result; + for (auto [i, j] : tiles_on_segment(p1, p2)) { + result.emplace_back(i, j); + } - return 0; + REQUIRE(result.size() == 1); + REQUIRE(result[0] == std::make_tuple(7, 8)); + } +} + +TEST_CASE("tile_segment_intersection function", "[tile_geometry]") { + SECTION("horizontal segment intersection") { + Vec2 p1(0.5f, 1.2f); + Vec2 p2(0.5f, 4.8f); + Vec2 inter = tile_segment_intersection(p1, p2, {0, 2}); + + REQUIRE(inter.is_valid()); + REQUIRE(inter.x == Approx(0.5f).epsilon(1e-6)); + REQUIRE(inter.y >= 2.0f); + REQUIRE(inter.y <= 3.0f); + } + + SECTION("diagonal segment intersection") { + Vec2 p1(1.1f, 1.1f); + Vec2 p2(3.9f, 3.9f); + Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); + + REQUIRE(inter.is_valid()); + REQUIRE(inter.x >= 2.0f); + REQUIRE(inter.x <= 3.0f); + REQUIRE(inter.y >= 2.0f); + REQUIRE(inter.y <= 3.0f); + } + + SECTION("no intersection") { + Vec2 p1(0.0f, 0.0f); + Vec2 p2(0.5f, 0.5f); + Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); + + REQUIRE_FALSE(inter.is_valid()); + } + + SECTION("segment starts inside tile") { + Vec2 p1(2.2f, 2.2f); + Vec2 p2(5.0f, 5.0f); + Vec2 inter = tile_segment_intersection(p1, p2, {2, 2}); + + REQUIRE(inter.is_valid()); + REQUIRE(inter.x == Approx(2.2f).epsilon(1e-6)); + REQUIRE(inter.y == Approx(2.2f).epsilon(1e-6)); + } } diff --git a/util/test/test_vec2.cpp b/util/test/test_vec2.cpp index 54deca4..373206a 100644 --- a/util/test/test_vec2.cpp +++ b/util/test/test_vec2.cpp @@ -1,45 +1,89 @@ #include "istd_util/vec2.h" -#include +#include +#include #include -using namespace istd; -int main() { +using namespace istd; +using Catch::Approx; + +TEST_CASE("Vec2 length operations", "[vec2]") { + Vec2 v1(3.0f, 4.0f); + + SECTION("length calculation") { + REQUIRE(v1.length() == 5.0f); + REQUIRE(v1.length_squared() == 25.0f); + } + + SECTION("normalized vector") { + Vec2 n = v1.normalized(); + REQUIRE(n.length() == Approx(1.0f).epsilon(1e-6)); + } +} + +TEST_CASE("Vec2 arithmetic operations", "[vec2]") { Vec2 v1(3.0f, 4.0f); Vec2 v2(1.0f, 2.0f); - // Test length and length_squared - assert(v1.length() == 5.0f); - assert(v1.length_squared() == 25.0f); + SECTION("addition") { + Vec2 v3 = v1 + v2; + REQUIRE(v3.x == 4.0f); + REQUIRE(v3.y == 6.0f); + } - // Test normalized - Vec2 n = v1.normalized(); - assert(std::abs(n.length() - 1.0f) < 1e-6); - - // Test addition and subtraction - Vec2 v3 = v1 + v2; - assert(v3.x == 4.0f && v3.y == 6.0f); - Vec2 v4 = v1 - v2; - assert(v4.x == 2.0f && v4.y == 2.0f); - - // Test floor and round - Vec2 v5(1.7f, -2.3f); - auto f = v5.floor(); - auto r = v5.round(); - assert(f == std::make_tuple(1, -3)); - assert(r == std::make_tuple(2, -2)); - - // Test inf and invalid - Vec2 vinf = Vec2::inf(); - assert(std::isinf(vinf.x) && std::isinf(vinf.y)); - Vec2 vinvalid = Vec2::invalid(); - assert(std::isnan(vinvalid.x) && std::isnan(vinvalid.y)); - - // Test is_valid - assert(v1.is_valid()); - assert(!vinvalid.is_valid()); - - // Test static dot and cross - assert(Vec2::dot(v1, v2) == 11.0f); - assert(Vec2::cross(v1, v2) == 2.0f); - return 0; + SECTION("subtraction") { + Vec2 v4 = v1 - v2; + REQUIRE(v4.x == 2.0f); + REQUIRE(v4.y == 2.0f); + } +} + +TEST_CASE("Vec2 rounding operations", "[vec2]") { + Vec2 v5(1.7f, -2.3f); + + SECTION("floor operation") { + auto [i, j] = v5.floor(); + REQUIRE(i == 1); + REQUIRE(j == -3); + } + + SECTION("round operation") { + auto [i, j] = v5.round(); + REQUIRE(i == 2); + REQUIRE(j == -2); + } +} + +TEST_CASE("Vec2 special values", "[vec2]") { + SECTION("infinity vector") { + Vec2 vinf = Vec2::inf(); + REQUIRE(std::isinf(vinf.x)); + REQUIRE(std::isinf(vinf.y)); + } + + SECTION("invalid vector") { + Vec2 vinvalid = Vec2::invalid(); + REQUIRE(std::isnan(vinvalid.x)); + REQUIRE(std::isnan(vinvalid.y)); + } + + SECTION("validity check") { + Vec2 v1(3.0f, 4.0f); + Vec2 vinvalid = Vec2::invalid(); + + REQUIRE(v1.is_valid()); + REQUIRE_FALSE(vinvalid.is_valid()); + } +} + +TEST_CASE("Vec2 static operations", "[vec2]") { + Vec2 v1(3.0f, 4.0f); + Vec2 v2(1.0f, 2.0f); + + SECTION("dot product") { + REQUIRE(Vec2::dot(v1, v2) == 11.0f); + } + + SECTION("cross product") { + REQUIRE(Vec2::cross(v1, v2) == 2.0f); + } }