graphcycles_test.cc
Go to the documentation of this file.
00001 // Copyright 2017 The Abseil Authors.
00002 //
00003 // Licensed under the Apache License, Version 2.0 (the "License");
00004 // you may not use this file except in compliance with the License.
00005 // You may obtain a copy of the License at
00006 //
00007 //      https://www.apache.org/licenses/LICENSE-2.0
00008 //
00009 // Unless required by applicable law or agreed to in writing, software
00010 // distributed under the License is distributed on an "AS IS" BASIS,
00011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012 // See the License for the specific language governing permissions and
00013 // limitations under the License.
00014 
00015 #include "absl/synchronization/internal/graphcycles.h"
00016 
00017 #include <map>
00018 #include <random>
00019 #include <unordered_set>
00020 #include <utility>
00021 #include <vector>
00022 
00023 #include "gtest/gtest.h"
00024 #include "absl/base/internal/raw_logging.h"
00025 #include "absl/base/macros.h"
00026 
00027 namespace absl {
00028 namespace synchronization_internal {
00029 
00030 // We emulate a GraphCycles object with a node vector and an edge vector.
00031 // We then compare the two implementations.
00032 
00033 using Nodes = std::vector<int>;
00034 struct Edge {
00035   int from;
00036   int to;
00037 };
00038 using Edges = std::vector<Edge>;
00039 using RandomEngine = std::mt19937_64;
00040 
00041 // Mapping from integer index to GraphId.
00042 typedef std::map<int, GraphId> IdMap;
00043 static GraphId Get(const IdMap& id, int num) {
00044   auto iter = id.find(num);
00045   return (iter == id.end()) ? InvalidGraphId() : iter->second;
00046 }
00047 
00048 // Return whether "to" is reachable from "from".
00049 static bool IsReachable(Edges *edges, int from, int to,
00050                         std::unordered_set<int> *seen) {
00051   seen->insert(from);     // we are investigating "from"; don't do it again
00052   if (from == to) return true;
00053   for (const auto &edge : *edges) {
00054     if (edge.from == from) {
00055       if (edge.to == to) {  // success via edge directly
00056         return true;
00057       } else if (seen->find(edge.to) == seen->end() &&  // success via edge
00058                  IsReachable(edges, edge.to, to, seen)) {
00059         return true;
00060       }
00061     }
00062   }
00063   return false;
00064 }
00065 
00066 static void PrintEdges(Edges *edges) {
00067   ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());
00068   for (const auto &edge : *edges) {
00069     int a = edge.from;
00070     int b = edge.to;
00071     ABSL_RAW_LOG(INFO, "%d %d", a, b);
00072   }
00073   ABSL_RAW_LOG(INFO, "---");
00074 }
00075 
00076 static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {
00077   ABSL_RAW_LOG(INFO, "GC EDGES");
00078   for (int a : *nodes) {
00079     for (int b : *nodes) {
00080       if (gc->HasEdge(Get(id, a), Get(id, b))) {
00081         ABSL_RAW_LOG(INFO, "%d %d", a, b);
00082       }
00083     }
00084   }
00085   ABSL_RAW_LOG(INFO, "---");
00086 }
00087 
00088 static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {
00089   ABSL_RAW_LOG(INFO, "Transitive closure");
00090   for (int a : *nodes) {
00091     for (int b : *nodes) {
00092       std::unordered_set<int> seen;
00093       if (IsReachable(edges, a, b, &seen)) {
00094         ABSL_RAW_LOG(INFO, "%d %d", a, b);
00095       }
00096     }
00097   }
00098   ABSL_RAW_LOG(INFO, "---");
00099 }
00100 
00101 static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,
00102                                      GraphCycles *gc) {
00103   ABSL_RAW_LOG(INFO, "GC Transitive closure");
00104   for (int a : *nodes) {
00105     for (int b : *nodes) {
00106       if (gc->IsReachable(Get(id, a), Get(id, b))) {
00107         ABSL_RAW_LOG(INFO, "%d %d", a, b);
00108       }
00109     }
00110   }
00111   ABSL_RAW_LOG(INFO, "---");
00112 }
00113 
00114 static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,
00115                                    GraphCycles *gc) {
00116   std::unordered_set<int> seen;
00117   for (const auto &a : *nodes) {
00118     for (const auto &b : *nodes) {
00119       seen.clear();
00120       bool gc_reachable = gc->IsReachable(Get(id, a), Get(id, b));
00121       bool reachable = IsReachable(edges, a, b, &seen);
00122       if (gc_reachable != reachable) {
00123         PrintEdges(edges);
00124         PrintGCEdges(nodes, id, gc);
00125         PrintTransitiveClosure(nodes, edges);
00126         PrintGCTransitiveClosure(nodes, id, gc);
00127         ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",
00128                      gc_reachable ? "true" : "false",
00129                      reachable ? "true" : "false", a, b);
00130       }
00131     }
00132   }
00133 }
00134 
00135 static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id,
00136                        GraphCycles *gc) {
00137   int count = 0;
00138   for (const auto &edge : *edges) {
00139     int a = edge.from;
00140     int b = edge.to;
00141     if (!gc->HasEdge(Get(id, a), Get(id, b))) {
00142       PrintEdges(edges);
00143       PrintGCEdges(nodes, id, gc);
00144       ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);
00145     }
00146   }
00147   for (const auto &a : *nodes) {
00148     for (const auto &b : *nodes) {
00149       if (gc->HasEdge(Get(id, a), Get(id, b))) {
00150         count++;
00151       }
00152     }
00153   }
00154   if (count != edges->size()) {
00155     PrintEdges(edges);
00156     PrintGCEdges(nodes, id, gc);
00157     ABSL_RAW_LOG(FATAL, "edges->size() %zu  count %d", edges->size(), count);
00158   }
00159 }
00160 
00161 static void CheckInvariants(const GraphCycles &gc) {
00162   if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))
00163     ABSL_RAW_LOG(FATAL, "CheckInvariants");
00164 }
00165 
00166 // Returns the index of a randomly chosen node in *nodes.
00167 // Requires *nodes be non-empty.
00168 static int RandomNode(RandomEngine* rng, Nodes *nodes) {
00169   std::uniform_int_distribution<int> uniform(0, nodes->size()-1);
00170   return uniform(*rng);
00171 }
00172 
00173 // Returns the index of a randomly chosen edge in *edges.
00174 // Requires *edges be non-empty.
00175 static int RandomEdge(RandomEngine* rng, Edges *edges) {
00176   std::uniform_int_distribution<int> uniform(0, edges->size()-1);
00177   return uniform(*rng);
00178 }
00179 
00180 // Returns the index of edge (from, to) in *edges or -1 if it is not in *edges.
00181 static int EdgeIndex(Edges *edges, int from, int to) {
00182   int i = 0;
00183   while (i != edges->size() &&
00184          ((*edges)[i].from != from || (*edges)[i].to != to)) {
00185     i++;
00186   }
00187   return i == edges->size()? -1 : i;
00188 }
00189 
00190 TEST(GraphCycles, RandomizedTest) {
00191   int next_node = 0;
00192   Nodes nodes;
00193   Edges edges;   // from, to
00194   IdMap id;
00195   GraphCycles graph_cycles;
00196   static const int kMaxNodes = 7;  // use <= 7 nodes to keep test short
00197   static const int kDataOffset = 17;  // an offset to the node-specific data
00198   int n = 100000;
00199   int op = 0;
00200   RandomEngine rng(testing::UnitTest::GetInstance()->random_seed());
00201   std::uniform_int_distribution<int> uniform(0, 5);
00202 
00203   auto ptr = [](intptr_t i) {
00204     return reinterpret_cast<void*>(i + kDataOffset);
00205   };
00206 
00207   for (int iter = 0; iter != n; iter++) {
00208     for (const auto &node : nodes) {
00209       ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), ptr(node)) << " node " << node;
00210     }
00211     CheckEdges(&nodes, &edges, id, &graph_cycles);
00212     CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
00213     op = uniform(rng);
00214     switch (op) {
00215     case 0:     // Add a node
00216       if (nodes.size() < kMaxNodes) {
00217         int new_node = next_node++;
00218         GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
00219         ASSERT_NE(new_gnode, InvalidGraphId());
00220         id[new_node] = new_gnode;
00221         ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
00222         nodes.push_back(new_node);
00223       }
00224       break;
00225 
00226     case 1:    // Remove a node
00227       if (nodes.size() > 0) {
00228         int node_index = RandomNode(&rng, &nodes);
00229         int node = nodes[node_index];
00230         nodes[node_index] = nodes.back();
00231         nodes.pop_back();
00232         graph_cycles.RemoveNode(ptr(node));
00233         ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), nullptr);
00234         id.erase(node);
00235         int i = 0;
00236         while (i != edges.size()) {
00237           if (edges[i].from == node || edges[i].to == node) {
00238             edges[i] = edges.back();
00239             edges.pop_back();
00240           } else {
00241             i++;
00242           }
00243         }
00244       }
00245       break;
00246 
00247     case 2:   // Add an edge
00248       if (nodes.size() > 0) {
00249         int from = RandomNode(&rng, &nodes);
00250         int to = RandomNode(&rng, &nodes);
00251         if (EdgeIndex(&edges, nodes[from], nodes[to]) == -1) {
00252           if (graph_cycles.InsertEdge(id[nodes[from]], id[nodes[to]])) {
00253             Edge new_edge;
00254             new_edge.from = nodes[from];
00255             new_edge.to = nodes[to];
00256             edges.push_back(new_edge);
00257           } else {
00258             std::unordered_set<int> seen;
00259             ASSERT_TRUE(IsReachable(&edges, nodes[to], nodes[from], &seen))
00260                 << "Edge " << nodes[to] << "->" << nodes[from];
00261           }
00262         }
00263       }
00264       break;
00265 
00266     case 3:    // Remove an edge
00267       if (edges.size() > 0) {
00268         int i = RandomEdge(&rng, &edges);
00269         int from = edges[i].from;
00270         int to = edges[i].to;
00271         ASSERT_EQ(i, EdgeIndex(&edges, from, to));
00272         edges[i] = edges.back();
00273         edges.pop_back();
00274         ASSERT_EQ(-1, EdgeIndex(&edges, from, to));
00275         graph_cycles.RemoveEdge(id[from], id[to]);
00276       }
00277       break;
00278 
00279     case 4:   // Check a path
00280       if (nodes.size() > 0) {
00281         int from = RandomNode(&rng, &nodes);
00282         int to = RandomNode(&rng, &nodes);
00283         GraphId path[2*kMaxNodes];
00284         int path_len = graph_cycles.FindPath(id[nodes[from]], id[nodes[to]],
00285                                              ABSL_ARRAYSIZE(path), path);
00286         std::unordered_set<int> seen;
00287         bool reachable = IsReachable(&edges, nodes[from], nodes[to], &seen);
00288         bool gc_reachable =
00289             graph_cycles.IsReachable(Get(id, nodes[from]), Get(id, nodes[to]));
00290         ASSERT_EQ(path_len != 0, reachable);
00291         ASSERT_EQ(path_len != 0, gc_reachable);
00292         // In the following line, we add one because a node can appear
00293         // twice, if the path is from that node to itself, perhaps via
00294         // every other node.
00295         ASSERT_LE(path_len, kMaxNodes + 1);
00296         if (path_len != 0) {
00297           ASSERT_EQ(id[nodes[from]], path[0]);
00298           ASSERT_EQ(id[nodes[to]], path[path_len-1]);
00299           for (int i = 1; i < path_len; i++) {
00300             ASSERT_TRUE(graph_cycles.HasEdge(path[i-1], path[i]));
00301           }
00302         }
00303       }
00304       break;
00305 
00306     case 5:  // Check invariants
00307       CheckInvariants(graph_cycles);
00308       break;
00309 
00310     default:
00311       ABSL_RAW_LOG(FATAL, "op %d", op);
00312     }
00313 
00314     // Very rarely, test graph expansion by adding then removing many nodes.
00315     std::bernoulli_distribution one_in_1024(1.0 / 1024);
00316     if (one_in_1024(rng)) {
00317       CheckEdges(&nodes, &edges, id, &graph_cycles);
00318       CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);
00319       for (int i = 0; i != 256; i++) {
00320         int new_node = next_node++;
00321         GraphId new_gnode = graph_cycles.GetId(ptr(new_node));
00322         ASSERT_NE(InvalidGraphId(), new_gnode);
00323         id[new_node] = new_gnode;
00324         ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));
00325         for (const auto &node : nodes) {
00326           ASSERT_NE(node, new_node);
00327         }
00328         nodes.push_back(new_node);
00329       }
00330       for (int i = 0; i != 256; i++) {
00331         ASSERT_GT(nodes.size(), 0);
00332         int node_index = RandomNode(&rng, &nodes);
00333         int node = nodes[node_index];
00334         nodes[node_index] = nodes.back();
00335         nodes.pop_back();
00336         graph_cycles.RemoveNode(ptr(node));
00337         id.erase(node);
00338         int j = 0;
00339         while (j != edges.size()) {
00340           if (edges[j].from == node || edges[j].to == node) {
00341             edges[j] = edges.back();
00342             edges.pop_back();
00343           } else {
00344             j++;
00345           }
00346         }
00347       }
00348       CheckInvariants(graph_cycles);
00349     }
00350   }
00351 }
00352 
00353 class GraphCyclesTest : public ::testing::Test {
00354  public:
00355   IdMap id_;
00356   GraphCycles g_;
00357 
00358   static void* Ptr(int i) {
00359     return reinterpret_cast<void*>(static_cast<uintptr_t>(i));
00360   }
00361 
00362   static int Num(void* ptr) {
00363     return static_cast<int>(reinterpret_cast<uintptr_t>(ptr));
00364   }
00365 
00366   // Test relies on ith NewNode() call returning Node numbered i
00367   GraphCyclesTest() {
00368     for (int i = 0; i < 100; i++) {
00369       id_[i] = g_.GetId(Ptr(i));
00370     }
00371     CheckInvariants(g_);
00372   }
00373 
00374   bool AddEdge(int x, int y) {
00375     return g_.InsertEdge(Get(id_, x), Get(id_, y));
00376   }
00377 
00378   void AddMultiples() {
00379     // For every node x > 0: add edge to 2*x, 3*x
00380     for (int x = 1; x < 25; x++) {
00381       EXPECT_TRUE(AddEdge(x, 2*x)) << x;
00382       EXPECT_TRUE(AddEdge(x, 3*x)) << x;
00383     }
00384     CheckInvariants(g_);
00385   }
00386 
00387   std::string Path(int x, int y) {
00388     GraphId path[5];
00389     int np = g_.FindPath(Get(id_, x), Get(id_, y), ABSL_ARRAYSIZE(path), path);
00390     std::string result;
00391     for (int i = 0; i < np; i++) {
00392       if (i >= ABSL_ARRAYSIZE(path)) {
00393         result += " ...";
00394         break;
00395       }
00396       if (!result.empty()) result.push_back(' ');
00397       char buf[20];
00398       snprintf(buf, sizeof(buf), "%d", Num(g_.Ptr(path[i])));
00399       result += buf;
00400     }
00401     return result;
00402   }
00403 };
00404 
00405 TEST_F(GraphCyclesTest, NoCycle) {
00406   AddMultiples();
00407   CheckInvariants(g_);
00408 }
00409 
00410 TEST_F(GraphCyclesTest, SimpleCycle) {
00411   AddMultiples();
00412   EXPECT_FALSE(AddEdge(8, 4));
00413   EXPECT_EQ("4 8", Path(4, 8));
00414   CheckInvariants(g_);
00415 }
00416 
00417 TEST_F(GraphCyclesTest, IndirectCycle) {
00418   AddMultiples();
00419   EXPECT_TRUE(AddEdge(16, 9));
00420   CheckInvariants(g_);
00421   EXPECT_FALSE(AddEdge(9, 2));
00422   EXPECT_EQ("2 4 8 16 9", Path(2, 9));
00423   CheckInvariants(g_);
00424 }
00425 
00426 TEST_F(GraphCyclesTest, LongPath) {
00427   ASSERT_TRUE(AddEdge(2, 4));
00428   ASSERT_TRUE(AddEdge(4, 6));
00429   ASSERT_TRUE(AddEdge(6, 8));
00430   ASSERT_TRUE(AddEdge(8, 10));
00431   ASSERT_TRUE(AddEdge(10, 12));
00432   ASSERT_FALSE(AddEdge(12, 2));
00433   EXPECT_EQ("2 4 6 8 10 ...", Path(2, 12));
00434   CheckInvariants(g_);
00435 }
00436 
00437 TEST_F(GraphCyclesTest, RemoveNode) {
00438   ASSERT_TRUE(AddEdge(1, 2));
00439   ASSERT_TRUE(AddEdge(2, 3));
00440   ASSERT_TRUE(AddEdge(3, 4));
00441   ASSERT_TRUE(AddEdge(4, 5));
00442   g_.RemoveNode(g_.Ptr(id_[3]));
00443   id_.erase(3);
00444   ASSERT_TRUE(AddEdge(5, 1));
00445 }
00446 
00447 TEST_F(GraphCyclesTest, ManyEdges) {
00448   const int N = 50;
00449   for (int i = 0; i < N; i++) {
00450     for (int j = 1; j < N; j++) {
00451       ASSERT_TRUE(AddEdge(i, i+j));
00452     }
00453   }
00454   CheckInvariants(g_);
00455   ASSERT_TRUE(AddEdge(2*N-1, 0));
00456   CheckInvariants(g_);
00457   ASSERT_FALSE(AddEdge(10, 9));
00458   CheckInvariants(g_);
00459 }
00460 
00461 }  // namespace synchronization_internal
00462 }  // namespace absl


abseil_cpp
Author(s):
autogenerated on Wed Jun 19 2019 19:42:14