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