Merge branch 'support/3.2' into develop

This commit is contained in:
odain
2025-11-07 20:33:14 +01:00
1837 changed files with 33034 additions and 34549 deletions

View File

@@ -1,4 +1,5 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -14,7 +15,6 @@ use MetaModel;
use SetupUtils;
use utils;
/**
* Class ItopCustomDatamodelTestCase
*
@@ -30,7 +30,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
{
/**
* @var UnitTestRunTimeEnvironment
*/
*/
protected $oEnvironment = null;
/**
@@ -49,15 +49,15 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
$this->setRunClassInSeparateProcess(true);
}
/**
/**
* @return string Abs path to the XML delta to use for the tests of that class
*/
abstract public function GetDatamodelDeltaAbsPath(): string;
protected function setUp(): void
{
static::LoadRequiredItopFiles();
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
static::LoadRequiredItopFiles();
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
parent::setUp();
}
@@ -107,8 +107,8 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
if (false === file_exists($this->GetTestEnvironmentFolderAbsPath())) {
return false;
}
return $this->oEnvironment->IsUpToDate();
}
return $this->oEnvironment->IsUpToDate();
}
/**
* @inheritDoc
@@ -124,10 +124,10 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
if (false === $this->IsEnvironmentReady()) {
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
$this->debug(" - $sCustomDatamodelFile");
}
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
$this->debug(" - $sCustomDatamodelFile");
}
//----------------------------------------------------
// Clear any previous "$sTestEnv" environment
@@ -169,7 +169,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
}
CMDBSource::CreateDB($oTestConfig->Get('db_name'));
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv);
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
MetaModel::DBCreate();
$this->debug("Custom environment '$sTestEnv' is ready!");

View File

@@ -1,4 +1,5 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -14,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use ReflectionMethod;
use SetupUtils;
use Symfony\Component\HttpKernel\KernelInterface;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
/**
@@ -97,7 +99,6 @@ abstract class ItopTestCase extends KernelTestCase
}
}
/**
* @param array $args
* @param string $sExportFileName relative to log folder
@@ -141,15 +142,16 @@ abstract class ItopTestCase extends KernelTestCase
}
$var_export = var_export($paramValues, true);
file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
file_put_contents(APPROOT.'/log/'.$sExportFileName, $var_export);
return $var_export;
}
protected function setUp(): void {
protected function setUp(): void
{
parent::setUp();
// Hack - Required the first time the Portal kernel is booted on a newly installed iTop
$_ENV['COMBODO_PORTAL_BASE_ABSOLUTE_PATH'] = __DIR__ . '/../../../../../env-production/itop-portal-base/portal/public/';
$_ENV['COMBODO_PORTAL_BASE_ABSOLUTE_PATH'] = __DIR__.'/../../../../../env-production/itop-portal-base/portal/public/';
$this->LoadRequiredItopFiles();
$this->LoadRequiredTestFiles();
@@ -188,22 +190,22 @@ abstract class ItopTestCase extends KernelTestCase
$sAppRootPath = static::GetFirstDirUpContainingFile(__DIR__, 'approot.inc.php');
return $sAppRootPath . '/';
return $sAppRootPath.'/';
}
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
{
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
$aGlobFiles = glob($sSearchPath.'/'.$sFileToFindGlobPattern);
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
return $sSearchPath . '/';
return $sSearchPath.'/';
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in ' . $sSearchPath;
return 'Could not find the approot file in '.$sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
@@ -211,7 +213,6 @@ abstract class ItopTestCase extends KernelTestCase
return null;
}
/**
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
*
@@ -222,7 +223,7 @@ abstract class ItopTestCase extends KernelTestCase
{
// At least make sure that the autoloader will be loaded, and that the APPROOT constant is defined
require_once __DIR__.'/../../../../approot.inc.php';
}
}
/**
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
@@ -246,7 +247,7 @@ abstract class ItopTestCase extends KernelTestCase
*/
protected function RequireOnceItopFile(string $sFileRelPath): void
{
require_once $this->GetAppRoot() . $sFileRelPath;
require_once $this->GetAppRoot().$sFileRelPath;
}
/**
@@ -263,7 +264,7 @@ abstract class ItopTestCase extends KernelTestCase
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$sCallerDirAbsPath = dirname($aStack[0]['file']);
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
require_once $sCallerDirAbsPath.DIRECTORY_SEPARATOR.$sFileRelPath;
}
protected function debug($sMsg)
@@ -272,11 +273,11 @@ abstract class ItopTestCase extends KernelTestCase
if (is_string($sMsg)) {
echo "$sMsg\n";
} else {
/** @noinspection ForgottenDebugOutputInspection */
print_r($sMsg);
}
}
}
/** @noinspection ForgottenDebugOutputInspection */
print_r($sMsg);
}
}
}
public function GetMicroTime()
{
@@ -287,8 +288,7 @@ abstract class ItopTestCase extends KernelTestCase
public function WriteToCsvHeader($sFilename, $aHeader)
{
$sResultFile = APPROOT.'log/'.$sFilename;
if (is_file($sResultFile))
{
if (is_file($sResultFile)) {
@unlink($sResultFile);
}
SetupUtils::builddir(dirname($sResultFile));
@@ -379,7 +379,9 @@ abstract class ItopTestCase extends KernelTestCase
{
$class = new \ReflectionClass($sClass);
foreach ($class->getProperties() as $property) {
if (!$property->isStatic()) continue;
if (!$property->isStatic()) {
continue;
}
$property->setAccessible(true);
static::$aBackupStaticProperties[$sClass][$property->getName()] = $property->getValue();
}
@@ -397,7 +399,9 @@ abstract class ItopTestCase extends KernelTestCase
{
$class = new \ReflectionClass($sClass);
foreach ($class->getProperties() as $property) {
if (!$property->isStatic()) continue;
if (!$property->isStatic()) {
continue;
}
$property->setAccessible(true);
$property->setValue(null, static::$aBackupStaticProperties[$sClass][$property->getName()]);
}
@@ -415,7 +419,6 @@ abstract class ItopTestCase extends KernelTestCase
return $oProperty;
}
/**
* @param object $oObject
* @param string $sProperty
@@ -455,38 +458,38 @@ abstract class ItopTestCase extends KernelTestCase
}
}
public static function CreateTmpdir() {
$sTmpDir=tempnam(sys_get_temp_dir(),'');
if (file_exists($sTmpDir))
{
public static function CreateTmpdir()
{
$sTmpDir = tempnam(sys_get_temp_dir(), '');
if (file_exists($sTmpDir)) {
unlink($sTmpDir);
}
mkdir($sTmpDir);
if (is_dir($sTmpDir))
{
if (is_dir($sTmpDir)) {
return $sTmpDir;
}
return sys_get_temp_dir();
}
public static function RecurseMkdir($sDir){
if (strpos($sDir, DIRECTORY_SEPARATOR) === 0){
public static function RecurseMkdir($sDir)
{
if (strpos($sDir, DIRECTORY_SEPARATOR) === 0) {
$sPath = DIRECTORY_SEPARATOR;
} else {
$sPath = "";
}
foreach (explode(DIRECTORY_SEPARATOR, $sDir) as $sSubDir){
foreach (explode(DIRECTORY_SEPARATOR, $sDir) as $sSubDir) {
if (($sSubDir === '..')) {
break;
}
if (( trim($sSubDir) === '' ) || ( $sSubDir === '.' )) {
if ((trim($sSubDir) === '') || ($sSubDir === '.')) {
continue;
}
$sPath .= $sSubDir . DIRECTORY_SEPARATOR;
$sPath .= $sSubDir.DIRECTORY_SEPARATOR;
if (!is_dir($sPath)) {
var_dump($sPath);
@mkdir($sPath);
@@ -495,16 +498,16 @@ abstract class ItopTestCase extends KernelTestCase
}
public static function RecurseCopy($src,$dst) {
public static function RecurseCopy($src, $dst)
{
$dir = opendir($src);
@mkdir($dst);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
if ( is_dir($src . '/' . $file) ) {
static::RecurseCopy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
}
else {
copy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src.'/'.$file)) {
static::RecurseCopy($src.DIRECTORY_SEPARATOR.$file, $dst.DIRECTORY_SEPARATOR.$file);
} else {
copy($src.DIRECTORY_SEPARATOR.$file, $dst.DIRECTORY_SEPARATOR.$file);
}
}
}
@@ -553,7 +556,7 @@ abstract class ItopTestCase extends KernelTestCase
/**
* @since 3.2.1
*/
static protected function AssertDateEqualsNow($sActualDate, $sMessage = ''): void
protected static function AssertDateEqualsNow($sActualDate, $sMessage = ''): void
{
$oActualDate = \DateTime::createFromFormat(\AttributeDate::GetInternalFormat(), $sActualDate);
$oNow = new \DateTime();
@@ -563,7 +566,7 @@ abstract class ItopTestCase extends KernelTestCase
/**
* @since 3.2.1
*/
static protected function AssertDateTimeEqualsNow($sActualDate, $sMessage = ''): void
protected static function AssertDateTimeEqualsNow($sActualDate, $sMessage = ''): void
{
$oActualDateTime = \DateTime::createFromFormat(\AttributeDateTime::GetInternalFormat(), $sActualDate);
$oNow = new \DateTime();
@@ -576,17 +579,17 @@ abstract class ItopTestCase extends KernelTestCase
*
* @see static::bootKernel(), static::getContainer()
* @see \Combodo\iTop\Kernel, \Combodo\iTop\Portal\Kernel
*
*
* @param string $sKernelClass
*
* @since 3.2.1
*/
static protected function SetKernelClass(string $sKernelClass): void
protected static function SetKernelClass(string $sKernelClass): void
{
$_SERVER['KERNEL_CLASS'] = $sKernelClass;
}
static protected function bootKernel(array $options = []): KernelInterface
protected static function bootKernel(array $options = []): KernelInterface
{
if (!array_key_exists('KERNEL_CLASS', $_SERVER)) {
throw new \LogicException('static::SetKernelClass() must be called before booting the kernel.');
@@ -599,31 +602,33 @@ abstract class ItopTestCase extends KernelTestCase
*
* @since 3.2.1
*/
static protected function ReadTail($sFilename, $iLines = 1)
protected static function ReadTail($sFilename, $iLines = 1)
{
$handle = fopen($sFilename, "r");
$iLineCounter = $iLines;
$iPos = -2;
$bBeginning = false;
$aLines = array();
$aLines = [];
while ($iLineCounter > 0) {
$sChar = " ";
while ($sChar != "\n") {
if(fseek($handle, $iPos, SEEK_END) == -1) {
if (fseek($handle, $iPos, SEEK_END) == -1) {
$bBeginning = true;
break;
}
$sChar = fgetc($handle);
$iPos --;
$iPos--;
}
$iLineCounter --;
$iLineCounter--;
if ($bBeginning) {
rewind($handle);
}
$aLines[$iLines - $iLineCounter - 1] = fgets($handle);
if ($bBeginning) break;
if ($bBeginning) {
break;
}
}
fclose ($handle);
fclose($handle);
return array_reverse($aLines);
}
}

View File

@@ -16,7 +16,6 @@ use RunTimeEnvironment;
use SetupUtils;
use utils;
/**
* Class UnitTestRunTimeEnvironment
*
@@ -32,18 +31,18 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
*/
protected $aCustomDatamodelFiles = null;
/**
* @var string
*/
protected $sSourceEnv;
/**
* @var string
*/
protected $sSourceEnv;
public function __construct($sSourceEnv, $sTargetEnv)
{
parent::__construct($sTargetEnv);
$this->sSourceEnv = $sSourceEnv;
}
public function __construct($sSourceEnv, $sTargetEnv)
{
parent::__construct($sTargetEnv);
$this->sSourceEnv = $sSourceEnv;
}
public function GetEnvironment(): string
public function GetEnvironment(): string
{
return $this->sFinalEnv;
}
@@ -60,19 +59,19 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
parent::CompileFrom($sSourceEnv, $bUseSymLinks);
}
public function IsUpToDate()
{
clearstatcache();
$fLastCompilationTime = filemtime(APPROOT.'env-'.$this->sFinalEnv);
$aModifiedFiles = [];
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'datamodels/2.x', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'extensions', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'data/production-modules', $aModifiedFiles);
foreach ($this->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
if (filemtime($sCustomDatamodelFile) > $fLastCompilationTime) {
$aModifiedFiles[] = $sCustomDatamodelFile;
}
}
public function IsUpToDate()
{
clearstatcache();
$fLastCompilationTime = filemtime(APPROOT.'env-'.$this->sFinalEnv);
$aModifiedFiles = [];
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'datamodels/2.x', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'extensions', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'data/production-modules', $aModifiedFiles);
foreach ($this->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
if (filemtime($sCustomDatamodelFile) > $fLastCompilationTime) {
$aModifiedFiles[] = $sCustomDatamodelFile;
}
}
// Keep only xml files
$aFilesToCompile = [];
foreach ($aModifiedFiles as $sModifiedFile) {
@@ -80,17 +79,17 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
$aFilesToCompile[] = $sModifiedFile;
}
}
$aModifiedFiles = $aFilesToCompile;
if (count($aModifiedFiles) > 0) {
echo "The following files have been modified after the last compilation:\n";
foreach ($aModifiedFiles as $sFile) {
echo " - $sFile\n";
}
}
return (count($aModifiedFiles) === 0);
$aModifiedFiles = $aFilesToCompile;
if (count($aModifiedFiles) > 0) {
echo "The following files have been modified after the last compilation:\n";
foreach ($aModifiedFiles as $sFile) {
echo " - $sFile\n";
}
}
return (count($aModifiedFiles) === 0);
}
/**
/**
* @inheritDoc
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
@@ -99,8 +98,8 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
$sDeltaName = basename($sDeltaFile);
$sDeltaDir = dirname($sDeltaFile);
$sDeltaName = basename($sDeltaFile);
$sDeltaDir = dirname($sDeltaFile);
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
$aRet[$sDeltaId] = $oDelta;
}
@@ -119,16 +118,16 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
// APPROOT, APPROOT/extensions/*, APPROOT/data/production-modules/*, APPROOT/data/production-modules/*/*
$aTestDirs = [];
foreach(['', 'extensions/*/', 'data/production-modules/*/', 'data/production-modules/*/*/'] as $sRoot) {
foreach (['', 'extensions/*/', 'data/production-modules/*/', 'data/production-modules/*/*/'] as $sRoot) {
$aTestDirs = array_merge($aTestDirs, glob(APPROOT.$sRoot.'tests', GLOB_ONLYDIR));
}
$aLoadedTestClasses = [];
foreach($aTestDirs as $sTestDir) {
foreach ($aTestDirs as $sTestDir) {
// Iterate on all PHP files in subdirectories
// Note: grep is not available on Windows, so we will use the PHP Reflection API
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sTestDir)) as $oFile) {
if ($oFile->isDir()){
if ($oFile->isDir()) {
continue;
}
if (! utils::EndsWith($oFile->getFilename(), 'Test.php')) {
@@ -154,16 +153,16 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
if (in_array($sClass, $aLoadedTestClasses)) {
continue;
}
$aLoadedTestClasses[]=$sClass;
require_once $sFile;
$aLoadedTestClasses[] = $sClass;
require_once $sFile;
$oReflectionClass = new ReflectionClass($sClass);
if ($oReflectionClass->isAbstract()) {
continue;
}
// Check if the class extends ItopCustomDatamodelTestCase
if (!$oReflectionClass->isSubclassOf(ItopCustomDatamodelTestCase::class)) {
continue;
}
// Check if the class extends ItopCustomDatamodelTestCase
if (!$oReflectionClass->isSubclassOf(ItopCustomDatamodelTestCase::class)) {
continue;
}
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
$oTestClassInstance = new $sClass();
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
@@ -182,18 +181,18 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
return $this->aCustomDatamodelFiles;
}
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
{
if (!is_dir($sPathToScan)) {
return;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sPathToScan)) as $oFile) {
if ($oFile->isDir()) {
continue;
}
if (filemtime($oFile->getPathname()) > $fReferenceTimestamp) {
$aModifiedFiles[] = $oFile->getPathname();
}
}
}
}
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
{
if (!is_dir($sPathToScan)) {
return;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sPathToScan)) as $oFile) {
if ($oFile->isDir()) {
continue;
}
if (filemtime($oFile->getPathname()) > $fReferenceTimestamp) {
$aModifiedFiles[] = $oFile->getPathname();
}
}
}
}