N°8760 - setupaudit and dry removal API review with Romain - avoid file deletion

N°8760 - be able to simulate extension removal by oerriding GetExtensionMap

be able to simulate SetupAudit errors in Setups for integration tests

fix rebase
This commit is contained in:
odain
2025-11-03 17:33:17 +01:00
parent 9cdc707bc5
commit a2b01b3ed4
11 changed files with 289 additions and 217 deletions

View File

@@ -2,35 +2,48 @@
namespace Combodo\iTop\Setup\FeatureRemoval;
use Config;
use CoreException;
use DBObjectSearch;
use DBObjectSet;
use Exception;
use MetaModel;
use RunTimeEnvironment;
use SetupUtils;
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
class SetupAudit
{
const DRY_REMOVAL_AUDIT_ENV = "extension-removal";
//file used when present to trigger audit exception when testing specific setups
const GETISSUE_ERROR_MSG_FILE_FORTESTONLY = '.setup_audit_error_msg.txt';
private string $sEnvBeforeExtensionRemoval;
private string $sEnvAfterExtensionRemoval;
private array $aClassesBeforeRemoval;
private array $aClassesAfterRemoval;
private array $aExtensionToRemove;
private array $aRemovedClasses;
private array $aFinalClassesRemoved;
public function __construct()
public function __construct(string $sEnvBeforeExtensionRemoval, string $sEnvAfterExtensionRemoval = DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV)
{
$this->aExtensionToRemove = [];
$this->aClassesBeforeRemoval = [];
$this->aClassesAfterRemoval = [];
$this->sEnvBeforeExtensionRemoval = $sEnvBeforeExtensionRemoval;
$this->sEnvAfterExtensionRemoval = $sEnvAfterExtensionRemoval;
$sCurrentEnvt = MetaModel::GetEnvironment();
if ($sCurrentEnvt === $this->sEnvBeforeExtensionRemoval) {
$this->aClassesBeforeRemoval = MetaModel::GetClasses();
} else {
$this->aClassesBeforeRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvBeforeExtensionRemoval);
}
if ($sCurrentEnvt === $this->sEnvAfterExtensionRemoval) {
$this->aClassesAfterRemoval = MetaModel::GetClasses();
} else {
$this->aClassesAfterRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvAfterExtensionRemoval);
}
$this->aRemovedClasses = [];
$this->aFinalClassesRemoved = [];
}
public function SetSelectedExtensions(Config $oConfig, array $aSelectedExtensions)
/*public function SetSelectedExtensions(Config $oConfig, array $aSelectedExtensions)
{
$oExtensionsMap = new \iTopExtensionsMap();
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
@@ -39,103 +52,7 @@ class SetupAudit
$this->aExtensionToRemove = $oExtensionsMap->GetMissingExtensions($aSelectedExtensions);
sort($this->aExtensionToRemove);
\SetupLog::Info(__METHOD__, null, ['aExtensionToRemove' => $this->aExtensionToRemove]);
}
public function ComputeClassesBeforeRemoval(string $sTargetEnv)
{
$this->aClassesBeforeRemoval = $this->GetModelFromEnvironment($sTargetEnv);
}
public function SetClassesAfterRemovalFromCurrentEnv()
{
$this->aClassesAfterRemoval = MetaModel::GetClasses();
}
public function SetClassesBeforeRemovalFromCurrentEnv()
{
$this->aClassesBeforeRemoval = MetaModel::GetClasses();
}
public function ComputeDryExtensionRemoval(array $aExtensionToRemove): void
{
$this->aExtensionToRemove = $aExtensionToRemove;
if (count($this->aExtensionToRemove) == 0) {
//avoid time consuming setup audit when no extension removed
return;
}
$sDryRemovalEnv = self::DRY_REMOVAL_AUDIT_ENV;
self::Cleanup($sDryRemovalEnv);
$sSourceEnvt = MetaModel::GetEnvironment();
$oDryRemovalRuntimeEnvt = new RunTimeEnvironment($sDryRemovalEnv);
$oDryRemovalConfig = clone(MetaModel::GetConfig());
$oDryRemovalConfig->ChangeModulesPath($sSourceEnvt, $sDryRemovalEnv);
$oDryRemovalRuntimeEnvt->WriteConfigFileSafe($oDryRemovalConfig);
SetupUtils::copydir(APPROOT."/data/$sSourceEnvt-modules", APPROOT."/data/$sDryRemovalEnv-modules");
$this->RemoveExtensionsLocally($sDryRemovalEnv, $this->aExtensionToRemove);
$oDryRemovalRuntimeEnvt->CompileFrom($sSourceEnvt);
$this->aClassesAfterRemoval = $this->GetModelFromEnvironment($sDryRemovalEnv);
$oDryRemovalRuntimeEnvt->Rollback();
self::Cleanup($sDryRemovalEnv);
}
public static function Cleanup(string $sEnv)
{
SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
SetupUtils::rrmdir(APPROOT."/data/cache-$sEnv");
SetupUtils::rrmdir(APPROOT."/env-$sEnv");
SetupUtils::rrmdir(APPROOT."/conf/$sEnv");
@unlink(APPROOT."/data/datamodel-$sEnv.xml");
}
public function GetModelFromEnvironment(string $sEnv): array
{
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
$sOutput = "";
$iRes = 0;
exec(sprintf("$sPHPExec %s/get_model_reflection.php --env='%s'", __DIR__, $sEnv), $sOutput, $iRes);
if ($iRes != 0) {
\IssueLog::Error("Cannot get classes", null, ['code' => $iRes, "output" => $sOutput]);
throw new CoreException("Cannot get classes");
}
$aClasses = json_decode($sOutput[0] ?? null, true);
if (false === $aClasses) {
\IssueLog::Error("Invalid JSON", null, ["output" => $sOutput]);
throw new Exception("cannot get classes");
}
if (!is_array($aClasses)) {
\IssueLog::Error("not an array", null, ["classes" => $aClasses]);
throw new Exception("cannot get classes");
}
return $aClasses;
}
private function RemoveExtensionsLocally(string $sTargetEnv, array $aExtensionCodes): void
{
$oExtensionsMap = new \iTopExtensionsMap($sTargetEnv);
foreach ($aExtensionCodes as $sCode) {
/** @var \iTopExtension $oExtension */
$oExtension = $oExtensionsMap->Get($sCode);
if (!is_null($oExtension)) {
$sDir = $oExtension->sSourceDir;
\IssueLog::Info(__METHOD__.": remove extension locally", null, [$oExtension->sCode => $sDir]);
SetupUtils::rrmdir($sDir);
} else {
\IssueLog::Warning(__METHOD__." cannot find extensions", null, ['env' => $sTargetEnv, 'code' => $sCode]);
}
}
}
}*/
public function GetRemovedClasses(): array
{
@@ -161,8 +78,23 @@ class SetupAudit
return $this->aRemovedClasses;
}
public function AuditExtensionsCleanupRules(bool $bStopAtFirstIssue = false): array
/** test only: return file path that force audit error being raised
*
* @return string
*/
public static function GetErrorMessageFilePathForTestOnly(): string
{
return APPROOT."/data/".self::GETISSUE_ERROR_MSG_FILE_FORTESTONLY;
}
public function GetIssues(bool $bThrowExceptionAtFirstIssue = false): array
{
$sErrorMessageFilePath = self::GetErrorMessageFilePathForTestOnly();
if ($bThrowExceptionAtFirstIssue && is_file($sErrorMessageFilePath)) {
$sMsg = file_get_contents($sErrorMessageFilePath);
throw new \Exception($sMsg);
}
$this->aFinalClassesRemoved = [];
foreach ($this->GetRemovedClasses() as $sClass) {
@@ -173,7 +105,7 @@ class SetupAudit
if (!MetaModel::IsStandaloneClass($sClass)) {
$iCount = $this->Count($sClass);
$this->aFinalClassesRemoved[$sClass] = $iCount;
if ($bStopAtFirstIssue && $iCount > 0) {
if ($bThrowExceptionAtFirstIssue && $iCount > 0) {
//setup envt: should raise issue ASAP
throw new \Exception($sClass);
}