- Patch #1503184 by aspilicious, Rob Loach, cweagans: convert Graph.inc to PSR-0.
parent
ea26475dac
commit
62a39c9a18
|
@ -1,145 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Directed acyclic graph functions.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Performs a depth-first sort on a directed acyclic graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A three dimensional associated array, with the first keys being the names
|
||||
* of the vertices, these can be strings or numbers. The second key is
|
||||
* 'edges' and the third one are again vertices, each such key representing
|
||||
* an edge. Values of array elements are copied over.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* $graph[1]['edges'][2] = 1;
|
||||
* $graph[2]['edges'][3] = 1;
|
||||
* $graph[2]['edges'][4] = 1;
|
||||
* $graph[3]['edges'][4] = 1;
|
||||
* @endcode
|
||||
*
|
||||
* On return you will also have:
|
||||
* @code
|
||||
* $graph[1]['paths'][2] = 1;
|
||||
* $graph[1]['paths'][3] = 1;
|
||||
* $graph[2]['reverse_paths'][1] = 1;
|
||||
* $graph[3]['reverse_paths'][1] = 1;
|
||||
* @endcode
|
||||
*
|
||||
* @return
|
||||
* The passed-in $graph with more secondary keys filled in:
|
||||
* - 'paths': Contains a list of vertices than can be reached on a path from
|
||||
* this vertex.
|
||||
* - 'reverse_paths': Contains a list of vertices that has a path from them
|
||||
* to this vertex.
|
||||
* - 'weight': If there is a path from a vertex to another then the weight of
|
||||
* the latter is higher.
|
||||
* - 'component': Vertices in the same component have the same component
|
||||
* identifier.
|
||||
*
|
||||
* @see _drupal_depth_first_search()
|
||||
*/
|
||||
function drupal_depth_first_search(&$graph) {
|
||||
$state = array(
|
||||
// The order of last visit of the depth first search. This is the reverse
|
||||
// of the topological order if the graph is acyclic.
|
||||
'last_visit_order' => array(),
|
||||
// The components of the graph.
|
||||
'components' => array(),
|
||||
);
|
||||
// Perform the actual sort.
|
||||
foreach ($graph as $start => $data) {
|
||||
_drupal_depth_first_search($graph, $state, $start);
|
||||
}
|
||||
|
||||
// We do such a numbering that every component starts with 0. This is useful
|
||||
// for module installs as we can install every 0 weighted module in one
|
||||
// request, and then every 1 weighted etc.
|
||||
$component_weights = array();
|
||||
|
||||
foreach ($state['last_visit_order'] as $vertex) {
|
||||
$component = $graph[$vertex]['component'];
|
||||
if (!isset($component_weights[$component])) {
|
||||
$component_weights[$component] = 0;
|
||||
}
|
||||
$graph[$vertex]['weight'] = $component_weights[$component]--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a depth-first sort on a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A three dimensional associated graph array.
|
||||
* @param $state
|
||||
* An associative array. The key 'last_visit_order' stores a list of the
|
||||
* vertices visited. The key components stores list of vertices belonging
|
||||
* to the same the component.
|
||||
* @param $start
|
||||
* An arbitrary vertex where we started traversing the graph.
|
||||
* @param $component
|
||||
* The component of the last vertex.
|
||||
*
|
||||
* @see drupal_depth_first_search()
|
||||
*/
|
||||
function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) {
|
||||
// Assign new component for each new vertex, i.e. when not called recursively.
|
||||
if (!isset($component)) {
|
||||
$component = $start;
|
||||
}
|
||||
// Nothing to do, if we already visited this vertex.
|
||||
if (isset($graph[$start]['paths'])) {
|
||||
return;
|
||||
}
|
||||
// Mark $start as visited.
|
||||
$graph[$start]['paths'] = array();
|
||||
|
||||
// Assign $start to the current component.
|
||||
$graph[$start]['component'] = $component;
|
||||
$state['components'][$component][] = $start;
|
||||
|
||||
// Visit edges of $start.
|
||||
if (isset($graph[$start]['edges'])) {
|
||||
foreach ($graph[$start]['edges'] as $end => $v) {
|
||||
// Mark that $start can reach $end.
|
||||
$graph[$start]['paths'][$end] = $v;
|
||||
|
||||
if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
|
||||
// This vertex already has a component, use that from now on and
|
||||
// reassign all the previously explored vertices.
|
||||
$new_component = $graph[$end]['component'];
|
||||
foreach ($state['components'][$component] as $vertex) {
|
||||
$graph[$vertex]['component'] = $new_component;
|
||||
$state['components'][$new_component][] = $vertex;
|
||||
}
|
||||
unset($state['components'][$component]);
|
||||
$component = $new_component;
|
||||
}
|
||||
// Only visit existing vertices.
|
||||
if (isset($graph[$end])) {
|
||||
// Visit the connected vertex.
|
||||
_drupal_depth_first_search($graph, $state, $end, $component);
|
||||
|
||||
// All vertices reachable by $end are also reachable by $start.
|
||||
$graph[$start]['paths'] += $graph[$end]['paths'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that any other subgraph has been explored, add $start to all reverse
|
||||
// paths.
|
||||
foreach ($graph[$start]['paths'] as $end => $v) {
|
||||
if (isset($graph[$end])) {
|
||||
$graph[$end]['reverse_paths'][$start] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Record the order of the last visit. This is the reverse of the
|
||||
// topological order if the graph is acyclic.
|
||||
$state['last_visit_order'][] = $start;
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
* API for loading and interacting with Drupal modules.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
|
||||
/**
|
||||
* Load all the modules that have been enabled in the system table.
|
||||
*
|
||||
|
@ -240,7 +242,6 @@ function system_list_reset() {
|
|||
* without this module.
|
||||
*/
|
||||
function _module_build_dependencies($files) {
|
||||
require_once DRUPAL_ROOT . '/core/includes/graph.inc';
|
||||
foreach ($files as $filename => $file) {
|
||||
$graph[$file->name]['edges'] = array();
|
||||
if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
|
||||
|
@ -250,7 +251,8 @@ function _module_build_dependencies($files) {
|
|||
}
|
||||
}
|
||||
}
|
||||
drupal_depth_first_search($graph);
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
foreach ($graph as $module => $data) {
|
||||
$files[$module]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array();
|
||||
$files[$module]->requires = isset($data['paths']) ? $data['paths'] : array();
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* installation. It is included and used extensively by update.php.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
|
||||
/**
|
||||
* Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
|
||||
*
|
||||
|
@ -562,7 +564,7 @@ function update_get_update_list() {
|
|||
*
|
||||
* In addition, the returned array also includes detailed information about the
|
||||
* dependency chain for each update, as provided by the depth-first search
|
||||
* algorithm in drupal_depth_first_search().
|
||||
* algorithm in Drupal\Component\Graph\Graph::searchAndSort().
|
||||
*
|
||||
* @param $starting_updates
|
||||
* An array whose keys contain the names of modules with updates to be run
|
||||
|
@ -575,10 +577,11 @@ function update_get_update_list() {
|
|||
* request, arranged in the order in which the update functions should be
|
||||
* run. (This includes the provided starting update for each module and all
|
||||
* subsequent updates that are available.) The values are themselves arrays
|
||||
* containing all the keys provided by the drupal_depth_first_search()
|
||||
* algorithm, which encode detailed information about the dependency chain
|
||||
* for this update function (for example: 'paths', 'reverse_paths', 'weight',
|
||||
* and 'component'), as well as the following additional keys:
|
||||
* containing all the keys provided by the
|
||||
* Drupal\Component\Graph\Graph::searchAndSort() algorithm, which encode
|
||||
* detailed information about the dependency chain for this update function
|
||||
* (for example: 'paths', 'reverse_paths', 'weight', and 'component'), as well
|
||||
* as the following additional keys:
|
||||
* - 'allowed': A boolean which is TRUE when the update function's
|
||||
* dependencies are met, and FALSE otherwise. Calling functions should
|
||||
* inspect this value before running the update.
|
||||
|
@ -588,16 +591,16 @@ function update_get_update_list() {
|
|||
* - 'module': The name of the module that this update function belongs to.
|
||||
* - 'number': The number of this update function within that module.
|
||||
*
|
||||
* @see drupal_depth_first_search()
|
||||
* @see Drupal\Component\Graph\Graph::searchAndSort()
|
||||
*/
|
||||
function update_resolve_dependencies($starting_updates) {
|
||||
// Obtain a dependency graph for the requested update functions.
|
||||
$update_functions = update_get_update_function_list($starting_updates);
|
||||
$graph = update_build_dependency_graph($update_functions);
|
||||
|
||||
// Perform the depth-first search and sort the results.
|
||||
require_once DRUPAL_ROOT . '/core/includes/graph.inc';
|
||||
drupal_depth_first_search($graph);
|
||||
// Perform the depth-first search and sort on the results.
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
uasort($graph, 'drupal_sort_weight');
|
||||
|
||||
foreach ($graph as $function => &$data) {
|
||||
|
@ -692,18 +695,19 @@ function update_get_update_function_list($starting_updates) {
|
|||
*
|
||||
* @return
|
||||
* A multidimensional array representing the dependency graph, suitable for
|
||||
* passing in to drupal_depth_first_search(), but with extra information
|
||||
* about each update function also included. Each array key contains the name
|
||||
* of an update function, including all update functions from the provided
|
||||
* list as well as any outside update functions which they directly depend
|
||||
* on. Each value is an associative array containing the following keys:
|
||||
* passing in to Drupal\Component\Graph\Graph::searchAndSort(), but with extra
|
||||
* information about each update function also included. Each array key
|
||||
* contains the name of an update function, including all update functions
|
||||
* from the provided list as well as any outside update functions which they
|
||||
* directly depend on. Each value is an associative array containing the
|
||||
* following keys:
|
||||
* - 'edges': A representation of any other update functions that immediately
|
||||
* depend on this one. See drupal_depth_first_search() for more details on
|
||||
* the format.
|
||||
* depend on this one. See Drupal\Component\Graph\Graph::searchAndSort() for
|
||||
* more details on the format.
|
||||
* - 'module': The name of the module that this update function belongs to.
|
||||
* - 'number': The number of this update function within that module.
|
||||
*
|
||||
* @see drupal_depth_first_search()
|
||||
* @see Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @see update_resolve_dependencies()
|
||||
*/
|
||||
function update_build_dependency_graph($update_functions) {
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Graph\Graph.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Graph;
|
||||
|
||||
/**
|
||||
* Directed acyclic graph manipulation.
|
||||
*/
|
||||
class Graph {
|
||||
|
||||
/**
|
||||
* Holds the directed acyclic graph.
|
||||
*/
|
||||
protected $graph;
|
||||
|
||||
/**
|
||||
* Instantiates the depth first search object.
|
||||
*
|
||||
* @param $graph
|
||||
* A three dimensional associated array, with the first keys being the names
|
||||
* of the vertices, these can be strings or numbers. The second key is
|
||||
* 'edges' and the third one are again vertices, each such key representing
|
||||
* an edge. Values of array elements are copied over.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* $graph[1]['edges'][2] = 1;
|
||||
* $graph[2]['edges'][3] = 1;
|
||||
* $graph[2]['edges'][4] = 1;
|
||||
* $graph[3]['edges'][4] = 1;
|
||||
* @endcode
|
||||
*
|
||||
* On return you will also have:
|
||||
* @code
|
||||
* $graph[1]['paths'][2] = 1;
|
||||
* $graph[1]['paths'][3] = 1;
|
||||
* $graph[2]['reverse_paths'][1] = 1;
|
||||
* $graph[3]['reverse_paths'][1] = 1;
|
||||
* @endcode
|
||||
*/
|
||||
public function __construct($graph) {
|
||||
$this->graph = $graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a depth-first search and sort on the directed acyclic graph.
|
||||
*
|
||||
* @return
|
||||
* The given $graph with more secondary keys filled in:
|
||||
* - 'paths': Contains a list of vertices than can be reached on a path from
|
||||
* this vertex.
|
||||
* - 'reverse_paths': Contains a list of vertices that has a path from them
|
||||
* to this vertex.
|
||||
* - 'weight': If there is a path from a vertex to another then the weight of
|
||||
* the latter is higher.
|
||||
* - 'component': Vertices in the same component have the same component
|
||||
* identifier.
|
||||
*/
|
||||
public function searchAndSort() {
|
||||
$state = array(
|
||||
// The order of last visit of the depth first search. This is the reverse
|
||||
// of the topological order if the graph is acyclic.
|
||||
'last_visit_order' => array(),
|
||||
// The components of the graph.
|
||||
'components' => array(),
|
||||
);
|
||||
// Perform the actual search.
|
||||
foreach ($this->graph as $start => $data) {
|
||||
$this->depthFirstSearch($state, $start);
|
||||
}
|
||||
|
||||
// We do such a numbering that every component starts with 0. This is useful
|
||||
// for module installs as we can install every 0 weighted module in one
|
||||
// request, and then every 1 weighted etc.
|
||||
$component_weights = array();
|
||||
|
||||
foreach ($state['last_visit_order'] as $vertex) {
|
||||
$component = $this->graph[$vertex]['component'];
|
||||
if (!isset($component_weights[$component])) {
|
||||
$component_weights[$component] = 0;
|
||||
}
|
||||
$this->graph[$vertex]['weight'] = $component_weights[$component]--;
|
||||
}
|
||||
|
||||
return $this->graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a depth-first search on a graph.
|
||||
*
|
||||
* @param $state
|
||||
* An associative array. The key 'last_visit_order' stores a list of the
|
||||
* vertices visited. The key components stores list of vertices belonging
|
||||
* to the same the component.
|
||||
* @param $start
|
||||
* An arbitrary vertex where we started traversing the graph.
|
||||
* @param $component
|
||||
* The component of the last vertex.
|
||||
*
|
||||
* @see Drupal\Component\Graph\Graph::searchAndSort()
|
||||
*/
|
||||
protected function depthFirstSearch(&$state, $start, &$component = NULL) {
|
||||
// Assign new component for each new vertex, i.e. when not called recursively.
|
||||
if (!isset($component)) {
|
||||
$component = $start;
|
||||
}
|
||||
// Nothing to do, if we already visited this vertex.
|
||||
if (isset($this->graph[$start]['paths'])) {
|
||||
return;
|
||||
}
|
||||
// Mark $start as visited.
|
||||
$this->graph[$start]['paths'] = array();
|
||||
|
||||
// Assign $start to the current component.
|
||||
$this->graph[$start]['component'] = $component;
|
||||
$state['components'][$component][] = $start;
|
||||
|
||||
// Visit edges of $start.
|
||||
if (isset($this->graph[$start]['edges'])) {
|
||||
foreach ($this->graph[$start]['edges'] as $end => $v) {
|
||||
// Mark that $start can reach $end.
|
||||
$this->graph[$start]['paths'][$end] = $v;
|
||||
|
||||
if (isset($this->graph[$end]['component']) && $component != $this->graph[$end]['component']) {
|
||||
// This vertex already has a component, use that from now on and
|
||||
// reassign all the previously explored vertices.
|
||||
$new_component = $this->graph[$end]['component'];
|
||||
foreach ($state['components'][$component] as $vertex) {
|
||||
$this->graph[$vertex]['component'] = $new_component;
|
||||
$state['components'][$new_component][] = $vertex;
|
||||
}
|
||||
unset($state['components'][$component]);
|
||||
$component = $new_component;
|
||||
}
|
||||
// Only visit existing vertices.
|
||||
if (isset($this->graph[$end])) {
|
||||
// Visit the connected vertex.
|
||||
$this->depthFirstSearch($state, $end, $component);
|
||||
|
||||
// All vertices reachable by $end are also reachable by $start.
|
||||
$this->graph[$start]['paths'] += $this->graph[$end]['paths'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that any other subgraph has been explored, add $start to all reverse
|
||||
// paths.
|
||||
foreach ($this->graph[$start]['paths'] as $end => $v) {
|
||||
if (isset($this->graph[$end])) {
|
||||
$this->graph[$end]['reverse_paths'][$start] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Record the order of the last visit. This is the reverse of the
|
||||
// topological order if the graph is acyclic.
|
||||
$state['last_visit_order'][] = $start;
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides unit tests for graph.inc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unit tests for the graph handling features.
|
||||
*/
|
||||
class GraphUnitTest extends DrupalUnitTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Graph',
|
||||
'description' => 'Graph handling unit tests.',
|
||||
'group' => 'System',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
require_once DRUPAL_ROOT . '/core/includes/graph.inc';
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test depth-first-search features.
|
||||
*/
|
||||
function testDepthFirstSearch() {
|
||||
// The sample graph used is:
|
||||
// 1 --> 2 --> 3 5 ---> 6
|
||||
// | ^ ^
|
||||
// | | |
|
||||
// | | |
|
||||
// +---> 4 <-- 7 8 ---> 9
|
||||
$graph = $this->normalizeGraph(array(
|
||||
1 => array(2),
|
||||
2 => array(3, 4),
|
||||
3 => array(),
|
||||
4 => array(3),
|
||||
5 => array(6),
|
||||
7 => array(4, 5),
|
||||
8 => array(9),
|
||||
9 => array(),
|
||||
));
|
||||
drupal_depth_first_search($graph);
|
||||
|
||||
$expected_paths = array(
|
||||
1 => array(2, 3, 4),
|
||||
2 => array(3, 4),
|
||||
3 => array(),
|
||||
4 => array(3),
|
||||
5 => array(6),
|
||||
7 => array(4, 3, 5, 6),
|
||||
8 => array(9),
|
||||
9 => array(),
|
||||
);
|
||||
$this->assertPaths($graph, $expected_paths);
|
||||
|
||||
$expected_reverse_paths = array(
|
||||
1 => array(),
|
||||
2 => array(1),
|
||||
3 => array(2, 1, 4, 7),
|
||||
4 => array(2, 1, 7),
|
||||
5 => array(7),
|
||||
7 => array(),
|
||||
8 => array(),
|
||||
9 => array(8),
|
||||
);
|
||||
$this->assertReversePaths($graph, $expected_reverse_paths);
|
||||
|
||||
// Assert that DFS didn't created "missing" vertexes automatically.
|
||||
$this->assertFALSE(isset($graph[6]), t('Vertex 6 has not been created'));
|
||||
|
||||
$expected_components = array(
|
||||
array(1, 2, 3, 4, 5, 7),
|
||||
array(8, 9),
|
||||
);
|
||||
$this->assertComponents($graph, $expected_components);
|
||||
|
||||
$expected_weights = array(
|
||||
array(1, 2, 3),
|
||||
array(2, 4, 3),
|
||||
array(7, 4, 3),
|
||||
array(7, 5),
|
||||
array(8, 9),
|
||||
);
|
||||
$this->assertWeights($graph, $expected_weights);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a normalized version of a graph.
|
||||
*/
|
||||
function normalizeGraph($graph) {
|
||||
$normalized_graph = array();
|
||||
foreach ($graph as $vertex => $edges) {
|
||||
// Create vertex even if it hasn't any edges.
|
||||
$normalized_graph[$vertex] = array();
|
||||
foreach ($edges as $edge) {
|
||||
$normalized_graph[$vertex]['edges'][$edge] = TRUE;
|
||||
}
|
||||
}
|
||||
return $normalized_graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by drupal_depth_first_search().
|
||||
* @param $expected_paths
|
||||
* An associative array containing vertices with their expected paths.
|
||||
*/
|
||||
function assertPaths($graph, $expected_paths) {
|
||||
foreach ($expected_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : array();
|
||||
$this->assertEqual($expected, $result, t('Expected paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected reverse paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by drupal_depth_first_search().
|
||||
* @param $expected_reverse_paths
|
||||
* An associative array containing vertices with their expected reverse
|
||||
* paths.
|
||||
*/
|
||||
function assertReversePaths($graph, $expected_reverse_paths) {
|
||||
foreach ($expected_reverse_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : array();
|
||||
$this->assertEqual($expected, $result, t('Expected reverse paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected components in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by drupal_depth_first_search().
|
||||
* @param $expected_components
|
||||
* An array containing of components defined as a list of their vertices.
|
||||
*/
|
||||
function assertComponents($graph, $expected_components) {
|
||||
$unassigned_vertices = array_fill_keys(array_keys($graph), TRUE);
|
||||
foreach ($expected_components as $component) {
|
||||
$result_components = array();
|
||||
foreach ($component as $vertex) {
|
||||
$result_components[] = $graph[$vertex]['component'];
|
||||
unset($unassigned_vertices[$vertex]);
|
||||
}
|
||||
$this->assertEqual(1, count(array_unique($result_components)), t('Expected one unique component for vertices @vertices, got @components', array('@vertices' => $this->displayArray($component), '@components' => $this->displayArray($result_components))));
|
||||
}
|
||||
$this->assertEqual(array(), $unassigned_vertices, t('Vertices not assigned to a component: @vertices', array('@vertices' => $this->displayArray($unassigned_vertices, TRUE))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected order in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by drupal_depth_first_search().
|
||||
* @param $expected_orders
|
||||
* An array containing lists of vertices in their expected order.
|
||||
*/
|
||||
function assertWeights($graph, $expected_orders) {
|
||||
foreach ($expected_orders as $order) {
|
||||
$previous_vertex = array_shift($order);
|
||||
foreach ($order as $vertex) {
|
||||
$this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], t('Weights of @previous-vertex and @vertex are correct relative to each other', array('@previous-vertex' => $previous_vertex, '@vertex' => $vertex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to output vertices as comma-separated list.
|
||||
*
|
||||
* @param $paths
|
||||
* An array containing a list of vertices.
|
||||
* @param $keys
|
||||
* (optional) Whether to output the keys of $paths instead of the values.
|
||||
*/
|
||||
function displayArray($paths, $keys = FALSE) {
|
||||
if (!empty($paths)) {
|
||||
return implode(', ', $keys ? array_keys($paths) : $paths);
|
||||
}
|
||||
else {
|
||||
return '(empty)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Component\Graph\Graph;
|
||||
|
||||
/**
|
||||
* @file
|
||||
|
@ -2589,6 +2590,193 @@ class SystemAdminTestCase extends DrupalWebTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests for the graph handling features.
|
||||
*
|
||||
* @see Drupal\Component\Graph\Graph
|
||||
*/
|
||||
class GraphUnitTest extends DrupalUnitTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Directed acyclic graph manipulation',
|
||||
'description' => 'Depth first search and sort unit tests.',
|
||||
'group' => 'System',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test depth-first-search features.
|
||||
*/
|
||||
function testDepthFirstSearch() {
|
||||
// The sample graph used is:
|
||||
// 1 --> 2 --> 3 5 ---> 6
|
||||
// | ^ ^
|
||||
// | | |
|
||||
// | | |
|
||||
// +---> 4 <-- 7 8 ---> 9
|
||||
$graph = $this->normalizeGraph(array(
|
||||
1 => array(2),
|
||||
2 => array(3, 4),
|
||||
3 => array(),
|
||||
4 => array(3),
|
||||
5 => array(6),
|
||||
7 => array(4, 5),
|
||||
8 => array(9),
|
||||
9 => array(),
|
||||
));
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
|
||||
$expected_paths = array(
|
||||
1 => array(2, 3, 4),
|
||||
2 => array(3, 4),
|
||||
3 => array(),
|
||||
4 => array(3),
|
||||
5 => array(6),
|
||||
7 => array(4, 3, 5, 6),
|
||||
8 => array(9),
|
||||
9 => array(),
|
||||
);
|
||||
$this->assertPaths($graph, $expected_paths);
|
||||
|
||||
$expected_reverse_paths = array(
|
||||
1 => array(),
|
||||
2 => array(1),
|
||||
3 => array(2, 1, 4, 7),
|
||||
4 => array(2, 1, 7),
|
||||
5 => array(7),
|
||||
7 => array(),
|
||||
8 => array(),
|
||||
9 => array(8),
|
||||
);
|
||||
$this->assertReversePaths($graph, $expected_reverse_paths);
|
||||
|
||||
// Assert that DFS didn't created "missing" vertexes automatically.
|
||||
$this->assertFALSE(isset($graph[6]), t('Vertex 6 has not been created'));
|
||||
|
||||
$expected_components = array(
|
||||
array(1, 2, 3, 4, 5, 7),
|
||||
array(8, 9),
|
||||
);
|
||||
$this->assertComponents($graph, $expected_components);
|
||||
|
||||
$expected_weights = array(
|
||||
array(1, 2, 3),
|
||||
array(2, 4, 3),
|
||||
array(7, 4, 3),
|
||||
array(7, 5),
|
||||
array(8, 9),
|
||||
);
|
||||
$this->assertWeights($graph, $expected_weights);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a normalized version of a graph.
|
||||
*/
|
||||
function normalizeGraph($graph) {
|
||||
$normalized_graph = array();
|
||||
foreach ($graph as $vertex => $edges) {
|
||||
// Create vertex even if it hasn't any edges.
|
||||
$normalized_graph[$vertex] = array();
|
||||
foreach ($edges as $edge) {
|
||||
$normalized_graph[$vertex]['edges'][$edge] = TRUE;
|
||||
}
|
||||
}
|
||||
return $normalized_graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by
|
||||
* Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param $expected_paths
|
||||
* An associative array containing vertices with their expected paths.
|
||||
*/
|
||||
function assertPaths($graph, $expected_paths) {
|
||||
foreach ($expected_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : array();
|
||||
$this->assertEqual($expected, $result, t('Expected paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected reverse paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param $expected_reverse_paths
|
||||
* An associative array containing vertices with their expected reverse
|
||||
* paths.
|
||||
*/
|
||||
function assertReversePaths($graph, $expected_reverse_paths) {
|
||||
foreach ($expected_reverse_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : array();
|
||||
$this->assertEqual($expected, $result, t('Expected reverse paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected components in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param $expected_components
|
||||
* An array containing of components defined as a list of their vertices.
|
||||
*/
|
||||
function assertComponents($graph, $expected_components) {
|
||||
$unassigned_vertices = array_fill_keys(array_keys($graph), TRUE);
|
||||
foreach ($expected_components as $component) {
|
||||
$result_components = array();
|
||||
foreach ($component as $vertex) {
|
||||
$result_components[] = $graph[$vertex]['component'];
|
||||
unset($unassigned_vertices[$vertex]);
|
||||
}
|
||||
$this->assertEqual(1, count(array_unique($result_components)), t('Expected one unique component for vertices @vertices, got @components', array('@vertices' => $this->displayArray($component), '@components' => $this->displayArray($result_components))));
|
||||
}
|
||||
$this->assertEqual(array(), $unassigned_vertices, t('Vertices not assigned to a component: @vertices', array('@vertices' => $this->displayArray($unassigned_vertices, TRUE))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected order in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param $expected_orders
|
||||
* An array containing lists of vertices in their expected order.
|
||||
*/
|
||||
function assertWeights($graph, $expected_orders) {
|
||||
foreach ($expected_orders as $order) {
|
||||
$previous_vertex = array_shift($order);
|
||||
foreach ($order as $vertex) {
|
||||
$this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], t('Weights of @previous-vertex and @vertex are correct relative to each other', array('@previous-vertex' => $previous_vertex, '@vertex' => $vertex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to output vertices as comma-separated list.
|
||||
*
|
||||
* @param $paths
|
||||
* An array containing a list of vertices.
|
||||
* @param $keys
|
||||
* (optional) Whether to output the keys of $paths instead of the values.
|
||||
*/
|
||||
function displayArray($paths, $keys = FALSE) {
|
||||
if (!empty($paths)) {
|
||||
return implode(', ', $keys ? array_keys($paths) : $paths);
|
||||
}
|
||||
else {
|
||||
return '(empty)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests authorize.php and related hooks.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue