summaryrefslogtreecommitdiff
path: root/test/gtest/scc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/gtest/scc.cpp')
-rw-r--r--test/gtest/scc.cpp311
1 files changed, 311 insertions, 0 deletions
diff --git a/test/gtest/scc.cpp b/test/gtest/scc.cpp
new file mode 100644
index 000000000..baf509d7f
--- /dev/null
+++ b/test/gtest/scc.cpp
@@ -0,0 +1,311 @@
+/*
+ * 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 <vector>
+
+#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<std::set<unsigned>> 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<unsigned>& children(unsigned parent) const {
+ return graph[parent];
+ }
+
+ IntIt begin() const { return {}; }
+ IntIt end() const { return {unsigned(graph.size())}; }
+};
+
+struct GraphSCCs : SCCs<IntIt, GraphSCCs> {
+ const Graph& graph;
+ GraphSCCs(const Graph& graph)
+ : SCCs<IntIt, GraphSCCs>(graph.begin(), graph.end()), graph(graph) {}
+
+ void pushChildren(unsigned parent) {
+ for (auto child : graph.children(parent)) {
+ push(child);
+ }
+ }
+};
+
+using SCCSet = std::set<std::set<unsigned>>;
+
+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}}));
+}