• 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 <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 
27 namespace absl {
28 ABSL_NAMESPACE_BEGIN
29 namespace synchronization_internal {
30 
31 // We emulate a GraphCycles object with a node vector and an edge vector.
32 // We then compare the two implementations.
33 
34 using Nodes = std::vector<int>;
35 struct Edge {
36   int from;
37   int to;
38 };
39 using Edges = std::vector<Edge>;
40 using RandomEngine = std::mt19937_64;
41 
42 // Mapping from integer index to GraphId.
43 typedef std::map<int, GraphId> IdMap;
Get(const IdMap & id,int num)44 static GraphId Get(const IdMap& id, int num) {
45   auto iter = id.find(num);
46   return (iter == id.end()) ? InvalidGraphId() : iter->second;
47 }
48 
49 // Return whether "to" is reachable from "from".
IsReachable(Edges * edges,int from,int to,std::unordered_set<int> * seen)50 static bool IsReachable(Edges *edges, int from, int to,
51                         std::unordered_set<int> *seen) {
52   seen->insert(from);     // we are investigating "from"; don't do it again
53   if (from == to) return true;
54   for (const auto &edge : *edges) {
55     if (edge.from == from) {
56       if (edge.to == to) {  // success via edge directly
57         return true;
58       } else if (seen->find(edge.to) == seen->end() &&  // success via edge
59                  IsReachable(edges, edge.to, to, seen)) {
60         return true;
61       }
62     }
63   }
64   return false;
65 }
66 
PrintEdges(Edges * edges)67 static void PrintEdges(Edges *edges) {
68   ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());
69   for (const auto &edge : *edges) {
70     int a = edge.from;
71     int b = edge.to;
72     ABSL_RAW_LOG(INFO, "%d %d", a, b);
73   }
74   ABSL_RAW_LOG(INFO, "---");
75 }
76 
PrintGCEdges(Nodes * nodes,const IdMap & id,GraphCycles * gc)77 static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {
78   ABSL_RAW_LOG(INFO, "GC EDGES");
79   for (int a : *nodes) {
80     for (int b : *nodes) {
81       if (gc->HasEdge(Get(id, a), Get(id, b))) {
82         ABSL_RAW_LOG(INFO, "%d %d", a, b);
83       }
84     }
85   }
86   ABSL_RAW_LOG(INFO, "---");
87 }
88 
PrintTransitiveClosure(Nodes * nodes,Edges * edges)89 static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {
90   ABSL_RAW_LOG(INFO, "Transitive closure");
91   for (int a : *nodes) {
92     for (int b : *nodes) {
93       std::unordered_set<int> seen;
94       if (IsReachable(edges, a, b, &seen)) {
95         ABSL_RAW_LOG(INFO, "%d %d", a, b);
96       }
97     }
98   }
99   ABSL_RAW_LOG(INFO, "---");
100 }
101 
PrintGCTransitiveClosure(Nodes * nodes,const IdMap & id,GraphCycles * gc)102 static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,
103                                      GraphCycles *gc) {
104   ABSL_RAW_LOG(INFO, "GC Transitive closure");
105   for (int a : *nodes) {
106     for (int b : *nodes) {
107       if (gc->IsReachable(Get(id, a), Get(id, b))) {
108         ABSL_RAW_LOG(INFO, "%d %d", a, b);
109       }
110     }
111   }
112   ABSL_RAW_LOG(INFO, "---");
113 }
114 
CheckTransitiveClosure(Nodes * nodes,Edges * edges,const IdMap & id,GraphCycles * gc)115 static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,
116                                    GraphCycles *gc) {
117   std::unordered_set<int> seen;
118   for (const auto &a : *nodes) {
119     for (const auto &b : *nodes) {
120       seen.clear();
121       bool gc_reachable = gc->IsReachable(Get(id, a), Get(id, b));
122       bool reachable = IsReachable(edges, a, b, &seen);
123       if (gc_reachable != reachable) {
124         PrintEdges(edges);
125         PrintGCEdges(nodes, id, gc);
126         PrintTransitiveClosure(nodes, edges);
127         PrintGCTransitiveClosure(nodes, id, gc);
128         ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",
129                      gc_reachable ? "true" : "false",
130                      reachable ? "true" : "false", a, b);
131       }
132     }
133   }
134 }
135 
CheckEdges(Nodes * nodes,Edges * edges,const IdMap & id,GraphCycles * gc)136 static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id,
137                        GraphCycles *gc) {
138   int count = 0;
139   for (const auto &edge : *edges) {
140     int a = edge.from;
141     int b = edge.to;
142     if (!gc->HasEdge(Get(id, a), Get(id, b))) {
143       PrintEdges(edges);
144       PrintGCEdges(nodes, id, gc);
145       ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);
146     }
147   }
148   for (const auto &a : *nodes) {
149     for (const auto &b : *nodes) {
150       if (gc->HasEdge(Get(id, a), Get(id, b))) {
151         count++;
152       }
153     }
154   }
155   if (count != edges->size()) {
156     PrintEdges(edges);
157     PrintGCEdges(nodes, id, gc);
158     ABSL_RAW_LOG(FATAL, "edges->size() %zu  count %d", edges->size(), count);
159   }
160 }
161 
CheckInvariants(const GraphCycles & gc)162 static void CheckInvariants(const GraphCycles &gc) {
163   if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))
164     ABSL_RAW_LOG(FATAL, "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       ABSL_RAW_LOG(FATAL, "op %d", 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 
462 }  // namespace synchronization_internal
463 ABSL_NAMESPACE_END
464 }  // namespace absl
465