blob: 3e8d1676929ca82c0a2ee8208a02bd31ca78fc0c [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <algorithm>
#include <cstddef>
#include <utility>
#include <vector>
#include "utility/DAG.hpp"
#include "gtest/gtest.h"
using std::find;
using std::pair;
using std::vector;
namespace quickstep {
class DummyPayload {
public:
explicit DummyPayload(const int payload_value)
: payload_value_(payload_value) {
}
inline int getPayloadValue() const {
return payload_value_;
}
private:
int payload_value_;
};
TEST(DAGTest, AddNodeTest) {
const int kPayloadCount = 300;
DAG<DummyPayload, int> dag_;
// Create a DAG with 300 nodes and test if the index assignment is correct.
for (int index = 0; index < kPayloadCount; ++index) {
EXPECT_EQ(static_cast<std::size_t>(index),
dag_.createNode(new DummyPayload(index)));
}
}
TEST(DAGTest, PayloadTest) {
const int kPayloadCount = 300;
DAG<DummyPayload, int> dag_;
// Create a DAG with 300 nodes and test if the payload is returned correctly.
for (int index = 0; index < kPayloadCount; ++index) {
EXPECT_EQ(static_cast<std::size_t>(index),
dag_.createNode(new DummyPayload(index + 5)));
}
// If all indices are correct, check the payload.
for (int index = 0; index < kPayloadCount; ++index) {
EXPECT_EQ(index + 5, dag_.getNodePayload(index).getPayloadValue());
EXPECT_EQ(index + 5, dag_.getNodePayloadMutable(index)->getPayloadValue());
}
}
TEST(DAGTest, SelfCycleTest) {
DAG<DummyPayload, int> dag_;
// No cycle in empty DAG.
EXPECT_FALSE(dag_.hasCycle());
ASSERT_EQ(0u, dag_.createNode(new DummyPayload(34)));
// No cycle with single node.
EXPECT_FALSE(dag_.hasCycle());
ASSERT_EQ(1u, dag_.createNode(new DummyPayload(44)));
dag_.createLink(0, 1, 1);
// This should test createLink function too.
EXPECT_FALSE(dag_.hasCycle());
dag_.createLink(0, 0, 1);
EXPECT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, TwoNodesCycleTest) {
DAG<DummyPayload, int> dag_;
// Two nodes with cycle.
ASSERT_EQ(0u, dag_.createNode(new DummyPayload(2)));
ASSERT_EQ(1u, dag_.createNode(new DummyPayload(3)));
dag_.createLink(0, 1, 2);
EXPECT_FALSE(dag_.hasCycle());
dag_.createLink(1, 0, 3);
EXPECT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, CycleExistenceTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0 1
* \ /
* 2
* / \
* 3->4
*
*/
dag_.createLink(0, 2, 3);
dag_.createLink(1, 2, 4);
dag_.createLink(2, 3, 3);
dag_.createLink(2, 4, 4);
dag_.createLink(3, 4, 5);
EXPECT_FALSE(dag_.hasCycle());
dag_.createLink(4, 3, 1);
EXPECT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, FiveNodesCycleTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 1->2->3
* ^ |
* | v
* 0 <-- 4
*
*/
dag_.createLink(0, 1, 0);
dag_.createLink(1, 2, 0);
dag_.createLink(2, 3, 0);
dag_.createLink(3, 4, 0);
dag_.createLink(4, 0, 1);
EXPECT_TRUE(dag_.hasCycle());
dag_.createLink(3, 0, 3);
EXPECT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, DisconnectedComponentsNoCycleTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
// A graph with 5 nodes. Nodes 1, 2, 3 and 4 are put in one component. Node 0
// is in another component. Both the components are acyclic.
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
// All 5 nodes are disconnected.
EXPECT_FALSE(dag_.hasCycle());
dag_.createLink(1, 2, 3);
dag_.createLink(2, 3, 2);
dag_.createLink(3, 4, 1);
// Node 0 is in a separate component.
EXPECT_FALSE(dag_.hasCycle());
}
TEST(DAGTest, DisconnectedComponentsWithCycleTest) {
const int kNodeSize = 6;
DAG<DummyPayload, int> dag_;
// A graph with 6 nodes. Nodes 1, 2 and 3 are put in one component. Nodes 0,
// 4 and 5 are in another component. Both the components have cycle within
// themselves.
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
dag_.createLink(1, 2, 4);
dag_.createLink(2, 3, 5);
dag_.createLink(3, 2, 2);
// Component with nodes 1, 2 and 3 is cyclic.
EXPECT_TRUE(dag_.hasCycle());
dag_.createLink(0, 4, 3);
EXPECT_TRUE(dag_.hasCycle());
dag_.createLink(4, 5, 2);
// Make the second component with nodes 0, 4 and 5 cyclic.
dag_.createLink(5, 0, 4);
EXPECT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, SixNodeStagesTest) {
const int kNodeSize = 6;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0 1 2
* \ / /
* 3 /
* \/
* 4
* |
* 5
*/
dag_.createLink(0, 3, 0);
dag_.createLink(1, 3, 1);
dag_.createLink(2, 4, 2);
dag_.createLink(3, 4, 2);
dag_.createLink(4, 5, 1);
vector<pair<vector<DAG<DummyPayload, int>::size_type_nodes>,
vector<DAG<DummyPayload, int>::size_type_nodes> > > stages = dag_.computeStageSequence();
ASSERT_EQ(4u, stages.size());
// stages[0]
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 0), stages[0].first.end());
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 1), stages[0].first.end());
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 2), stages[0].first.end());
// Check if there's no other element in stages[0].first than the ones we've
// tested.
EXPECT_EQ(3u, stages[0].first.size());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 0), stages[0].second.end());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 1), stages[0].second.end());
EXPECT_EQ(2u, stages[0].second.size());
// stages[1]
EXPECT_NE(find(stages[1].first.begin(), stages[1].first.end(), 3), stages[1].first.end());
EXPECT_EQ(1u, stages[1].first.size());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 2), stages[1].second.end());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 3), stages[1].second.end());
EXPECT_EQ(2u, stages[1].second.size());
// stages[2]
EXPECT_NE(find(stages[2].first.begin(), stages[2].first.end(), 4), stages[2].first.end());
EXPECT_EQ(1u, stages[2].first.size());
EXPECT_NE(find(stages[2].second.begin(), stages[2].second.end(), 4), stages[2].second.end());
EXPECT_EQ(1u, stages[2].second.size());
// stages[3]
EXPECT_NE(find(stages[3].first.begin(), stages[3].first.end(), 5), stages[3].first.end());
EXPECT_EQ(1u, stages[3].first.size());
EXPECT_NE(find(stages[3].second.begin(), stages[3].second.end(), 5), stages[3].second.end());
EXPECT_EQ(1u, stages[3].second.size());
}
TEST(DAGTest, DiamondNoCycleStagesTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0
* / \
* v v
* 1 2
* \ /
* v
* 3
* |
* v
* 4
*
*/
dag_.createLink(0, 1, 2);
dag_.createLink(0, 2, 1);
dag_.createLink(1, 3, 1);
dag_.createLink(2, 3, 1);
dag_.createLink(3, 4, 1);
ASSERT_FALSE(dag_.hasCycle());
vector<pair<vector<DAG<DummyPayload, int>::size_type_nodes>,
vector<DAG<DummyPayload, int>::size_type_nodes> > > stages = dag_.computeStageSequence();
ASSERT_EQ(4u, stages.size());
// stages[0]
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 0), stages[0].first.end());
EXPECT_EQ(1u, stages[0].first.size());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 0), stages[0].second.end());
EXPECT_EQ(1u, stages[0].second.size());
// stages[1]
EXPECT_NE(find(stages[1].first.begin(), stages[1].first.end(), 1), stages[1].first.end());
EXPECT_NE(find(stages[1].first.begin(), stages[1].first.end(), 2), stages[1].first.end());
EXPECT_EQ(2u, stages[1].first.size());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 1), stages[1].second.end());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 2), stages[1].second.end());
EXPECT_EQ(2u, stages[1].second.size());
// stages[2]
EXPECT_NE(find(stages[2].first.begin(), stages[2].first.end(), 3), stages[2].first.end());
EXPECT_EQ(1u, stages[2].first.size());
EXPECT_NE(find(stages[2].second.begin(), stages[2].second.end(), 3), stages[2].second.end());
EXPECT_EQ(1u, stages[2].second.size());
// stages[3]
EXPECT_NE(find(stages[3].first.begin(), stages[3].first.end(), 4), stages[3].first.end());
EXPECT_EQ(1u, stages[3].first.size());
EXPECT_NE(find(stages[3].second.begin(), stages[3].second.end(), 4), stages[3].second.end());
EXPECT_EQ(1u, stages[3].second.size());
}
TEST(DAGTest, DiamondCycleExistenceTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
// A cycle is present within the diamond shape
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0
* / ^
* v \
* 1 2
* \ /
* v /
* 3
* |
* v
* 4
*
*/
dag_.createLink(0, 1, 1);
dag_.createLink(1, 3, 2);
dag_.createLink(3, 2, 1);
dag_.createLink(3, 4, 2);
dag_.createLink(2, 0, 1);
ASSERT_TRUE(dag_.hasCycle());
}
TEST(DAGTest, DisconnectedComponentsNoCycleStagesTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
// A graph with 5 nodes. Nodes 1, 2, 3 and 4 are put in one component. Node 0
// is in another component. Both the components are acyclic. As node 0 has no
// dependencies, it can be processed in any stage, starting from 0 till 4.
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
dag_.createLink(1, 2, 0);
dag_.createLink(2, 3, 1);
dag_.createLink(3, 4, 2);
EXPECT_FALSE(dag_.hasCycle());
vector<pair<vector<DAG<DummyPayload, int>::size_type_nodes>,
vector<DAG<DummyPayload, int>::size_type_nodes> > > stages = dag_.computeStageSequence();
ASSERT_EQ(4u, stages.size());
// stages[0] Node 0 can begin execution here.
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 0), stages[0].first.end());
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 1), stages[0].first.end());
EXPECT_EQ(2u, stages[0].first.size());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 1), stages[0].second.end());
EXPECT_EQ(1u, stages[0].second.size());
// stages[1]
EXPECT_NE(find(stages[1].first.begin(), stages[1].first.end(), 2), stages[1].first.end());
EXPECT_EQ(1u, stages[1].first.size());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 2), stages[1].second.end());
EXPECT_EQ(1u, stages[1].second.size());
// stages[2]
EXPECT_NE(find(stages[2].first.begin(), stages[2].first.end(), 3), stages[2].first.end());
EXPECT_EQ(1u, stages[2].first.size());
EXPECT_NE(find(stages[2].second.begin(), stages[2].second.end(), 3), stages[2].second.end());
EXPECT_EQ(1u, stages[2].second.size());
// stages[3] Node 0 must finish execution here.
EXPECT_NE(find(stages[3].first.begin(), stages[3].first.end(), 4), stages[3].first.end());
EXPECT_EQ(1u, stages[3].first.size());
EXPECT_NE(find(stages[3].second.begin(), stages[3].second.end(), 0), stages[3].second.end());
EXPECT_NE(find(stages[3].second.begin(), stages[3].second.end(), 4), stages[3].second.end());
EXPECT_EQ(2u, stages[3].second.size());
}
TEST(DAGTest, LinkMetadataIntTest) {
DAG<DummyPayload, int> dag_;
const int kNodeSize = 2;
for (int index = 0; index < kNodeSize; ++index) {
EXPECT_EQ(static_cast<std::size_t>(index),
dag_.createNode(new DummyPayload(index)));
}
dag_.createLink(0, 1, 0);
EXPECT_EQ(0, dag_.getLinkMetadata(0, 1));
dag_.createLink(1, 0, 1);
EXPECT_EQ(1, dag_.getLinkMetadata(1, 0));
// Change the weight of the link 0->1
dag_.setLinkMetadata(0, 1, 2);
EXPECT_EQ(2, dag_.getLinkMetadata(0, 1));
}
TEST(DAGTest, LinkMetadataBoolTest) {
DAG<DummyPayload, bool> dag_;
const int kNodeSize = 2;
for (int index = 0; index < kNodeSize; ++index) {
EXPECT_EQ(static_cast<std::size_t>(index),
dag_.createNode(new DummyPayload(index)));
}
dag_.createLink(0, 1, false);
EXPECT_FALSE(dag_.getLinkMetadata(0, 1));
dag_.createLink(1, 0, false);
EXPECT_FALSE(dag_.getLinkMetadata(1, 0));
// Change the weight of the link 0->1
dag_.setLinkMetadata(0, 1, true);
EXPECT_TRUE(dag_.getLinkMetadata(0, 1));
// Make sure link weight 1->0 is not affected.
EXPECT_FALSE(dag_.getLinkMetadata(1, 0));
// Change the weight of the link 0->1 again
dag_.setLinkMetadata(0, 1, false);
EXPECT_FALSE(dag_.getLinkMetadata(0, 1));
// Make sure link weight 1->0 is not affected.
EXPECT_FALSE(dag_.getLinkMetadata(1, 0));
}
TEST(DAGTest, TopoSortTest) {
const int kNodeSize = 5;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0
* / \
* v v
* 1 2
* \ /
* v
* 3
* |
* v
* 4
*
*/
dag_.createLink(0, 1, 2);
dag_.createLink(0, 2, 1);
dag_.createLink(1, 3, 1);
dag_.createLink(2, 3, 1);
dag_.createLink(3, 4, 1);
vector<DAG<DummyPayload, int>::size_type_nodes> sorted_list =
dag_.getTopologicalSorting();
std::unordered_map<DAG<DummyPayload, int>::size_type_nodes, bool> node_exists;
// First check if the ordering is a legal sequence of nodes, i.e. every node
// appears exactly once.
for (auto node_id = 0u; node_id < dag_.size(); ++node_id) {
node_exists[node_id] = false;
}
for (auto i : sorted_list) {
node_exists[i] = true;
}
for (auto node_id = 0u; node_id < dag_.size(); ++node_id) {
ASSERT_TRUE(node_exists[node_id]);
}
// We apply the following condition for verifying if we have obtained a valid
// topological sorting.
// If there is an edge i->j between two nodes i and j, then i comes before j
// in the topologically sorted order.
// We use a map to store the position of a given node in the sorted order.
// Key = node ID, value = position of the node in the sorted order.
std::unordered_map<std::size_t, std::size_t> position_in_sorted_order;
for (std::size_t i = 0; i < sorted_list.size(); ++i) {
position_in_sorted_order[sorted_list[i]] = i;
}
std::vector<std::tuple<std::size_t, std::size_t>> edges;
// Populate the list of edges.
edges.emplace_back(0, 1);
edges.emplace_back(0, 2);
edges.emplace_back(1, 3);
edges.emplace_back(2, 3);
edges.emplace_back(3, 4);
for (auto curr_edge : edges) {
// (i, j) : i is "from node", j is "to node".
std::size_t from_node_position = position_in_sorted_order[std::get<0>(curr_edge)];
std::size_t to_node_position = position_in_sorted_order[std::get<1>(curr_edge)];
ASSERT_LT(from_node_position, to_node_position);
}
// Now extend the same logic that we applied for edges for paths in the DAG.
// We have already verified paths with length = 1 (edges), so we will only
// consider paths with length more than one.
std::vector<std::tuple<std::size_t, std::size_t>> paths;
paths.emplace_back(0, 3);
paths.emplace_back(0, 4);
paths.emplace_back(1, 4);
paths.emplace_back(2, 4);
for (auto curr_path : paths) {
// (i, j) : i is "from node", j is "to node".
std::size_t from_node_position = position_in_sorted_order[std::get<0>(curr_path)];
std::size_t to_node_position = position_in_sorted_order[std::get<1>(curr_path)];
ASSERT_LT(from_node_position, to_node_position);
}
}
#ifdef QUICKSTEP_DEBUG
TEST(DAGDeathTest, SixNodeStagesCycleTest) {
const int kNodeSize = 6;
DAG<DummyPayload, int> dag_;
for (int node_index = 0; node_index < kNodeSize; ++node_index) {
ASSERT_EQ(static_cast<std::size_t>(node_index),
dag_.createNode(new DummyPayload(node_index)));
}
/*
* 0 1 2
* \ / /
* 3 /
* \/
* 4
* |
* 5
*/
dag_.createLink(0, 3, 0);
dag_.createLink(1, 3, 1);
dag_.createLink(2, 4, 0);
dag_.createLink(3, 4, 1);
dag_.createLink(4, 5, 0);
vector<pair<vector<DAG<DummyPayload, int>::size_type_nodes>,
vector<DAG<DummyPayload, int>::size_type_nodes> > > stages = dag_.computeStageSequence();
ASSERT_EQ(4u, stages.size());
// stages[0]
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 0), stages[0].first.end());
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 1), stages[0].first.end());
EXPECT_NE(find(stages[0].first.begin(), stages[0].first.end(), 2), stages[0].first.end());
// Check if there's no other element in stages[0].first than the ones we've
// tested.
EXPECT_EQ(3u, stages[0].first.size());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 0), stages[0].second.end());
EXPECT_NE(find(stages[0].second.begin(), stages[0].second.end(), 1), stages[0].second.end());
EXPECT_EQ(2u, stages[0].second.size());
// stages[1]
EXPECT_NE(find(stages[1].first.begin(), stages[1].first.end(), 3), stages[1].first.end());
EXPECT_EQ(1u, stages[1].first.size());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 2), stages[1].second.end());
EXPECT_NE(find(stages[1].second.begin(), stages[1].second.end(), 3), stages[1].second.end());
EXPECT_EQ(2u, stages[1].second.size());
// stages[2]
EXPECT_NE(find(stages[2].first.begin(), stages[2].first.end(), 4), stages[2].first.end());
EXPECT_EQ(1u, stages[2].first.size());
EXPECT_NE(find(stages[2].second.begin(), stages[2].second.end(), 4), stages[2].second.end());
EXPECT_EQ(1u, stages[2].second.size());
// stages[3]
EXPECT_NE(find(stages[3].first.begin(), stages[3].first.end(), 5), stages[3].first.end());
EXPECT_EQ(1u, stages[3].first.size());
EXPECT_NE(find(stages[3].second.begin(), stages[3].second.end(), 5), stages[3].second.end());
EXPECT_EQ(1u, stages[3].second.size());
dag_.createLink(5, 4, 3);
// computeStageSequence goes in an infinite loop when graph has cycle
// DCHECK(!hasCycle()) should abort the execution
EXPECT_DEATH(dag_.computeStageSequence(), "");
}
#endif
} // namespace quickstep