blob: 58e8477b6d1a8385e20b3a165b4694dfb195fa61 [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001// Copyright 2017 The Abseil Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "absl/synchronization/internal/graphcycles.h"
16
17#include <map>
18#include <random>
19#include <unordered_set>
20#include <utility>
21#include <vector>
22
23#include "gtest/gtest.h"
24#include "absl/base/internal/raw_logging.h"
25#include "absl/base/macros.h"
26
27namespace absl {
28namespace synchronization_internal {
29
30// We emulate a GraphCycles object with a node vector and an edge vector.
31// We then compare the two implementations.
32
33using Nodes = std::vector<int>;
34struct Edge {
35 int from;
36 int to;
37};
38using Edges = std::vector<Edge>;
39using RandomEngine = std::mt19937_64;
40
41// Mapping from integer index to GraphId.
42typedef std::map<int, GraphId> IdMap;
43static GraphId Get(const IdMap& id, int num) {
44 auto iter = id.find(num);
45 return (iter == id.end()) ? InvalidGraphId() : iter->second;
46}
47
48// Return whether "to" is reachable from "from".
49static bool IsReachable(Edges *edges, int from, int to,
50 std::unordered_set<int> *seen) {
51 seen->insert(from); // we are investigating "from"; don't do it again
52 if (from == to) return true;
53 for (const auto &edge : *edges) {
54 if (edge.from == from) {
55 if (edge.to == to) { // success via edge directly
56 return true;
57 } else if (seen->find(edge.to) == seen->end() && // success via edge
58 IsReachable(edges, edge.to, to, seen)) {
59 return true;
60 }
61 }
62 }
63 return false;
64}
65
66static void PrintEdges(Edges *edges) {
67 ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());
68 for (const auto &edge : *edges) {
69 int a = edge.from;
70 int b = edge.to;
71 ABSL_RAW_LOG(INFO, "%d %d", a, b);
72 }
73 ABSL_RAW_LOG(INFO, "---");
74}
75
76static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {
77 ABSL_RAW_LOG(INFO, "GC EDGES");
78 for (int a : *nodes) {
79 for (int b : *nodes) {
80 if (gc->HasEdge(Get(id, a), Get(id, b))) {
81 ABSL_RAW_LOG(INFO, "%d %d", a, b);
82 }
83 }
84 }
85 ABSL_RAW_LOG(INFO, "---");
86}
87
88static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {
89 ABSL_RAW_LOG(INFO, "Transitive closure");
90 for (int a : *nodes) {
91 for (int b : *nodes) {
92 std::unordered_set<int> seen;
93 if (IsReachable(edges, a, b, &seen)) {
94 ABSL_RAW_LOG(INFO, "%d %d", a, b);
95 }
96 }
97 }
98 ABSL_RAW_LOG(INFO, "---");
99}
100
101static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,
102 GraphCycles *gc) {
103 ABSL_RAW_LOG(INFO, "GC Transitive closure");
104 for (int a : *nodes) {
105 for (int b : *nodes) {
106 if (gc->IsReachable(Get(id, a), Get(id, b))) {
107 ABSL_RAW_LOG(INFO, "%d %d", a, b);
108 }
109 }
110 }
111 ABSL_RAW_LOG(INFO, "---");
112}
113
114static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,
115 GraphCycles *gc) {
116 std::unordered_set<int> seen;
117 for (const auto &a : *nodes) {
118 for (const auto &b : *nodes) {
119 seen.clear();
120 bool gc_reachable = gc->IsReachable(Get(id, a), Get(id, b));
121 bool reachable = IsReachable(edges, a, b, &seen);
122 if (gc_reachable != reachable) {
123 PrintEdges(edges);
124 PrintGCEdges(nodes, id, gc);
125 PrintTransitiveClosure(nodes, edges);
126 PrintGCTransitiveClosure(nodes, id, gc);
127 ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",
128 gc_reachable ? "true" : "false",
129 reachable ? "true" : "false", a, b);
130 }
131 }
132 }
133}
134
135static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id,
136 GraphCycles *gc) {
137 int count = 0;
138 for (const auto &edge : *edges) {
139 int a = edge.from;
140 int b = edge.to;
141 if (!gc->HasEdge(Get(id, a), Get(id, b))) {
142 PrintEdges(edges);
143 PrintGCEdges(nodes, id, gc);
144 ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);
145 }
146 }
147 for (const auto &a : *nodes) {
148 for (const auto &b : *nodes) {
149 if (gc->HasEdge(Get(id, a), Get(id, b))) {
150 count++;
151 }
152 }
153 }
154 if (count != edges->size()) {
155 PrintEdges(edges);
156 PrintGCEdges(nodes, id, gc);
157 ABSL_RAW_LOG(FATAL, "edges->size() %zu count %d", edges->size(), count);
158 }
159}
160
161static void CheckInvariants(const GraphCycles &gc) {
162 if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))
163 ABSL_RAW_LOG(FATAL, "CheckInvariants");
164}
165
166// Returns the index of a randomly chosen node in *nodes.
167// Requires *nodes be non-empty.
168static int RandomNode(RandomEngine* rng, Nodes *nodes) {
169 std::uniform_int_distribution<int> uniform(0, nodes->size()-1);
170 return uniform(*rng);
171}
172
173// Returns the index of a randomly chosen edge in *edges.
174// Requires *edges be non-empty.
175static int RandomEdge(RandomEngine* rng, Edges *edges) {
176 std::uniform_int_distribution<int> uniform(0, edges->size()-1);
177 return uniform(*rng);
178}
179
180// Returns the index of edge (from, to) in *edges or -1 if it is not in *edges.
181static int EdgeIndex(Edges *edges, int from, int to) {
182 int i = 0;
183 while (i != edges->size() &&
184 ((*edges)[i].from != from || (*edges)[i].to != to)) {
185 i++;
186 }
187 return i == edges->size()? -1 : i;
188}
189
190TEST(GraphCycles, RandomizedTest) {
191 int next_node = 0;
192 Nodes nodes;
193 Edges edges; // from, to
194 IdMap id;
195 GraphCycles graph_cycles;
196 static const int kMaxNodes = 7; // use <= 7 nodes to keep test short
197 static const int kDataOffset = 17; // an offset to the node-specific data
198 int n = 100000;
199 int op = 0;
200 RandomEngine rng(testing::UnitTest::GetInstance()->random_seed());
201 std::uniform_int_distribution<int> uniform(0, 5);
202
203 auto ptr = [](intptr_t i) {
204 return reinterpret_cast<void*>(i + kDataOffset);
205 };
206
207 for (int iter = 0; iter != n; iter++) {
208 for (const auto &node : nodes) {
209 ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), ptr(node)) << " node " << node;
210 }
211 CheckEdges(&nodes, &edges, id, &graph_cycles);
212 CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
213 op = uniform(rng);
214 switch (op) {
215 case 0: // Add a node
216 if (nodes.size() < kMaxNodes) {
217 int new_node = next_node++;
218 GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
219 ASSERT_NE(new_gnode, InvalidGraphId());
220 id[new_node] = new_gnode;
221 ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
222 nodes.push_back(new_node);
223 }
224 break;
225
226 case 1: // Remove a node
227 if (nodes.size() > 0) {
228 int node_index = RandomNode(&rng, &nodes);
229 int node = nodes[node_index];
230 nodes[node_index] = nodes.back();
231 nodes.pop_back();
232 graph_cycles.RemoveNode(ptr(node));
233 ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), nullptr);
234 id.erase(node);
235 int i = 0;
236 while (i != edges.size()) {
237 if (edges[i].from == node || edges[i].to == node) {
238 edges[i] = edges.back();
239 edges.pop_back();
240 } else {
241 i++;
242 }
243 }
244 }
245 break;
246
247 case 2: // Add an edge
248 if (nodes.size() > 0) {
249 int from = RandomNode(&rng, &nodes);
250 int to = RandomNode(&rng, &nodes);
251 if (EdgeIndex(&edges, nodes[from], nodes[to]) == -1) {
252 if (graph_cycles.InsertEdge(id[nodes[from]], id[nodes[to]])) {
253 Edge new_edge;
254 new_edge.from = nodes[from];
255 new_edge.to = nodes[to];
256 edges.push_back(new_edge);
257 } else {
258 std::unordered_set<int> seen;
259 ASSERT_TRUE(IsReachable(&edges, nodes[to], nodes[from], &seen))
260 << "Edge " << nodes[to] << "->" << nodes[from];
261 }
262 }
263 }
264 break;
265
266 case 3: // Remove an edge
267 if (edges.size() > 0) {
268 int i = RandomEdge(&rng, &edges);
269 int from = edges[i].from;
270 int to = edges[i].to;
271 ASSERT_EQ(i, EdgeIndex(&edges, from, to));
272 edges[i] = edges.back();
273 edges.pop_back();
274 ASSERT_EQ(-1, EdgeIndex(&edges, from, to));
275 graph_cycles.RemoveEdge(id[from], id[to]);
276 }
277 break;
278
279 case 4: // Check a path
280 if (nodes.size() > 0) {
281 int from = RandomNode(&rng, &nodes);
282 int to = RandomNode(&rng, &nodes);
283 GraphId path[2*kMaxNodes];
284 int path_len = graph_cycles.FindPath(id[nodes[from]], id[nodes[to]],
285 ABSL_ARRAYSIZE(path), path);
286 std::unordered_set<int> seen;
287 bool reachable = IsReachable(&edges, nodes[from], nodes[to], &seen);
288 bool gc_reachable =
289 graph_cycles.IsReachable(Get(id, nodes[from]), Get(id, nodes[to]));
290 ASSERT_EQ(path_len != 0, reachable);
291 ASSERT_EQ(path_len != 0, gc_reachable);
292 // In the following line, we add one because a node can appear
293 // twice, if the path is from that node to itself, perhaps via
294 // every other node.
295 ASSERT_LE(path_len, kMaxNodes + 1);
296 if (path_len != 0) {
297 ASSERT_EQ(id[nodes[from]], path[0]);
298 ASSERT_EQ(id[nodes[to]], path[path_len-1]);
299 for (int i = 1; i < path_len; i++) {
300 ASSERT_TRUE(graph_cycles.HasEdge(path[i-1], path[i]));
301 }
302 }
303 }
304 break;
305
306 case 5: // Check invariants
307 CheckInvariants(graph_cycles);
308 break;
309
310 default:
311 ABSL_RAW_LOG(FATAL, "op %d", op);
312 }
313
314 // Very rarely, test graph expansion by adding then removing many nodes.
315 std::bernoulli_distribution one_in_1024(1.0 / 1024);
316 if (one_in_1024(rng)) {
317 CheckEdges(&nodes, &edges, id, &graph_cycles);
318 CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
319 for (int i = 0; i != 256; i++) {
320 int new_node = next_node++;
321 GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
322 ASSERT_NE(InvalidGraphId(), new_gnode);
323 id[new_node] = new_gnode;
324 ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
325 for (const auto &node : nodes) {
326 ASSERT_NE(node, new_node);
327 }
328 nodes.push_back(new_node);
329 }
330 for (int i = 0; i != 256; i++) {
331 ASSERT_GT(nodes.size(), 0);
332 int node_index = RandomNode(&rng, &nodes);
333 int node = nodes[node_index];
334 nodes[node_index] = nodes.back();
335 nodes.pop_back();
336 graph_cycles.RemoveNode(ptr(node));
337 id.erase(node);
338 int j = 0;
339 while (j != edges.size()) {
340 if (edges[j].from == node || edges[j].to == node) {
341 edges[j] = edges.back();
342 edges.pop_back();
343 } else {
344 j++;
345 }
346 }
347 }
348 CheckInvariants(graph_cycles);
349 }
350 }
351}
352
353class GraphCyclesTest : public ::testing::Test {
354 public:
355 IdMap id_;
356 GraphCycles g_;
357
358 static void* Ptr(int i) {
359 return reinterpret_cast<void*>(static_cast<uintptr_t>(i));
360 }
361
362 static int Num(void* ptr) {
363 return static_cast<int>(reinterpret_cast<uintptr_t>(ptr));
364 }
365
366 // Test relies on ith NewNode() call returning Node numbered i
367 GraphCyclesTest() {
368 for (int i = 0; i < 100; i++) {
369 id_[i] = g_.GetId(Ptr(i));
370 }
371 CheckInvariants(g_);
372 }
373
374 bool AddEdge(int x, int y) {
375 return g_.InsertEdge(Get(id_, x), Get(id_, y));
376 }
377
378 void AddMultiples() {
379 // For every node x > 0: add edge to 2*x, 3*x
380 for (int x = 1; x < 25; x++) {
381 EXPECT_TRUE(AddEdge(x, 2*x)) << x;
382 EXPECT_TRUE(AddEdge(x, 3*x)) << x;
383 }
384 CheckInvariants(g_);
385 }
386
387 std::string Path(int x, int y) {
388 GraphId path[5];
389 int np = g_.FindPath(Get(id_, x), Get(id_, y), ABSL_ARRAYSIZE(path), path);
390 std::string result;
391 for (int i = 0; i < np; i++) {
392 if (i >= ABSL_ARRAYSIZE(path)) {
393 result += " ...";
394 break;
395 }
396 if (!result.empty()) result.push_back(' ');
397 char buf[20];
398 snprintf(buf, sizeof(buf), "%d", Num(g_.Ptr(path[i])));
399 result += buf;
400 }
401 return result;
402 }
403};
404
405TEST_F(GraphCyclesTest, NoCycle) {
406 AddMultiples();
407 CheckInvariants(g_);
408}
409
410TEST_F(GraphCyclesTest, SimpleCycle) {
411 AddMultiples();
412 EXPECT_FALSE(AddEdge(8, 4));
413 EXPECT_EQ("4 8", Path(4, 8));
414 CheckInvariants(g_);
415}
416
417TEST_F(GraphCyclesTest, IndirectCycle) {
418 AddMultiples();
419 EXPECT_TRUE(AddEdge(16, 9));
420 CheckInvariants(g_);
421 EXPECT_FALSE(AddEdge(9, 2));
422 EXPECT_EQ("2 4 8 16 9", Path(2, 9));
423 CheckInvariants(g_);
424}
425
426TEST_F(GraphCyclesTest, LongPath) {
427 ASSERT_TRUE(AddEdge(2, 4));
428 ASSERT_TRUE(AddEdge(4, 6));
429 ASSERT_TRUE(AddEdge(6, 8));
430 ASSERT_TRUE(AddEdge(8, 10));
431 ASSERT_TRUE(AddEdge(10, 12));
432 ASSERT_FALSE(AddEdge(12, 2));
433 EXPECT_EQ("2 4 6 8 10 ...", Path(2, 12));
434 CheckInvariants(g_);
435}
436
437TEST_F(GraphCyclesTest, RemoveNode) {
438 ASSERT_TRUE(AddEdge(1, 2));
439 ASSERT_TRUE(AddEdge(2, 3));
440 ASSERT_TRUE(AddEdge(3, 4));
441 ASSERT_TRUE(AddEdge(4, 5));
442 g_.RemoveNode(g_.Ptr(id_[3]));
443 id_.erase(3);
444 ASSERT_TRUE(AddEdge(5, 1));
445}
446
447TEST_F(GraphCyclesTest, ManyEdges) {
448 const int N = 50;
449 for (int i = 0; i < N; i++) {
450 for (int j = 1; j < N; j++) {
451 ASSERT_TRUE(AddEdge(i, i+j));
452 }
453 }
454 CheckInvariants(g_);
455 ASSERT_TRUE(AddEdge(2*N-1, 0));
456 CheckInvariants(g_);
457 ASSERT_FALSE(AddEdge(10, 9));
458 CheckInvariants(g_);
459}
460
461} // namespace synchronization_internal
462} // namespace absl