xref: /aosp_15_r20/external/abseil-cpp/absl/synchronization/internal/graphcycles_test.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
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