blob: baf509d7f53192d739c52cb0b36f7e956b148fe9 [file] [log] [blame] [edit]
/*
* 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}}));
}