tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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