mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-19 08:38:45 +02:00
Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts: # tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
This commit is contained in:
@@ -55,12 +55,6 @@ define('TAG_ATTCODE', 'domains');
|
||||
*
|
||||
* Helper class to extend for tests needing access to iTop's metamodel
|
||||
*
|
||||
* **⚠ Warning** Each class extending this one needs to add the following annotations :
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4624 processIsolation is disabled by default and must be enabled in each test needing it (basically all tests using
|
||||
* iTop datamodel)
|
||||
*/
|
||||
@@ -69,10 +63,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
private $iTestOrgId;
|
||||
|
||||
// For cleanup
|
||||
private $aCreatedObjects = array();
|
||||
|
||||
// Counts
|
||||
public $aReloadCount = [];
|
||||
private $aCreatedObjects = [];
|
||||
private $aEventListeners = [];
|
||||
|
||||
/**
|
||||
* @var string Default environment to use for test cases
|
||||
@@ -81,6 +73,23 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = false;
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
\UserRights::FlushPrivileges();
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -98,8 +107,6 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
$this->CreateTestOrganization();
|
||||
}
|
||||
|
||||
EventService::RegisterListener(EVENT_DB_OBJECT_RELOAD, [$this, 'CountObjectReload']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +114,9 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
static::SetNonPublicStaticProperty(\cmdbAbstractObject::class, 'aObjectsAwaitingEventDbLinksChanged', []);
|
||||
\cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
|
||||
if (static::USE_TRANSACTION) {
|
||||
$this->debug("ROLLBACK !!!");
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -128,6 +138,15 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
}
|
||||
}
|
||||
// As soon as a rollback has been performed, each object memoized should be discarded
|
||||
CMDBObject::SetCurrentChange(null);
|
||||
|
||||
// Leave the place clean
|
||||
\UserRights::Logoff();
|
||||
|
||||
foreach ($this->aEventListeners as $sListenerId) {
|
||||
EventService::UnRegisterListener($sListenerId);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
@@ -186,6 +205,31 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
return $this->iTestOrgId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// Facades for environment settings
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Facade for EventService::RegisterListener
|
||||
*
|
||||
* @param string $sEvent
|
||||
* @param callable $callback
|
||||
* @param $sEventSource
|
||||
* @param array $aCallbackData
|
||||
* @param $context
|
||||
* @param float $fPriority
|
||||
* @param $sModuleId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function EventService_RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
||||
{
|
||||
$ret = EventService::RegisterListener($sEvent, $callback, $sEventSource, $aCallbackData, $context, $fPriority, $sModuleId);
|
||||
if (false !== $ret) {
|
||||
$this->aEventListeners[] = $ret;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// MetaModel Utilities
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -880,49 +924,6 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
return $oOrg;
|
||||
}
|
||||
|
||||
public function ResetReloadCount()
|
||||
{
|
||||
$this->aReloadCount = [];
|
||||
}
|
||||
|
||||
public function DebugReloadCount($sMsg, $bResetCount = true)
|
||||
{
|
||||
$iTotalCount = 0;
|
||||
$aTotalPerClass = [];
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$iClassCount = 0;
|
||||
foreach ($aCountByKeys as $iCount) {
|
||||
$iClassCount += $iCount;
|
||||
}
|
||||
$iTotalCount += $iClassCount;
|
||||
$aTotalPerClass[$sClass] = $iClassCount;
|
||||
}
|
||||
$this->debug("$sMsg - $iTotalCount reload(s)");
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$this->debug(" $sClass => $aTotalPerClass[$sClass] reload(s)");
|
||||
foreach ($aCountByKeys as $sKey => $iCount) {
|
||||
$this->debug(" $sClass::$sKey => $iCount");
|
||||
}
|
||||
}
|
||||
if ($bResetCount) {
|
||||
$this->ResetReloadCount();
|
||||
}
|
||||
}
|
||||
|
||||
public function CountObjectReload(EventData $oData)
|
||||
{
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$sKey = $oObject->GetKey();
|
||||
$iCount = $this->GetObjectReloadCount($sClass, $sKey);
|
||||
$this->aReloadCount[$sClass][$sKey] = 1 + $iCount;
|
||||
}
|
||||
|
||||
public function GetObjectReloadCount($sClass, $sKey)
|
||||
{
|
||||
return $this->aReloadCount[$sClass][$sKey] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a series of operations will trigger a given number of MySL queries
|
||||
*
|
||||
@@ -949,6 +950,17 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
}
|
||||
|
||||
protected function assertDBChangeOpCount(string $sClass, $iId, int $iExpectedCount)
|
||||
{
|
||||
$oSearch = new \DBObjectSearch('CMDBChangeOp');
|
||||
$oSearch->AddCondition('objclass', $sClass);
|
||||
$oSearch->AddCondition('objkey', $iId);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
$iCount = $oSet->Count();
|
||||
$this->assertEquals($iExpectedCount, $iCount, "Found $iCount changes for object $sClass::$iId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a set of XML files describing a consistent set of iTop objects
|
||||
* @param string[] $aFiles
|
||||
|
||||
@@ -21,30 +21,54 @@ use SetupUtils;
|
||||
*/
|
||||
abstract class ItopTestCase extends TestCase
|
||||
{
|
||||
const TEST_LOG_DIR = 'test';
|
||||
static $DEBUG_UNIT_TEST = false;
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
public static $DEBUG_UNIT_TEST = false;
|
||||
|
||||
/** @noinspection UsingInclusionOnceReturnValueInspection avoid errors for approot includes */
|
||||
protected function setUp(): void {
|
||||
$sAppRootRelPath = 'approot.inc.php';
|
||||
$sDepthSeparator = '../';
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
if (file_exists($sAppRootRelPath)) {
|
||||
require_once $sAppRootRelPath;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Override the default value to disable the backup of globals in case of tests run in a separate process
|
||||
*/
|
||||
protected $preserveGlobalState = false;
|
||||
|
||||
$sAppRootRelPath = $sDepthSeparator.$sAppRootRelPath;
|
||||
}
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
static::$DEBUG_UNIT_TEST = getenv('DEBUG_UNIT_TEST');
|
||||
|
||||
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
|
||||
require_once static::GetAppRoot() . 'approot.inc.php';
|
||||
|
||||
if (false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME)) {
|
||||
if (false === defined('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME')) {
|
||||
// setUp might be called multiple times, so protecting the define() call !
|
||||
define(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME, true);
|
||||
define('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
parent::tearDownAfterClass();
|
||||
|
||||
if (method_exists('utils', 'GetConfig')) {
|
||||
// Reset the config by forcing the load from disk
|
||||
$oConfig = \utils::GetConfig(true);
|
||||
if (method_exists('MetaModel', 'SetConfig')) {
|
||||
\MetaModel::SetConfig($oConfig);
|
||||
}
|
||||
}
|
||||
if (method_exists('Dict', 'SetUserLanguage')) {
|
||||
\Dict::SetUserLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
|
||||
|
||||
$this->LoadRequiredItopFiles();
|
||||
$this->LoadRequiredTestFiles();
|
||||
@@ -60,10 +84,38 @@ abstract class ItopTestCase extends TestCase
|
||||
|
||||
if (CMDBSource::IsInsideTransaction()) {
|
||||
// Nested transactions were opened but not finished !
|
||||
// Rollback to avoid side effects on next tests
|
||||
while (CMDBSource::IsInsideTransaction()) {
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper than can be called in the context of a data provider */
|
||||
public static function GetAppRoot()
|
||||
{
|
||||
if (defined('APPROOT')) {
|
||||
return APPROOT;
|
||||
}
|
||||
$sSearchPath = __DIR__;
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
if (file_exists($sSearchPath.'/approot.inc.php')) {
|
||||
break;
|
||||
}
|
||||
$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;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return $sSearchPath.'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
|
||||
*
|
||||
@@ -97,7 +149,7 @@ abstract class ItopTestCase extends TestCase
|
||||
*/
|
||||
protected function RequireOnceItopFile(string $sFileRelPath): void
|
||||
{
|
||||
require_once APPROOT . $sFileRelPath;
|
||||
require_once $this->GetAppRoot() . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +217,7 @@ abstract class ItopTestCase extends TestCase
|
||||
/**
|
||||
* @since 2.7.4 3.0.0
|
||||
*/
|
||||
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs)
|
||||
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs = [])
|
||||
{
|
||||
return $this->InvokeNonPublicMethod($sObjectClass, $sMethodName, null, $aArgs);
|
||||
}
|
||||
@@ -182,7 +234,7 @@ abstract class ItopTestCase extends TestCase
|
||||
*
|
||||
* @since 2.7.4 3.0.0
|
||||
*/
|
||||
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs)
|
||||
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs = [])
|
||||
{
|
||||
$class = new \ReflectionClass($sObjectClass);
|
||||
$method = $class->getMethod($sMethodName);
|
||||
|
||||
Reference in New Issue
Block a user