diff --git a/setup/module/XmlModule.php b/setup/module/XmlModule.php deleted file mode 100644 index 34594e6a8..000000000 --- a/setup/module/XmlModule.php +++ /dev/null @@ -1,84 +0,0 @@ -sModuleName = $sModuleName; - } - - public function AddDependency(string $sXmlMetaInfoUID, array $aDefiningModuleNames, array $aModules) - { - $aRemainingModules=[]; - foreach ($aDefiningModuleNames as $sDefiningModuleName) { - if ($sDefiningModuleName === $this->sModuleName) { - continue; - } - - if ($sDefiningModuleName === "core" || $sDefiningModuleName === "application") { - continue; - } - - $aRemainingModules[]=$sDefiningModuleName; - } - - if (count($aRemainingModules)==0){ - return; - } - - /*$aLog = ['itop-bridge-datacenter-mgmt-services', 'itop-datacenter-mgmt']; - if (in_array($sDefiningModuleName, $aLog) && in_array($this->sModuleName, $aLog)){ - echo $this->sModuleName . " => $sDefiningModuleName === " . $sXmlMetaInfoUID . "\n"; - }*/ - - $sKey=implode(' || ', $aRemainingModules); - if (! array_key_exists($sKey, $this->aXMlMetaInfosByModuleNames)){ - $this->aXMlMetaInfosByModuleNames[$sKey]=[$sXmlMetaInfoUID]; - } else { - if (! in_array($sXmlMetaInfoUID, $this->aXMlMetaInfosByModuleNames[$sKey])){ - $this->aXMlMetaInfosByModuleNames[$sKey][]=$sXmlMetaInfoUID; - } - } - - if (! array_key_exists($sKey, $this->aDependencyModulesNames)){ - $aCurrentModules=[]; - foreach ($aRemainingModules as $sDefiningModuleName) { - /** @var XmlModule $oXmlModule */ - $oXmlModule = $aModules[$sDefiningModuleName]; - $aCurrentModules[]=$oXmlModule; - } - $this->aDependencyModulesNames[$sKey]=$aCurrentModules; - } - } - - public function Depends(string $sModuleName) : bool - { - return array_key_exists($sModuleName, $this->aDependencyModulesNames) || array_key_exists($sModuleName, $this->aAllDependencyModulesNames); - } - - public function __toString(): string - { - return sprintf("%s (%s)", $this->sModuleName, implode(' & ', array_keys($this->aDependencyModulesNames))); - } - - public function CompleteModuleDependencies(array $aAllModules) : void - { - foreach ($this->aDependencyModulesNames as $sDirectDependency => $oXmlModules){ - /** @var \Combodo\iTop\Test\UnitTest\XmlModule $oDirectDepXmlModule */ - $oDirectDepXmlModule = $aAllModules[$sDirectDependency] ?? null; - if (! is_null($oDirectDepXmlModule)) { - foreach ($oDirectDepXmlModule->aDependencyModulesNames as $sDirectDependency2 => $oXmlModules2) { - if (!array_key_exists($sDirectDependency2, $this->aDependencyModulesNames) && !in_array($sDirectDependency2, $this->aAllDependencyModulesNames)) { - $this->aAllDependencyModulesNames[] = $sDirectDependency2; - } - } - } - } - } - - -} \ No newline at end of file diff --git a/setup/module/XmlModuleMetaInfo.php b/setup/module/XmlModuleMetaInfo.php deleted file mode 100644 index 86c9e775e..000000000 --- a/setup/module/XmlModuleMetaInfo.php +++ /dev/null @@ -1,27 +0,0 @@ -sLastNodeId = $sLastNodeId; - $this->sNodeName = $sNodeName; - $this->sPath = $sPath; - $this->sDelta = $sDelta; - } - - public function IsDefine() : bool { - return - $this->sDelta === 'define_if_not_exists' - || $this->sDelta === 'define'; - } - - public function GetUID() : string - { - return $this->sNodeName . '_' . $this->sPath; - } -} \ No newline at end of file diff --git a/setup/module/iTopModulesDependencyValidationService.php b/setup/module/iTopModulesDependencyValidationService.php deleted file mode 100644 index e93d80d82..000000000 --- a/setup/module/iTopModulesDependencyValidationService.php +++ /dev/null @@ -1,465 +0,0 @@ - $aModuleData) { - list($sModuleName,) = \ModuleDiscovery::GetModuleName($sModuleId); - self::$aModulesDataByModuleName[$sModuleName] = $aModuleData; - } - } - - return self::$aModulesDataByModuleName; - } - - public function ListDatamodelFiles() : array - { - if (count($this->aAllDmFiles)==0){ - $aGlobPAtterns = [ - APPROOT.'datamodels/2.x/*', - APPROOT.'application', - APPROOT.'core', - APPROOT.'extensions', - APPROOT.'extensions/*', - APPROOT.'data/production-modules', - APPROOT.'data/production-modules/*', - ]; - - foreach ($aGlobPAtterns as $sPattern) { - $this->aAllDmFiles = array_merge($this->aAllDmFiles, glob("$sPattern/datamodel.*.xml")); - } - } - return $this->aAllDmFiles; - } - - private function CreateIfNeededAndGetXmlModule(string $sModuleName) : XmlModule { - /** @var XmlModule $oCurrentXmlModule */ - $oCurrentXmlModule = $this->aModules[$sModuleName] ?? null; - if (is_null($oCurrentXmlModule)){ - $oCurrentXmlModule = new XmlModule($sModuleName); - $this->aModules[$sModuleName] = $oCurrentXmlModule; - } - - return $oCurrentXmlModule; - } - - public function FetchAllDependenciesViaDM() - { - foreach ($this->ListDatamodelFiles() as $sFile) { - $this->FetchXmlMetaInfo($sFile); - } - - foreach ($this->aDefineNodes as $sKey => $aModules){ - foreach ($aModules as $sModuleName){ - $this->CreateIfNeededAndGetXmlModule($sModuleName); - } - - $aSoftlyDependentModules = $this->aSoftDependencyNodes[$sKey] ?? null; - if (is_null($aSoftlyDependentModules)){ - continue; - } - - foreach ($aSoftlyDependentModules as $sModuleName){ - $oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName); - $oCurrentXmlModule->AddDependency($sKey, $aModules, $this->aModules); - } - } - - foreach ($this->aDependencyNodes as $sKey => $aModules){ - foreach ($aModules as $sModuleName){ - $aDefiningModules = $this->aDefineNodes[$sKey] ?? null; - if (is_null($aDefiningModules)){ - continue; - } - - $oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName); - $oCurrentXmlModule->AddDependency($sKey, $aDefiningModules, $this->aModules); - } - } - - $this->OrderModules(); - $this->CompleteModuleDependencies(); - } - - public function FetchAllDependenciesViaModulesFiles() - { - $aFullnameClassesByModuleName=[]; - foreach (self::GetModulesDataByModuleName() as $sModuleName => $aModuleData){ - //echo "$sModuleName\n"; - $aFiles = $aModuleData['datamodel'] ?? []; - $sDir = dirname($aModuleData['module_file_path']); - - $aDeps=[]; - foreach ($aFiles as $sFile){ - if (preg_match("|.*model\.$sModuleName\.php|", $sFile)){ - continue; - } - //echo "$sDir/$sFile\n"; - $aDeps=array_merge($aDeps, $this->ListDeclaredFullnameClassesFromPhpFile("$sDir/$sFile")); - } - - $aFullnameClassesByModuleName[$sModuleName]=$aDeps; - } - - foreach ($aFullnameClassesByModuleName as $sModuleName => $aFullnameClasses){ - foreach (self::GetModulesDataByModuleName() as $sModuleName2 => $aModuleData){ - if ($sModuleName2 === $sModuleName){ - continue; - } - - $sDir = dirname($aModuleData['module_file_path']); - - if (count($aFullnameClassesByModuleName)==0){ - continue; - } - - $sStr = ""; - foreach ($aFullnameClasses as $sClass){ - $sStr .= <<GetFolders($sModuleName2, $sDir) as $sFolderDir => $sGrepRecursiveCliOption) { - $sCliCmd = str_replace('\\', '\\\\\\\\', sprintf("grep -l%s %s $sFolderDir", $sGrepRecursiveCliOption, $sStr)); - $sOutput = exec($sCliCmd); - - if (strlen($sOutput) != 0) { - $bFound=true; - $sCliCmd = str_replace('\\', '\\\\\\\\', sprintf("grep -o%s %s $sFolderDir", $sGrepRecursiveCliOption, $sStr)); - $sOutput = exec($sCliCmd); - //echo "|$sOutput|\n"; - break; - } - } - - if ($bFound){ - $oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName); - $oCurrentXmlModule2 = $this->CreateIfNeededAndGetXmlModule($sModuleName2); - try{ - $sKey = $this->GetFirstFoundDepsUID($sOutput); - } catch(\Exception $e){ - var_dump($aFullnameClassesByModuleName); - var_dump( - [ $sStr, $sCliCmd, $sModuleName, $sModuleName2, $sOutput] - ); - throw $e; - } - $oCurrentXmlModule2->AddDependency($sKey, [$sModuleName], $this->aModules); - //echo "$sModuleName2 => $sModuleName\n"; - } - } - } - } - - public function GetFirstFoundDepsUID(string $sOutput) : string { - if (preg_match_all('|.*:(.*)|m', $sOutput, $aMatches)){ - //var_dump($aMatches); - return $aMatches[1][0]; - } - - throw new \Exception('no match: ' . $sOutput); - } - - private function GetFolders($sModuleName2, $sDir) : array - { - if (array_key_exists($sModuleName2, $this->aSubfoldersByModulename)){ - return $this->aSubfoldersByModulename[$sModuleName2]; - } - - $aRes=[]; - $aFiles=[]; - foreach (glob("$sDir/*") as $sPath){ - if (! is_dir($sPath)){ - $aFiles[]=$sPath; - continue; - } - - if (strpos($sPath, '\.git') !== false){ - continue; - } - - if (strpos($sPath, 'vendor') !== false){ - continue; - } - - if (strpos($sPath, 'test') !== false){ - continue; - } - - $aRes[$sPath]="r"; - } - - $aRes=[ implode(' ', $aFiles) => "" ]; - $this->aSubfoldersByModulename[$sModuleName2]=$aRes; - return $aRes; - } - - private function GetModuleSuffix($sFile) : string - { - if (! preg_match('|.*datamodel\.([^\.]+)\.xml|', $sFile, $aMatches)){ - throw new \Exception("Regexp issue: $sFile"); - } - return $aMatches[1]; - } - - public function FetchXmlMetaInfo($sFile) : void { - $oDomDoc = new \DOMDocument('1.0', 'UTF-8'); - libxml_clear_errors(); - $oDomDoc->loadXml(file_get_contents($sFile)); - $aErrors = libxml_get_errors(); - if (count($aErrors) > 0) - { - throw new \Exception("Malformed XML"); - } - - $this->sCurrentModule = $this->GetModuleSuffix($sFile); - - if (! isset($this->aDefineNodes)){ - $this->aDefineNodes=[]; - } - - if (! isset($this->aDependencyNodes)){ - $this->aDependencyNodes=[]; - } - - foreach ($oDomDoc->childNodes as $oDomNode){ - $this->FetchMetaInfo($oDomDoc->childNodes); - } - } - - private function FetchMetaInfo(\DOMNodeList $oDomNodeList, ?string $sPath=null) - { - /** @var \DOMNode $oDomNode */ - foreach ($oDomNodeList as $oDomNode) { - /** @var \DOMAttr $oDelta */ - $oDelta = $oDomNode->attributes['_delta'] ?? null; - /** @var \DOMAttr $oId */ - $oId = $oDomNode->attributes['id'] ?? null; - - if (! is_null($oId)) { - $sId = $oId->nodeValue; - $sCurrentPath = $sPath ? $sPath."->".$sId : $sId; - - if (!is_null($oDelta)) { - $oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, $oDelta->nodeValue); - $sKey = $oXmlModuleMetaInfo->GetUID(); - if ($oXmlModuleMetaInfo->IsDefine()) { - if (array_key_exists($sKey, $this->aDefineNodes)) { - $this->aDefineNodes[$sKey][] = $this->sCurrentModule; - } else { - $this->aDefineNodes[$sKey] = [$this->sCurrentModule]; - } - } else { - if (array_key_exists($sKey, $this->aDependencyNodes)) { - $this->aDependencyNodes[$sKey][] = $this->sCurrentModule; - } else { - $this->aDependencyNodes[$sKey] = [$this->sCurrentModule]; - } - } - } else { - $oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, "nodelta"); - $sKey = $oXmlModuleMetaInfo->GetUID(); - if (array_key_exists($sKey, $this->aSoftDependencyNodes)) { - $this->aSoftDependencyNodes[$sKey][] = $this->sCurrentModule; - } else { - $this->aSoftDependencyNodes[$sKey] = [$this->sCurrentModule]; - } - } - } else if ($oDomNode instanceof \DOMElement){ - $sCurrentPath = $sPath ? $sPath . '->' . $oDomNode->nodeName : $oDomNode->nodeName; - } else{ - $sCurrentPath = $sPath; - } - - $this->FetchMetaInfo($oDomNode->childNodes, $sCurrentPath); - } - } - - private function OrderModules() - { - $aModuleDepsCount = []; - /** @var XmlModule $oXmlModule */ - foreach ($this->aModules as $oXmlModule) { - $aModuleDepsCount[$oXmlModule->sModuleName] = count($oXmlModule->aDependencyModulesNames); - } - - $aOrderModules=[]; - while (count($aModuleDepsCount)>0) { - asort($aModuleDepsCount); - - foreach ($aModuleDepsCount as $sModuleName => $iCount){ - if ($iCount>0){ - throw new \Exception("still deps with $sModuleName"); - } - - unset($aModuleDepsCount[$sModuleName]); - $aOrderModules[$sModuleName] = $this->aModules[$sModuleName]; - break; - } - - //echo "$sModuleName\n"; - foreach ($aModuleDepsCount as $sStillToProcessModuleName => $c){ - /** @var XmlModule $oXmlStillToProcessModule */ - $oXmlStillToProcessModule = $this->aModules[$sStillToProcessModuleName]; - if ($oXmlStillToProcessModule->Depends($sModuleName)){ - $aModuleDepsCount[$sStillToProcessModuleName] = $c - 1 ; - } - } - } - - $this->aModules = $aOrderModules; - } - - private function CompleteModuleDependencies() - { - /** @var XmlModule $oXmlModule */ - foreach ($this->aModules as $oXmlModule) { - $oXmlModule->CompleteModuleDependencies($this->aModules); - } - } - - /** - * Read declared classes/interfaces in modules.php file (either directly listed files or inside autoload) - * @param string : module file path - * - * @return array: list of fullname classes - */ - public function ListDeclaredFullnameClassesFromPhpFile(string $sPath) : array - { - if (false !== strpos($sPath, 'autoload.php')){ - return $this->ListDeclaredFullnameClassesFromAutoloadFile($sPath); - } - - $aRes=[]; - - $sContent = file_get_contents($sPath); - - $sNamespace=''; - if (preg_match('|namespace (.*)[ ]*;|', $sContent, $aMatches)){ - $sNamespace=trim($aMatches[1]) . '\\'; - } - - - if (preg_match_all('|^class ([a-zA-Z]*) |m', $sContent, $aMatches)){ - foreach($aMatches[1] as $sClass){ - $aRes[]=$sNamespace.$sClass; - } - } - - if (preg_match_all('|^interface ([a-zA-Z]*)|m', $sContent, $aMatches)){ - foreach($aMatches[1] as $sInterface){ - $aRes[]=$sNamespace.$sInterface; - } - } - - return $aRes; - } - - /** - * Read declared classes/interfaces autoload file - * - * @param string : module file path - * - * @return array: list of fullname classes - */ - private function ListDeclaredFullnameClassesFromAutoloadFile(string $sPath) : array - { - $sAutoloadClassMap = dirname($sPath) . "/composer/autoload_classmap.php"; - //echo $sAutoloadClassMap . '\n'; - if (!is_file($sAutoloadClassMap)) { - return []; - } - - $sTempfile = tempnam(sys_get_temp_dir(), 'autoload_'); - $sContent = file_get_contents($sAutoloadClassMap); - $sReplace=<<aAllDependencies = $aAllDependencies; - $this->aOngoingDependencies = []; + $this->aInitialDependencies = $aAllDependencies; + $this->aRemainingDependenciesToResolve = []; - foreach ($aAllDependencies as $sDepString){ - $this->aOngoingDependencies[$sDepString]= new iTopCoreModuleDependency($sDepString); + foreach ($aAllDependencies as $sDependencyExpression){ + $this->aRemainingDependenciesToResolve[$sDependencyExpression]= new ModuleDependency($sDependencyExpression); } } @@ -73,9 +76,9 @@ class iTopCoreModule { { $aNextDependencies=[]; $bDependenciesSolved = true; - foreach($this->aOngoingDependencies as $sDepId => $oModuleDependency) + foreach($this->aRemainingDependenciesToResolve as $sDepId => $oModuleDependency) { - /** @var iTopCoreModuleDependency $oModuleDependency*/ + /** @var ModuleDependency $oModuleDependency*/ if (!$oModuleDependency->IsDependencyResolved($aModuleVersions, $aSelectedModules)) { $aNextDependencies[$sDepId]=$oModuleDependency; @@ -83,7 +86,7 @@ class iTopCoreModule { } } - $this->aOngoingDependencies=$aNextDependencies; + $this->aRemainingDependenciesToResolve=$aNextDependencies; return $bDependenciesSolved; } @@ -94,8 +97,8 @@ class iTopCoreModule { public function GetUnresolvedDependencyModuleNames(): array { $aRes=[]; - foreach($this->aOngoingDependencies as $sDepId => $oModuleDependency) { - /** @var iTopCoreModuleDependency $oModuleDependency */ + foreach($this->aRemainingDependenciesToResolve as $sDepId => $oModuleDependency) { + /** @var ModuleDependency $oModuleDependency */ $aRes = array_merge($aRes, $oModuleDependency->GetPotentialPrerequisiteModuleNames()); } diff --git a/setup/module/ItopCoreModuleDependency.class.inc.php b/setup/moduledependency/moduledependency.class.inc.php similarity index 78% rename from setup/module/ItopCoreModuleDependency.class.inc.php rename to setup/moduledependency/moduledependency.class.inc.php index abcf1181a..9e522e521 100644 --- a/setup/module/ItopCoreModuleDependency.class.inc.php +++ b/setup/moduledependency/moduledependency.class.inc.php @@ -1,25 +1,29 @@ sDepString = $sDepString; + $this->sDependencyExpression = $sDependencyExpression; $this->aParamsPerModuleId = []; - $this->aPotentialPrerequisites = []; + $this->aDepencyModuleNames = []; - if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) + if (preg_match_all('/([^\(\)&| ]+)/', $sDependencyExpression, $aMatches)) { foreach($aMatches as $aMatch) { @@ -31,7 +35,7 @@ class iTopCoreModuleDependency { $aModuleMatches = array(); if (preg_match('|^([^/]+)/(?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) { $sModuleName = $aModuleMatches[1]; - $this->aPotentialPrerequisites[$sModuleName] = true; + $this->aDepencyModuleNames[$sModuleName] = true; $sOperator = $aModuleMatches[2]; if ($sOperator == '') { $sOperator = '>='; @@ -43,7 +47,7 @@ class iTopCoreModuleDependency { } } } else { - $this->bAlwaysUnresolved=true; + $this->bInvalidDependencyRegexp=true; } } @@ -62,7 +66,7 @@ class iTopCoreModuleDependency { */ public function GetPotentialPrerequisiteModuleNames() : array { - return array_keys($this->aPotentialPrerequisites); + return array_keys($this->aDepencyModuleNames); } /** @@ -74,7 +78,7 @@ class iTopCoreModuleDependency { */ public function IsDependencyResolved(array $aModuleVersions, array $aSelectedModules) : bool { - if ($this->bAlwaysUnresolved){ + if ($this->bInvalidDependencyRegexp){ return false; } @@ -86,8 +90,8 @@ class iTopCoreModuleDependency { $sCurrentVersion = $aModuleVersions[$sModuleName]; if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) { - if (array_key_exists($sModuleName, $this->aPotentialPrerequisites)) { - unset($this->aPotentialPrerequisites[$sModuleName]); + if (array_key_exists($sModuleName, $this->aDepencyModuleNames)) { + unset($this->aDepencyModuleNames[$sModuleName]); } $aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing // a function call that results in a runtime fatal error @@ -106,7 +110,7 @@ class iTopCoreModuleDependency { } } - foreach ($this->aPotentialPrerequisites as $sModuleName) + foreach ($this->aDepencyModuleNames as $sModuleName => $c) { if (array_key_exists($sModuleName, $aSelectedModules)) { @@ -119,7 +123,7 @@ class iTopCoreModuleDependency { } $bResult=false; - $sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $this->sDepString); + $sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $this->sDependencyExpression); try{ $bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr); } catch(ModuleFileReaderException $e){ diff --git a/setup/module/iTopCoreModuleDependencySort.class.inc.php b/setup/moduledependency/moduledependencysort.class.inc.php similarity index 50% rename from setup/module/iTopCoreModuleDependencySort.class.inc.php rename to setup/moduledependency/moduledependencysort.class.inc.php index c65b8a95f..927121c0a 100644 --- a/setup/module/iTopCoreModuleDependencySort.class.inc.php +++ b/setup/moduledependency/moduledependencysort.class.inc.php @@ -1,17 +1,21 @@ $oModule) { - /** @var iTopCoreModule $oModule */ + /** @var Module $oModule */ $aDependsOnModuleName[$oModule->GetModuleName()]=[]; } foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) { $iInDegreeCounter = 0; - /** @var iTopCoreModule $oModule */ + /** @var Module $oModule */ $aUnresolvedDependencyModuleNames = $oModule->GetUnresolvedDependencyModuleNames(); foreach ($aUnresolvedDependencyModuleNames as $sModuleName) { if (array_key_exists($sModuleName, $aDependsOnModuleName)) { @@ -115,86 +119,4 @@ class iTopCoreModuleDependencySort { $aUnresolvedDependencyModules=$aRes; } - - /** - * Arrange an list of modules, based on their (inter) dependencies - * @param array $aModules The list of modules to process: 'id' => $aModuleInfo - * @param bool $bAbortOnMissingDependency ... - * @param array $aModulesToLoad List of modules to search for, defaults to all if omitted - * @return array - * @throws \MissingDependencyException - */ - public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null) - { - // Order the modules to take into account their inter-dependencies - $aUnresolvedDependencyModules = []; - $aSelectedModules = []; - foreach($aModules as $sModuleId => $aModule) - { - $oModule = new iTopCoreModule($sModuleId); - $sModuleName = $oModule->GetModuleName(); - if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) - { - $oModule->SetDependencies($aModule['dependencies']); - $aUnresolvedDependencyModules[$sModuleId]=$oModule; - $aSelectedModules[$sModuleName] = true; - } - } - - ksort($aUnresolvedDependencyModules); - $aOrderedModules = []; - $aModuleVersions=[]; - $iLoopCount = 1; - while(($iLoopCount < count($aModules)+1) && (count($aUnresolvedDependencyModules) > 0) ) - { - foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) - { - /** @var iTopCoreModule $oModule */ - if ($oModule->IsModuleResolved($aModuleVersions, $aSelectedModules)){ - $aOrderedModules[] = $sModuleId; - $aModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion(); - unset($aUnresolvedDependencyModules[$sModuleId]); - } - } - - $iLoopCount++; - } - - if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) - { - self::SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - - $aModulesInfo = []; - $aModuleDeps = []; - foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) - { - $aModule = $aModules[$sModuleId]; - $aDepsWithIcons = []; - foreach($oModule->aAllDependencies as $sIndex => $sDepId) - { - if (array_key_exists($sDepId, $oModule->aOngoingDependencies)) - { - $aDepsWithIcons[$sIndex] = '❌ ' . $sDepId; - } else - { - $aDepsWithIcons[$sIndex] = '✅ ' . $sDepId; - } - } - $aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons); - $aModulesInfo[$sModuleId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons); - } - $sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps); - $oException = new MissingDependencyException($sMessage); - $oException->aModulesInfo = $aModulesInfo; - throw $oException; - } - - // Return the ordered list, so that the dependencies are met... - $aResult = []; - foreach($aOrderedModules as $sId) - { - $aResult[$sId] = $aModules[$sId]; - } - return $aResult; - } } \ No newline at end of file diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index abecc7364..f6112a7fc 100755 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -20,8 +20,11 @@ */ require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php'); -require_once(__DIR__ . '/module/iTopCoreModuleDependencySort.class.inc.php'); +require_once(__DIR__ . '/moduledependency/moduledependencysort.class.inc.php'); + use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; +use \Combodo\iTop\Setup\ModuleDependency\Module; +use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort; class MissingDependencyException extends CoreException { @@ -232,68 +235,70 @@ class ModuleDiscovery public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null) { // Order the modules to take into account their inter-dependencies - $aDependencies = []; + $aUnresolvedDependencyModules = []; $aSelectedModules = []; - foreach($aModules as $sId => $aModule) + foreach($aModules as $sModuleId => $aModule) { - list($sModuleName, ) = self::GetModuleName($sId); + $oModule = new Module($sModuleId); + $sModuleName = $oModule->GetModuleName(); if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) { - $aDependencies[$sId] = $aModule['dependencies']; + $oModule->SetDependencies($aModule['dependencies']); + $aUnresolvedDependencyModules[$sModuleId]=$oModule; $aSelectedModules[$sModuleName] = true; } } - ksort($aDependencies); + + ksort($aUnresolvedDependencyModules); $aOrderedModules = []; + $aModuleVersions=[]; $iLoopCount = 1; - while(($iLoopCount < count($aModules)+1) && (count($aDependencies) > 0) ) + while(($iLoopCount < count($aModules)+1) && (count($aUnresolvedDependencyModules) > 0) ) { - foreach($aDependencies as $sId => $aRemainingDeps) + foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) { - $bDependenciesSolved = true; - foreach($aRemainingDeps as $sDepId) - { - if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) - { - $bDependenciesSolved = false; - } - } - if ($bDependenciesSolved) - { - $aOrderedModules[] = $sId; - unset($aDependencies[$sId]); + /** @var Module $oModule */ + if ($oModule->IsModuleResolved($aModuleVersions, $aSelectedModules)){ + $aOrderedModules[] = $sModuleId; + $aModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion(); + unset($aUnresolvedDependencyModules[$sModuleId]); } } + $iLoopCount++; } - if ($bAbortOnMissingDependency && count($aDependencies) > 0) + + if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) { + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $aModulesInfo = []; $aModuleDeps = []; - foreach($aDependencies as $sId => $aDeps) + foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) { - $aModule = $aModules[$sId]; + $aModule = $aModules[$sModuleId]; $aDepsWithIcons = []; - foreach($aDeps as $sIndex => $sDepId) + foreach($oModule->aInitialDependencies as $sIndex => $sDepId) { - if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) - { - $aDepsWithIcons[$sIndex] = '✅ ' . $sDepId; - } else + if (array_key_exists($sDepId, $oModule->aRemainingDependenciesToResolve)) { $aDepsWithIcons[$sIndex] = '❌ ' . $sDepId; + } else + { + $aDepsWithIcons[$sIndex] = '✅ ' . $sDepId; } } - $aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons); - $aModulesInfo[$sId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons); + $aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons); + $aModulesInfo[$sModuleId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons); } $sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps); $oException = new MissingDependencyException($sMessage); $oException->aModulesInfo = $aModulesInfo; throw $oException; } + // Return the ordered list, so that the dependencies are met... - $aResult = array(); + $aResult = []; foreach($aOrderedModules as $sId) { $aResult[$sId] = $aModules[$sId]; @@ -301,18 +306,6 @@ class ModuleDiscovery return $aResult; } - /** - * Remove the duplicate modules (i.e. modules with the same name but with a different version) from the supplied list of modules - * @param array $aModules - * @return array The ordered modules as a duplicate-free list of modules - */ - public static function RemoveDuplicateModules($aModules) - { - // No longer needed, kept only for compatibility - // The de-duplication is now done directly by the AddModule method - return $aModules; - } - private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator { if (!isset(static::$oPhpExpressionEvaluator)) { @@ -322,99 +315,6 @@ class ModuleDiscovery return static::$oPhpExpressionEvaluator; } - protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules) - { - $bResult = false; - $aModuleVersions = array(); - // Separate the module names from their version for an easier comparison later - foreach($aOrderedModules as $sModuleId) - { - $aMatches = array(); - if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches)) - { - $aModuleVersions[$aMatches[1]] = $aMatches[2]; - } - else - { - // No version number found, assume 1.0.0 - $aModuleVersions[$sModuleId] = '1.0.0'; - } - } - if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) - { - $aReplacements = array(); - $aPotentialPrerequisites = array(); - foreach($aMatches as $aMatch) - { - foreach($aMatch as $sModuleId) - { - // $sModuleId in the dependency string is made of a / - // where the operator is < <= = > >= (by default >=) - $aModuleMatches = array(); - if(preg_match('|^([^/]+)/(?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) - { - $sModuleName = $aModuleMatches[1]; - $aPotentialPrerequisites[$sModuleName] = true; - $sOperator = $aModuleMatches[2]; - if ($sOperator == '') - { - $sOperator = '>='; - } - $sExpectedVersion = $aModuleMatches[3]; - if (array_key_exists($sModuleName, $aModuleVersions)) - { - // module is present, check the version - $sCurrentVersion = $aModuleVersions[$sModuleName]; - if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) - { - $aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error - } - else - { - $aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error - } - } - else - { - // module is not present - $aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error - } - } - } - } - $bMissingPrerequisite = false; - foreach (array_keys($aPotentialPrerequisites) as $sModuleName) - { - if (array_key_exists($sModuleName, $aSelectedModules)) - { - // This module is actually a prerequisite - if (!array_key_exists($sModuleName, $aModuleVersions)) - { - $bMissingPrerequisite = true; - } - } - } - if ($bMissingPrerequisite) - { - $bResult = false; - } - else - { - $sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString); - try{ - $bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr); - } catch(ModuleFileReaderException $e){ - //logged already - echo "Failed to parse the boolean Expression = '$sBooleanExpr'
"; - } - } - } - return $bResult; - } - /** * Search (on the disk) for all defined iTop modules, load them and returns the list (as an array) * of the possible iTop modules to install diff --git a/tests/php-unit-tests/integration-tests/iTopModulesDependencyValidationServiceTest.php b/tests/php-unit-tests/integration-tests/iTopModulesDependencyValidationServiceTest.php deleted file mode 100644 index 3214bd122..000000000 --- a/tests/php-unit-tests/integration-tests/iTopModulesDependencyValidationServiceTest.php +++ /dev/null @@ -1,114 +0,0 @@ -RequireOnceItopFile('setup/modulediscovery.class.inc.php'); - $this->RequireOnceItopFile('setup/module/iTopModulesDependencyValidationService.php'); - } - - protected function tearDown(): void - { - parent::tearDown(); // TODO: Change the autogenerated stub - foreach ($this->aFilesToRemove as $sTmpFile){ - @unlink($sTmpFile); - } - iTopModulesDependencyValidationService::SetInstance(null); - } - - /** - * Module dependency validation: make sure dependencies are correct toward classes/interfaces coming from PHP/Xml datamodel files - */ - public function testReadModuleFileData() - { - iTopModulesDependencyValidationService::GetInstance()->FetchAllDependenciesViaModulesFiles(); - $this->testModulesBasedOnDMFilesOnly(); - } - - /** - * Module dependency validation: make sure dependencies are correct toward classes/interfaces coming from Xml datamodel files - */ - public function testModulesBasedOnDMFilesOnly() - { - iTopModulesDependencyValidationService::GetInstance()->FetchAllDependenciesViaDM(); - - $aErrors=[]; - /** @var XmlModule $oXmlModule */ - foreach (iTopModulesDependencyValidationService::GetInstance()->aModules as $sModuleName => $oXmlModule) { - $aCurrentDeps = iTopModulesDependencyValidationService::GetModulesDataByModuleName()[$sModuleName]['dependencies'] ?? []; - $aModuleErrors=[]; - foreach ($oXmlModule->aDependencyModulesNames as $sDepModuleName => $oXmlModule2){ - $sXmlUIDs = implode('|', $oXmlModule->aXMlMetaInfosByModuleNames[$sDepModuleName]); - $bResolved=false; - foreach ($aCurrentDeps as $sDepString){ - $oModuleDependency = new \iTopCoreModuleDependency($sDepString); - - if (in_array($sDepModuleName, $oModuleDependency->GetPotentialPrerequisiteModuleNames())) { - $bResolved=true; - break; - } - - foreach ($oModuleDependency->GetPotentialPrerequisiteModuleNames() as $sPotentialDepModuleName){ - /** @var XmlModule $oXmlModule2 */ - $oXmlModule2 = iTopModulesDependencyValidationService::GetInstance()->aModules[$sPotentialDepModuleName]??null; - - if (! is_null($oXmlModule2) && $oXmlModule2->Depends($sDepModuleName)){ - $bResolved=true; - break; - } - } - - if ($bResolved) { - break; - } - } - - if (! $bResolved){ - $aModuleErrors []= "$sModuleName depends on $sDepModuleName but missing in module dependencies: " . implode(' & ', $aCurrentDeps) . ". ($sXmlUIDs)"; - } - } - - if (count($aModuleErrors)){ - $aErrors[$sModuleName]=$aModuleErrors; - } - } - - $this->assertEquals(0, count($aErrors), var_export($aErrors, true)); - - } -} - - diff --git a/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php index 7b6faa885..3c8c947ed 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php @@ -3,7 +3,6 @@ namespace Combodo\iTop\Test\UnitTest\Setup; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; -use iTopCoreModuleDependencySort; use MissingDependencyException; use ModuleDiscovery; @@ -14,29 +13,7 @@ class ModuleDiscoveryTest extends ItopDataTestCase $this->RequireOnceItopFile('setup/modulediscovery.class.inc.php'); } - public static function CheckMissingDependenciesAreCorrectlyOrderedInTheExceptionProvider() - { - $sLegacyExpectedMessage = << [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], - 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], - ]; - } - - /** - * @dataProvider CheckMissingDependenciesAreCorrectlyOrderedInTheExceptionProvider - */ - public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException(string $sMessage, bool $bIsLegacy) + public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException() { $aModules=[ "id1/123" => [ @@ -49,39 +26,18 @@ MSG; ], ]; - $this->expectException(MissingDependencyException::class); - $this->expectExceptionMessage($sMessage); - - if ($bIsLegacy){ - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } else { - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); - } - } - - public static function ValidateExceptionWithSomeDependenciesResolvedProvider() - { $sExpectedMessage = <<expectException(MissingDependencyException::class); + $this->expectExceptionMessage($sExpectedMessage); - return [ - 'legacy' => [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], - 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], - ]; + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); } - /** - * @dataProvider ValidateExceptionWithSomeDependenciesResolvedProvider - */ - public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved(string $sMessage, bool $bIsLegacy) + public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved() { $aModules=[ "id1/123" => [ @@ -98,14 +54,15 @@ MSG; ], ]; + $sExpectedMessage = <<expectException(MissingDependencyException::class); - $this->expectExceptionMessage($sMessage); + $this->expectExceptionMessage($sExpectedMessage); - if ($bIsLegacy){ - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } else { - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); - } + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); } public function testOrderModulesByDependencies_KeepGoingEvenWithFailure_WithSomeDependenciesResolved() @@ -125,44 +82,16 @@ MSG; ], ]; - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, false, null); - $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, false, null); + $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, false, null); $aExpected = [ 'id2/456', ]; - $this->assertEquals($aExpected, array_keys($aLegacyResult)); - $this->assertEquals( $aLegacyResult, $aResult); + $this->assertEquals($aExpected, array_keys($aResult)); } - public static function UnResolveWithCircularDependencyProvider() - { - $sExpectedMessage = << [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], - 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], - ]; - } - - /** - * @dataProvider UnResolveWithCircularDependencyProvider - */ - public function testOrderModulesByDependencies_UnResolveWithCircularDependency(string $sMessage, bool $bIsLegacy) + public function testOrderModulesByDependencies_UnResolveWithCircularDependency() { $aModules=[ "id1/1" => [ @@ -183,14 +112,17 @@ MSG; ], ]; + $sExpectedMessage = <<expectException(MissingDependencyException::class); - $this->expectExceptionMessage($sMessage); + $this->expectExceptionMessage($sExpectedMessage); - if ($bIsLegacy){ - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } else { - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); - } + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); } public function testOrderModulesByDependencies_ResolveOk() @@ -226,13 +158,50 @@ MSG; "id0/1", ]; - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); - $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - $this->assertEquals($aExpected, array_keys($aLegacyResult)); - $this->assertEquals( $aLegacyResult, $aResult); + $this->assertEquals($aExpected, array_keys($aResult)); } + public function testOrderModulesByDependencies_ResolveOk2() + { + $aModules=[ + "id0/1" => [ + 'dependencies' => [ 'id2/2'], + 'label' => 'label1', + ], + "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', + ], + ]; + + $aExpected = [ + "id4/4", + "id3/3", + "id2/2", + "id0/1", + "id1/1", + ]; + + $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + + $this->assertEquals($aExpected, array_keys($aResult)); + } + + public function testOrderModulesByDependencies_ResolveNoDependendenciesOrderByAlphabeticalOrder() { $aModules=[ @@ -266,11 +235,9 @@ MSG; "id4/4", ]; - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); - $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - $this->assertEquals($aExpected, array_keys($aLegacyResult)); - $this->assertEquals( $aLegacyResult, $aResult); + $this->assertEquals($aExpected, array_keys($aResult)); } public function testOrderModulesByDependencies_ResolveOk_ModulesToLoadProvided() @@ -301,103 +268,20 @@ MSG; "id1/1", ]; - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); - $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); + $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); - $this->assertEquals($aExpected, array_keys($aLegacyResult)); - $this->assertEquals( $aLegacyResult, $aResult); + $this->assertEquals($aExpected, array_keys($aResult)); } } public function testOrderModulesByDependenciesNewComputation_RealExample(){ $aModules = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true); - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); + //$aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids.json'), true); - $this->assertEquals( $aLegacyResult, $aResult); + //$this->assertEquals( $aLegacyResult, $aResult); $this->assertEquals($aExpected, array_keys($aLegacyResult)); } - - public function testSortModulesByCountOfDepencenciesDescending_NoDependencies(){ - $aUnresolvedDependencyModules = []; - foreach (['c', 'b', 'a'] as $sModuleId){ - $this->AddModule($aUnresolvedDependencyModules, $sModuleId, []); - } - iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - $this->assertEquals(['a', 'b', 'c'], array_keys($aUnresolvedDependencyModules)); - } - - public function testSortModulesByCountOfDepencenciesDescending_NominalUseCase(){ - $aUnresolvedDependencyModules = []; - $this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']); - $this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']); - $this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']); - $this->AddModule($aUnresolvedDependencyModules, 'itop-structure/2.7.1', []); - - iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - $this->assertEquals( - [ - 'itop-structure/2.7.1', - 'itop-config-mgmt/123', - 'itop-tickets/2.0.0', - 'itop-change-mgmt/456', - ], array_keys($aUnresolvedDependencyModules)); - } - - public function testSortModulesByCountOfDepencenciesDescending_NominalUseCaseWithMissingDependency(){ - $aUnresolvedDependencyModules = []; - $this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']); - $this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']); - $this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']); - - iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - $this->assertEquals( - [ - 'itop-config-mgmt/123', - 'itop-tickets/2.0.0', - 'itop-change-mgmt/456', - ], - array_keys($aUnresolvedDependencyModules)); - } - - public function testSortModulesByCountOfDepencenciesDescending_FurtherVersionsOfSameModule(){ - $aUnresolvedDependencyModules = []; - $this->AddModule($aUnresolvedDependencyModules, 'moduleA/1', []); - $this->AddModule($aUnresolvedDependencyModules, 'moduleA/2', ['moduleC/1']); - $this->AddModule($aUnresolvedDependencyModules, 'moduleB/1', ['moduleA/1']); - $this->AddModule($aUnresolvedDependencyModules, 'moduleC/1', []); - - iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - $this->assertEquals( - [ - 'moduleA/1', - 'moduleC/1', - 'moduleA/2', - 'moduleB/1', - ], - array_keys($aUnresolvedDependencyModules)); - } - - private function AddModule(array &$aUnresolvedDependencyModules, string $sModuleId, array $aDeps){ - $oModule = new \iTopCoreModule($sModuleId); - $oModule->SetDependencies($aDeps); - $aUnresolvedDependencyModules[$sModuleId]= $oModule; - } - - public function testSortModulesByCountOfDepencenciesDescending_RealExample(){ - $aUnresolvedDependencyModules = []; - $aDependencies = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true); - foreach ($aDependencies as $sModuleId => $aModuleData){ - $this->AddModule($aUnresolvedDependencyModules, $sModuleId, $aModuleData['dependencies']); - } - - iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - - $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids2.json'), true); - $this->assertEquals( - $aExpected, - array_keys($aUnresolvedDependencyModules)); - } } \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/module/iTopModulesDependencyValidationServiceTest.php b/tests/php-unit-tests/unitary-tests/setup/module/iTopModulesDependencyValidationServiceTest.php deleted file mode 100644 index e9d25bdd6..000000000 --- a/tests/php-unit-tests/unitary-tests/setup/module/iTopModulesDependencyValidationServiceTest.php +++ /dev/null @@ -1,99 +0,0 @@ -RequireOnceItopFile('setup/module/iTopModulesDependencyValidationService.php'); - } - - protected function tearDown(): void - { - parent::tearDown(); // TODO: Change the autogenerated stub - foreach ($this->aFilesToRemove as $sTmpFile){ - @unlink($sTmpFile); - } - iTopModulesDependencyValidationService::SetInstance(null); - } - - public function testListDeclaredFullnameClassesFromPhpFile() - { - $aExpected = [ - 'CMDBChangeOp', - 'CMDBChangeOpCreate', - 'CMDBChangeOpDelete', - 'CMDBChangeOpSetAttribute', - 'CMDBChangeOpSetAttributeScalar', - 'CMDBChangeOpSetAttributeTagSet', - 'CMDBChangeOpSetAttributeURL', - 'CMDBChangeOpSetAttributeBlob', - 'CMDBChangeOpSetAttributeOneWayPassword', - 'CMDBChangeOpSetAttributeEncrypted', - 'CMDBChangeOpSetAttributeText', - 'CMDBChangeOpSetAttributeLongText', - 'CMDBChangeOpSetAttributeHTML', - 'CMDBChangeOpSetAttributeCaseLog', - 'CMDBChangeOpPlugin', - 'CMDBChangeOpSetAttributeLinksAddRemove', - 'CMDBChangeOpSetAttributeLinksTune', - 'CMDBChangeOpSetAttributeCustomFields', - 'iCMDBChangeOp', - ]; - $this->assertEquals($aExpected, iTopModulesDependencyValidationService::GetInstance()->ListDeclaredFullnameClassesFromPhpFile(APPROOT . 'core/cmdbchangeop.class.inc.php')); - } - - public function testListDeclaredFullnameClassesFromAutoloadFile() - { - $aExpected = [ - 'Combodo\iTop\OAuthClient\Controller\AjaxOauthClientController', - 'Combodo\iTop\OAuthClient\Controller\OAuthClientController', - 'Combodo\iTop\OAuthClient\Service\ApplicationUIExtension', - 'Combodo\iTop\OAuthClient\Service\PopupMenuExtension', - ]; - $this->assertEquals($aExpected, iTopModulesDependencyValidationService::GetInstance()->ListDeclaredFullnameClassesFromPhpFile(APPROOT . 'datamodels/2.x/itop-oauth-client/vendor/autoload.php')); - } - - public function testGetFirstFoundDepsUID() { - $sOutput=<<assertEquals('Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension', iTopModulesDependencyValidationService::GetInstance()->GetFirstFoundDepsUID($sOutput)); - - - $sOutput=<<assertEquals('Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension', iTopModulesDependencyValidationService::GetInstance()->GetFirstFoundDepsUID($sOutput)); - } -} - - diff --git a/tests/php-unit-tests/unitary-tests/setup/moduledependency/ModuleDiscoveryTest.php b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ModuleDiscoveryTest.php new file mode 100644 index 000000000..3bb6820b1 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ModuleDiscoveryTest.php @@ -0,0 +1,97 @@ +RequireOnceItopFile('setup/moduledependency/moduledependencysort.class.inc.php'); + } + + public function testSortModulesByCountOfDepencenciesDescending_NoDependencies(){ + $aUnresolvedDependencyModules = []; + foreach (['c', 'b', 'a'] as $sModuleId){ + $this->AddModule($aUnresolvedDependencyModules, $sModuleId, []); + } + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $this->assertEquals(['a', 'b', 'c'], array_keys($aUnresolvedDependencyModules)); + } + + public function testSortModulesByCountOfDepencenciesDescending_NominalUseCase(){ + $aUnresolvedDependencyModules = []; + $this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']); + $this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']); + $this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']); + $this->AddModule($aUnresolvedDependencyModules, 'itop-structure/2.7.1', []); + + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $this->assertEquals( + [ + 'itop-structure/2.7.1', + 'itop-config-mgmt/123', + 'itop-tickets/2.0.0', + 'itop-change-mgmt/456', + ], array_keys($aUnresolvedDependencyModules)); + } + + public function testSortModulesByCountOfDepencenciesDescending_NominalUseCaseWithMissingDependency(){ + $aUnresolvedDependencyModules = []; + $this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']); + $this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']); + $this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']); + + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $this->assertEquals( + [ + 'itop-config-mgmt/123', + 'itop-tickets/2.0.0', + 'itop-change-mgmt/456', + ], + array_keys($aUnresolvedDependencyModules)); + } + + public function testSortModulesByCountOfDepencenciesDescending_FurtherVersionsOfSameModule(){ + $aUnresolvedDependencyModules = []; + $this->AddModule($aUnresolvedDependencyModules, 'moduleA/1', []); + $this->AddModule($aUnresolvedDependencyModules, 'moduleA/2', ['moduleC/1']); + $this->AddModule($aUnresolvedDependencyModules, 'moduleB/1', ['moduleA/1']); + $this->AddModule($aUnresolvedDependencyModules, 'moduleC/1', []); + + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $this->assertEquals( + [ + 'moduleA/1', + 'moduleC/1', + 'moduleA/2', + 'moduleB/1', + ], + array_keys($aUnresolvedDependencyModules)); + } + + private function AddModule(array &$aUnresolvedDependencyModules, string $sModuleId, array $aDeps){ + $oModule = new Module($sModuleId); + $oModule->SetDependencies($aDeps); + $aUnresolvedDependencyModules[$sModuleId]= $oModule; + } + + public function testSortModulesByCountOfDepencenciesDescending_RealExample(){ + $aUnresolvedDependencyModules = []; + $aDependencies = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true); + foreach ($aDependencies as $sModuleId => $aModuleData){ + $this->AddModule($aUnresolvedDependencyModules, $sModuleId, $aModuleData['dependencies']); + } + + ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + + $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids2.json'), true); + $this->assertEquals( + $aExpected, + array_keys($aUnresolvedDependencyModules)); + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/expected_ordered_module_ids2.json b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/expected_ordered_module_ids2.json new file mode 100644 index 000000000..a8dd80bbc --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/expected_ordered_module_ids2.json @@ -0,0 +1 @@ +["authent-cas\/3.2.1","authent-external\/3.2.1","authent-ldap\/3.2.1","authent-local\/3.2.1","combodo-backoffice-darkmoon-theme\/3.2.1","combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1","combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1","combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1","itop-attachments\/3.2.1","itop-backup\/3.2.1","itop-config\/3.2.1","itop-files-information\/3.2.1","itop-portal-base\/3.2.1","itop-profiles-itil\/3.2.1","itop-sla-computation\/3.2.1","itop-structure\/3.2.1","itop-welcome-itil\/3.2.1","combodo-db-tools\/3.2.1","itop-config-mgmt\/3.2.1","itop-datacenter-mgmt\/3.2.1","itop-endusers-devices\/3.2.1","itop-hub-connector\/3.2.1","itop-knownerror-mgmt\/3.2.1","itop-oauth-client\/3.2.1","itop-portal\/3.2.1","itop-storage-mgmt\/3.2.1","itop-themes-compat\/3.2.1","itop-tickets\/3.2.1","itop-problem-mgmt\/3.2.1","itop-request-mgmt-itil\/3.2.1","itop-request-mgmt\/3.2.1","itop-service-mgmt-provider\/3.2.1","itop-service-mgmt\/3.2.1","itop-virtualization-mgmt\/3.2.1","itop-bridge-cmdb-ticket\/3.2.1","itop-bridge-virtualization-storage\/3.2.1","itop-change-mgmt-itil\/3.2.1","itop-change-mgmt\/3.2.1","itop-core-update\/3.2.1","itop-faq-light\/3.2.1","itop-bridge-cmdb-services\/3.2.1","itop-incident-mgmt-itil\/3.2.1","itop-full-itil\/3.2.1","itop-bridge-datacenter-mgmt-services\/3.2.1","itop-bridge-endusers-devices-services\/3.2.1","itop-bridge-storage-mgmt-services\/3.2.1","itop-bridge-virtualization-mgmt-services\/3.2.1"] \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/module_deps.json b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/module_deps.json new file mode 100644 index 000000000..02946914b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/moduledependency/ressources/module_deps.json @@ -0,0 +1,267 @@ +{ + "authent-cas\/3.2.1": { + "label": "CAS SSO", + "dependencies": [] + }, + "authent-external\/3.2.1": { + "label": "External user authentication", + "dependencies": [] + }, + "authent-ldap\/3.2.1": { + "label": "User authentication based on LDAP", + "dependencies": [] + }, + "authent-local\/3.2.1": { + "label": "User authentication based on the local DB", + "dependencies": [] + }, + "combodo-backoffice-darkmoon-theme\/3.2.1": { + "label": "Backoffice: Darkmoon theme", + "dependencies": [] + }, + "combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1": { + "label": "Backoffice: Fullmoon with high contrast accessibility theme", + "dependencies": [] + }, + "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1": { + "label": "Backoffice: Fullmoon with protonopia & deuteranopia accessibility theme", + "dependencies": [] + }, + "combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1": { + "label": "Backoffice: Fullmoon with tritanopia accessibility theme", + "dependencies": [] + }, + "combodo-db-tools\/3.2.1": { + "label": "Database maintenance tools", + "dependencies": [ + "itop-structure\/3.0.0" + ] + }, + "itop-attachments\/3.2.1": { + "label": "Tickets Attachments", + "dependencies": [] + }, + "itop-backup\/3.2.1": { + "label": "Backup utilities", + "dependencies": [] + }, + "itop-bridge-cmdb-services\/3.2.1": { + "label": "Bridge for CMDB and Services", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1" + ] + }, + "itop-bridge-cmdb-ticket\/3.2.1": { + "label": "Bridge for CMDB and Ticket", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-tickets\/2.7.0" + ] + }, + "itop-bridge-datacenter-mgmt-services\/3.2.1": { + "label": "Bridge for CMDB Virtualization objects and Services", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1", + "itop-datacenter-mgmt\/3.1.0" + ] + }, + "itop-bridge-endusers-devices-services\/3.2.1": { + "label": "Bridge for CMDB endusers objects and Services", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1", + "itop-endusers-devices\/3.1.0" + ] + }, + "itop-bridge-storage-mgmt-services\/3.2.1": { + "label": "Bridge for CMDB Virtualization objects and Services", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1", + "itop-storage-mgmt\/3.1.0" + ] + }, + "itop-bridge-virtualization-mgmt-services\/3.2.1": { + "label": "Bridge for CMDB Virtualization objects and Services", + "dependencies": [ + "itop-config-mgmt\/2.7.1", + "itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1", + "itop-virtualization-mgmt\/3.1.0" + ] + }, + "itop-bridge-virtualization-storage\/3.2.1": { + "label": "Links between virtualization and storage", + "dependencies": [ + "itop-storage-mgmt\/2.2.0", + "itop-virtualization-mgmt\/2.2.0" + ] + }, + "itop-change-mgmt-itil\/3.2.1": { + "label": "Change Management ITIL", + "dependencies": [ + "itop-config-mgmt\/2.2.0", + "itop-tickets\/2.0.0" + ] + }, + "itop-change-mgmt\/3.2.1": { + "label": "Change Management", + "dependencies": [ + "itop-config-mgmt\/2.2.0", + "itop-tickets\/2.0.0" + ] + }, + "itop-config-mgmt\/3.2.1": { + "label": "Configuration Management (CMDB)", + "dependencies": [ + "itop-structure\/2.7.1" + ] + }, + "itop-config\/3.2.1": { + "label": "Configuration editor", + "dependencies": [] + }, + "itop-core-update\/3.2.1": { + "label": "iTop Core Update", + "dependencies": [ + "itop-files-information\/2.7.0", + "combodo-db-tools\/2.7.0" + ] + }, + "itop-datacenter-mgmt\/3.2.1": { + "label": "Datacenter Management", + "dependencies": [ + "itop-config-mgmt\/2.2.0" + ] + }, + "itop-endusers-devices\/3.2.1": { + "label": "End-user Devices Management", + "dependencies": [ + "itop-config-mgmt\/2.2.0" + ] + }, + "itop-faq-light\/3.2.1": { + "label": "Frequently Asked Questions Database", + "dependencies": [ + "itop-structure\/3.0.0 || itop-portal\/3.0.0" + ] + }, + "itop-files-information\/3.2.1": { + "label": "iTop files information", + "dependencies": [] + }, + "itop-full-itil\/3.2.1": { + "label": "Bridge - Request management ITIL + Incident management ITIL", + "dependencies": [ + "itop-request-mgmt-itil\/2.3.0", + "itop-incident-mgmt-itil\/2.3.0" + ] + }, + "itop-hub-connector\/3.2.1": { + "label": "iTop Hub Connector", + "dependencies": [ + "itop-config-mgmt\/2.4.0" + ] + }, + "itop-incident-mgmt-itil\/3.2.1": { + "label": "Incident Management ITIL", + "dependencies": [ + "itop-config-mgmt\/2.4.0", + "itop-tickets\/2.4.0", + "itop-profiles-itil\/2.3.0" + ] + }, + "itop-knownerror-mgmt\/3.2.1": { + "label": "Known Errors Database", + "dependencies": [ + "itop-config-mgmt\/2.2.0" + ] + }, + "itop-oauth-client\/3.2.1": { + "label": "OAuth 2.0 client", + "dependencies": [ + "itop-welcome-itil\/3.1.0," + ] + }, + "itop-portal-base\/3.2.1": { + "label": "Portal Development Library", + "dependencies": [] + }, + "itop-portal\/3.2.1": { + "label": "Enhanced Customer Portal", + "dependencies": [ + "itop-portal-base\/2.7.0" + ] + }, + "itop-problem-mgmt\/3.2.1": { + "label": "Problem Management", + "dependencies": [ + "itop-tickets\/2.0.0" + ] + }, + "itop-profiles-itil\/3.2.1": { + "label": "Create standard ITIL profiles", + "dependencies": [] + }, + "itop-request-mgmt-itil\/3.2.1": { + "label": "User request Management ITIL", + "dependencies": [ + "itop-tickets\/2.4.0" + ] + }, + "itop-request-mgmt\/3.2.1": { + "label": "Simple Ticket Management", + "dependencies": [ + "itop-tickets\/2.4.0" + ] + }, + "itop-service-mgmt-provider\/3.2.1": { + "label": "Service Management for Service Providers", + "dependencies": [ + "itop-tickets\/2.0.0" + ] + }, + "itop-service-mgmt\/3.2.1": { + "label": "Service Management", + "dependencies": [ + "itop-tickets\/2.0.0" + ] + }, + "itop-sla-computation\/3.2.1": { + "label": "SLA Computation", + "dependencies": [] + }, + "itop-storage-mgmt\/3.2.1": { + "label": "Advanced Storage Management", + "dependencies": [ + "itop-config-mgmt\/2.4.0" + ] + }, + "itop-structure\/3.2.1": { + "label": "Core iTop Structure", + "dependencies": [] + }, + "itop-themes-compat\/3.2.1": { + "label": "Light grey and Test red themes compatibility", + "dependencies": [ + "itop-structure\/3.1.0" + ] + }, + "itop-tickets\/3.2.1": { + "label": "Tickets Management", + "dependencies": [ + "itop-structure\/2.7.1" + ] + }, + "itop-virtualization-mgmt\/3.2.1": { + "label": "Virtualization Management", + "dependencies": [ + "itop-config-mgmt\/2.4.0" + ] + }, + "itop-welcome-itil\/3.2.1": { + "label": "ITIL skin", + "dependencies": [] + } +} \ No newline at end of file