tvl-depot/absl/synchronization/internal/graphcycles_test.cc

463 lines
13 KiB
C++
Raw Normal View History

2017-09-19 22:54:40 +02:00
// Copyright 2017 The Abseil Authors.
//
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
2017-09-19 22:54:40 +02:00
//
// 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 "absl/synchronization/internal/graphcycles.h"
#include <map>
#include <random>
#include <unordered_set>
Changes imported from Abseil "staging" branch: - b527a3e4b36b644ac424e3c525b1cd393f6f6c40 Fix some typos in the usage examples by Jorg Brown <jorg@google.com> - 82be4a9adf3bb0ddafc0d46274969c99afffe870 Fix typo in optional.h comment. by Abseil Team <absl-team@google.com> - d6ee63bf8fc51fba074c23b33cebc28c808d7f07 Remove internal-only identifiers from code. by Daniel Katz <katzdm@google.com> - f9c3ad2f0d73f53b21603638af8b4bed636e79f4 Use easier understandable names for absl::StartsWith and ... by Abseil Team <absl-team@google.com> - 7c16c14fefee89c927b8789d6043c4691bcffc9b Add -Wno-missing-prototypes back to the LLVM copts. by Derek Mauro <dmauro@google.com> - 2f4b7d2e50c7023240242f1e15db60ccd7e8768d IWYU | absl/strings by Juemin Yang <jueminyang@google.com> - a99cbcc1daa34a2d6a2bb26de275e05173cc77e9 IWYU | absl/type by Juemin Yang <jueminyang@google.com> - 12e1146d0fc76c071d7e0ebaabb62f0a984fae66 Use LLVM_FLAGS and LLVM_TEST_FLAGS when --compiler=llvm. by Derek Mauro <dmauro@google.com> - cd6bea616abda558d0bace5bd77455662a233688 IWYU | absl/debugging by Juemin Yang <jueminyang@google.com> - d9a7382e59d46a8581b6b7a31cd5a48bb89326e9 IWYU | absl/synchronization by Juemin Yang <jueminyang@google.com> - 07ec7d6d5a4a666f4183c5d0ed9c342baa7b24bc IWYU | absl/numeric by Juemin Yang <jueminyang@google.com> - 12bfe40051f4270f8707e191af5652f83f2f750c Remove the RoundTrip{Float,Double}ToBuffer routines from ... by Jorg Brown <jorg@google.com> - eeb4fd67c9d97f66cb9475c3c5e51ab132f1c810 Adds conversion functions for converting between absl/tim... by Greg Miller <jgm@google.com> - 59a2108d05d4ea85dc5cc11e49b2cd2335d4295a Change Substitute to use %.6g formatting rather than 15/1... by Jorg Brown <jorg@google.com> - 394becb48e0fcd161642cdaac5120d32567e0ef8 IWYU | absl/meta by Juemin Yang <jueminyang@google.com> - 1e5da6e8da336699b2469dcf6dda025b9b0ec4c9 Rewrite atomic_hook.h to not use std::atomic<T*> under Wi... by Greg Falcon <gfalcon@google.com> GitOrigin-RevId: b527a3e4b36b644ac424e3c525b1cd393f6f6c40 Change-Id: I14e331d91c956ef045ac7927091a9f179716de0c
2017-09-24 17:20:48 +02:00
#include <utility>
#include <vector>
2017-09-19 22:54:40 +02:00
#include "gtest/gtest.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
namespace absl {
namespace synchronization_internal {
// We emulate a GraphCycles object with a node vector and an edge vector.
// We then compare the two implementations.
using Nodes = std::vector<int>;
struct Edge {
int from;
int to;
};
using Edges = std::vector<Edge>;
using RandomEngine = std::mt19937_64;
// Mapping from integer index to GraphId.
typedef std::map<int, GraphId> IdMap;
static GraphId Get(const IdMap& id, int num) {
auto iter = id.find(num);
return (iter == id.end()) ? InvalidGraphId() : iter->second;
}
// Return whether "to" is reachable from "from".
static bool IsReachable(Edges *edges, int from, int to,
std::unordered_set<int> *seen) {
seen->insert(from); // we are investigating "from"; don't do it again
if (from == to) return true;
for (const auto &edge : *edges) {
if (edge.from == from) {
if (edge.to == to) { // success via edge directly
return true;
} else if (seen->find(edge.to) == seen->end() && // success via edge
IsReachable(edges, edge.to, to, seen)) {
return true;
}
}
}
return false;
}
static void PrintEdges(Edges *edges) {
ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());
for (const auto &edge : *edges) {
int a = edge.from;
int b = edge.to;
ABSL_RAW_LOG(INFO, "%d %d", a, b);
}
ABSL_RAW_LOG(INFO, "---");
}
static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {
ABSL_RAW_LOG(INFO, "GC EDGES");
for (int a : *nodes) {
for (int b : *nodes) {
if (gc->HasEdge(Get(id, a), Get(id, b))) {
ABSL_RAW_LOG(INFO, "%d %d", a, b);
}
}
}
ABSL_RAW_LOG(INFO, "---");
}
static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {
ABSL_RAW_LOG(INFO, "Transitive closure");
for (int a : *nodes) {
for (int b : *nodes) {
std::unordered_set<int> seen;
if (IsReachable(edges, a, b, &seen)) {
ABSL_RAW_LOG(INFO, "%d %d", a, b);
}
}
}
ABSL_RAW_LOG(INFO, "---");
}
static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,
GraphCycles *gc) {
ABSL_RAW_LOG(INFO, "GC Transitive closure");
for (int a : *nodes) {
for (int b : *nodes) {
if (gc->IsReachable(Get(id, a), Get(id, b))) {
ABSL_RAW_LOG(INFO, "%d %d", a, b);
}
}
}
ABSL_RAW_LOG(INFO, "---");
}
static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,
GraphCycles *gc) {
std::unordered_set<int> seen;
for (const auto &a : *nodes) {
for (const auto &b : *nodes) {
seen.clear();
bool gc_reachable = gc->IsReachable(Get(id, a), Get(id, b));
bool reachable = IsReachable(edges, a, b, &seen);
if (gc_reachable != reachable) {
PrintEdges(edges);
PrintGCEdges(nodes, id, gc);
PrintTransitiveClosure(nodes, edges);
PrintGCTransitiveClosure(nodes, id, gc);
ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",
gc_reachable ? "true" : "false",
reachable ? "true" : "false", a, b);
}
}
}
}
static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id,
GraphCycles *gc) {
int count = 0;
for (const auto &edge : *edges) {
int a = edge.from;
int b = edge.to;
if (!gc->HasEdge(Get(id, a), Get(id, b))) {
PrintEdges(edges);
PrintGCEdges(nodes, id, gc);
ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);
}
}
for (const auto &a : *nodes) {
for (const auto &b : *nodes) {
if (gc->HasEdge(Get(id, a), Get(id, b))) {
count++;
}
}
}
if (count != edges->size()) {
PrintEdges(edges);
PrintGCEdges(nodes, id, gc);
ABSL_RAW_LOG(FATAL, "edges->size() %zu count %d", edges->size(), count);
}
}
static void CheckInvariants(const GraphCycles &gc) {
if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))
ABSL_RAW_LOG(FATAL, "CheckInvariants");
}
// Returns the index of a randomly chosen node in *nodes.
// Requires *nodes be non-empty.
static int RandomNode(RandomEngine* rng, Nodes *nodes) {
std::uniform_int_distribution<int> uniform(0, nodes->size()-1);
return uniform(*rng);
}
// Returns the index of a randomly chosen edge in *edges.
// Requires *edges be non-empty.
static int RandomEdge(RandomEngine* rng, Edges *edges) {
std::uniform_int_distribution<int> uniform(0, edges->size()-1);
return uniform(*rng);
}
// Returns the index of edge (from, to) in *edges or -1 if it is not in *edges.
static int EdgeIndex(Edges *edges, int from, int to) {
int i = 0;
while (i != edges->size() &&
((*edges)[i].from != from || (*edges)[i].to != to)) {
i++;
}
return i == edges->size()? -1 : i;
}
TEST(GraphCycles, RandomizedTest) {
int next_node = 0;
Nodes nodes;
Edges edges; // from, to
IdMap id;
GraphCycles graph_cycles;
static const int kMaxNodes = 7; // use <= 7 nodes to keep test short
static const int kDataOffset = 17; // an offset to the node-specific data
int n = 100000;
int op = 0;
RandomEngine rng(testing::UnitTest::GetInstance()->random_seed());
std::uniform_int_distribution<int> uniform(0, 5);
auto ptr = [](intptr_t i) {
return reinterpret_cast<void*>(i + kDataOffset);
};
for (int iter = 0; iter != n; iter++) {
for (const auto &node : nodes) {
ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), ptr(node)) << " node " << node;
}
CheckEdges(&nodes, &edges, id, &graph_cycles);
CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
op = uniform(rng);
switch (op) {
case 0: // Add a node
if (nodes.size() < kMaxNodes) {
int new_node = next_node++;
GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
ASSERT_NE(new_gnode, InvalidGraphId());
id[new_node] = new_gnode;
ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
nodes.push_back(new_node);
}
break;
case 1: // Remove a node
if (nodes.size() > 0) {
int node_index = RandomNode(&rng, &nodes);
int node = nodes[node_index];
nodes[node_index] = nodes.back();
nodes.pop_back();
graph_cycles.RemoveNode(ptr(node));
ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), nullptr);
id.erase(node);
int i = 0;
while (i != edges.size()) {
if (edges[i].from == node || edges[i].to == node) {
edges[i] = edges.back();
edges.pop_back();
} else {
i++;
}
}
}
break;
case 2: // Add an edge
if (nodes.size() > 0) {
int from = RandomNode(&rng, &nodes);
int to = RandomNode(&rng, &nodes);
if (EdgeIndex(&edges, nodes[from], nodes[to]) == -1) {
if (graph_cycles.InsertEdge(id[nodes[from]], id[nodes[to]])) {
Edge new_edge;
new_edge.from = nodes[from];
new_edge.to = nodes[to];
edges.push_back(new_edge);
} else {
std::unordered_set<int> seen;
ASSERT_TRUE(IsReachable(&edges, nodes[to], nodes[from], &seen))
<< "Edge " << nodes[to] << "->" << nodes[from];
}
}
}
break;
case 3: // Remove an edge
if (edges.size() > 0) {
int i = RandomEdge(&rng, &edges);
int from = edges[i].from;
int to = edges[i].to;
ASSERT_EQ(i, EdgeIndex(&edges, from, to));
edges[i] = edges.back();
edges.pop_back();
ASSERT_EQ(-1, EdgeIndex(&edges, from, to));
graph_cycles.RemoveEdge(id[from], id[to]);
}
break;
case 4: // Check a path
if (nodes.size() > 0) {
int from = RandomNode(&rng, &nodes);
int to = RandomNode(&rng, &nodes);
GraphId path[2*kMaxNodes];
int path_len = graph_cycles.FindPath(id[nodes[from]], id[nodes[to]],
ABSL_ARRAYSIZE(path), path);
std::unordered_set<int> seen;
bool reachable = IsReachable(&edges, nodes[from], nodes[to], &seen);
bool gc_reachable =
graph_cycles.IsReachable(Get(id, nodes[from]), Get(id, nodes[to]));
ASSERT_EQ(path_len != 0, reachable);
ASSERT_EQ(path_len != 0, gc_reachable);
// In the following line, we add one because a node can appear
// twice, if the path is from that node to itself, perhaps via
// every other node.
ASSERT_LE(path_len, kMaxNodes + 1);
if (path_len != 0) {
ASSERT_EQ(id[nodes[from]], path[0]);
ASSERT_EQ(id[nodes[to]], path[path_len-1]);
for (int i = 1; i < path_len; i++) {
ASSERT_TRUE(graph_cycles.HasEdge(path[i-1], path[i]));
}
}
}
break;
case 5: // Check invariants
CheckInvariants(graph_cycles);
break;
default:
ABSL_RAW_LOG(FATAL, "op %d", op);
}
// Very rarely, test graph expansion by adding then removing many nodes.
std::bernoulli_distribution one_in_1024(1.0 / 1024);
if (one_in_1024(rng)) {
CheckEdges(&nodes, &edges, id, &graph_cycles);
CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
for (int i = 0; i != 256; i++) {
int new_node = next_node++;
GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
ASSERT_NE(InvalidGraphId(), new_gnode);
id[new_node] = new_gnode;
ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
for (const auto &node : nodes) {
ASSERT_NE(node, new_node);
}
nodes.push_back(new_node);
}
for (int i = 0; i != 256; i++) {
ASSERT_GT(nodes.size(), 0);
int node_index = RandomNode(&rng, &nodes);
int node = nodes[node_index];
nodes[node_index] = nodes.back();
nodes.pop_back();
graph_cycles.RemoveNode(ptr(node));
id.erase(node);
int j = 0;
while (j != edges.size()) {
if (edges[j].from == node || edges[j].to == node) {
edges[j] = edges.back();
edges.pop_back();
} else {
j++;
}
}
}
CheckInvariants(graph_cycles);
}
}
}
class GraphCyclesTest : public ::testing::Test {
public:
IdMap id_;
GraphCycles g_;
static void* Ptr(int i) {
return reinterpret_cast<void*>(static_cast<uintptr_t>(i));
}
static int Num(void* ptr) {
return static_cast<int>(reinterpret_cast<uintptr_t>(ptr));
}
// Test relies on ith NewNode() call returning Node numbered i
GraphCyclesTest() {
for (int i = 0; i < 100; i++) {
id_[i] = g_.GetId(Ptr(i));
}
CheckInvariants(g_);
}
bool AddEdge(int x, int y) {
return g_.InsertEdge(Get(id_, x), Get(id_, y));
}
void AddMultiples() {
// For every node x > 0: add edge to 2*x, 3*x
for (int x = 1; x < 25; x++) {
EXPECT_TRUE(AddEdge(x, 2*x)) << x;
EXPECT_TRUE(AddEdge(x, 3*x)) << x;
}
CheckInvariants(g_);
}
std::string Path(int x, int y) {
GraphId path[5];
int np = g_.FindPath(Get(id_, x), Get(id_, y), ABSL_ARRAYSIZE(path), path);
std::string result;
for (int i = 0; i < np; i++) {
if (i >= ABSL_ARRAYSIZE(path)) {
result += " ...";
break;
}
if (!result.empty()) result.push_back(' ');
char buf[20];
snprintf(buf, sizeof(buf), "%d", Num(g_.Ptr(path[i])));
result += buf;
}
return result;
}
};
TEST_F(GraphCyclesTest, NoCycle) {
AddMultiples();
CheckInvariants(g_);
}
TEST_F(GraphCyclesTest, SimpleCycle) {
AddMultiples();
EXPECT_FALSE(AddEdge(8, 4));
EXPECT_EQ("4 8", Path(4, 8));
CheckInvariants(g_);
}
TEST_F(GraphCyclesTest, IndirectCycle) {
AddMultiples();
EXPECT_TRUE(AddEdge(16, 9));
CheckInvariants(g_);
EXPECT_FALSE(AddEdge(9, 2));
EXPECT_EQ("2 4 8 16 9", Path(2, 9));
CheckInvariants(g_);
}
TEST_F(GraphCyclesTest, LongPath) {
ASSERT_TRUE(AddEdge(2, 4));
ASSERT_TRUE(AddEdge(4, 6));
ASSERT_TRUE(AddEdge(6, 8));
ASSERT_TRUE(AddEdge(8, 10));
ASSERT_TRUE(AddEdge(10, 12));
ASSERT_FALSE(AddEdge(12, 2));
EXPECT_EQ("2 4 6 8 10 ...", Path(2, 12));
CheckInvariants(g_);
}
TEST_F(GraphCyclesTest, RemoveNode) {
ASSERT_TRUE(AddEdge(1, 2));
ASSERT_TRUE(AddEdge(2, 3));
ASSERT_TRUE(AddEdge(3, 4));
ASSERT_TRUE(AddEdge(4, 5));
g_.RemoveNode(g_.Ptr(id_[3]));
id_.erase(3);
ASSERT_TRUE(AddEdge(5, 1));
}
TEST_F(GraphCyclesTest, ManyEdges) {
const int N = 50;
for (int i = 0; i < N; i++) {
for (int j = 1; j < N; j++) {
ASSERT_TRUE(AddEdge(i, i+j));
}
}
CheckInvariants(g_);
ASSERT_TRUE(AddEdge(2*N-1, 0));
CheckInvariants(g_);
ASSERT_FALSE(AddEdge(10, 9));
CheckInvariants(g_);
}
} // namespace synchronization_internal
} // namespace absl