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';