GraphTest.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. namespace Drupal\Tests\Component\Graph;
  3. use Drupal\Component\Graph\Graph;
  4. use PHPUnit\Framework\TestCase;
  5. /**
  6. * @coversDefaultClass \Drupal\Component\Graph\Graph
  7. * @group Graph
  8. */
  9. class GraphTest extends TestCase {
  10. /**
  11. * Test depth-first-search features.
  12. */
  13. public function testDepthFirstSearch() {
  14. // The sample graph used is:
  15. // 1 --> 2 --> 3 5 ---> 6
  16. // | ^ ^
  17. // | | |
  18. // | | |
  19. // +---> 4 <-- 7 8 ---> 9
  20. $graph = $this->normalizeGraph([
  21. 1 => [2],
  22. 2 => [3, 4],
  23. 3 => [],
  24. 4 => [3],
  25. 5 => [6],
  26. 7 => [4, 5],
  27. 8 => [9],
  28. 9 => [],
  29. ]);
  30. $graph_object = new Graph($graph);
  31. $graph = $graph_object->searchAndSort();
  32. $expected_paths = [
  33. 1 => [2, 3, 4],
  34. 2 => [3, 4],
  35. 3 => [],
  36. 4 => [3],
  37. 5 => [6],
  38. 7 => [4, 3, 5, 6],
  39. 8 => [9],
  40. 9 => [],
  41. ];
  42. $this->assertPaths($graph, $expected_paths);
  43. $expected_reverse_paths = [
  44. 1 => [],
  45. 2 => [1],
  46. 3 => [2, 1, 4, 7],
  47. 4 => [2, 1, 7],
  48. 5 => [7],
  49. 7 => [],
  50. 8 => [],
  51. 9 => [8],
  52. ];
  53. $this->assertReversePaths($graph, $expected_reverse_paths);
  54. // Assert that DFS didn't created "missing" vertexes automatically.
  55. $this->assertFalse(isset($graph[6]), 'Vertex 6 has not been created');
  56. $expected_components = [
  57. [1, 2, 3, 4, 5, 7],
  58. [8, 9],
  59. ];
  60. $this->assertComponents($graph, $expected_components);
  61. $expected_weights = [
  62. [1, 2, 3],
  63. [2, 4, 3],
  64. [7, 4, 3],
  65. [7, 5],
  66. [8, 9],
  67. ];
  68. $this->assertWeights($graph, $expected_weights);
  69. }
  70. /**
  71. * Normalizes a graph.
  72. *
  73. * @param $graph
  74. * A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
  75. *
  76. * @return array
  77. * The normalized version of a graph.
  78. */
  79. protected function normalizeGraph($graph) {
  80. $normalized_graph = [];
  81. foreach ($graph as $vertex => $edges) {
  82. // Create vertex even if it hasn't any edges.
  83. $normalized_graph[$vertex] = [];
  84. foreach ($edges as $edge) {
  85. $normalized_graph[$vertex]['edges'][$edge] = TRUE;
  86. }
  87. }
  88. return $normalized_graph;
  89. }
  90. /**
  91. * Verify expected paths in a graph.
  92. *
  93. * @param $graph
  94. * A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
  95. * @param $expected_paths
  96. * An associative array containing vertices with their expected paths.
  97. */
  98. protected function assertPaths($graph, $expected_paths) {
  99. foreach ($expected_paths as $vertex => $paths) {
  100. // Build an array with keys = $paths and values = TRUE.
  101. $expected = array_fill_keys($paths, TRUE);
  102. $result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : [];
  103. $this->assertEquals($expected, $result, sprintf('Expected paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
  104. }
  105. }
  106. /**
  107. * Verify expected reverse paths in a graph.
  108. *
  109. * @param $graph
  110. * A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
  111. * @param $expected_reverse_paths
  112. * An associative array containing vertices with their expected reverse
  113. * paths.
  114. */
  115. protected function assertReversePaths($graph, $expected_reverse_paths) {
  116. foreach ($expected_reverse_paths as $vertex => $paths) {
  117. // Build an array with keys = $paths and values = TRUE.
  118. $expected = array_fill_keys($paths, TRUE);
  119. $result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : [];
  120. $this->assertEquals($expected, $result, sprintf('Expected reverse paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
  121. }
  122. }
  123. /**
  124. * Verify expected components in a graph.
  125. *
  126. * @param $graph
  127. * A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort().
  128. * @param $expected_components
  129. * An array containing of components defined as a list of their vertices.
  130. */
  131. protected function assertComponents($graph, $expected_components) {
  132. $unassigned_vertices = array_fill_keys(array_keys($graph), TRUE);
  133. foreach ($expected_components as $component) {
  134. $result_components = [];
  135. foreach ($component as $vertex) {
  136. $result_components[] = $graph[$vertex]['component'];
  137. unset($unassigned_vertices[$vertex]);
  138. }
  139. $this->assertCount(1, array_unique($result_components), sprintf('Expected one unique component for vertices %s, got %s', $this->displayArray($component), $this->displayArray($result_components)));
  140. }
  141. $this->assertEquals([], $unassigned_vertices, sprintf('Vertices not assigned to a component: %s', $this->displayArray($unassigned_vertices, TRUE)));
  142. }
  143. /**
  144. * Verify expected order in a graph.
  145. *
  146. * @param $graph
  147. * A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
  148. * @param $expected_orders
  149. * An array containing lists of vertices in their expected order.
  150. */
  151. protected function assertWeights($graph, $expected_orders) {
  152. foreach ($expected_orders as $order) {
  153. $previous_vertex = array_shift($order);
  154. foreach ($order as $vertex) {
  155. $this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], sprintf('Weights of %s and %s are correct relative to each other', $previous_vertex, $vertex));
  156. }
  157. }
  158. }
  159. /**
  160. * Helper function to output vertices as comma-separated list.
  161. *
  162. * @param $paths
  163. * An array containing a list of vertices.
  164. * @param $keys
  165. * (optional) Whether to output the keys of $paths instead of the values.
  166. */
  167. protected function displayArray($paths, $keys = FALSE) {
  168. if (!empty($paths)) {
  169. return implode(', ', $keys ? array_keys($paths) : $paths);
  170. }
  171. else {
  172. return '(empty)';
  173. }
  174. }
  175. }