diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index 16afbb38ca..fd37bedb90 100644 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -186,7 +186,7 @@ class ModuleDiscovery $sDir = dirname($sFilePath); $aDirs = [ $sDir => self::$m_sModulePath, - $sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries' + $sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries', ]; foreach ($aDirs as $sRootDir => $sPath) { @@ -221,6 +221,13 @@ class ModuleDiscovery return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad); } + public static function SortModulesByCountOfDepencenciesDescending(array &$aOngoingDependencies) : void + { + uasort($aOngoingDependencies, function (array $aDeps1, array $aDeps2){ + return count($aDeps1) - count($aDeps2); + }); + } + /** * Arrange an list of modules, based on their (inter) dependencies * @param array $aModules The list of modules to process: 'id' => $aModuleInfo @@ -233,28 +240,34 @@ class ModuleDiscovery { // Order the modules to take into account their inter-dependencies $aDependencies = []; + $aOngoingDependencies = []; $aSelectedModules = []; foreach($aModules as $sId => $aModule) { list($sModuleName, ) = self::GetModuleName($sId); if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) { - $aDependencies[$sId] = $aModule['dependencies']; + $aCurrentDependencies = $aModule['dependencies']; + $aDependencies[$sId] = $aCurrentDependencies; + $aOngoingDependencies[$sId] = $aCurrentDependencies; $aSelectedModules[$sModuleName] = true; } } - ksort($aDependencies); + self::SortModulesByCountOfDepencenciesDescending($aOngoingDependencies); $aOrderedModules = []; $iLoopCount = 1; - while(($iLoopCount < count($aModules)) && (count($aDependencies) > 0) ) + $iModulesCount = count($aModules); + while(($iLoopCount < $iModulesCount) && (count($aOngoingDependencies) > 0) ) { - foreach($aDependencies as $sId => $aRemainingDeps) + foreach($aOngoingDependencies as $sId => $aCurrentRemainingDeps) { + $aNextDependencies=[]; $bDependenciesSolved = true; - foreach($aRemainingDeps as $sDepId) + foreach($aCurrentRemainingDeps as $sDepId) { if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) { + $aNextDependencies[]=$sDepId; $bDependenciesSolved = false; } } @@ -262,26 +275,32 @@ class ModuleDiscovery { $aOrderedModules[] = $sId; unset($aDependencies[$sId]); + unset($aOngoingDependencies[$sId]); + continue; } + + $aOngoingDependencies[$sId]=$aNextDependencies; } $iLoopCount++; + self::SortModulesByCountOfDepencenciesDescending($aOngoingDependencies); } - if ($bAbortOnMissingDependency && count($aDependencies) > 0) + if ($bAbortOnMissingDependency && count($aOngoingDependencies) > 0) { $aModulesInfo = []; $aModuleDeps = []; - foreach($aDependencies as $sId => $aDeps) + foreach($aOngoingDependencies as $sId => $aCurrentRemainingDeps) { $aModule = $aModules[$sId]; $aDepsWithIcons = []; + $aDeps=$aDependencies[$sId]; foreach($aDeps as $sIndex => $sDepId) { - if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) - { - $aDepsWithIcons[$sIndex] = '✅ ' . $sDepId; - } else + if (in_array($sDepId, $aCurrentRemainingDeps)) { $aDepsWithIcons[$sIndex] = '❌ ' . $sDepId; + } else + { + $aDepsWithIcons[$sIndex] = '✅ ' . $sDepId; } } $aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons); diff --git a/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php new file mode 100644 index 0000000000..cc42e3c089 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php @@ -0,0 +1,110 @@ +RequireOnceItopFile('setup/modulediscovery.class.inc.php'); + } + + public function testSortModulesByCountOfDepencenciesDescending() + { + $aOngoingDependencies=[]; + $aExpectedKeys=[]; + for($i=5; $i>0; $i--){ + $sKey = "k$i"; + $aExpectedKeys[]=$sKey; + $aDeps=[]; + for ($j=0; $j<$i; $j++){ + $aDeps[]=$j; + } + $aOngoingDependencies[$sKey]=$aDeps; + } + sort($aExpectedKeys); + + \ModuleDiscovery::SortModulesByCountOfDepencenciesDescending($aOngoingDependencies); + + $this->assertEquals($aExpectedKeys, array_keys($aOngoingDependencies)); + } + + public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException() + { + $sExpectedMessage = <<expectExceptionMessage($sExpectedMessage); + + $aModules=[ + "id1/123" => [ + 'dependencies' => [ 'id3/666', 'id4/666'], + 'label' => 'label1', + ], + "id2/456" => [ + 'dependencies' => ['id3/666'], + 'label' => 'label2', + ], + ]; + \ModuleDiscovery::OrderModulesByDependencies($aModules, true); + } + + public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved() + { + $sExpectedMessage = <<expectExceptionMessage($sExpectedMessage); + + $aModules=[ + "id1/123" => [ + 'dependencies' => [ 'id2/456', 'id4/666'], + 'label' => 'label1', + ], + "id2/456" => [ + 'dependencies' => [], + 'label' => 'label2', + ], + ]; + \ModuleDiscovery::OrderModulesByDependencies($aModules, true); + } + + public function testOrderModulesByDependencies_ResolveOk() + { + + $aModules=[ + "id1/1" => [ + 'dependencies' => [ 'id2/2'], + 'label' => 'label1', + ], + "id2/2" => [ + 'dependencies' => ['id3/3'], + 'label' => 'label2', + ], + "id3/3" => [ + 'dependencies' => ['id4/4'], + 'label' => 'label3', + ], + "id4/4" => [ + 'dependencies' => [], + 'label' => 'label4', + ], + ]; + $aResult = \ModuleDiscovery::OrderModulesByDependencies($aModules, true); + + $aExpected = [ + "id4/4", + "id3/3", + "id2/2", + "id1/1", + ]; + $this->assertEquals($aExpected, array_keys($aResult)); + } +} \ No newline at end of file