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