/* * Copyright 2024 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "support/strongly_connected_components.h" #include "gtest/gtest.h" using namespace wasm; struct IntIt { using value_type = unsigned; using difference_type = std::ptrdiff_t; using reference = unsigned&; using pointer = unsigned*; using iterator_category = std::input_iterator_tag; unsigned i = 0; bool operator==(const IntIt& other) const { return i == other.i; } bool operator!=(const IntIt& other) const { return !(*this == other); } unsigned operator*() { return i; } IntIt& operator++() { ++i; return *this; } IntIt operator++(int) { IntIt it = *this; ++(*this); return it; } }; struct Graph { std::vector> graph; explicit Graph(unsigned size) : graph(size) {} void addEdge(unsigned from, unsigned to) { assert(from < graph.size()); assert(to < graph.size()); graph[from].insert(to); } const std::set& children(unsigned parent) const { return graph[parent]; } IntIt begin() const { return {}; } IntIt end() const { return {unsigned(graph.size())}; } }; struct GraphSCCs : SCCs { const Graph& graph; GraphSCCs(const Graph& graph) : SCCs(graph.begin(), graph.end()), graph(graph) {} void pushChildren(unsigned parent) { for (auto child : graph.children(parent)) { push(child); } } }; using SCCSet = std::set>; SCCSet getSCCs(const Graph& graph) { SCCSet set; for (auto scc : GraphSCCs(graph)) { set.emplace(scc.begin(), scc.end()); } return set; } TEST(SCCTest, Empty) { Graph graph(0); GraphSCCs sccs(graph); auto it = sccs.begin(); EXPECT_EQ(it, sccs.end()); EXPECT_EQ(getSCCs(graph), SCCSet{}); } TEST(SCCTest, Singleton) { Graph graph(1); GraphSCCs sccs(graph); auto it = sccs.begin(); ASSERT_NE(it, sccs.end()); auto scc = *it; auto sccIt = scc.begin(); ASSERT_NE(sccIt, scc.end()); EXPECT_EQ(*sccIt, 0u); ++sccIt; EXPECT_EQ(sccIt, scc.end()); ASSERT_NE(it, sccs.end()); ++it; EXPECT_EQ(it, sccs.end()); EXPECT_EQ(getSCCs(graph), SCCSet{{0}}); } TEST(SCCTest, SingletonLoop) { // Same as above, but now there is an edge. The results are the same. Graph graph(1); graph.addEdge(0, 0); GraphSCCs sccs(graph); auto it = sccs.begin(); ASSERT_NE(it, sccs.end()); auto scc = *it; auto sccIt = scc.begin(); ASSERT_NE(sccIt, scc.end()); EXPECT_EQ(*sccIt, 0u); ++sccIt; EXPECT_EQ(sccIt, scc.end()); ASSERT_NE(it, sccs.end()); ++it; EXPECT_EQ(it, sccs.end()); EXPECT_EQ(getSCCs(graph), SCCSet{{0}}); } TEST(SCCTest, DerefPostIncrement) { // Valid input iterators must have *it++ yield the value from before the // increment. Graph graph(1); GraphSCCs sccs(graph); auto it = sccs.begin()->begin(); EXPECT_EQ(*it++, 0u); } TEST(SCCTest, Disconnected) { // There are no edges between the vertices. Graph graph(3); EXPECT_EQ(getSCCs(graph), (SCCSet{{0}, {1}, {2}})); } TEST(SCCTest, Chain) { // 0 -> 1 -> 2 Graph graph(3); graph.addEdge(0, 1); graph.addEdge(1, 2); EXPECT_EQ(getSCCs(graph), (SCCSet{{0}, {1}, {2}})); } TEST(SCCTest, ChainReverse) { // 0 <- 1 <- 2 Graph graph(3); graph.addEdge(2, 1); graph.addEdge(1, 0); EXPECT_EQ(getSCCs(graph), (SCCSet{{0}, {1}, {2}})); } TEST(SCCTest, Loop) { // 0 -> 1 -> 2 -> 0 // There is only one SCC here. Graph graph(3); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 0); EXPECT_EQ(getSCCs(graph), (SCCSet({{0, 1, 2}}))); } TEST(SCCTest, LoopReverse) { // 0 <- 1 <- 2 <- 0 // There is only one SCC here. Graph graph(3); graph.addEdge(0, 2); graph.addEdge(2, 1); graph.addEdge(1, 0); EXPECT_EQ(getSCCs(graph), (SCCSet({{0, 1, 2}}))); } TEST(SCCTest, Full) { // Fully connected graph has one SCC. Graph graph(3); for (unsigned i = 0; i < 3; ++i) { for (unsigned j = 0; j < 3; ++j) { graph.addEdge(i, j); } } EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1, 2}})); } TEST(SCCTest, TwoAndOne) { // 0 <-> 1 -> 2 Graph graph(3); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(1, 2); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1}, {2}})); } TEST(SCCTest, TwoAndOneRedundant) { // 2 <- 0 <-> 1 -> 2 Graph graph(3); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(1, 2); // New from previous, doesn't affect result. graph.addEdge(0, 2); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1}, {2}})); } TEST(SCCTest, OneAndTwo) { // 0 -> 1 <-> 2 Graph graph(3); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 1); EXPECT_EQ(getSCCs(graph), (SCCSet{{0}, {1, 2}})); } TEST(SCCTest, TwoAndTwoDisconnected) { // 0 <-> 1 2 <-> 3 Graph graph(4); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(2, 3); graph.addEdge(3, 2); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1}, {2, 3}})); } TEST(SCCTest, TwoAndTwo) { // 0 <-> 1 -> 2 <-> 3 Graph graph(4); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(2, 3); graph.addEdge(3, 2); // New from previous, doesn't affect result graph.addEdge(1, 2); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1}, {2, 3}})); } TEST(SCCTest, DoublyLinkedList) { // 0 <-> 1 <-> 2 <-> 3 Graph graph(4); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(2, 3); graph.addEdge(3, 2); graph.addEdge(1, 2); // New from previous, combines SCCs. graph.addEdge(2, 1); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1, 2, 3}})); } TEST(SCCTest, BigTree) { // 012 <- 345 -> 678 Graph graph(9); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 0); graph.addEdge(3, 4); graph.addEdge(4, 5); graph.addEdge(5, 3); graph.addEdge(6, 7); graph.addEdge(7, 8); graph.addEdge(8, 6); graph.addEdge(3, 2); graph.addEdge(5, 6); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}})); } TEST(SCCTest, BigDiamond) { // 67 <- 01 <- 23 -> 45 -> 67 Graph graph(8); graph.addEdge(0, 1); graph.addEdge(1, 0); graph.addEdge(2, 3); graph.addEdge(3, 2); graph.addEdge(4, 5); graph.addEdge(5, 4); graph.addEdge(6, 7); graph.addEdge(7, 6); graph.addEdge(2, 0); graph.addEdge(2, 4); graph.addEdge(0, 6); graph.addEdge(4, 6); EXPECT_EQ(getSCCs(graph), (SCCSet{{0, 1}, {2, 3}, {4, 5}, {6, 7}})); }