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