graphcycles_test.cc (14255B)
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; 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". 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 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 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 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 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 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 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 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. 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. 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. 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 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 360 static void* Ptr(int i) { 361 return reinterpret_cast<void*>(static_cast<uintptr_t>(i)); 362 } 363 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 369 GraphCyclesTest() { 370 for (int i = 0; i < 100; i++) { 371 id_[i] = g_.GetId(Ptr(i)); 372 } 373 CheckInvariants(g_); 374 } 375 376 bool AddEdge(int x, int y) { 377 return g_.InsertEdge(Get(id_, x), Get(id_, y)); 378 } 379 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 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 407 TEST_F(GraphCyclesTest, NoCycle) { 408 AddMultiples(); 409 CheckInvariants(g_); 410 } 411 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 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 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 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 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 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