• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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