<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\TestFramework; use Magento\Framework\Autoload\AutoloaderInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\App\DeploymentConfig\Reader; use Magento\Framework\Filesystem\Glob; /** * Encapsulates application installation, initialization and uninstall. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ class Application { /** * Default application area. */ const DEFAULT_APP_AREA = 'global'; /** * DB vendor adapter instance. * * @var \Magento\TestFramework\Db\AbstractDb */ protected $_db; /** * Shell command executor. * * @var \Magento\Framework\Shell */ protected $_shell; /** * Configuration file that contains installation parameters. * * @var string */ private $installConfigFile; /** * The loaded installation parameters. * * @var array */ protected $installConfig; /** * Application *.xml configuration files. * * @var array */ protected $_globalConfigDir; /** * Installation destination directory. * * @var string */ protected $installDir; /** * Installation destination directory with configuration files. * * @var string */ protected $_configDir; /** * Application initialization parameters. * * @var array */ protected $_initParams = []; /** * Mode to run application. * * @var string */ protected $_appMode; /** * Application area. * * @var null */ protected $_appArea = null; /** * Primary DI Config. * * @var array */ protected $_primaryConfigData = []; /** * Object manager factory. * * @var \Magento\TestFramework\ObjectManagerFactory */ protected $_factory; /** * Directory list. * * @var \Magento\Framework\App\Filesystem\DirectoryList */ protected $dirList; /** * Config file for integration tests. * * @var string */ private $globalConfigFile; /** * Defines whether load test extension attributes or not. * * @var bool */ private $loadTestExtensionAttributes; /** * @var bool */ protected $dumpDb = true; /** * @var bool */ protected $canLoadArea = true; /** * @var bool */ protected $canInstallSequence = true; /** * Constructor. * * @param \Magento\Framework\Shell $shell * @param string $installDir * @param array $installConfigFile * @param string $globalConfigFile * @param string $globalConfigDir * @param string $appMode * @param AutoloaderInterface $autoloadWrapper * @param bool|null $loadTestExtensionAttributes */ public function __construct( \Magento\Framework\Shell $shell, $installDir, $installConfigFile, $globalConfigFile, $globalConfigDir, $appMode, AutoloaderInterface $autoloadWrapper, $loadTestExtensionAttributes = false ) { if (getcwd() != BP . '/dev/tests/integration') { chdir(BP . '/dev/tests/integration'); } $this->_shell = $shell; $this->installConfigFile = $installConfigFile; $this->_globalConfigDir = realpath($globalConfigDir); $this->_appMode = $appMode; $this->installDir = $installDir; $this->loadTestExtensionAttributes = $loadTestExtensionAttributes; $customDirs = $this->getCustomDirs(); $this->dirList = new \Magento\Framework\App\Filesystem\DirectoryList(BP, $customDirs); \Magento\Framework\Autoload\Populator::populateMappings( $autoloadWrapper, $this->dirList ); $this->_initParams = [ \Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS => $customDirs, \Magento\Framework\App\State::PARAM_MODE => $appMode ]; $driverPool = new \Magento\Framework\Filesystem\DriverPool; $configFilePool = new \Magento\Framework\Config\File\ConfigFilePool; $this->_factory = new \Magento\TestFramework\ObjectManagerFactory($this->dirList, $driverPool, $configFilePool); $this->_configDir = $this->dirList->getPath(DirectoryList::CONFIG); $this->globalConfigFile = $globalConfigFile; } /** * Retrieve the database adapter instance. * * @return \Magento\TestFramework\Db\AbstractDb */ public function getDbInstance() { if (null === $this->_db) { if ($this->isInstalled()) { $configPool = new \Magento\Framework\Config\File\ConfigFilePool(); $driverPool = new \Magento\Framework\Filesystem\DriverPool(); $reader = new Reader($this->dirList, $driverPool, $configPool); $deploymentConfig = new DeploymentConfig($reader, []); $host = $deploymentConfig->get( ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT . '/' . ConfigOptionsListConstants::KEY_HOST ); $user = $deploymentConfig->get( ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT . '/' . ConfigOptionsListConstants::KEY_USER ); $password = $deploymentConfig->get( ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT . '/' . ConfigOptionsListConstants::KEY_PASSWORD ); $dbName = $deploymentConfig->get( ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT . '/' . ConfigOptionsListConstants::KEY_NAME ); } else { $installConfig = $this->getInstallConfig(); $host = $installConfig['db-host']; $user = $installConfig['db-user']; $password = $installConfig['db-password']; $dbName = $installConfig['db-name']; } $this->_db = new Db\Mysql( $host, $user, $password, $dbName, $this->getTempDir(), $this->_shell ); } return $this->_db; } /** * Gets installation parameters. * * @return array */ protected function getInstallConfig() { if (null === $this->installConfig) { $this->installConfig = include $this->installConfigFile; } return $this->installConfig; } /** * Gets deployment configuration path. * * @return string */ private function getLocalConfig() { return $this->_configDir . '/config.php'; } /** * Get path to temporary directory. * * @return string */ public function getTempDir() { return $this->installDir; } /** * Retrieve application initialization parameters. * * @return array */ public function getInitParams() { return $this->_initParams; } /** * Weather the application is installed or not. * * @return bool */ public function isInstalled() { return is_file($this->getLocalConfig()); } /** * Create logger instance and rewrite already exist one in ObjectManager. * * @return \Psr\Log\LoggerInterface */ private function initLogger() { $objectManager = Helper\Bootstrap::getObjectManager(); /** @var \Psr\Log\LoggerInterface $logger */ $logger = $objectManager->create( \Magento\TestFramework\ErrorLog\Logger::class, [ 'name' => 'integration-tests', 'handlers' => [ 'system' => $objectManager->create( \Magento\Framework\Logger\Handler\System::class, [ 'exceptionHandler' => $objectManager->create( \Magento\Framework\Logger\Handler\Exception::class, ['filePath' => $this->installDir] ), 'filePath' => $this->installDir ] ), 'debug' => $objectManager->create( \Magento\Framework\Logger\Handler\Debug::class, ['filePath' => $this->installDir] ), ] ] ); $objectManager->removeSharedInstance(\Magento\Framework\Logger\Monolog::class); $objectManager->addSharedInstance($logger, \Magento\Framework\Logger\Monolog::class); return $logger; } /** * Initialize application * * @param array $overriddenParams * @return void */ public function initialize($overriddenParams = []) { $overriddenParams[\Magento\Framework\App\State::PARAM_MODE] = $this->_appMode; $overriddenParams = $this->_customizeParams($overriddenParams); $directories = isset($overriddenParams[\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]) ? $overriddenParams[\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $directories); /** @var \Magento\TestFramework\ObjectManager $objectManager */ $objectManager = Helper\Bootstrap::getObjectManager(); if (!$objectManager) { $objectManager = $this->_factory->create($overriddenParams); $objectManager->addSharedInstance($directoryList, \Magento\Framework\App\Filesystem\DirectoryList::class); $objectManager->addSharedInstance($directoryList, \Magento\Framework\Filesystem\DirectoryList::class); } else { $objectManager = $this->_factory->restore($objectManager, $directoryList, $overriddenParams); } /** @var \Magento\TestFramework\App\Filesystem $filesystem */ $filesystem = $objectManager->get(\Magento\TestFramework\App\Filesystem::class); $objectManager->removeSharedInstance(\Magento\Framework\Filesystem::class); $objectManager->addSharedInstance($filesystem, \Magento\Framework\Filesystem::class); Helper\Bootstrap::setObjectManager($objectManager); $this->initLogger(); $sequenceBuilder = $objectManager->get(\Magento\TestFramework\Db\Sequence\Builder::class); $objectManager->addSharedInstance($sequenceBuilder, \Magento\SalesSequence\Model\Builder::class); $objectManagerConfiguration = [ 'preferences' => [ \Magento\Framework\App\State::class => \Magento\TestFramework\App\State::class, \Magento\Framework\Mail\TransportInterface::class => \Magento\TestFramework\Mail\TransportInterfaceMock::class, \Magento\Framework\Mail\Template\TransportBuilder::class => \Magento\TestFramework\Mail\Template\TransportBuilderMock::class, ] ]; if ($this->loadTestExtensionAttributes) { $objectManagerConfiguration = array_merge( $objectManagerConfiguration, [ \Magento\Framework\Api\ExtensionAttribute\Config\Reader::class => [ 'arguments' => [ 'fileResolver' => [ 'instance' => \Magento\TestFramework\Api\Config\Reader\FileResolver::class ], ], ], ] ); } $objectManager->configure($objectManagerConfiguration); /** Register event observer of Integration Framework */ /** @var \Magento\Framework\Event\Config\Data $eventConfigData */ $eventConfigData = $objectManager->get(\Magento\Framework\Event\Config\Data::class); $eventConfigData->merge( [ 'core_app_init_current_store_after' => [ 'integration_tests' => [ 'instance' => \Magento\TestFramework\Event\Magento::class, 'name' => 'integration_tests' ] ] ] ); if ($this->canLoadArea) { $this->loadArea(\Magento\TestFramework\Application::DEFAULT_APP_AREA); } \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->configure( $objectManager->get(\Magento\Framework\ObjectManager\DynamicConfigInterface::class)->getConfiguration() ); \Magento\Framework\Phrase::setRenderer( $objectManager->get(\Magento\Framework\Phrase\Renderer\Placeholder::class) ); if ($this->canInstallSequence) { /** @var \Magento\TestFramework\Db\Sequence $sequence */ $sequence = $objectManager->get(\Magento\TestFramework\Db\Sequence::class); $sequence->generateSequences(); } $objectManager->create(\Magento\TestFramework\Config::class, ['configPath' => $this->globalConfigFile]) ->rewriteAdditionalConfig(); } /** * Reset and initialize again an already installed application * * @param array $overriddenParams * @return void */ public function reinitialize(array $overriddenParams = []) { $this->_resetApp(); $this->initialize($overriddenParams); } /** * Run application normally, but with encapsulated initialization options * * @return void */ public function run() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Framework\App\Http $app */ $app = $objectManager->get(\Magento\Framework\App\Http::class); $response = $app->launch(); $response->sendResponse(); } /** * Create install dir for integration framework * * @return void */ public function createInstallDir() { $this->_ensureDirExists($this->installDir); $this->_ensureDirExists($this->_configDir); $this->copyAppConfigFiles(); } /** * Cleanup both the database and the file system * * @return void */ public function cleanup() { $this->createInstallDir(); /** * @see \Magento\Setup\Mvc\Bootstrap\InitParamListener::BOOTSTRAP_PARAM */ $this->_shell->execute( PHP_BINARY . ' -f %s setup:uninstall -vvv -n --magento-init-params=%s', [BP . '/bin/magento', $this->getInitParamsQuery()] ); } /** * Install an application * * @param bool $cleanup * @return void * @throws \Magento\Framework\Exception\LocalizedException */ public function install($cleanup) { $dirs = \Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS; $this->_ensureDirExists($this->installDir); $this->_ensureDirExists($this->_configDir); $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::PUB][DirectoryList::PATH]); $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::MEDIA][DirectoryList::PATH]); $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::STATIC_VIEW][DirectoryList::PATH]); $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::VAR_DIR][DirectoryList::PATH]); $this->copyAppConfigFiles(); $this->copyGlobalConfigFile(); $installParams = $this->getInstallCliParams(); // performance optimization: restore DB from last good dump to make installation on top of it (much faster) // do not restore from the database if the cleanup option is set to ensure we have a clean DB to test on $db = $this->getDbInstance(); if ($db->isDbDumpExists() && !$cleanup) { $db->restoreFromDbDump(); } // run install script $this->_shell->execute( PHP_BINARY . ' -f %s setup:install -vvv ' . implode(' ', array_keys($installParams)), array_merge([BP . '/bin/magento'], array_values($installParams)) ); // enable only specified list of caches $initParamsQuery = $this->getInitParamsQuery(); $this->_shell->execute( PHP_BINARY . ' -f %s cache:disable -vvv --bootstrap=%s', [BP . '/bin/magento', $initParamsQuery] ); $this->_shell->execute( PHP_BINARY . ' -f %s cache:enable -vvv %s %s %s %s --bootstrap=%s', [ BP . '/bin/magento', \Magento\Framework\App\Cache\Type\Config::TYPE_IDENTIFIER, \Magento\Framework\App\Cache\Type\Layout::TYPE_IDENTIFIER, \Magento\Framework\App\Cache\Type\Translate::TYPE_IDENTIFIER, \Magento\Eav\Model\Cache\Type::TYPE_IDENTIFIER, $initParamsQuery, ] ); // right after a clean installation, store DB dump for future reuse in tests or running the test suite again if (!$db->isDbDumpExists() && $this->dumpDb) { $this->getDbInstance()->storeDbDump(); } } /** * Copies configuration files from the main code base, so the installation could proceed in the tests directory * * @return void */ private function copyAppConfigFiles() { $globalConfigFiles = Glob::glob( $this->_globalConfigDir . '/{di.xml,*/di.xml,db_schema.xml,vendor_path.php}', Glob::GLOB_BRACE ); foreach ($globalConfigFiles as $file) { $targetFile = $this->_configDir . str_replace($this->_globalConfigDir, '', $file); $this->_ensureDirExists(dirname($targetFile)); if ($file !== $targetFile) { copy($file, $targetFile); } } } /** * Copies global configuration file from the tests folder (see TESTS_GLOBAL_CONFIG_FILE) * * @return void */ private function copyGlobalConfigFile() { $targetFile = $this->_configDir . '/config.local.php'; copy($this->globalConfigFile, $targetFile); } /** * Gets a list of CLI params for installation * * @return array */ private function getInstallCliParams() { $params = $this->getInstallConfig(); /** * Literal value is used instead of constant, because autoloader is not integrated with Magento Setup app * @see \Magento\Setup\Mvc\Bootstrap\InitParamListener::BOOTSTRAP_PARAM */ $params['magento-init-params'] = $this->getInitParamsQuery(); $result = []; foreach ($params as $key => $value) { if (!empty($value)) { $result["--{$key}=%s"] = $value; } } return $result; } /** * Encodes init params into a query string * * @return string */ private function getInitParamsQuery() { return urldecode(http_build_query($this->_initParams)); } /** * Sub-routine for merging custom parameters with the ones defined in object state * * @param array $params * @return array */ public function _customizeParams($params) { return array_replace_recursive($this->_initParams, $params); } /** * Reset application global state */ protected function _resetApp() { /** @var $objectManager \Magento\TestFramework\ObjectManager */ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $objectManager->clearCache(); \Magento\Framework\Data\Form::setElementRenderer(null); \Magento\Framework\Data\Form::setFieldsetRenderer(null); \Magento\Framework\Data\Form::setFieldsetElementRenderer(null); $this->_appArea = null; } /** * Create a directory with write permissions or don't touch existing one * * @param string $dir * @return void * @throws \Magento\Framework\Exception\LocalizedException */ protected function _ensureDirExists($dir) { if (!file_exists($dir)) { $old = umask(0); mkdir($dir, 0777, true); umask($old); } elseif (!is_dir($dir)) { throw new \Magento\Framework\Exception\LocalizedException(__("'%1' is not a directory.", $dir)); } } /** * Ge current application area * * @return string */ public function getArea() { return $this->_appArea; } /** * Load application area * * @param string $areaCode * @return void * @throws \Magento\Framework\Exception\LocalizedException */ public function loadArea($areaCode) { $this->_appArea = $areaCode; $scope = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( \Magento\Framework\Config\Scope::class ); $scope->setCurrentScope($areaCode); \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->configure( \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( \Magento\Framework\App\ObjectManager\ConfigLoader::class )->load( $areaCode ) ); $app = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\App\AreaList::class); $areasForPartialLoading = [ \Magento\Framework\App\Area::AREA_GLOBAL, \Magento\Framework\App\Area::AREA_WEBAPI_REST, \Magento\Framework\App\Area::AREA_WEBAPI_SOAP, \Magento\Framework\App\Area::AREA_CRONTAB, \Magento\Framework\App\Area::AREA_GRAPHQL ]; if (in_array($areaCode, $areasForPartialLoading, true)) { $app->getArea($areaCode)->load(\Magento\Framework\App\Area::PART_CONFIG); } else { \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea($areaCode); } } /** * Gets customized directory paths * * @return array */ protected function getCustomDirs() { $path = DirectoryList::PATH; $var = "{$this->installDir}/var"; $generated = "{$this->installDir}/generated"; $customDirs = [ DirectoryList::CONFIG => [$path => "{$this->installDir}/etc"], DirectoryList::VAR_DIR => [$path => $var], DirectoryList::MEDIA => [$path => "{$this->installDir}/pub/media"], DirectoryList::STATIC_VIEW => [$path => "{$this->installDir}/pub/static"], DirectoryList::TMP_MATERIALIZATION_DIR => [$path => "{$var}/view_preprocessed/pub/static"], DirectoryList::GENERATED_CODE => [$path => "{$generated}/code"], DirectoryList::CACHE => [$path => "{$var}/cache"], DirectoryList::LOG => [$path => "{$var}/log"], DirectoryList::SESSION => [$path => "{$var}/session"], DirectoryList::TMP => [$path => "{$var}/tmp"], DirectoryList::UPLOAD => [$path => "{$var}/upload"], DirectoryList::PUB => [$path => "{$this->installDir}/pub"], ]; return $customDirs; } }