Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.341 diff -u -p -r1.341 menu.inc --- includes/menu.inc 24 Aug 2009 01:49:41 -0000 1.341 +++ includes/menu.inc 2 Sep 2009 13:58:36 -0000 @@ -1225,28 +1225,29 @@ function menu_tree_data(array $links, ar * the next menu link. */ function _menu_tree_data(&$links, $parents, $depth) { - $done = FALSE; $tree = array(); - while (!$done && $item = array_pop($links)) { + while ($item = array_pop($links)) { // We need to determine if we're on the path to root so we can later build // the correct active trail and breadcrumb. $item['in_active_trail'] = in_array($item['mlid'], $parents); - // Look ahead to the next link, but leave it on the array so it's available - // to other recursive function calls if we return or build a sub-tree. - $next = end($links); // Add the current link to the tree. $tree[$item['mlid']] = array( 'link' => $item, 'below' => array(), ); + // Look ahead to the next link, but leave it on the array so it's available + // to other recursive function calls if we return or build a sub-tree. + $next = end($links); // Check whether the next link is the first in a new sub-tree. if ($next && $next['depth'] > $depth) { // Recursively call _menu_tree_data to build the sub-tree. $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']); + // Fetch next link after filling the sub-tree. + $next = end($links); } - else { - // Determine if we should exit the loop and return. - $done = (!$next || $next['depth'] < $depth); + // Determine if we should exit the loop and return. + if (!$next || $next['depth'] < $depth) { + break; } } return $tree; Index: modules/simpletest/tests/menu.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v retrieving revision 1.13 diff -u -p -r1.13 menu.test --- modules/simpletest/tests/menu.test 14 Jul 2009 20:53:16 -0000 1.13 +++ modules/simpletest/tests/menu.test 2 Sep 2009 13:58:36 -0000 @@ -164,3 +164,82 @@ class MenuRebuildTestCase extends Drupal } } + +class MenuRecursionTestCase extends DrupalUnitTestCase { + public static function getInfo() { + return array( + 'name' => 'Menu recursion tests', + 'description' => 'Tests recursive functions in menu.inc', + 'group' => 'Menu', + ); + } + + /** + * Test if menu_tree_data() works properly. + */ + function testMenuTreeData() { + // Generate a very large menu tree with random depths. + $text1 = $this->_generate(200); + + $menu = $this->_text_to_menu($text1); + $tree = menu_tree_data($menu); + $text2 = $this->_tree_to_text($tree); + + $this->assertEqual($text1, $text2, t('menu_tree_data() builds a tree with the correct depths.')); + } + + /** + * Prints a menu with random (but valid) depths. + * The format is: + * 0 + * -1 + * --2 + * ---3 + * -4 + * --5 + */ + function _generate($num) { + $menu = ''; + // Start at level 0. + $depth = 0; + for ($i = 0; $i < $num; $i++) { + $menu .= str_pad('', $depth, '-') . $i . "\n"; + // Modify depth (don't increase it by more than one, but make an increase more likely than a decrease). + $depth += min(1, rand(-5, 10)); + // Don't go out of level 0. + $depth = max($depth, 0); + } + return $menu; + } + + /** + * Read the text generated above and turn it into a menu link array. + */ + function _text_to_menu($text) { + $menu = array(); + foreach (explode("\n", $text) as $line) { + if (preg_match('/^(-*)(.+)$/', $line, $match)) { + $menu[] = array( + 'mlid' => $match[2], + // The menu system expects depth to start at 1, not 0. + 'depth' => strlen($match[1]) + 1, + ); + } + } + return $menu; + } + + /** + * Take a recursive menu tree tree and render it into the same format as generate(). + */ + function _tree_to_text($tree, $indent = '') { + $text = ''; + foreach ($tree as $item) { + $text .= $indent . $item['link']['mlid'] . "\n"; + $text .= $this->_tree_to_text($item['below'], $indent . '-'); + } + return $text; + } +} + +