graphcycles_test.cc 13 KB

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