diff --git a/test/common/test_version.cpp b/test/common/test_version.cpp index c6e51d5..f4c7279 100644 --- a/test/common/test_version.cpp +++ b/test/common/test_version.cpp @@ -8,6 +8,8 @@ namespace { +namespace CM = Catch::Matchers; + CATCH_TEST_CASE("WHIRLWIND_VERSION_EPOCH", "[version]") { using T = decltype(WHIRLWIND_VERSION_EPOCH); @@ -24,8 +26,7 @@ CATCH_TEST_CASE("WHIRLWIND_VERSION_PATCH", "[version]") CATCH_TEST_CASE("WHIRLWIND_VERSION_STRING", "[version]") { - using Catch::Matchers::Matches; - CATCH_CHECK_THAT(WHIRLWIND_VERSION_STRING, Matches(R"(^\d{8}\.\d+$)")); + CATCH_CHECK_THAT(WHIRLWIND_VERSION_STRING, CM::Matches(R"(^\d{8}\.\d+$)")); } } // namespace diff --git a/test/graph/test_csr_graph.cpp b/test/graph/test_csr_graph.cpp index 6975263..c11c3d6 100644 --- a/test/graph/test_csr_graph.cpp +++ b/test/graph/test_csr_graph.cpp @@ -9,10 +9,12 @@ #include #include +#include "../testing/matchers/graph_matchers.hpp" #include "../testing/string_conversions.hpp" // IWYU pragma: keep namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; CATCH_TEST_CASE("CSRGraph (empty)", "[graph]") @@ -27,8 +29,8 @@ CATCH_TEST_CASE("CSRGraph (empty)", "[graph]") CATCH_SECTION("contains_{vertex,edge}") { - CATCH_CHECK_FALSE(graph.contains_vertex(0U)); - CATCH_CHECK_FALSE(graph.contains_edge(0U)); + CATCH_CHECK_THAT(graph, !ww::testing::ContainsVertex(0U)); + CATCH_CHECK_THAT(graph, !ww::testing::ContainsEdge(0U)); } } @@ -75,22 +77,23 @@ CATCH_TEST_CASE("CSRGraph", "[graph]") CATCH_SECTION("{vertices,edges}") { - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(graph.vertices(), RangeEquals(vertices)); - CATCH_CHECK_THAT(graph.edges(), RangeEquals(edges)); + CATCH_CHECK_THAT(graph.vertices(), CM::RangeEquals(vertices)); + CATCH_CHECK_THAT(graph.edges(), CM::RangeEquals(edges)); } CATCH_SECTION("contains_{vertex,edge}") { - CATCH_CHECK(graph.contains_vertex(0U)); - CATCH_CHECK(graph.contains_vertex(3U)); - CATCH_CHECK_FALSE(graph.contains_vertex(999U)); - CATCH_CHECK_FALSE(graph.contains_vertex(4U)); - - CATCH_CHECK(graph.contains_edge(0U)); - CATCH_CHECK(graph.contains_edge(4U)); - CATCH_CHECK_FALSE(graph.contains_edge(999U)); - CATCH_CHECK_FALSE(graph.contains_edge(5U)); + using ww::testing::ContainsVertex; + CATCH_CHECK_THAT(graph, ContainsVertex(0U)); + CATCH_CHECK_THAT(graph, ContainsVertex(3U)); + CATCH_CHECK_THAT(graph, !ContainsVertex(999U)); + CATCH_CHECK_THAT(graph, !ContainsVertex(4U)); + + using ww::testing::ContainsEdge; + CATCH_CHECK_THAT(graph, ContainsEdge(0U)); + CATCH_CHECK_THAT(graph, ContainsEdge(4U)); + CATCH_CHECK_THAT(graph, !ContainsEdge(999U)); + CATCH_CHECK_THAT(graph, !ContainsEdge(5U)); } CATCH_SECTION("outdegree") @@ -105,8 +108,7 @@ CATCH_TEST_CASE("CSRGraph", "[graph]") { using Pair = std::pair; const auto outgoing_edges = {Pair(0U, 1U), Pair(1U, 2U), Pair(2U, 3U)}; - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(graph.outgoing_edges(0U), RangeEquals(outgoing_edges)); + CATCH_CHECK_THAT(graph.outgoing_edges(0U), CM::RangeEquals(outgoing_edges)); } } @@ -127,19 +129,18 @@ CATCH_TEST_CASE("CSRGraph (nonconsecutive vertices)", "[graph]") CATCH_SECTION("{vertices,edges}") { - using Catch::Matchers::RangeEquals; - const auto vertices = {0U, 1U, 2U, 3U, 4U, 5U}; - CATCH_CHECK_THAT(graph.vertices(), RangeEquals(vertices)); - const auto edges = {0U, 1U, 2U}; - CATCH_CHECK_THAT(graph.edges(), RangeEquals(edges)); + + CATCH_CHECK_THAT(graph.vertices(), CM::RangeEquals(vertices)); + CATCH_CHECK_THAT(graph.edges(), CM::RangeEquals(edges)); } CATCH_SECTION("contains_vertex") { - CATCH_CHECK(graph.contains_vertex(3U)); - CATCH_CHECK_FALSE(graph.contains_vertex(6U)); + using ww::testing::ContainsVertex; + CATCH_CHECK_THAT(graph, ContainsVertex(3U)); + CATCH_CHECK_THAT(graph, !ContainsVertex(6U)); } CATCH_SECTION("outdegree") @@ -191,13 +192,11 @@ CATCH_TEST_CASE("CSRGraph (unsorted edges)", "[graph]") CATCH_SECTION("{vertices,edges}") { - using Catch::Matchers::RangeEquals; - const auto vertices = {0U, 1U, 2U, 3U}; - CATCH_CHECK_THAT(graph.vertices(), RangeEquals(vertices)); - const auto edges = {0U, 1U, 2U, 3U, 4U}; - CATCH_CHECK_THAT(graph.edges(), RangeEquals(edges)); + + CATCH_CHECK_THAT(graph.vertices(), CM::RangeEquals(vertices)); + CATCH_CHECK_THAT(graph.edges(), CM::RangeEquals(edges)); } CATCH_SECTION("outgoing_edges") @@ -207,8 +206,7 @@ CATCH_TEST_CASE("CSRGraph (unsorted edges)", "[graph]") using Pair = std::pair; const auto outgoing_edges = {Pair(0U, 1U), Pair(1U, 2U), Pair(2U, 3U)}; - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(graph.outgoing_edges(0U), RangeEquals(outgoing_edges)); + CATCH_CHECK_THAT(graph.outgoing_edges(0U), CM::RangeEquals(outgoing_edges)); } } @@ -251,8 +249,9 @@ CATCH_TEST_CASE("CSRGraph (self loops)", "[graph]") CATCH_SECTION("contains_vertex") { - CATCH_CHECK(graph.contains_vertex(0U)); - CATCH_CHECK(graph.contains_vertex(2U)); + using ww::testing::ContainsVertex; + CATCH_CHECK_THAT(graph, ContainsVertex(0U)); + CATCH_CHECK_THAT(graph, ContainsVertex(2U)); } CATCH_SECTION("outdegree") { CATCH_CHECK(graph.outdegree(1U) == 4U); } @@ -265,8 +264,7 @@ CATCH_TEST_CASE("CSRGraph (self loops)", "[graph]") const auto outgoing_edges = {Pair(0U, 0U), Pair(1U, 1U), Pair(2U, 1U), Pair(3U, 2U)}; - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(graph.outgoing_edges(1U), RangeEquals(outgoing_edges)); + CATCH_CHECK_THAT(graph.outgoing_edges(1U), CM::RangeEquals(outgoing_edges)); } } diff --git a/test/graph/test_dial.cpp b/test/graph/test_dial.cpp index 5c66ed1..0030fef 100644 --- a/test/graph/test_dial.cpp +++ b/test/graph/test_dial.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -16,27 +17,33 @@ #include #include +#include "../testing/matchers/forest_matchers.hpp" +#include "../testing/matchers/range_matchers.hpp" + namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; CATCH_TEST_CASE("Dial", "[graph]") { - using D = int; - using G = ww::CSRGraph<>; + using Distance = int; + using Graph = ww::CSRGraph<>; + + constexpr auto max_distance = std::numeric_limits::max(); auto edgelist = ww::EdgeList(); edgelist.add_edge(0U, 1U); edgelist.add_edge(1U, 2U); edgelist.add_edge(2U, 3U); - const auto graph = G(edgelist); + const auto graph = Graph(edgelist); const auto num_buckets = 101U; - auto dial = ww::Dial(graph, num_buckets); + auto dial = ww::Dial(graph, num_buckets); - using Distance = decltype(dial)::distance_type; - using Graph = decltype(dial)::graph_type; + using Distance_ = decltype(dial)::distance_type; + using Graph_ = decltype(dial)::graph_type; using Vertex = decltype(dial)::vertex_type; using Edge = decltype(dial)::edge_type; using Size = decltype(dial)::size_type; @@ -45,20 +52,29 @@ CATCH_TEST_CASE("Dial", "[graph]") { CATCH_CHECK(std::addressof(dial.graph()) == std::addressof(graph)); CATCH_CHECK(dial.num_buckets() == num_buckets); + CATCH_CHECK(std::size(dial.buckets()) == num_buckets); - using Catch::Matchers::AllMatch; - using Catch::Matchers::IsEmpty; - CATCH_CHECK_THAT(dial.buckets(), AllMatch(IsEmpty())); + CATCH_CHECK_THAT(dial.buckets(), CM::AllMatch(CM::IsEmpty())); CATCH_CHECK(dial.current_bucket_id() == 0U); + CATCH_CHECK(dial.done()); + + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(dial))); + + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return dial.distance_to_vertex(vertex); + }); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(max_distance)); } CATCH_SECTION("{distance,graph,vertex,edge}_type") { - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); CATCH_STATIC_REQUIRE((std::is_same_v)); } @@ -90,9 +106,7 @@ CATCH_TEST_CASE("Dial", "[graph]") bucket.pop(); } - using Catch::Matchers::AllMatch; - using Catch::Matchers::IsEmpty; - CATCH_CHECK_THAT(dial.buckets(), AllMatch(IsEmpty())); + CATCH_CHECK_THAT(dial.buckets(), CM::AllMatch(CM::IsEmpty())); } CATCH_SECTION("pop_next_unvisited_vertex") @@ -106,7 +120,7 @@ CATCH_TEST_CASE("Dial", "[graph]") const auto [vertex1, distance1] = dial.pop_next_unvisited_vertex(); CATCH_CHECK(dial.current_bucket_id() == 0U); - CATCH_CHECK_THAT(dial.current_bucket(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dial.current_bucket(), CM::IsEmpty()); CATCH_CHECK(vertex1 == vertex0); CATCH_CHECK(distance1 == distance0); } @@ -120,24 +134,18 @@ CATCH_TEST_CASE("Dial", "[graph]") CATCH_CHECK(dial.current_bucket_id() == 0U); CATCH_CHECK(std::size(dial.current_bucket()) == std::size(sources)); + CATCH_CHECK_THAT(sources, CM::AllMatch(ww::testing::WasReachedBy(dial))); - const auto has_reached_sources = - sources | ranges::views::transform([&](const auto& source) { - return dial.has_reached_vertex(source); - }); - CATCH_CHECK_THAT(has_reached_sources, Catch::Matchers::AllTrue()); - - using Catch::Matchers::Contains; const auto [vertex0, distance0] = dial.pop_next_unvisited_vertex(); - CATCH_CHECK_THAT(sources, Contains(vertex0)); + CATCH_CHECK_THAT(sources, CM::Contains(vertex0)); CATCH_CHECK(distance0 == 0); const auto [vertex1, distance1] = dial.pop_next_unvisited_vertex(); - CATCH_CHECK_THAT(sources, Contains(vertex1)); + CATCH_CHECK_THAT(sources, CM::Contains(vertex1)); CATCH_CHECK(distance1 == 0); CATCH_CHECK(dial.current_bucket_id() == 0U); - CATCH_CHECK_THAT(dial.current_bucket(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dial.current_bucket(), CM::IsEmpty()); CATCH_CHECK(vertex0 != vertex1); } @@ -147,19 +155,20 @@ CATCH_TEST_CASE("Dial", "[graph]") const auto distance0 = 0; dial.add_source(vertex0); - CATCH_CHECK_FALSE(dial.has_visited_vertex(vertex0)); + using ww::testing::WasVisitedBy; + CATCH_CHECK_THAT(vertex0, !WasVisitedBy(dial)); dial.visit_vertex(vertex0, distance0); - CATCH_CHECK(dial.has_visited_vertex(vertex0)); + CATCH_CHECK_THAT(vertex0, WasVisitedBy(dial)); CATCH_CHECK(dial.distance_to_vertex(vertex0) == distance0); const auto edge = 0U; const auto vertex1 = 1U; const auto distance1 = 10; dial.relax_edge(edge, vertex0, vertex1, distance1); - CATCH_CHECK_FALSE(dial.has_visited_vertex(vertex1)); + CATCH_CHECK_THAT(vertex1, !WasVisitedBy(dial)); dial.visit_vertex(vertex1, distance1); - CATCH_CHECK(dial.has_visited_vertex(vertex1)); + CATCH_CHECK_THAT(vertex1, WasVisitedBy(dial)); CATCH_CHECK(dial.distance_to_vertex(vertex1) == distance1); } @@ -178,8 +187,8 @@ CATCH_TEST_CASE("Dial", "[graph]") const auto length = 10; dial.relax_edge(edge, tail, head, distance + length); - CATCH_CHECK(dial.has_reached_vertex(head)); - CATCH_CHECK_FALSE(dial.has_visited_vertex(head)); + CATCH_CHECK_THAT(head, ww::testing::WasReachedBy(dial)); + CATCH_CHECK_THAT(head, !ww::testing::WasVisitedBy(dial)); CATCH_CHECK(dial.distance_to_vertex(head) == distance + length); const auto bucket_id = dial.get_bucket_id(distance + length); @@ -216,11 +225,19 @@ CATCH_TEST_CASE("Dial", "[graph]") } dial.reset(); - using Catch::Matchers::AllMatch; - using Catch::Matchers::IsEmpty; - CATCH_CHECK_THAT(dial.buckets(), AllMatch(IsEmpty())); + + CATCH_CHECK_THAT(dial.buckets(), CM::AllMatch(CM::IsEmpty())); CATCH_CHECK(dial.current_bucket_id() == 0U); CATCH_CHECK(dial.done()); + + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(dial))); + + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return dial.distance_to_vertex(vertex); + }); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(max_distance)); } } diff --git a/test/graph/test_dijkstra.cpp b/test/graph/test_dijkstra.cpp index a336e31..878a38a 100644 --- a/test/graph/test_dijkstra.cpp +++ b/test/graph/test_dijkstra.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -15,42 +16,58 @@ #include #include +#include "../testing/matchers/forest_matchers.hpp" +#include "../testing/matchers/range_matchers.hpp" + namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; CATCH_TEST_CASE("Dijkstra", "[graph]") { - using D = int; - using G = ww::CSRGraph<>; + using Distance = int; + using Graph = ww::CSRGraph<>; + + constexpr auto max_distance = std::numeric_limits::max(); auto edgelist = ww::EdgeList(); edgelist.add_edge(0U, 1U); edgelist.add_edge(1U, 2U); edgelist.add_edge(2U, 3U); - const auto graph = G(edgelist); + const auto graph = Graph(edgelist); - auto dijkstra = ww::Dijkstra(graph); + auto dijkstra = ww::Dijkstra(graph); - using Distance = decltype(dijkstra)::distance_type; - using Graph = decltype(dijkstra)::graph_type; + using Distance_ = decltype(dijkstra)::distance_type; + using Graph_ = decltype(dijkstra)::graph_type; using Vertex = decltype(dijkstra)::vertex_type; using Edge = decltype(dijkstra)::edge_type; CATCH_SECTION("Dijkstra") { CATCH_CHECK(std::addressof(dijkstra.graph()) == std::addressof(graph)); - CATCH_CHECK_THAT(dijkstra.heap(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dijkstra.heap(), CM::IsEmpty()); + CATCH_CHECK(dijkstra.done()); + + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(dijkstra))); + + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return dijkstra.distance_to_vertex(vertex); + }); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(max_distance)); } CATCH_SECTION("{distance,graph,vertex,edge}_type") { - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); } CATCH_SECTION("heap") @@ -82,7 +99,7 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") dijkstra.heap().pop(); } - CATCH_CHECK_THAT(dijkstra.heap(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dijkstra.heap(), CM::IsEmpty()); } CATCH_SECTION("pop_next_unvisited_vertex") @@ -96,7 +113,7 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") CATCH_CHECK(distance1 == distance); const auto [vertex2, distance2] = dijkstra.pop_next_unvisited_vertex(); - CATCH_CHECK_THAT(dijkstra.heap(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dijkstra.heap(), CM::IsEmpty()); CATCH_CHECK(vertex2 == vertex); CATCH_CHECK(distance2 == distance); } @@ -109,23 +126,17 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") } CATCH_CHECK(std::size(dijkstra.heap()) == std::size(sources)); + CATCH_CHECK_THAT(sources, CM::AllMatch(ww::testing::WasReachedBy(dijkstra))); - const auto has_reached_sources = - sources | ranges::views::transform([&](const auto& source) { - return dijkstra.has_reached_vertex(source); - }); - CATCH_CHECK_THAT(has_reached_sources, Catch::Matchers::AllTrue()); - - using Catch::Matchers::Contains; const auto [vertex0, distance0] = dijkstra.pop_next_unvisited_vertex(); - CATCH_CHECK_THAT(sources, Contains(vertex0)); + CATCH_CHECK_THAT(sources, CM::Contains(vertex0)); CATCH_CHECK(distance0 == 0); const auto [vertex1, distance1] = dijkstra.pop_next_unvisited_vertex(); - CATCH_CHECK_THAT(sources, Contains(vertex1)); + CATCH_CHECK_THAT(sources, CM::Contains(vertex1)); CATCH_CHECK(distance1 == 0); - CATCH_CHECK_THAT(dijkstra.heap(), Catch::Matchers::IsEmpty()); + CATCH_CHECK_THAT(dijkstra.heap(), CM::IsEmpty()); CATCH_CHECK(vertex0 != vertex1); } @@ -135,19 +146,20 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") const auto distance0 = 0; dijkstra.add_source(vertex0); - CATCH_CHECK_FALSE(dijkstra.has_visited_vertex(vertex0)); + using ww::testing::WasVisitedBy; + CATCH_CHECK_THAT(vertex0, !WasVisitedBy(dijkstra)); dijkstra.visit_vertex(vertex0, distance0); - CATCH_CHECK(dijkstra.has_visited_vertex(vertex0)); + CATCH_CHECK_THAT(vertex0, WasVisitedBy(dijkstra)); CATCH_CHECK(dijkstra.distance_to_vertex(vertex0) == distance0); const auto edge = 0U; const auto vertex1 = 1U; const auto distance1 = 10; dijkstra.relax_edge(edge, vertex0, vertex1, distance1); - CATCH_CHECK_FALSE(dijkstra.has_visited_vertex(vertex1)); + CATCH_CHECK_THAT(vertex1, !WasVisitedBy(dijkstra)); dijkstra.visit_vertex(vertex1, distance1); - CATCH_CHECK(dijkstra.has_visited_vertex(vertex1)); + CATCH_CHECK_THAT(vertex1, WasVisitedBy(dijkstra)); CATCH_CHECK(dijkstra.distance_to_vertex(vertex1) == distance1); } @@ -164,8 +176,8 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") const auto length = 10; dijkstra.relax_edge(edge, tail, head, distance + length); - CATCH_CHECK(dijkstra.has_reached_vertex(head)); - CATCH_CHECK_FALSE(dijkstra.has_visited_vertex(head)); + CATCH_CHECK_THAT(head, ww::testing::WasReachedBy(dijkstra)); + CATCH_CHECK_THAT(head, !ww::testing::WasVisitedBy(dijkstra)); CATCH_CHECK(dijkstra.distance_to_vertex(head) == distance + length); CATCH_CHECK(std::size(dijkstra.heap()) == 1U); @@ -182,6 +194,39 @@ CATCH_TEST_CASE("Dijkstra", "[graph]") dijkstra.pop_next_unvisited_vertex(); CATCH_CHECK(dijkstra.done()); } + + CATCH_SECTION("reset") + { + const auto source = 0U; + dijkstra.add_source(source); + + const auto edges = {0U, 1U, 2U}; + const auto heads = {1U, 2U, 3U}; + const auto lengths = {1, 10, 100}; + + auto tail = source; + auto total_distance = 0; + for (auto&& [edge, head, length] : ranges::views::zip(edges, heads, lengths)) { + dijkstra.visit_vertex(tail, total_distance); + total_distance += length; + dijkstra.relax_edge(edge, tail, head, total_distance); + tail = head; + } + + dijkstra.reset(); + + CATCH_CHECK_THAT(dijkstra.heap(), CM::IsEmpty()); + CATCH_CHECK(dijkstra.done()); + + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(dijkstra))); + + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return dijkstra.distance_to_vertex(vertex); + }); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(max_distance)); + } } CATCH_TEST_CASE("Dijkstra (sorted)", "[graph]") @@ -215,7 +260,7 @@ CATCH_TEST_CASE("Dijkstra (sorted)", "[graph]") for (auto&& [v, d] : ranges::views::zip(vertices, distances)) { const auto [vertex, distance] = dijkstra.pop_next_unvisited_vertex(); CATCH_CHECK(vertex == v); - CATCH_CHECK_THAT(distance, Catch::Matchers::WithinAbs(d, 1e-6)); + CATCH_CHECK_THAT(distance, CM::WithinAbs(d, 1e-12)); } } diff --git a/test/graph/test_forest.cpp b/test/graph/test_forest.cpp index b26d79d..5bdd864 100644 --- a/test/graph/test_forest.cpp +++ b/test/graph/test_forest.cpp @@ -14,10 +14,12 @@ #include #include +#include "../testing/matchers/forest_matchers.hpp" #include "../testing/string_conversions.hpp" // IWYU pragma: keep namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; CATCH_TEST_CASE("Forest (const)", "[graph]") @@ -49,16 +51,13 @@ CATCH_TEST_CASE("Forest (const)", "[graph]") graph.vertices() | ranges::views::transform([&](const auto& vertex) { return forest.predecessor_vertex(vertex); }); - CATCH_CHECK_THAT(pred_vertices, Catch::Matchers::RangeEquals(graph.vertices())); + CATCH_CHECK_THAT(pred_vertices, CM::RangeEquals(graph.vertices())); } CATCH_SECTION("is_root_vertex") { - const auto vertices_are_roots = - graph.vertices() | ranges::views::transform([&](const auto& vertex) { - return forest.is_root_vertex(vertex); - }); - CATCH_CHECK_THAT(vertices_are_roots, Catch::Matchers::AllTrue()); + using ww::testing::IsRootVertexIn; + CATCH_CHECK_THAT(graph.vertices(), CM::AllMatch(IsRootVertexIn(forest))); } CATCH_SECTION("edge_fill_value") @@ -91,11 +90,16 @@ CATCH_TEST_CASE("Forest (non-const)", "[graph]") CATCH_SECTION("make_root_vertex") { - CATCH_CHECK(forest.is_root_vertex(2U)); - forest.set_predecessor(2U, 1U, 0U); - CATCH_CHECK_FALSE(forest.is_root_vertex(2U)); - forest.make_root_vertex(2U); - CATCH_CHECK(forest.is_root_vertex(2U)); + const auto vertex = 2U; + const auto pred_vertex = 1U; + const auto pred_edge = 0U; + + using ww::testing::IsRootVertexIn; + CATCH_CHECK_THAT(vertex, IsRootVertexIn(forest)); + forest.set_predecessor(vertex, pred_vertex, pred_edge); + CATCH_CHECK_THAT(vertex, !IsRootVertexIn(forest)); + forest.make_root_vertex(vertex); + CATCH_CHECK_THAT(vertex, IsRootVertexIn(forest)); } CATCH_SECTION("predecessors") @@ -110,18 +114,20 @@ CATCH_TEST_CASE("Forest (non-const)", "[graph]") using Pred = decltype(forest)::pred_type; const auto preds = {Pred(2U, 1U), Pred(1U, 0U)}; - CATCH_CHECK_THAT(forest.predecessors(3U), Catch::Matchers::RangeEquals(preds)); + CATCH_CHECK_THAT(forest.predecessors(3U), CM::RangeEquals(preds)); } CATCH_SECTION("reset") { forest.set_predecessor(2U, 1U, 0U); forest.set_predecessor(3U, 2U, 1U); - CATCH_CHECK_FALSE(forest.is_root_vertex(2U)); - CATCH_CHECK_FALSE(forest.is_root_vertex(3U)); + + using ww::testing::IsRootVertexIn; + CATCH_CHECK_THAT(2U, !IsRootVertexIn(forest)); + CATCH_CHECK_THAT(3U, !IsRootVertexIn(forest)); forest.reset(); - CATCH_CHECK(forest.is_root_vertex(2U)); - CATCH_CHECK(forest.is_root_vertex(3U)); + CATCH_CHECK_THAT(2U, IsRootVertexIn(forest)); + CATCH_CHECK_THAT(3U, IsRootVertexIn(forest)); } } diff --git a/test/graph/test_forest_concepts.cpp b/test/graph/test_forest_concepts.cpp index 13e13ff..7da6246 100644 --- a/test/graph/test_forest_concepts.cpp +++ b/test/graph/test_forest_concepts.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -47,12 +49,19 @@ CATCH_TEST_CASE("MutableForestType", "[graph]") require_satisfies_mutable_forest_type>(); } -CATCH_TEMPLATE_TEST_CASE("ShortestPathForestType", "[graph]", int, float) +CATCH_TEMPLATE_TEST_CASE("ShortestPathForestType", "[graph]", int, unsigned long long) { using Distance = TestType; using Graph = ww::CSRGraph<>; - using Forest = ww::ShortestPathForest; - require_satisfies_shortest_path_forest_type(); + + using ShortestPathForest = ww::ShortestPathForest; + require_satisfies_shortest_path_forest_type(); + + using Dijkstra = ww::Dijkstra; + require_satisfies_shortest_path_forest_type(); + + using Dial = ww::Dial; + require_satisfies_shortest_path_forest_type(); } CATCH_TEMPLATE_TEST_CASE("MutableShortestPathForestType", "[graph]", int, float) diff --git a/test/graph/test_shortest_path_forest.cpp b/test/graph/test_shortest_path_forest.cpp index a2f8202..e3d8265 100644 --- a/test/graph/test_shortest_path_forest.cpp +++ b/test/graph/test_shortest_path_forest.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -11,63 +10,48 @@ #include #include -#include #include #include +#include "../testing/matchers/forest_matchers.hpp" +#include "../testing/matchers/range_matchers.hpp" +#include "../testing/string_conversions.hpp" // IWYU pragma: keep + namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; -[[nodiscard]] constexpr auto -is_each_vertex_unreached(const ww::ShortestPathForestType auto& shortest_paths) -{ - return shortest_paths.graph().vertices() | - ranges::views::transform([&](const auto& vertex) { - return !shortest_paths.has_reached_vertex(vertex); - }); -} - -template -[[nodiscard]] constexpr auto -is_each_vertex_unvisited(const ShortestPathForest& shortest_paths) -{ - return shortest_paths.graph().vertices() | - ranges::views::transform([&](const auto& vertex) { - return !shortest_paths.has_visited_vertex(vertex); - }); -} - CATCH_TEST_CASE("ShortestPathForest (const)", "[graph]") { - using D = float; - using G = ww::RectangularGridGraph<>; - const auto graph = G(4U, 4U); - const auto shortest_paths = ww::ShortestPathForest(graph); + using Distance = float; + using Graph = ww::RectangularGridGraph<>; + const auto graph = Graph(4U, 4U); + const auto shortest_paths = ww::ShortestPathForest(graph); - using Distance = decltype(shortest_paths)::distance_type; - using Graph = decltype(shortest_paths)::graph_type; + using Distance_ = decltype(shortest_paths)::distance_type; + using Graph_ = decltype(shortest_paths)::graph_type; using Vertex = decltype(shortest_paths)::vertex_type; using Edge = decltype(shortest_paths)::edge_type; CATCH_SECTION("{distance,graph,vertex,edge}_type") { - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); - CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); + CATCH_STATIC_REQUIRE((std::is_same_v)); } CATCH_SECTION("has_reached_vertex") { - using Catch::Matchers::AllTrue; - CATCH_CHECK_THAT(is_each_vertex_unreached(shortest_paths), AllTrue()); + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(shortest_paths))); } CATCH_SECTION("has_visited_vertex") { - using Catch::Matchers::AllTrue; - CATCH_CHECK_THAT(is_each_vertex_unvisited(shortest_paths), AllTrue()); + using ww::testing::WasVisitedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasVisitedBy(shortest_paths))); } CATCH_SECTION("{reached,visited}_vertices") @@ -78,12 +62,12 @@ CATCH_TEST_CASE("ShortestPathForest (const)", "[graph]") CATCH_SECTION("distance_to_vertex") { - const auto distances_are_inf = - shortest_paths.graph().vertices() | - ranges::views::transform([&](const auto& vertex) { - return std::isinf(shortest_paths.distance_to_vertex(vertex)); + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return shortest_paths.distance_to_vertex(vertex); }); - CATCH_CHECK_THAT(distances_are_inf, Catch::Matchers::AllTrue()); + constexpr auto inf = std::numeric_limits::infinity(); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(inf)); } } @@ -101,41 +85,38 @@ CATCH_TEST_CASE("ShortestPathForest (non-const)", "[graph]") CATCH_SECTION("label_vertex_reached") { - CATCH_CHECK_FALSE(shortest_paths.has_reached_vertex(0U)); - CATCH_CHECK_FALSE(shortest_paths.has_reached_vertex(1U)); - - shortest_paths.label_vertex_reached(0U); - shortest_paths.label_vertex_reached(1U); + using ww::testing::WasReachedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(shortest_paths))); - CATCH_CHECK(shortest_paths.has_reached_vertex(0U)); - CATCH_CHECK(shortest_paths.has_reached_vertex(1U)); + const auto vertices = {0U, 1U}; + for (const auto& vertex : vertices) { + shortest_paths.label_vertex_reached(vertex); + } - const auto reached = {0U, 1U}; - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(shortest_paths.reached_vertices(), RangeEquals(reached)); + CATCH_CHECK_THAT(vertices, CM::AllMatch(WasReachedBy(shortest_paths))); + CATCH_CHECK_THAT(2U, !WasReachedBy(shortest_paths)); + CATCH_CHECK_THAT(shortest_paths.reached_vertices(), CM::RangeEquals(vertices)); } CATCH_SECTION("label_vertex_visited") { - CATCH_CHECK_FALSE(shortest_paths.has_visited_vertex(0U)); - CATCH_CHECK_FALSE(shortest_paths.has_visited_vertex(1U)); + using ww::testing::WasVisitedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasVisitedBy(shortest_paths))); - for (auto&& vertex : shortest_paths.graph().vertices()) { + for (auto&& vertex : graph.vertices()) { shortest_paths.label_vertex_reached(vertex); } - CATCH_CHECK_FALSE(shortest_paths.has_visited_vertex(0U)); - CATCH_CHECK_FALSE(shortest_paths.has_visited_vertex(1U)); + const auto vertices = {0U, 1U}; + CATCH_CHECK_THAT(vertices, NoneMatch(WasVisitedBy(shortest_paths))); - shortest_paths.label_vertex_visited(0U); - shortest_paths.label_vertex_visited(1U); - - CATCH_CHECK(shortest_paths.has_visited_vertex(0U)); - CATCH_CHECK(shortest_paths.has_visited_vertex(1U)); + for (const auto& vertex : vertices) { + shortest_paths.label_vertex_visited(vertex); + } - const auto visited = {0U, 1U}; - using Catch::Matchers::RangeEquals; - CATCH_CHECK_THAT(shortest_paths.visited_vertices(), RangeEquals(visited)); + CATCH_CHECK_THAT(vertices, CM::AllMatch(WasVisitedBy(shortest_paths))); + CATCH_CHECK_THAT(2U, !WasVisitedBy(shortest_paths)); + CATCH_CHECK_THAT(shortest_paths.visited_vertices(), CM::RangeEquals(vertices)); } CATCH_SECTION("set_distance_to_vertex") @@ -168,16 +149,16 @@ CATCH_TEST_CASE("ShortestPathForest (non-const)", "[graph]") shortest_paths.reset(); - using Catch::Matchers::AllTrue; - CATCH_CHECK_THAT(is_each_vertex_unreached(shortest_paths), AllTrue()); - CATCH_CHECK_THAT(is_each_vertex_unvisited(shortest_paths), AllTrue()); + using ww::testing::WasReachedBy; + using ww::testing::WasVisitedBy; + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasReachedBy(shortest_paths))); + CATCH_CHECK_THAT(graph.vertices(), CM::NoneMatch(WasVisitedBy(shortest_paths))); - const auto distances_are_max = - shortest_paths.graph().vertices() | - ranges::views::transform([&](const auto& vertex) { - return shortest_paths.distance_to_vertex(vertex) == max_distance; + const auto distances = + graph.vertices() | ranges::views::transform([&](const auto& vertex) { + return shortest_paths.distance_to_vertex(vertex); }); - CATCH_CHECK_THAT(distances_are_max, AllTrue()); + CATCH_CHECK_THAT(distances, ww::testing::AllEqualTo(max_distance)); } } diff --git a/test/math/test_numbers.cpp b/test/math/test_numbers.cpp index 163e10a..d91721b 100644 --- a/test/math/test_numbers.cpp +++ b/test/math/test_numbers.cpp @@ -10,6 +10,7 @@ namespace { +namespace CM = Catch::Matchers; namespace ww = whirlwind; CATCH_TEST_CASE("zero", "[numbers]") @@ -32,9 +33,8 @@ CATCH_TEST_CASE("one", "[numbers]") CATCH_TEST_CASE("pi", "[numbers]") { - using Catch::Matchers::WithinAbs; - CATCH_CHECK_THAT(ww::pi(), WithinAbs(3.141'592'7, 1e-7)); - CATCH_CHECK_THAT(ww::pi(), WithinAbs(3.141'592'653'589'793'2, 1e-16)); + CATCH_CHECK_THAT(ww::pi(), CM::WithinAbs(3.141'592'7, 1e-7)); + CATCH_CHECK_THAT(ww::pi(), CM::WithinAbs(3.141'592'653'589'793'2, 1e-16)); } CATCH_TEST_CASE("pi (consteval)", "[numbers]") @@ -45,9 +45,8 @@ CATCH_TEST_CASE("pi (consteval)", "[numbers]") CATCH_TEST_CASE("tau", "[numbers]") { - using Catch::Matchers::WithinAbs; - CATCH_CHECK_THAT(ww::tau(), WithinAbs(6.283'185'3, 2e-7)); - CATCH_CHECK_THAT(ww::tau(), WithinAbs(6.283'185'307'179'586'4, 1e-16)); + CATCH_CHECK_THAT(ww::tau(), CM::WithinAbs(6.283'185'3, 2e-7)); + CATCH_CHECK_THAT(ww::tau(), CM::WithinAbs(6.283'185'307'179'586'4, 1e-16)); } CATCH_TEST_CASE("tau (consteval)", "[numbers]") diff --git a/test/testing/matchers/forest_matchers.hpp b/test/testing/matchers/forest_matchers.hpp new file mode 100644 index 0000000..a937a41 --- /dev/null +++ b/test/testing/matchers/forest_matchers.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include + +#include +#include + +WHIRLWIND_NAMESPACE_BEGIN +namespace testing { + +template +class IsRootVertexIn : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr IsRootVertexIn(const Forest& forest) noexcept : forest_(forest) + {} + + [[nodiscard]] constexpr auto + match(const typename Forest::vertex_type& vertex) const -> bool + { + return forest_.is_root_vertex(vertex); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + return "is a root vertex"; + } + +private: + const Forest& forest_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +template +class WasReachedBy : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr WasReachedBy(const Forest& forest) noexcept : forest_(forest) {} + + [[nodiscard]] constexpr auto + match(const typename Forest::vertex_type& vertex) const -> bool + { + return forest_.has_reached_vertex(vertex); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + return "vertex was reached"; + } + +private: + const Forest& forest_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +template +class WasVisitedBy : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr WasVisitedBy(const Forest& forest) noexcept : forest_(forest) {} + + [[nodiscard]] constexpr auto + match(const typename Forest::vertex_type& vertex) const -> bool + { + return forest_.has_visited_vertex(vertex); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + return "vertex was visited"; + } + +private: + const Forest& forest_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +} // namespace testing +WHIRLWIND_NAMESPACE_END diff --git a/test/testing/matchers/graph_matchers.hpp b/test/testing/matchers/graph_matchers.hpp new file mode 100644 index 0000000..cc784e5 --- /dev/null +++ b/test/testing/matchers/graph_matchers.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +WHIRLWIND_NAMESPACE_BEGIN +namespace testing { + +template +class ContainsVertex : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr ContainsVertex(Vertex vertex) noexcept + : vertex_(std::move(vertex)) + {} + + [[nodiscard]] constexpr auto + match(const GraphType auto& graph) const -> bool + { + return graph.contains_vertex(vertex_); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + std::stringstream ss; + ss << "contains vertex " << vertex_; + return std::move(ss).str(); + } + +private: + Vertex vertex_; +}; + +template +ContainsVertex(const Vertex&) -> ContainsVertex; + +template +ContainsVertex(Vertex&&) -> ContainsVertex; + +template +class ContainsEdge : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr ContainsEdge(Edge edge) noexcept : edge_(std::move(edge)) {} + + [[nodiscard]] constexpr auto + match(const GraphType auto& graph) const -> bool + { + return graph.contains_edge(edge_); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + std::stringstream ss; + ss << "contains edge " << edge_; + return std::move(ss).str(); + } + +private: + Edge edge_; +}; + +template +ContainsEdge(const Edge&) -> ContainsEdge; + +template +ContainsEdge(Edge&&) -> ContainsEdge; + +} // namespace testing +WHIRLWIND_NAMESPACE_END diff --git a/test/testing/matchers/range_matchers.hpp b/test/testing/matchers/range_matchers.hpp new file mode 100644 index 0000000..3b53a00 --- /dev/null +++ b/test/testing/matchers/range_matchers.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +WHIRLWIND_NAMESPACE_BEGIN +namespace testing { + +template> +class AllEqualTo : public Catch::Matchers::MatcherGenericBase { +public: + explicit constexpr AllEqualTo(Value value, Pred pred = {}) noexcept + : value_(std::move(value)), pred_(std::move(pred)) + {} + + template + [[nodiscard]] constexpr auto + match(Range&& r) const -> bool + { + return ranges::all_of(std::forward(r), + [&](const auto& item) { return pred_(item, value_); }); + } + + [[nodiscard]] auto + describe() const -> std::string override + { + std::stringstream ss; + ss << "all elements in range are equal to " << value_; + return std::move(ss).str(); + } + +private: + Value value_; + [[no_unique_address]] Pred pred_; +}; + +template +AllEqualTo(const T&) -> AllEqualTo; + +template +AllEqualTo(T&&) -> AllEqualTo; + +} // namespace testing +WHIRLWIND_NAMESPACE_END