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