From aef3c2e609a6ad60b8c3b0c34a52c20312e82822 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 3 Aug 2023 16:47:53 +0200 Subject: [PATCH 1/8] =?UTF-8?q?N=C2=B06097=20-=20Fix=20\CMDBSource::DropDB?= =?UTF-8?q?()=20not=20resetting=20cache=20like=20\CMDBSource::DropTable()?= =?UTF-8?q?=20which=20can=20lead=20to=20errors=20when=20trying=20to=20re-c?= =?UTF-8?q?reate=20it=20afterwards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/cmdbsource.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 6c0839cc7..eab8fffb5 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -535,6 +535,7 @@ class CMDBSource { self::$m_sDBName = ''; } + self::_TablesInfoCacheReset(); // reset the table info cache! } public static function CreateTable($sQuery) From 851ab9c3563b139c2986fff4d02b8e1f3b966b57 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 3 Aug 2023 16:49:06 +0200 Subject: [PATCH 2/8] =?UTF-8?q?N=C2=B06097=20-=20Add=20\utils::GetDataPath?= =?UTF-8?q?()=20method=20to=20avoid=20duplicating=20manual=20path=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/utils.inc.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index ba9d3160c..15195c1f3 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1228,13 +1228,23 @@ class utils } } + /** + * @return string A path to the folder into which data can be written + * @internal + * @since N°6097 2.7.10 3.0.4 3.1.1 + */ + public static function GetDataPath(): string + { + return APPROOT.'data/'; + } + /** * @return string A path to a folder into which any module can store cache data * The corresponding folder is created or cleaned upon code compilation */ public static function GetCachePath() { - return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/'; + return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/'; } /** * @return string A path to a folder into which any module can store log From fe3467309d73380084811dc9b614f3ea0edbbe1c Mon Sep 17 00:00:00 2001 From: Molkobain Date: Mon, 13 Mar 2023 10:33:54 +0100 Subject: [PATCH 3/8] =?UTF-8?q?N=C2=B06097=20-=20Tests:=20Refactor=20base?= =?UTF-8?q?=20test=20classes=20for=20better=20extensibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/php-unit-tests/ItopDataTestCase.php | 59 ++++++++++++++++++++--- tests/php-unit-tests/ItopTestCase.php | 31 +++++++++--- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/tests/php-unit-tests/ItopDataTestCase.php b/tests/php-unit-tests/ItopDataTestCase.php index 0d72db075..d3b9c5f2e 100644 --- a/tests/php-unit-tests/ItopDataTestCase.php +++ b/tests/php-unit-tests/ItopDataTestCase.php @@ -1,5 +1,5 @@ RequireOnceItopFile('application/startup.inc.php'); - $this->RequireOnceItopFile('application/utils.inc.php'); - $sEnv = 'production'; - $sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE; - MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv); + $this->PrepareEnvironment(); if (static::USE_TRANSACTION) { @@ -128,6 +128,51 @@ class ItopDataTestCase extends ItopTestCase parent::tearDown(); } + /** + * @inheritDoc + */ + protected function LoadRequiredFiles(): void + { + $this->RequireOnceItopFile('application/startup.inc.php'); + $this->RequireOnceItopFile('application/utils.inc.php'); + } + + /** + * @return string Environment in the test will run + * @since 2.7.9 3.0.4 3.1.0 + */ + protected function GetTestEnvironment(): string + { + return 'production'; + } + + /** + * @return string Absolute path of the configuration file used for the test + * @since 2.7.9 3.0.4 3.1.0 + */ + protected function GetConfigFileAbsPath(): string + { + return utils::GetConfigFilePath($this->GetTestEnvironment()); + } + + /** + * Prepare the iTop environment for test to run + * + * @return void + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MySQLException + * @since 2.7.9 3.0.4 3.1.0 + */ + protected function PrepareEnvironment(): void + { + $sEnv = $this->GetTestEnvironment(); + $sConfigFile = $this->GetConfigFileAbsPath(); + + // Start MetaModel for the prepared environment + MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv); + } + /** * @return mixed */ diff --git a/tests/php-unit-tests/ItopTestCase.php b/tests/php-unit-tests/ItopTestCase.php index 94407524d..f95c09545 100644 --- a/tests/php-unit-tests/ItopTestCase.php +++ b/tests/php-unit-tests/ItopTestCase.php @@ -1,6 +1,6 @@ + * @package Combodo\iTop\Test\UnitTest + */ +abstract class ItopTestCase extends TestCase { const TEST_LOG_DIR = 'test'; @@ -49,6 +51,8 @@ class ItopTestCase extends TestCase $sAppRootRelPath = $sDepthSeparator.$sAppRootRelPath; } + + $this->LoadRequiredFiles(); } /** @@ -65,6 +69,17 @@ class ItopTestCase extends TestCase } } + /** + * Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()} and {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()} + * + * @return void + * @since 2.7.9 3.0.4 3.1.0 + */ + protected function LoadRequiredFiles(): void + { + // Empty until we actually need to require some files in the class + } + /** * Require once an iTop file (core or extension) from its relative path to the iTop root dir. * This ensure to always use the right absolute path, especially in {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()} From b86d70623ecd412a3bbf3849c92f119ce27e3cbd Mon Sep 17 00:00:00 2001 From: Molkobain Date: Mon, 13 Mar 2023 14:21:17 +0100 Subject: [PATCH 4/8] =?UTF-8?q?N=C2=B06097=20-=20Tests:=20Temporarily=20ad?= =?UTF-8?q?d=20test=20case=20for=20the=20new=20ItopCustomDatamodelTestCase?= =?UTF-8?q?=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unitary-tests/core/TestGLATest.delta.xml | 14 +++++++ .../unitary-tests/core/TestGLATest.php | 39 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml create mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLATest.php diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml b/tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml new file mode 100644 index 000000000..a69608f08 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml @@ -0,0 +1,14 @@ + + + + + + + tested_attribute + + true + + + + + diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLATest.php b/tests/php-unit-tests/unitary-tests/core/TestGLATest.php new file mode 100644 index 000000000..10e876327 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/TestGLATest.php @@ -0,0 +1,39 @@ + + *
  • MakeGroupByQuery
  • + * + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + * @backupGlobals disabled + */ +class TestGLATest extends ItopCustomDatamodelTestCase +{ + /** + * @inheritDoc + */ + public function GetDatamodelDeltaAbsPath(): string + { + return APPROOT.'tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml'; + } + + public function testFoo() + { + static::assertFalse(MetaModel::IsValidAttCode('Person', 'non_existing_attribute')); + static::assertTrue(MetaModel::IsValidAttCode('Person', 'tested_attribute')); + } +} From f002aa04cdbe728be02ae8b49a7d4c72767c09c0 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Mon, 13 Mar 2023 10:34:32 +0100 Subject: [PATCH 5/8] =?UTF-8?q?N=C2=B06097=20-=20Tests:=20Enable=20PHP=20u?= =?UTF-8?q?nit=20tests=20on=20a=20custom=20DataModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ItopCustomDatamodelTestCase.php | 152 +++++++++++++ tests/php-unit-tests/ItopDataTestCase.php | 5 +- tests/php-unit-tests/ItopTestCase.php | 18 +- .../UnitTestRunTimeEnvironment.php | 79 +++++++ .../ItopCustomDatamodelTestCase.php | 200 ++++++++++++++++++ .../core/TestGLA/TestGLA2Test.delta.xml | 14 ++ .../core/TestGLA/TestGLA2Test.php | 34 +++ .../core/{ => TestGLA}/TestGLATest.delta.xml | 0 .../core/{ => TestGLA}/TestGLATest.php | 9 +- tests/php-unit-tests/unittestautoload.php | 1 + 10 files changed, 500 insertions(+), 12 deletions(-) create mode 100644 tests/php-unit-tests/ItopCustomDatamodelTestCase.php create mode 100644 tests/php-unit-tests/UnitTestRunTimeEnvironment.php create mode 100644 tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php create mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml create mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php rename tests/php-unit-tests/unitary-tests/core/{ => TestGLA}/TestGLATest.delta.xml (100%) rename tests/php-unit-tests/unitary-tests/core/{ => TestGLA}/TestGLATest.php (85%) diff --git a/tests/php-unit-tests/ItopCustomDatamodelTestCase.php b/tests/php-unit-tests/ItopCustomDatamodelTestCase.php new file mode 100644 index 000000000..6eff53a52 --- /dev/null +++ b/tests/php-unit-tests/ItopCustomDatamodelTestCase.php @@ -0,0 +1,152 @@ +GetTestEnvironment().'.log'; + IssueLog::Enable($sLogFileAbsPath); + } + + + /** + * @return string Abs path to the XML delta to use for the tests of that class + */ + abstract public function GetDatamodelDeltaAbsPath(): string; + + /** + * @inheritDoc + */ + protected function LoadRequiredFiles(): void + { + $this->RequireOnceItopFile('setup/setuputils.class.inc.php'); + $this->RequireOnceItopFile('setup/runtimeenv.class.inc.php'); + $this->RequireOnceUnitTestFile('UnitTestRunTimeEnvironment.php'); + } + + /** + * @return string Environment used as a base (conf. file, modules, DB, ...) to prepare the test environment + */ + protected function GetSourceEnvironment(): string + { + return 'production'; + } + + /** + * @inheritDoc + * + * This is final for now as we don't support yet to have several environments in // to test incompatible DMs / deltas. + * When / if we do this, keep in mind that should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global test time. + */ + final public function GetTestEnvironment(): string + { + return 'php-unit-tests'; + } + + /** + * @inheritDoc + */ + protected function PrepareEnvironment(): void + { + $sSourceEnv = $this->GetSourceEnvironment(); + $sTestEnv = $this->GetTestEnvironment(); + + // Check if test env. if already set and only prepare it if it doesn't already exist + // + // Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once. + // This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457 + if (false === static::$bIsCustomEnvironmentReady) { + //---------------------------------------------------- + // Clear any previous "$sTestEnv" environment + //---------------------------------------------------- + + // - Configuration file + $sConfFile = utils::GetConfigFilePath($sTestEnv); + $sConfFolder = dirname($sConfFile); + if (is_file($sConfFile)) { + chmod($sConfFile, 0777); + SetupUtils::tidydir($sConfFolder); + } + + // - Datamodel delta files + // - Cache folder + // - Compiled folder + // We don't need to clean them as they are already by the compilation + + // - Drop database + // We don't do that now, it will be done before re-creating the DB, once the metamodel is started + + //---------------------------------------------------- + // Prepare "$sTestEnv" environment + //---------------------------------------------------- + + // All the following is greatly inspired by the toolkit's sandbox script + // - Prepare config file + $oSourceConf = new Config(utils::GetConfigFilePath($sSourceEnv)); + if ($oSourceConf->Get('source_dir') === '') { + throw new Exception('Missing entry source_dir from the config file'); + } + + $oTestConfig = clone($oSourceConf); + $oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv); + // - Switch DB name to a dedicated one so we don't mess with the original one + $sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv); + $oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName); + + // - Compile env. based on the existing 'production' env. + $oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv); + $oEnvironment->WriteConfigFileSafe($oTestConfig); + $oEnvironment->CompileFrom($sSourceEnv, false); + + // - Force re-creating of the DB +// // TODO: Create tmp DB + // But how to use it now when the metamodel is not started yet ?? +// MetaModel::LoadConfig($oTestConfig); +// if (MetaModel::DBExists()) { +// MetaModel::DBDrop(); +// } +// MetaModel::DBCreate(); + + static::$bIsCustomEnvironmentReady = true; + } + + parent::PrepareEnvironment(); + } +} diff --git a/tests/php-unit-tests/ItopDataTestCase.php b/tests/php-unit-tests/ItopDataTestCase.php index d3b9c5f2e..c68e116db 100644 --- a/tests/php-unit-tests/ItopDataTestCase.php +++ b/tests/php-unit-tests/ItopDataTestCase.php @@ -131,9 +131,10 @@ abstract class ItopDataTestCase extends ItopTestCase /** * @inheritDoc */ - protected function LoadRequiredFiles(): void + protected function LoadRequiredItopFiles(): void { - $this->RequireOnceItopFile('application/startup.inc.php'); + parent::LoadRequiredItopFiles(); + $this->RequireOnceItopFile('application/utils.inc.php'); } diff --git a/tests/php-unit-tests/ItopTestCase.php b/tests/php-unit-tests/ItopTestCase.php index f95c09545..12eeeb434 100644 --- a/tests/php-unit-tests/ItopTestCase.php +++ b/tests/php-unit-tests/ItopTestCase.php @@ -52,7 +52,8 @@ abstract class ItopTestCase extends TestCase $sAppRootRelPath = $sDepthSeparator.$sAppRootRelPath; } - $this->LoadRequiredFiles(); + $this->LoadRequiredItopFiles(); + $this->LoadRequiredTestFiles(); } /** @@ -70,12 +71,23 @@ abstract class ItopTestCase extends TestCase } /** - * Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()} and {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()} + * Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()} * * @return void * @since 2.7.9 3.0.4 3.1.0 */ - protected function LoadRequiredFiles(): void + protected function LoadRequiredItopFiles(): void + { + // Empty until we actually need to require some files in the class + } + + /** + * Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()} + * + * @return void + * @since 2.7.10 3.0.4 3.1.0 + */ + protected function LoadRequiredTestFiles(): void { // Empty until we actually need to require some files in the class } diff --git a/tests/php-unit-tests/UnitTestRunTimeEnvironment.php b/tests/php-unit-tests/UnitTestRunTimeEnvironment.php new file mode 100644 index 000000000..fc1e8793b --- /dev/null +++ b/tests/php-unit-tests/UnitTestRunTimeEnvironment.php @@ -0,0 +1,79 @@ + + * @since N°6097 2.7.10 3.0.4 3.1.1 + */ +class UnitTestRunTimeEnvironment extends RunTimeEnvironment +{ + /** + * @inheritDoc + */ + protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) + { + $aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir); + + /** @var string[] $aDeltaFiles Referential of loaded deltas. Mostly to avoid duplicates. */ + $aDeltaFiles = []; + foreach (get_declared_classes() as $sClass) { + // Filter on classes derived from this \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCaseItopCustomDatamodelTestCase + if (false === is_a($sClass, ItopCustomDatamodelTestCase::class, true)) { + continue; + } + + $oReflectionClass = new ReflectionClass($sClass); + $oReflectionMethod = $oReflectionClass->getMethod('GetDatamodelDeltaAbsPath'); + + // Filter on classes with an actual XML delta (eg. not \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase and maybe some other deriving from a class with a delta) + if ($oReflectionMethod->isAbstract()) { + continue; + } + + /** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */ + $oTestClassInstance = new $sClass(); + + // Check test class is for desired environment + if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) { + continue; + } + + // Check XML delta actually exists + $sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath(); + if (false === is_file($sDeltaFile)) { + $this->fail("Could not prepare '$this->sFinalEnv' as the XML delta file '$sDeltaFile' (used in $sClass) does not seem to exist"); + } + + // Avoid duplicates + if (in_array($sDeltaFile, $aDeltaFiles)) { + continue; + } + + // Prepare fake module name for delta + $sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile); + $oDelta = new MFDeltaModule($sDeltaFile); + + IssueLog::Debug('XML delta found for unit tests', static::class, [ + 'Unit test class' => $sClass, + 'Delta file path' => $sDeltaFile, + ]); + + $aDeltaFiles[] = $sDeltaFile; + $aRet[$sDeltaName] = $oDelta; + } + + return $aRet; + } + +} \ No newline at end of file diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php new file mode 100644 index 000000000..093aab38d --- /dev/null +++ b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php @@ -0,0 +1,200 @@ +setRunClassInSeparateProcess(true); + } + + /** + * @return string Abs path to the XML delta to use for the tests of that class + */ + abstract public function GetDatamodelDeltaAbsPath(): string; + + /** + * @inheritDoc + */ + protected function LoadRequiredItopFiles(): void + { + parent::LoadRequiredItopFiles(); + + $this->RequireOnceItopFile('setup/setuputils.class.inc.php'); + $this->RequireOnceItopFile('setup/runtimeenv.class.inc.php'); + } + + /** + * @return string Environment used as a base (conf. file, modules, DB, ...) to prepare the test environment + */ + protected function GetSourceEnvironment(): string + { + return static::DEFAULT_TEST_ENVIRONMENT; + } + + /** + * @inheritDoc + * @warning This should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global testing time. + */ + public function GetTestEnvironment(): string + { + return 'php-unit-tests'; + } + + /** + * @return string Absolute path to the {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} folder + */ + final private function GetTestEnvironmentFolderAbsPath(): string + { + return APPROOT.'env-'.$this->GetTestEnvironment().'/'; + } + + /** + * Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled) + * + * @return void + */ + final private function MarkEnvironmentReady(): void + { + if (false === $this->IsEnvironmentReady()) { + touch(static::GetTestEnvironmentFolderAbsPath()); + } + } + + /** + * @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started) + * + * @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run + */ + final private function IsEnvironmentReady(): bool + { + // As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of: + // - its own env- folder + // - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook} + $sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath(); + $sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath(); + + clearstatcache(); + if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) { + return false; + } + + $iRunStartedFileModificationTime = filemtime($sRunStartedFilePath); + $iEnvFolderModificationTime = filemtime($sEnvFolderPath); + + return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime; + } + + /** + * @inheritDoc + */ + protected function PrepareEnvironment(): void + { + $sSourceEnv = $this->GetSourceEnvironment(); + $sTestEnv = $this->GetTestEnvironment(); + + // Check if test env. is already set and only prepare it if it's not up-to-date + // + // Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once. + // This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457 + if (false === $this->IsEnvironmentReady()) { + //---------------------------------------------------- + // Clear any previous "$sTestEnv" environment + //---------------------------------------------------- + + // - Configuration file + $sConfFile = utils::GetConfigFilePath($sTestEnv); + $sConfFolder = dirname($sConfFile); + if (is_file($sConfFile)) { + chmod($sConfFile, 0777); + SetupUtils::tidydir($sConfFolder); + } + + // - Datamodel delta files + // - Cache folder + // - Compiled folder + // We don't need to clean them as they are already by the compilation + + // - Drop database + // We don't do that now, it will be done before re-creating the DB, once the metamodel is started + + //---------------------------------------------------- + // Prepare "$sTestEnv" environment + //---------------------------------------------------- + + // All the following is greatly inspired by the toolkit's sandbox script + // - Prepare config file + $oSourceConf = new Config(utils::GetConfigFilePath($sSourceEnv)); + if ($oSourceConf->Get('source_dir') === '') { + throw new Exception('Missing entry source_dir from the config file'); + } + + $oTestConfig = clone($oSourceConf); + $oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv); + // - Switch DB name to a dedicated one so we don't mess with the original one + $sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv); + $oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName); + + // - Compile env. based on the existing 'production' env. + $oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv); + $oEnvironment->WriteConfigFileSafe($oTestConfig); + $oEnvironment->CompileFrom($sSourceEnv, false); + + // - Force re-creating a fresh DB + CMDBSource::InitFromConfig($oTestConfig); + if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) { + CMDBSource::DropDB(); + } + CMDBSource::CreateDB($oTestConfig->Get('db_name')); + MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv); + + $this->MarkEnvironmentReady(); + $this->debug('Preparation of custom environment "'.$sTestEnv.'" done.'); + } + + parent::PrepareEnvironment(); + } +} diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml new file mode 100644 index 000000000..b9a98184d --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml @@ -0,0 +1,14 @@ + + + + + + + tested_attribute2 + + true + + + + + diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php new file mode 100644 index 000000000..7173aff4d --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php @@ -0,0 +1,34 @@ + - *
  • MakeGroupByQuery
  • - * - * - * @runTestsInSeparateProcesses + * @runTestsInSeparateProcesseszzz * @preserveGlobalState disabled * @backupGlobals disabled */ @@ -28,7 +23,7 @@ class TestGLATest extends ItopCustomDatamodelTestCase */ public function GetDatamodelDeltaAbsPath(): string { - return APPROOT.'tests/php-unit-tests/unitary-tests/core/TestGLATest.delta.xml'; + return APPROOT.'tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.delta.xml'; } public function testFoo() diff --git a/tests/php-unit-tests/unittestautoload.php b/tests/php-unit-tests/unittestautoload.php index 1e96a56bf..d78b3b2f1 100644 --- a/tests/php-unit-tests/unittestautoload.php +++ b/tests/php-unit-tests/unittestautoload.php @@ -7,3 +7,4 @@ require_once 'vendor/autoload.php'; // - Custom test case PHP classes require_once 'ItopTestCase.php'; require_once 'ItopDataTestCase.php'; +require_once 'ItopCustomDatamodelTestCase.php'; From 1ad28312ecabc30447b9606d84dde9afaa2565fc Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 20 Jul 2023 17:37:11 +0200 Subject: [PATCH 6/8] =?UTF-8?q?N=C2=B06097=20-=20Tests:=20Introduce=20auto?= =?UTF-8?q?loader=20for=20"utility"=20classes=20and=20move=20them=20to=20a?= =?UTF-8?q?=20sub-folder=20for=20better=20organization=20as=20folder=20was?= =?UTF-8?q?=20still=20messy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that unittestautoload.php is now useless. We just keep for now until everything is migrated (projects / branches / modules) --- .../ItopCustomDatamodelTestCase.php | 152 ------------------ tests/php-unit-tests/composer.json | 6 + .../ItopCustomDatamodelTestCase.php | 116 ++++--------- .../BaseTestCase}/ItopDataTestCase.php | 21 +-- .../{ => src/BaseTestCase}/ItopTestCase.php | 19 +-- .../Service}/UnitTestRunTimeEnvironment.php | 6 + tests/php-unit-tests/unittestautoload.php | 9 +- 7 files changed, 57 insertions(+), 272 deletions(-) delete mode 100644 tests/php-unit-tests/ItopCustomDatamodelTestCase.php rename tests/php-unit-tests/{ => src/BaseTestCase}/ItopDataTestCase.php (96%) rename tests/php-unit-tests/{ => src/BaseTestCase}/ItopTestCase.php (90%) rename tests/php-unit-tests/{ => src/Service}/UnitTestRunTimeEnvironment.php (95%) diff --git a/tests/php-unit-tests/ItopCustomDatamodelTestCase.php b/tests/php-unit-tests/ItopCustomDatamodelTestCase.php deleted file mode 100644 index 6eff53a52..000000000 --- a/tests/php-unit-tests/ItopCustomDatamodelTestCase.php +++ /dev/null @@ -1,152 +0,0 @@ -GetTestEnvironment().'.log'; - IssueLog::Enable($sLogFileAbsPath); - } - - - /** - * @return string Abs path to the XML delta to use for the tests of that class - */ - abstract public function GetDatamodelDeltaAbsPath(): string; - - /** - * @inheritDoc - */ - protected function LoadRequiredFiles(): void - { - $this->RequireOnceItopFile('setup/setuputils.class.inc.php'); - $this->RequireOnceItopFile('setup/runtimeenv.class.inc.php'); - $this->RequireOnceUnitTestFile('UnitTestRunTimeEnvironment.php'); - } - - /** - * @return string Environment used as a base (conf. file, modules, DB, ...) to prepare the test environment - */ - protected function GetSourceEnvironment(): string - { - return 'production'; - } - - /** - * @inheritDoc - * - * This is final for now as we don't support yet to have several environments in // to test incompatible DMs / deltas. - * When / if we do this, keep in mind that should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global test time. - */ - final public function GetTestEnvironment(): string - { - return 'php-unit-tests'; - } - - /** - * @inheritDoc - */ - protected function PrepareEnvironment(): void - { - $sSourceEnv = $this->GetSourceEnvironment(); - $sTestEnv = $this->GetTestEnvironment(); - - // Check if test env. if already set and only prepare it if it doesn't already exist - // - // Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once. - // This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457 - if (false === static::$bIsCustomEnvironmentReady) { - //---------------------------------------------------- - // Clear any previous "$sTestEnv" environment - //---------------------------------------------------- - - // - Configuration file - $sConfFile = utils::GetConfigFilePath($sTestEnv); - $sConfFolder = dirname($sConfFile); - if (is_file($sConfFile)) { - chmod($sConfFile, 0777); - SetupUtils::tidydir($sConfFolder); - } - - // - Datamodel delta files - // - Cache folder - // - Compiled folder - // We don't need to clean them as they are already by the compilation - - // - Drop database - // We don't do that now, it will be done before re-creating the DB, once the metamodel is started - - //---------------------------------------------------- - // Prepare "$sTestEnv" environment - //---------------------------------------------------- - - // All the following is greatly inspired by the toolkit's sandbox script - // - Prepare config file - $oSourceConf = new Config(utils::GetConfigFilePath($sSourceEnv)); - if ($oSourceConf->Get('source_dir') === '') { - throw new Exception('Missing entry source_dir from the config file'); - } - - $oTestConfig = clone($oSourceConf); - $oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv); - // - Switch DB name to a dedicated one so we don't mess with the original one - $sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv); - $oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName); - - // - Compile env. based on the existing 'production' env. - $oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv); - $oEnvironment->WriteConfigFileSafe($oTestConfig); - $oEnvironment->CompileFrom($sSourceEnv, false); - - // - Force re-creating of the DB -// // TODO: Create tmp DB - // But how to use it now when the metamodel is not started yet ?? -// MetaModel::LoadConfig($oTestConfig); -// if (MetaModel::DBExists()) { -// MetaModel::DBDrop(); -// } -// MetaModel::DBCreate(); - - static::$bIsCustomEnvironmentReady = true; - } - - parent::PrepareEnvironment(); - } -} diff --git a/tests/php-unit-tests/composer.json b/tests/php-unit-tests/composer.json index 61b516347..1386f61b9 100644 --- a/tests/php-unit-tests/composer.json +++ b/tests/php-unit-tests/composer.json @@ -2,5 +2,11 @@ "require-dev": { "phpunit/phpunit": "^8.5.23", "sempro/phpunit-pretty-print": "^1.4" + }, + "autoload": { + "psr-4": { + "Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/", + "Combodo\\iTop\\Test\\UnitTest\\Service\\": "src/Service/" + } } } diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php index 093aab38d..d10e609a6 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php @@ -6,13 +6,10 @@ namespace Combodo\iTop\Test\UnitTest; -use CMDBSource; -use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook; use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment; use Config; use Exception; use IssueLog; -use MetaModel; use SetupUtils; use utils; @@ -20,37 +17,38 @@ use utils; /** * Class ItopCustomDatamodelTestCase * - * Helper class to extend for tests needing a custom DataModel (eg. classes, attributes, etc conditions not available in the standard DM) - * Usage: - * - Create a test case class extending this one - * - Override the {@see ItopCustomDatamodelTestCase::GetDatamodelDeltaAbsPath()} method to define where you XML delta is - * - Implement your test case methods as usual + * Helper class to extend for tests needing a custom DataModel access to iTop's metamodel * - * @since N°6097 2.7.9 3.0.4 3.1.0 + * **⚠ Warning** Each class extending this one needs to NOT have @runTestsInSeparateProcesses annotation; otherwise the test env. will be re-compiled each time. + * + * @runTestsInSeparateProcesseszzz + * @preserveGlobalState disabled + * @backupGlobals disabled + * + * @since 2.7.9 3.0.4 3.1.0 */ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase { /** - * @var bool[] + * @var bool + * @since N°6097 2.7.10 3.0.4 3.1.1 3.2.0 + * + * @note If we change this to an array (with {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} as the key), we could eventually have several environments in // to test incompatible DMs / deltas. */ - protected static $aReadyCustomEnvironments = []; + protected static $bIsCustomEnvironmentReady = false; /** * @inheritDoc - * @since N°6097 Workaround to make the "runClassInSeparateProcess" directive work */ - public function __construct($name = null, array $data = [], $dataName = '') + protected function setUp(): void { - parent::__construct($name, $data, $dataName); + parent::setUp(); - // Ensure that a test class derived from this one runs in a dedicated process as it changes the MetaModel / environment on the fly and - // for now we have no way of switching environments properly in memory and it will result in other (regular) test classes to fail as they won't be on the expected environment. - // - // If we don't do this, we would have to add the `@runTestsInSeparateProcesses` on *each* test classes which we want to avoid for obvious possible mistakes. - // Note that the `@runClassInSeparateProcess` don't work in PHPUnit yet. - $this->setRunClassInSeparateProcess(true); + $sLogFileAbsPath = APPROOT.'log/php_unit_tests_-_custom_datamodel_for_env_-_'.$this->GetTestEnvironment().'.log'; + IssueLog::Enable($sLogFileAbsPath); } + /** * @return string Abs path to the XML delta to use for the tests of that class */ @@ -59,10 +57,8 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase /** * @inheritDoc */ - protected function LoadRequiredItopFiles(): void + protected function LoadRequiredFiles(): void { - parent::LoadRequiredItopFiles(); - $this->RequireOnceItopFile('setup/setuputils.class.inc.php'); $this->RequireOnceItopFile('setup/runtimeenv.class.inc.php'); } @@ -72,62 +68,20 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase */ protected function GetSourceEnvironment(): string { - return static::DEFAULT_TEST_ENVIRONMENT; + return 'production'; } /** * @inheritDoc - * @warning This should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global testing time. + * + * This is final for now as we don't support yet to have several environments in // to test incompatible DMs / deltas. + * When / if we do this, keep in mind that should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global test time. */ - public function GetTestEnvironment(): string + final public function GetTestEnvironment(): string { return 'php-unit-tests'; } - /** - * @return string Absolute path to the {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} folder - */ - final private function GetTestEnvironmentFolderAbsPath(): string - { - return APPROOT.'env-'.$this->GetTestEnvironment().'/'; - } - - /** - * Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled) - * - * @return void - */ - final private function MarkEnvironmentReady(): void - { - if (false === $this->IsEnvironmentReady()) { - touch(static::GetTestEnvironmentFolderAbsPath()); - } - } - - /** - * @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started) - * - * @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run - */ - final private function IsEnvironmentReady(): bool - { - // As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of: - // - its own env- folder - // - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook} - $sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath(); - $sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath(); - - clearstatcache(); - if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) { - return false; - } - - $iRunStartedFileModificationTime = filemtime($sRunStartedFilePath); - $iEnvFolderModificationTime = filemtime($sEnvFolderPath); - - return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime; - } - /** * @inheritDoc */ @@ -136,11 +90,11 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase $sSourceEnv = $this->GetSourceEnvironment(); $sTestEnv = $this->GetTestEnvironment(); - // Check if test env. is already set and only prepare it if it's not up-to-date + // Check if test env. if already set and only prepare it if it doesn't already exist // // Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once. // This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457 - if (false === $this->IsEnvironmentReady()) { + if (false === static::$bIsCustomEnvironmentReady) { //---------------------------------------------------- // Clear any previous "$sTestEnv" environment //---------------------------------------------------- @@ -183,16 +137,16 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase $oEnvironment->WriteConfigFileSafe($oTestConfig); $oEnvironment->CompileFrom($sSourceEnv, false); - // - Force re-creating a fresh DB - CMDBSource::InitFromConfig($oTestConfig); - if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) { - CMDBSource::DropDB(); - } - CMDBSource::CreateDB($oTestConfig->Get('db_name')); - MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv); + // - Force re-creating of the DB +// // TODO: Create tmp DB + // But how to use it now when the metamodel is not started yet ?? +// MetaModel::LoadConfig($oTestConfig); +// if (MetaModel::DBExists()) { +// MetaModel::DBDrop(); +// } +// MetaModel::DBCreate(); - $this->MarkEnvironmentReady(); - $this->debug('Preparation of custom environment "'.$sTestEnv.'" done.'); + static::$bIsCustomEnvironmentReady = true; } parent::PrepareEnvironment(); diff --git a/tests/php-unit-tests/ItopDataTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php similarity index 96% rename from tests/php-unit-tests/ItopDataTestCase.php rename to tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php index c68e116db..77dabbef9 100644 --- a/tests/php-unit-tests/ItopDataTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php @@ -1,21 +1,8 @@ -// +/* + * @copyright Copyright (C) 2010-2023 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ namespace Combodo\iTop\Test\UnitTest; diff --git a/tests/php-unit-tests/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php similarity index 90% rename from tests/php-unit-tests/ItopTestCase.php rename to tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php index 12eeeb434..3eaddccd9 100644 --- a/tests/php-unit-tests/ItopTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php @@ -1,20 +1,7 @@ Date: Thu, 20 Jul 2023 19:47:25 +0200 Subject: [PATCH 7/8] =?UTF-8?q?N=C2=B06097=20-=20Tests:=20Optimize=20perfo?= =?UTF-8?q?rmances=20by=20creating=20custom=20env.=20only=20once=20and=20r?= =?UTF-8?q?e-using=20it=20across=20test=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/php-unit-tests/composer.json | 1 + .../ItopCustomDatamodelTestCase.php | 114 ++++++++++++------ .../src/BaseTestCase/ItopDataTestCase.php | 9 +- .../src/Hook/TestsRunStartHook.php | 63 ++++++++++ .../Service/UnitTestRunTimeEnvironment.php | 7 +- .../core/TestGLA/TestGLA2Test.delta.xml | 14 --- .../core/TestGLA/TestGLA2Test.php | 34 ------ .../core/TestGLA/TestGLATest.delta.xml | 14 --- .../core/TestGLA/TestGLATest.php | 34 ------ 9 files changed, 155 insertions(+), 135 deletions(-) create mode 100644 tests/php-unit-tests/src/Hook/TestsRunStartHook.php delete mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml delete mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php delete mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.delta.xml delete mode 100644 tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.php diff --git a/tests/php-unit-tests/composer.json b/tests/php-unit-tests/composer.json index 1386f61b9..fef0397e9 100644 --- a/tests/php-unit-tests/composer.json +++ b/tests/php-unit-tests/composer.json @@ -6,6 +6,7 @@ "autoload": { "psr-4": { "Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/", + "Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/", "Combodo\\iTop\\Test\\UnitTest\\Service\\": "src/Service/" } } diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php index d10e609a6..91c335736 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopCustomDatamodelTestCase.php @@ -6,10 +6,13 @@ namespace Combodo\iTop\Test\UnitTest; +use CMDBSource; +use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook; use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment; use Config; use Exception; use IssueLog; +use MetaModel; use SetupUtils; use utils; @@ -17,38 +20,37 @@ use utils; /** * Class ItopCustomDatamodelTestCase * - * Helper class to extend for tests needing a custom DataModel access to iTop's metamodel + * Helper class to extend for tests needing a custom DataModel (eg. classes, attributes, etc conditions not available in the standard DM) + * Usage: + * - Create a test case class extending this one + * - Override the {@see ItopCustomDatamodelTestCase::GetDatamodelDeltaAbsPath()} method to define where you XML delta is + * - Implement your test case methods as usual * - * **⚠ Warning** Each class extending this one needs to NOT have @runTestsInSeparateProcesses annotation; otherwise the test env. will be re-compiled each time. - * - * @runTestsInSeparateProcesseszzz - * @preserveGlobalState disabled - * @backupGlobals disabled - * - * @since 2.7.9 3.0.4 3.1.0 + * @since N°6097 2.7.9 3.0.4 3.1.0 */ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase { /** - * @var bool - * @since N°6097 2.7.10 3.0.4 3.1.1 3.2.0 - * - * @note If we change this to an array (with {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} as the key), we could eventually have several environments in // to test incompatible DMs / deltas. + * @var bool[] */ - protected static $bIsCustomEnvironmentReady = false; + protected static $aReadyCustomEnvironments = []; /** * @inheritDoc + * @since N°6097 Workaround to make the "runClassInSeparateProcess" directive work */ - protected function setUp(): void + public function __construct($name = null, array $data = [], $dataName = '') { - parent::setUp(); + parent::__construct($name, $data, $dataName); - $sLogFileAbsPath = APPROOT.'log/php_unit_tests_-_custom_datamodel_for_env_-_'.$this->GetTestEnvironment().'.log'; - IssueLog::Enable($sLogFileAbsPath); + // Ensure that a test class derived from this one runs in a dedicated process as it changes the MetaModel / environment on the fly and + // for now we have no way of switching environments properly in memory and it will result in other (regular) test classes to fail as they won't be on the expected environment. + // + // If we don't do this, we would have to add the `@runTestsInSeparateProcesses` on *each* test classes which we want to avoid for obvious possible mistakes. + // Note that the `@runClassInSeparateProcess` don't work in PHPUnit yet. + $this->setRunClassInSeparateProcess(true); } - /** * @return string Abs path to the XML delta to use for the tests of that class */ @@ -57,8 +59,10 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase /** * @inheritDoc */ - protected function LoadRequiredFiles(): void + protected function LoadRequiredItopFiles(): void { + parent::LoadRequiredItopFiles(); + $this->RequireOnceItopFile('setup/setuputils.class.inc.php'); $this->RequireOnceItopFile('setup/runtimeenv.class.inc.php'); } @@ -73,15 +77,57 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase /** * @inheritDoc - * - * This is final for now as we don't support yet to have several environments in // to test incompatible DMs / deltas. - * When / if we do this, keep in mind that should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global test time. + * @warning This should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global testing time. */ - final public function GetTestEnvironment(): string + public function GetTestEnvironment(): string { return 'php-unit-tests'; } + /** + * @return string Absolute path to the {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} folder + */ + final private function GetTestEnvironmentFolderAbsPath(): string + { + return APPROOT.'env-'.$this->GetTestEnvironment().'/'; + } + + /** + * Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled) + * + * @return void + */ + final private function MarkEnvironmentReady(): void + { + if (false === $this->IsEnvironmentReady()) { + touch(static::GetTestEnvironmentFolderAbsPath()); + } + } + + /** + * @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started) + * + * @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run + */ + final private function IsEnvironmentReady(): bool + { + // As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of: + // - its own env- folder + // - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook} + $sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath(); + $sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath(); + + clearstatcache(); + if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) { + return false; + } + + $iRunStartedFileModificationTime = filemtime($sRunStartedFilePath); + $iEnvFolderModificationTime = filemtime($sEnvFolderPath); + + return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime; + } + /** * @inheritDoc */ @@ -90,11 +136,11 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase $sSourceEnv = $this->GetSourceEnvironment(); $sTestEnv = $this->GetTestEnvironment(); - // Check if test env. if already set and only prepare it if it doesn't already exist + // Check if test env. is already set and only prepare it if it's not up-to-date // // Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once. // This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457 - if (false === static::$bIsCustomEnvironmentReady) { + if (false === $this->IsEnvironmentReady()) { //---------------------------------------------------- // Clear any previous "$sTestEnv" environment //---------------------------------------------------- @@ -137,16 +183,16 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase $oEnvironment->WriteConfigFileSafe($oTestConfig); $oEnvironment->CompileFrom($sSourceEnv, false); - // - Force re-creating of the DB -// // TODO: Create tmp DB - // But how to use it now when the metamodel is not started yet ?? -// MetaModel::LoadConfig($oTestConfig); -// if (MetaModel::DBExists()) { -// MetaModel::DBDrop(); -// } -// MetaModel::DBCreate(); + // - Force re-creating a fresh DB + CMDBSource::InitFromConfig($oTestConfig); + if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) { + CMDBSource::DropDB(); + } + CMDBSource::CreateDB($oTestConfig->Get('db_name')); + MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv); - static::$bIsCustomEnvironmentReady = true; + $this->MarkEnvironmentReady(); + $this->debug('Preparation of custom environment "'.$sTestEnv.'" done.'); } parent::PrepareEnvironment(); diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php index 77dabbef9..6b6aa550c 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php @@ -15,6 +15,7 @@ namespace Combodo\iTop\Test\UnitTest; use ArchivedObjectException; use CMDBSource; +use Config; use Contact; use DBObject; use DBObjectSet; @@ -63,6 +64,10 @@ abstract class ItopDataTestCase extends ItopTestCase // For cleanup private $aCreatedObjects = array(); + /** + * @var string Default environment to use for test cases + */ + const DEFAULT_TEST_ENVIRONMENT = 'production'; const USE_TRANSACTION = true; const CREATE_TEST_ORG = false; @@ -126,12 +131,12 @@ abstract class ItopDataTestCase extends ItopTestCase } /** - * @return string Environment in the test will run + * @return string Environment the test will run in * @since 2.7.9 3.0.4 3.1.0 */ protected function GetTestEnvironment(): string { - return 'production'; + return self::DEFAULT_TEST_ENVIRONMENT; } /** diff --git a/tests/php-unit-tests/src/Hook/TestsRunStartHook.php b/tests/php-unit-tests/src/Hook/TestsRunStartHook.php new file mode 100644 index 000000000..7d2f6c212 --- /dev/null +++ b/tests/php-unit-tests/src/Hook/TestsRunStartHook.php @@ -0,0 +1,63 @@ + + * @package Combodo\iTop\Test\UnitTest\Hook + * @since N°6097 2.7.10 3.0.4 3.1.1 + */ +class TestsRunStartHook implements BeforeFirstTestHook, AfterLastTestHook +{ + /** + * Use the modification time on this file to check whereas it is newer than the requirements in a test case + * + * @return string Abs. path to a file generated when the global tests run starts. + */ + public static function GetRunStartedFileAbsPath(): string + { + // Note: This can't be put in the cache- folder as we have multiple running across the test cases + // We also don't want to put it in the unit tests folder as it is not supposed to be writable + return APPROOT.'data/.php-unit-tests-run-started'; + } + + /** + * @inheritDoc + */ + public function executeBeforeFirstTest(): void + { + // Create / change modification timestamp of file marking the beginning of the tests run + touch(static::GetRunStartedFileAbsPath()); + } + + /** + * @inheritDoc + */ + public function executeAfterLastTest(): void + { + // Cleanup of file marking the beginning of the tests run + if (file_exists(static::GetRunStartedFileAbsPath())) { + unlink(static::GetRunStartedFileAbsPath()); + } + } + + +} \ No newline at end of file diff --git a/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php b/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php index d718893ce..63ded52c7 100644 --- a/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php +++ b/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php @@ -9,7 +9,7 @@ namespace Combodo\iTop\Test\UnitTest\Service; use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase; use IssueLog; -use MFDeltaModule; +use MFCoreModule; use ReflectionClass; use RunTimeEnvironment; @@ -17,7 +17,7 @@ use RunTimeEnvironment; /** * Class UnitTestRunTimeEnvironment * - * Runtime env. dedicated to creating an temp. environment for a group of unit tests with XML deltas. + * Runtime env. dedicated to creating a temp. environment for a group of unit tests with XML deltas. * * @author Guillaume Lajarige * @since N°6097 2.7.10 3.0.4 3.1.1 @@ -68,7 +68,8 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment // Prepare fake module name for delta $sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile); - $oDelta = new MFDeltaModule($sDeltaFile); + // Note: We can't use \MFDeltaModule as we can't specify the ID which leads to only 1 delta being applied... In the future we might introduce a new MFXXXModule, but in the meantime it feels alright (GLA / RQU) + $oDelta = new MFCoreModule($sDeltaName, $sDeltaName, $sDeltaFile); IssueLog::Debug('XML delta found for unit tests', static::class, [ 'Unit test class' => $sClass, diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml deleted file mode 100644 index b9a98184d..000000000 --- a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.delta.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - tested_attribute2 - - true - - - - - diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php deleted file mode 100644 index 7173aff4d..000000000 --- a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLA2Test.php +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - tested_attribute - - true - - - - - diff --git a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.php b/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.php deleted file mode 100644 index 7f0eea95a..000000000 --- a/tests/php-unit-tests/unitary-tests/core/TestGLA/TestGLATest.php +++ /dev/null @@ -1,34 +0,0 @@ - Date: Fri, 11 Aug 2023 09:05:30 +0200 Subject: [PATCH 8/8] :green_heart: Fix typo in extended class name --- .../unitary-tests/core/ExpressionEvaluateTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php index 8df6d632a..46d08908a 100644 --- a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php +++ b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php @@ -5,7 +5,7 @@ namespace Combodo\iTop\Test\UnitTest\Core; use CMDBSource; -use Combodo\iTop\Test\UnitTest\iTopDataTestCase; +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use DateInterval; use DateTime; use Expression; @@ -18,7 +18,7 @@ use ScalarExpression; * @preserveGlobalState disabled * @backupGlobals disabled */ -class ExpressionEvaluateTest extends iTopDataTestCase +class ExpressionEvaluateTest extends ItopDataTestCase { const USE_TRANSACTION = false;