| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 | // Copyright 2017 The Abseil Authors.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////      http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#include "absl/synchronization/internal/graphcycles.h"#include <map>#include <random>#include <unordered_set>#include <utility>#include <vector>#include "gtest/gtest.h"#include "absl/base/internal/raw_logging.h"#include "absl/base/macros.h"namespace absl {namespace synchronization_internal {// We emulate a GraphCycles object with a node vector and an edge vector.// We then compare the two implementations.using Nodes = std::vector<int>;struct Edge {  int from;  int to;};using Edges = std::vector<Edge>;using RandomEngine = std::mt19937_64;// Mapping from integer index to GraphId.typedef std::map<int, GraphId> IdMap;static GraphId Get(const IdMap& id, int num) {  auto iter = id.find(num);  return (iter == id.end()) ? InvalidGraphId() : iter->second;}// Return whether "to" is reachable from "from".static bool IsReachable(Edges *edges, int from, int to,                        std::unordered_set<int> *seen) {  seen->insert(from);     // we are investigating "from"; don't do it again  if (from == to) return true;  for (const auto &edge : *edges) {    if (edge.from == from) {      if (edge.to == to) {  // success via edge directly        return true;      } else if (seen->find(edge.to) == seen->end() &&  // success via edge                 IsReachable(edges, edge.to, to, seen)) {        return true;      }    }  }  return false;}static void PrintEdges(Edges *edges) {  ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());  for (const auto &edge : *edges) {    int a = edge.from;    int b = edge.to;    ABSL_RAW_LOG(INFO, "%d %d", a, b);  }  ABSL_RAW_LOG(INFO, "---");}static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {  ABSL_RAW_LOG(INFO, "GC EDGES");  for (int a : *nodes) {    for (int b : *nodes) {      if (gc->HasEdge(Get(id, a), Get(id, b))) {        ABSL_RAW_LOG(INFO, "%d %d", a, b);      }    }  }  ABSL_RAW_LOG(INFO, "---");}static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {  ABSL_RAW_LOG(INFO, "Transitive closure");  for (int a : *nodes) {    for (int b : *nodes) {      std::unordered_set<int> seen;      if (IsReachable(edges, a, b, &seen)) {        ABSL_RAW_LOG(INFO, "%d %d", a, b);      }    }  }  ABSL_RAW_LOG(INFO, "---");}static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,                                     GraphCycles *gc) {  ABSL_RAW_LOG(INFO, "GC Transitive closure");  for (int a : *nodes) {    for (int b : *nodes) {      if (gc->IsReachable(Get(id, a), Get(id, b))) {        ABSL_RAW_LOG(INFO, "%d %d", a, b);      }    }  }  ABSL_RAW_LOG(INFO, "---");}static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,                                   GraphCycles *gc) {  std::unordered_set<int> seen;  for (const auto &a : *nodes) {    for (const auto &b : *nodes) {      seen.clear();      bool gc_reachable = gc->IsReachable(Get(id, a), Get(id, b));      bool reachable = IsReachable(edges, a, b, &seen);      if (gc_reachable != reachable) {        PrintEdges(edges);        PrintGCEdges(nodes, id, gc);        PrintTransitiveClosure(nodes, edges);        PrintGCTransitiveClosure(nodes, id, gc);        ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",                     gc_reachable ? "true" : "false",                     reachable ? "true" : "false", a, b);      }    }  }}static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id,                       GraphCycles *gc) {  int count = 0;  for (const auto &edge : *edges) {    int a = edge.from;    int b = edge.to;    if (!gc->HasEdge(Get(id, a), Get(id, b))) {      PrintEdges(edges);      PrintGCEdges(nodes, id, gc);      ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);    }  }  for (const auto &a : *nodes) {    for (const auto &b : *nodes) {      if (gc->HasEdge(Get(id, a), Get(id, b))) {        count++;      }    }  }  if (count != edges->size()) {    PrintEdges(edges);    PrintGCEdges(nodes, id, gc);    ABSL_RAW_LOG(FATAL, "edges->size() %zu  count %d", edges->size(), count);  }}static void CheckInvariants(const GraphCycles &gc) {  if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))    ABSL_RAW_LOG(FATAL, "CheckInvariants");}// Returns the index of a randomly chosen node in *nodes.// Requires *nodes be non-empty.static int RandomNode(RandomEngine* rng, Nodes *nodes) {  std::uniform_int_distribution<int> uniform(0, nodes->size()-1);  return uniform(*rng);}// Returns the index of a randomly chosen edge in *edges.// Requires *edges be non-empty.static int RandomEdge(RandomEngine* rng, Edges *edges) {  std::uniform_int_distribution<int> uniform(0, edges->size()-1);  return uniform(*rng);}// Returns the index of edge (from, to) in *edges or -1 if it is not in *edges.static int EdgeIndex(Edges *edges, int from, int to) {  int i = 0;  while (i != edges->size() &&         ((*edges)[i].from != from || (*edges)[i].to != to)) {    i++;  }  return i == edges->size()? -1 : i;}TEST(GraphCycles, RandomizedTest) {  int next_node = 0;  Nodes nodes;  Edges edges;   // from, to  IdMap id;  GraphCycles graph_cycles;  static const int kMaxNodes = 7;  // use <= 7 nodes to keep test short  static const int kDataOffset = 17;  // an offset to the node-specific data  int n = 100000;  int op = 0;  RandomEngine rng(testing::UnitTest::GetInstance()->random_seed());  std::uniform_int_distribution<int> uniform(0, 5);  auto ptr = [](intptr_t i) {    return reinterpret_cast<void*>(i + kDataOffset);  };  for (int iter = 0; iter != n; iter++) {    for (const auto &node : nodes) {      ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), ptr(node)) << " node " << node;    }    CheckEdges(&nodes, &edges, id, &graph_cycles);    CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);    op = uniform(rng);    switch (op) {    case 0:     // Add a node      if (nodes.size() < kMaxNodes) {        int new_node = next_node++;        GraphId new_gnode = graph_cycles.GetId(ptr(new_node));        ASSERT_NE(new_gnode, InvalidGraphId());        id[new_node] = new_gnode;        ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));        nodes.push_back(new_node);      }      break;    case 1:    // Remove a node      if (nodes.size() > 0) {        int node_index = RandomNode(&rng, &nodes);        int node = nodes[node_index];        nodes[node_index] = nodes.back();        nodes.pop_back();        graph_cycles.RemoveNode(ptr(node));        ASSERT_EQ(graph_cycles.Ptr(Get(id, node)), nullptr);        id.erase(node);        int i = 0;        while (i != edges.size()) {          if (edges[i].from == node || edges[i].to == node) {            edges[i] = edges.back();            edges.pop_back();          } else {            i++;          }        }      }      break;    case 2:   // Add an edge      if (nodes.size() > 0) {        int from = RandomNode(&rng, &nodes);        int to = RandomNode(&rng, &nodes);        if (EdgeIndex(&edges, nodes[from], nodes[to]) == -1) {          if (graph_cycles.InsertEdge(id[nodes[from]], id[nodes[to]])) {            Edge new_edge;            new_edge.from = nodes[from];            new_edge.to = nodes[to];            edges.push_back(new_edge);          } else {            std::unordered_set<int> seen;            ASSERT_TRUE(IsReachable(&edges, nodes[to], nodes[from], &seen))                << "Edge " << nodes[to] << "->" << nodes[from];          }        }      }      break;    case 3:    // Remove an edge      if (edges.size() > 0) {        int i = RandomEdge(&rng, &edges);        int from = edges[i].from;        int to = edges[i].to;        ASSERT_EQ(i, EdgeIndex(&edges, from, to));        edges[i] = edges.back();        edges.pop_back();        ASSERT_EQ(-1, EdgeIndex(&edges, from, to));        graph_cycles.RemoveEdge(id[from], id[to]);      }      break;    case 4:   // Check a path      if (nodes.size() > 0) {        int from = RandomNode(&rng, &nodes);        int to = RandomNode(&rng, &nodes);        GraphId path[2*kMaxNodes];        int path_len = graph_cycles.FindPath(id[nodes[from]], id[nodes[to]],                                             ABSL_ARRAYSIZE(path), path);        std::unordered_set<int> seen;        bool reachable = IsReachable(&edges, nodes[from], nodes[to], &seen);        bool gc_reachable =            graph_cycles.IsReachable(Get(id, nodes[from]), Get(id, nodes[to]));        ASSERT_EQ(path_len != 0, reachable);        ASSERT_EQ(path_len != 0, gc_reachable);        // In the following line, we add one because a node can appear        // twice, if the path is from that node to itself, perhaps via        // every other node.        ASSERT_LE(path_len, kMaxNodes + 1);        if (path_len != 0) {          ASSERT_EQ(id[nodes[from]], path[0]);          ASSERT_EQ(id[nodes[to]], path[path_len-1]);          for (int i = 1; i < path_len; i++) {            ASSERT_TRUE(graph_cycles.HasEdge(path[i-1], path[i]));          }        }      }      break;    case 5:  // Check invariants      CheckInvariants(graph_cycles);      break;    default:      ABSL_RAW_LOG(FATAL, "op %d", op);    }    // Very rarely, test graph expansion by adding then removing many nodes.    std::bernoulli_distribution one_in_1024(1.0 / 1024);    if (one_in_1024(rng)) {      CheckEdges(&nodes, &edges, id, &graph_cycles);      CheckTransitiveClosure(&nodes, &edges, id, &graph_cycles);      for (int i = 0; i != 256; i++) {        int new_node = next_node++;        GraphId new_gnode = graph_cycles.GetId(ptr(new_node));        ASSERT_NE(InvalidGraphId(), new_gnode);        id[new_node] = new_gnode;        ASSERT_EQ(ptr(new_node), graph_cycles.Ptr(new_gnode));        for (const auto &node : nodes) {          ASSERT_NE(node, new_node);        }        nodes.push_back(new_node);      }      for (int i = 0; i != 256; i++) {        ASSERT_GT(nodes.size(), 0);        int node_index = RandomNode(&rng, &nodes);        int node = nodes[node_index];        nodes[node_index] = nodes.back();        nodes.pop_back();        graph_cycles.RemoveNode(ptr(node));        id.erase(node);        int j = 0;        while (j != edges.size()) {          if (edges[j].from == node || edges[j].to == node) {            edges[j] = edges.back();            edges.pop_back();          } else {            j++;          }        }      }      CheckInvariants(graph_cycles);    }  }}class GraphCyclesTest : public ::testing::Test { public:  IdMap id_;  GraphCycles g_;  static void* Ptr(int i) {    return reinterpret_cast<void*>(static_cast<uintptr_t>(i));  }  static int Num(void* ptr) {    return static_cast<int>(reinterpret_cast<uintptr_t>(ptr));  }  // Test relies on ith NewNode() call returning Node numbered i  GraphCyclesTest() {    for (int i = 0; i < 100; i++) {      id_[i] = g_.GetId(Ptr(i));    }    CheckInvariants(g_);  }  bool AddEdge(int x, int y) {    return g_.InsertEdge(Get(id_, x), Get(id_, y));  }  void AddMultiples() {    // For every node x > 0: add edge to 2*x, 3*x    for (int x = 1; x < 25; x++) {      EXPECT_TRUE(AddEdge(x, 2*x)) << x;      EXPECT_TRUE(AddEdge(x, 3*x)) << x;    }    CheckInvariants(g_);  }  std::string Path(int x, int y) {    GraphId path[5];    int np = g_.FindPath(Get(id_, x), Get(id_, y), ABSL_ARRAYSIZE(path), path);    std::string result;    for (int i = 0; i < np; i++) {      if (i >= ABSL_ARRAYSIZE(path)) {        result += " ...";        break;      }      if (!result.empty()) result.push_back(' ');      char buf[20];      snprintf(buf, sizeof(buf), "%d", Num(g_.Ptr(path[i])));      result += buf;    }    return result;  }};TEST_F(GraphCyclesTest, NoCycle) {  AddMultiples();  CheckInvariants(g_);}TEST_F(GraphCyclesTest, SimpleCycle) {  AddMultiples();  EXPECT_FALSE(AddEdge(8, 4));  EXPECT_EQ("4 8", Path(4, 8));  CheckInvariants(g_);}TEST_F(GraphCyclesTest, IndirectCycle) {  AddMultiples();  EXPECT_TRUE(AddEdge(16, 9));  CheckInvariants(g_);  EXPECT_FALSE(AddEdge(9, 2));  EXPECT_EQ("2 4 8 16 9", Path(2, 9));  CheckInvariants(g_);}TEST_F(GraphCyclesTest, LongPath) {  ASSERT_TRUE(AddEdge(2, 4));  ASSERT_TRUE(AddEdge(4, 6));  ASSERT_TRUE(AddEdge(6, 8));  ASSERT_TRUE(AddEdge(8, 10));  ASSERT_TRUE(AddEdge(10, 12));  ASSERT_FALSE(AddEdge(12, 2));  EXPECT_EQ("2 4 6 8 10 ...", Path(2, 12));  CheckInvariants(g_);}TEST_F(GraphCyclesTest, RemoveNode) {  ASSERT_TRUE(AddEdge(1, 2));  ASSERT_TRUE(AddEdge(2, 3));  ASSERT_TRUE(AddEdge(3, 4));  ASSERT_TRUE(AddEdge(4, 5));  g_.RemoveNode(g_.Ptr(id_[3]));  id_.erase(3);  ASSERT_TRUE(AddEdge(5, 1));}TEST_F(GraphCyclesTest, ManyEdges) {  const int N = 50;  for (int i = 0; i < N; i++) {    for (int j = 1; j < N; j++) {      ASSERT_TRUE(AddEdge(i, i+j));    }  }  CheckInvariants(g_);  ASSERT_TRUE(AddEdge(2*N-1, 0));  CheckInvariants(g_);  ASSERT_FALSE(AddEdge(10, 9));  CheckInvariants(g_);}}  // namespace synchronization_internal}  // namespace absl
 |