This commit is contained in:
2026-09-15 10:26:34 +08:00
commit ecf36afa56
10493 changed files with 1404888 additions and 0 deletions
@@ -0,0 +1,105 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Annotation;
use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Filesystem\Io\File;
use Magento\TestFramework\Deploy\CliCommand;
use Magento\TestFramework\Deploy\TestModuleManager;
use PHPUnit\Util\Test as TestUtil;
/**
* Handler for applying reinstallMagento annotation.
*/
class CopyModules
{
/**
* @var TestModuleManager
*/
private $moduleManager;
/**
* @var CliCommand
*/
private $cliCommand;
/**
* CopyModules constructor.
*/
public function __construct()
{
$this->moduleManager = new TestModuleManager();
$this->cliCommand = new CliCommand($this->moduleManager);
}
/**
* Handler for 'startTest' event.
*
* @param \PHPUnit\Framework\TestCase $test
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function startTest(\PHPUnit\Framework\TestCase $test)
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($test),
$test->getName(false)
);
//This annotation can be declared only on method level
if (isset($annotations['method']['moduleName'])) {
$moduleNames = $annotations['method']['moduleName'];
foreach ($moduleNames as $moduleName) {
$this->cliCommand->introduceModule($moduleName);
//Include module`s registration.php to load it
$path = MAGENTO_MODULES_PATH . explode("_", $moduleName)[1] . '/registration.php';
include $path;
}
}
}
/**
* Handler for 'startTest' event
*
* @param \PHPUnit\Framework\TestCase $test
*/
public function endTest(\PHPUnit\Framework\TestCase $test)
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($test),
$test->getName(false)
);
//This annotation can be declared only on method level
if (!empty($annotations['method']['moduleName'])) {
foreach ($annotations['method']['moduleName'] as $moduleName) {
$path = MAGENTO_MODULES_PATH .
//Take only module name from Magento_ModuleName
explode("_", $moduleName)[1];
File::rmdirRecursive($path);
$this->unsergisterModuleFromComponentRegistrar($moduleName);
}
}
}
/**
* Unregister module from component registrar.
* The component registrar uses static private variable and does not provide unregister method,
* however unregister is required to remove registered modules after they are deleted from app/code.
*
* @param string $moduleName
*
* @return void
*/
private function unsergisterModuleFromComponentRegistrar($moduleName)
{
$reflection = new \ReflectionClass(ComponentRegistrar::class);
$reflectionProperty = $reflection->getProperty('paths');
$reflectionProperty->setAccessible(true);
$value = $reflectionProperty->getValue();
unset($value[ComponentRegistrar::MODULE][$moduleName]);
$reflectionProperty->setValue(null, $value);
}
}
@@ -0,0 +1,151 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Annotation;
use Magento\Framework\DB\Adapter\SqlVersionProvider;
use Magento\TestFramework\Deploy\CliCommand;
use Magento\TestFramework\Deploy\TestModuleManager;
use Magento\TestFramework\TestCase\MutableDataInterface;
use PHPUnit\Util\Test as TestUtil;
/**
* Handler for applying reinstallMagento annotation.
*/
class DataProviderFromFile
{
/**
* @var string
*/
public const FALLBACK_VALUE = 'default';
/**
* @var array
*/
public const POSSIBLE_SUFFIXES = [
SqlVersionProvider::MYSQL_8_0_VERSION => 'mysql8',
SqlVersionProvider::MARIA_DB_10_4_VERSION => 'mariadb10',
SqlVersionProvider::MARIA_DB_10_6_VERSION => 'mariadb106',
SqlVersionProvider::MYSQL_8_0_29_VERSION => 'mysql829',
SqlVersionProvider::MARIA_DB_10_4_27_VERSION => 'mariadb10427',
SqlVersionProvider::MARIA_DB_10_6_11_VERSION => 'mariadb10611',
SqlVersionProvider::MARIA_DB_10_11_11_VERSION => 'mariadb101111',
SqlVersionProvider::MARIA_DB_10_11_VERSION => 'mariadb1011'
];
/**
* @var TestModuleManager
*/
private $moduleManager;
/**
* @var CliCommand
*/
private $cliCommand;
/**
* CopyModules constructor.
*/
public function __construct()
{
$this->moduleManager = new TestModuleManager();
$this->cliCommand = new CliCommand($this->moduleManager);
}
/**
* Start test.
*
* @param \PHPUnit\Framework\TestCase $test
* @throws \Exception
*/
public function startTest(\PHPUnit\Framework\TestCase $test)
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($test),
$test->getName(false)
);
//This annotation can be declared only on method level
if (isset($annotations['method']['dataProviderFromFile']) && $test instanceof MutableDataInterface) {
$test->setData(
$this->loadAllFiles(TESTS_MODULES_PATH . "/" . $annotations['method']['dataProviderFromFile'][0])
);
} elseif (!$test instanceof MutableDataInterface) {
throw new \Exception("Test type do not supports @dataProviderFromFile annotation");
}
}
/**
* Finish test.
*
* @param \PHPUnit\Framework\TestCase $test
* @throws \Exception
*/
public function endTest(\PHPUnit\Framework\TestCase $test)
{
if ($test instanceof MutableDataInterface) {
$test->flushData();
}
}
/**
* Load different db version files for different databases.
*
* @param string $path The path of the inital file.
*
* @return array
*/
private function loadAllFiles(string $path): array
{
$result = [];
$pathWithoutExtension = $this->removeFileExtension($path);
foreach (glob($pathWithoutExtension . '.*') as $file) {
/* Search database string in file name like mysql8 with
possibility to use version until patch level. */
preg_match('/\.([\D]*[\d]*(?:\.[\d]+){0,2})/', $file, $splitedParts);
$dbKey = self::FALLBACK_VALUE;
if (count($splitedParts) > 1) {
$database = array_pop($splitedParts);
if ($this->isValidDatabaseSuffix($database)) {
$dbKey = $database;
}
}
$result[$dbKey] = include $file;
}
return $result;
}
/**
* Remove the file extension from path.
*
* @param string $path The file path.
*
* @return string
*/
private function removeFileExtension(string $path)
{
$result = explode('.', $path);
array_pop($result);
return implode('.', $result);
}
/**
* Check if database suffix is valid.
*
* @param string $databaseSuffix The suffix of the database from the file
*
* @return bool
*/
private function isValidDatabaseSuffix(string $databaseSuffix): bool
{
return in_array($databaseSuffix, self::POSSIBLE_SUFFIXES);
}
}
@@ -0,0 +1,55 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Annotation;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Module\ModuleResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;
/**
* Handler for applying reinstallMagento annotation.
*/
class ReinstallInstance
{
/**
* @var \Magento\TestFramework\Application
*/
private $application;
/**
* Constructor
*
* @param \Magento\TestFramework\Application $application
*/
public function __construct(\Magento\TestFramework\Application $application)
{
$this->application = $application;
}
public function startTest()
{
/** @var ObjectManager $objectManager */
$objectManager = Bootstrap::getObjectManager();
$resourceConnection = $objectManager->create(ResourceConnection::class);
$objectManager->removeSharedInstance(ResourceConnection::class);
$objectManager->addSharedInstance($resourceConnection, ResourceConnection::class);
$this->application->reinitialize();
}
/**
* Handler for 'endTest' event.
*
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function endTest()
{
$this->application->cleanup();
$this->application->reinitialize();
ModuleResource::flush();
}
}
@@ -0,0 +1,198 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* Implementation of the @magentoSchemaFixture DocBlock annotation.
*/
namespace Magento\TestFramework\Annotation;
use PHPUnit\Util\Test as TestUtil;
/**
* Represents following construction handling:
*
* @magentoSchemaFixture {link_to_file.php}
*/
class SchemaFixture
{
/**
* Fixtures base directory.
*
* @var string
*/
protected $fixtureBaseDir;
/**
* Fixtures that have been applied.
*
* @var array
*/
private $appliedFixtures = [];
/**
* Constructor.
*
* @param string $fixtureBaseDir
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function __construct($fixtureBaseDir)
{
if (!is_dir($fixtureBaseDir)) {
throw new \Magento\Framework\Exception\LocalizedException(
new \Magento\Framework\Phrase("Fixture base directory '%1' does not exist.", [$fixtureBaseDir])
);
}
$this->fixtureBaseDir = realpath($fixtureBaseDir);
}
/**
* Apply magento data fixture on.
*
* @param \PHPUnit\Framework\TestCase $test
* @return void
*/
public function startTest(\PHPUnit\Framework\TestCase $test)
{
if ($this->_getFixtures($test)) {
$this->_applyFixtures($this->_getFixtures($test));
}
}
/**
* Finish test execution.
*
* @param \PHPUnit\Framework\TestCase $test
*/
public function endTest(\PHPUnit\Framework\TestCase $test)
{
if ($this->_getFixtures($test)) {
$this->_revertFixtures();
}
}
/**
* Retrieve fixtures from annotation.
*
* @param \PHPUnit\Framework\TestCase $test
* @param string $scope
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
protected function _getFixtures(\PHPUnit\Framework\TestCase $test, $scope = null)
{
if ($scope === null) {
$annotations = $this->getAnnotations($test);
} else {
$source = TestUtil::parseTestMethodAnnotations(
get_class($test),
$test->getName(false)
);
$annotations = $source[$scope];
}
$result = [];
if (!empty($annotations['magentoSchemaFixture'])) {
foreach ($annotations['magentoSchemaFixture'] as $fixture) {
if (strpos($fixture, '\\') !== false) {
// usage of a single directory separator symbol streamlines search across the source code
throw new \Magento\Framework\Exception\LocalizedException(
new \Magento\Framework\Phrase('Directory separator "\\" is prohibited in fixture declaration.')
);
}
$fixtureMethod = [get_class($test), $fixture];
if (is_callable($fixtureMethod)) {
$result[] = $fixtureMethod;
} else {
$result[] = $this->fixtureBaseDir . '/' . $fixture;
}
}
}
return $result;
}
/**
* Get annotations for test.
*
* @param \PHPUnit\Framework\TestCase $test
* @return array
*/
private function getAnnotations(\PHPUnit\Framework\TestCase $test)
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($test),
$test->getName(false)
);
return array_replace($annotations['class'], $annotations['method']);
}
/**
* Execute single fixture script.
*
* @param string|array $fixture
* @throws \Exception
*/
protected function _applyOneFixture($fixture)
{
try {
if (is_callable($fixture)) {
call_user_func($fixture);
} else {
include $fixture;
}
} catch (\Exception $e) {
throw new \Exception(
sprintf("Error in fixture: %s.\n %s", json_encode($fixture), $e->getMessage()),
500,
$e
);
}
}
/**
* Execute fixture scripts if any.
*
* @param array $fixtures
* @throws \Magento\Framework\Exception\LocalizedException
*/
protected function _applyFixtures(array $fixtures)
{
/* Execute fixture scripts */
foreach ($fixtures as $oneFixture) {
/* Skip already applied fixtures */
if (in_array($oneFixture, $this->appliedFixtures, true)) {
continue;
}
$this->_applyOneFixture($oneFixture);
$this->appliedFixtures[] = $oneFixture;
}
}
/**
* Revert changes done by fixtures.
*/
protected function _revertFixtures()
{
foreach ($this->appliedFixtures as $fixture) {
if (is_callable($fixture)) {
$fixture[1] .= 'Rollback';
if (is_callable($fixture)) {
$this->_applyOneFixture($fixture);
}
} else {
$fileInfo = pathinfo($fixture);
$extension = '';
if (isset($fileInfo['extension'])) {
$extension = '.' . $fileInfo['extension'];
}
$rollbackScript = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension;
if (file_exists($rollbackScript)) {
$this->_applyOneFixture($rollbackScript);
}
}
}
$this->appliedFixtures = [];
}
}
@@ -0,0 +1,37 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\TestFramework\App;
use Magento\Framework\App\Area\FrontNameResolverFactory;
use Magento\Framework\ObjectManagerInterface;
/**
* Stub for \Magento\Framework\App\AreaList
*/
class AreaList extends \Magento\Framework\App\AreaList
{
/**
* @param ObjectManagerInterface $objectManager
* @param FrontNameResolverFactory $resolverFactory
* @param array $areas
* @param string|null $default
*/
public function __construct(
ObjectManagerInterface $objectManager,
FrontNameResolverFactory $resolverFactory,
array $areas = [],
$default = null
) {
parent::__construct($objectManager, $resolverFactory, $areas, $default);
/**
* Then Magento is installed for setup-integration tests, di.xml files are parsed from all Magento modules,
* causing Magento\Framework\App\AreaList _areas property to be filled with arguments from disabled modules.
*/
$this->_areas = [];
}
}
@@ -0,0 +1,40 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Bootstrap;
/**
* Bootstrap of the custom DocBlock annotations.
*
* \Magento\TestFramework\Isolation\DeploymentConfig was excluded for setup/upgrade tests.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SetupDocBlock extends \Magento\TestFramework\Bootstrap\DocBlock
{
/**
* Get list of subscribers. In addition, register <b>reinstallMagento</b> annotation processing.
*
* @param \Magento\TestFramework\Application $application
* @return array
*/
protected function _getSubscribers(\Magento\TestFramework\Application $application)
{
return [
new \Magento\TestFramework\Workaround\Segfault(),
new \Magento\TestFramework\Workaround\Cleanup\TestCaseProperties(),
new \Magento\TestFramework\Workaround\Cleanup\StaticProperties(),
new \Magento\TestFramework\Isolation\WorkingDirectory(),
new \Magento\TestFramework\Workaround\DeploymentConfig(),
new \Magento\TestFramework\Annotation\ComponentRegistrarFixture($this->_fixturesBaseDir),
new \Magento\TestFramework\Annotation\SchemaFixture($this->_fixturesBaseDir),
new \Magento\TestFramework\Annotation\Cache(),
new \Magento\TestFramework\Workaround\CacheClean(),
new \Magento\TestFramework\Annotation\ReinstallInstance($application),
new \Magento\TestFramework\Annotation\CopyModules(),
new \Magento\TestFramework\Annotation\DataProviderFromFile()
];
}
}
@@ -0,0 +1,283 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\App\DeploymentConfig;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Shell;
use Magento\Framework\Shell\CommandRenderer;
use Magento\Setup\Console\Command\InstallCommand;
/**
* The purpose of this class is enable/disable module and upgrade commands execution.
*
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
*/
class CliCommand
{
/**
* @var Shell
*/
private $shell;
/**
* @var TestModuleManager
*/
private $testEnv;
/**
* @var ParametersHolder
*/
private $parametersHolder;
/**
* @var DeploymentConfig
*/
private $deploymentConfig;
/**
* @param TestModuleManager $testEnv
* @internal param Shell $shell
*/
public function __construct(
\Magento\TestFramework\Deploy\TestModuleManager $testEnv
) {
$this->shell = new Shell(new CommandRenderer());
$this->testEnv = $testEnv;
$this->parametersHolder = new ParametersHolder();
}
/**
* Copy Test module files and execute enable module command.
*
* @param string $moduleName
* @return string
* @throws LocalizedException
*/
public function introduceModule($moduleName)
{
$this->testEnv->addModuleFiles($moduleName);
return $this->enableModule($moduleName);
}
/**
* Execute enable module command.
*
* @param string $moduleName
* @return string
* @throws LocalizedException
*/
public function enableModule($moduleName)
{
$initParams = $this->parametersHolder->getInitParams();
$enableModuleCommand = $this->getCliScriptCommand() . ' module:enable ' . $moduleName
. ' -n -vvv --magento-init-params="' . $initParams['magento-init-params'] . '"';
return $this->shell->execute($enableModuleCommand);
}
/**
* Execute upgrade magento command.
*
* @param array $installParams
* @return string
* @throws LocalizedException
*/
public function upgrade($installParams = [])
{
$initParams = $this->parametersHolder->getInitParams();
$upgradeCommand = $this->getCliScriptCommandWithDI() . 'setup:upgrade -vvv -n --magento-init-params="'
. $initParams['magento-init-params'] . '"';
$installParams = $this->toCliArguments($installParams);
$upgradeCommand .= ' ' . implode(" ", array_keys($installParams));
return $this->shell->execute($upgradeCommand, array_values($installParams));
}
/**
* Execute disable module command.
*
* @param string $moduleName
* @return string
* @throws LocalizedException
*/
public function disableModule($moduleName)
{
$initParams = $this->parametersHolder->getInitParams();
$disableModuleCommand = $this->getCliScriptCommand() . ' module:disable ' . $moduleName
. ' -vvv --magento-init-params="' . $initParams['magento-init-params'] . '"';
return $this->shell->execute($disableModuleCommand);
}
/**
* Split quote db configuration.
*
* @return void
* @throws LocalizedException
* @deprecated split database solution is deprecated and will be removed
*/
public function splitQuote()
{
//phpcs:ignore Magento2.Functions.DiscouragedFunction
trigger_error('Method is deprecated', E_USER_DEPRECATED);
$initParams = $this->parametersHolder->getInitParams();
$installParams = $this->toCliArguments(
$this->parametersHolder->getDbData('checkout')
);
$command = $this->getCliScriptCommand() . ' setup:db-schema:split-quote ' .
implode(" ", array_keys($installParams)) .
' -vvv --no-interaction --magento-init-params="' .
$initParams['magento-init-params'] . '"';
$this->shell->execute($command, array_values($installParams));
}
/**
* Split sales db configuration.
*
* @return void
* @throws LocalizedException
* @deprecated split database solution is deprecated and will be removed
*/
public function splitSales()
{
//phpcs:ignore Magento2.Functions.DiscouragedFunction
trigger_error('Method is deprecated', E_USER_DEPRECATED);
$initParams = $this->parametersHolder->getInitParams();
$installParams = $this->toCliArguments(
$this->parametersHolder->getDbData('sales')
);
$command = $this->getCliScriptCommand() . ' setup:db-schema:split-sales ' .
implode(" ", array_keys($installParams)) .
' -vvv --magento-init-params="' .
$initParams['magento-init-params'] . '"';
$this->shell->execute($command, array_values($installParams));
}
/**
* Clean all types of cache
*/
public function cacheClean()
{
$initParams = $this->parametersHolder->getInitParams();
$command = $this->getCliScriptCommand() . ' cache:clean ' .
' -vvv --magento-init-params=' .
$initParams['magento-init-params'];
$this->shell->execute($command);
}
/**
* Uninstall module
*
* @param string $moduleName
* @throws LocalizedException
*/
public function uninstallModule($moduleName)
{
$initParams = $this->parametersHolder->getInitParams();
$command = $this->getCliScriptCommand() . ' module:uninstall ' . $moduleName . ' --remove-data ' .
' -vvv --non-composer --magento-init-params="' .
$initParams['magento-init-params'] . '"';
$this->shell->execute($command);
}
/**
* Convert from raw params to CLI arguments, like --admin-username.
*
* @param array $params
* @return array
*/
private function toCliArguments(array $params)
{
$result = [];
foreach ($params as $key => $value) {
if (!empty($value)) {
$result["--{$key}=%s"] = $value;
}
}
return $result;
}
/**
* Execute install command.
*
* @param array $modules
* @param array $installParams
* @return string
* @throws \Exception
*/
public function install(array $modules, array $installParams = [])
{
if (empty($modules)) {
throw new \Exception("Cannot install Magento without modules");
}
$params = $this->parametersHolder->getInitParams();
$installParams += [
InstallCommand::INPUT_KEY_ENABLE_MODULES => implode(",", $modules),
InstallCommand::INPUT_KEY_DISABLE_MODULES => 'all'
];
$installParams = $this->toCliArguments(
array_merge(
$params,
$this->parametersHolder->getDbData('default'),
$installParams
)
);
// run install script
$exitCode = $this->shell->execute(
PHP_BINARY . ' -f %s setup:install -vvv ' . implode(' ', array_keys($installParams)),
array_merge([BP . '/bin/magento'], array_values($installParams))
);
$this->afterInstall();
return $exitCode;
}
/**
* You can decorate this function in order to add your own events here
*
* @return void
*/
public function afterInstall()
{
//Take current deployment config in order to flush it cache after installation
//Before installation usually we do not have any connections - so we need to add them
$this->deploymentConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
->get(DeploymentConfig::class);
$this->deploymentConfig->resetData();
}
/**
* Get custom magento-cli command with additional DI configuration
*
* @return string
*/
private function getCliScriptCommandWithDI(): string
{
$params['MAGE_DIRS']['base']['path'] = BP;
$params['INTEGRATION_TESTS_CLI_AUTOLOADER'] = TESTS_BASE_DIR . '/framework/autoload.php';
$params['TESTS_BASE_DIR'] = TESTS_BASE_DIR;
return 'INTEGRATION_TEST_PARAMS="' . urldecode(http_build_query($params)) . '"'
. ' ' . PHP_BINARY . ' -f ' . INTEGRATION_TESTS_BASE_DIR
. '/bin/magento ';
}
/**
* Get basic magento-cli command
*
* @return string
*/
private function getCliScriptCommand()
{
return PHP_BINARY . ' -f ' . BP . '/bin/magento ';
}
}
@@ -0,0 +1,60 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DbSchemaReader;
/**
* The purpose of this class is adding test modules files to Magento code base.
*/
class DescribeTable
{
/**
* Schema reader.
*
* @var DbSchemaReader
*/
private $dbSchemaReader;
/**
* This registry is used to ignore some tables, during comparison
*
* @var array
*/
private static $ignoredSystemTables = ['cache', 'cache_tag', 'flag', 'session', 'setup_module', 'patch_list'];
/**
* Constructor.
*
* @param DbSchemaReader $dbSchemaReader
*/
public function __construct(DbSchemaReader $dbSchemaReader)
{
$this->dbSchemaReader = $dbSchemaReader;
}
/**
* Describe shards.
*
* @param string $shardName
* @return array
*/
public function describeShard($shardName)
{
$data = [];
$tables = $this->dbSchemaReader->readTables($shardName);
foreach ($tables as $table) {
if (in_array($table, self::$ignoredSystemTables)) {
continue;
}
$data[$table] = $this->dbSchemaReader->getCreateTableSql($table, $shardName)['Create Table'];
}
return $data;
}
}
@@ -0,0 +1,73 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Shell;
use Magento\Setup\Console\Command\InstallCommand;
/**
* The purpose of this class is enable/disable module and upgrade commands execution.
*/
class ParametersHolder
{
/**
* Initialize params.
*
* @var array
*/
private $initParams;
/**
* Return application initialization parameters.
*
* @return array
*/
public function getInitParams()
{
if (!isset($this->initParams)) {
$customDirs = $this->getCustomDirs();
$initParams = [
\Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS => $customDirs,
];
$this->initParams = ['magento-init-params' => urldecode(http_build_query($initParams))];
}
return $this->initParams;
}
/**
* Include data from config file and convert it to db format:
* -db-name
* -db-user-name
* -db-password
* -db-host
*
* @param string $resource can be default, checkout, sales
* @return array
*/
public function getDbData($resource)
{
$dbData = include TESTS_INSTALLATION_DB_CONFIG_FILE;
return $dbData[$resource];
}
/**
* Get customized directory paths.
*
* @return array
*/
private function getCustomDirs()
{
$installDir = TESTS_TEMP_DIR;
$path = DirectoryList::PATH;
$var = "{$installDir}/var";
$customDirs = [
DirectoryList::CONFIG => [$path => "{$installDir}/etc"],
DirectoryList::VAR_DIR => [$path => $var],
];
return $customDirs;
}
}
@@ -0,0 +1,64 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\App\DeploymentConfig\Reader as ConfigReader;
use Magento\Framework\App\DeploymentConfig\Writer as ConfigWriter;
use Magento\Framework\Config\File\ConfigFilePool;
/**
* The purpose of this class is add sharding db connections to env file.
*/
class ShardingConfig
{
/**
* @var ConfigWriter
*/
private $configWriter;
/**
* @var ConfigReader
*/
private $configReader;
/**
* @param ConfigWriter $configWriter
* @param ConfigReader $configReader
*/
public function __construct(ConfigWriter $configWriter, ConfigReader $configReader)
{
$this->configWriter = $configWriter;
$this->configReader = $configReader;
}
/**
* Apply sharding database connection to env file
*/
public function applyConfiguration()
{
$allDbData = include TESTS_INSTALLATION_DB_CONFIG_FILE;
$config = $this->configReader->load(ConfigFilePool::APP_ENV);
foreach ($allDbData as $connectionName => $dbData) {
if (!isset($config['db']['connection'][$connectionName]) && $connectionName !== 'default') {
$config['db']['connection'][$connectionName] = [
'host' => $dbData['host'],
'username' => $dbData['username'],
'password' => $dbData['password'],
'dbname' => $dbData['dbname'],
'model' => 'mysql4',
'engine' => 'innodb',
'initStatements' => 'SET NAMES utf8;',
'active' => '1'
];
$config['resource'][$connectionName] = [
'connection' => $connectionName
];
$this->configWriter->saveConfig([ConfigFilePool::APP_ENV => $config], true);
}
}
}
}
@@ -0,0 +1,43 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\App\ResourceConnection;
/**
* The purpose of this class is to describe what data is in table
*/
class TableData
{
/**
* @var ResourceConnection
*/
private $resourceConnection;
/**
* TableData constructor.
* @param ResourceConnection $resourceConnection
*/
public function __construct(ResourceConnection $resourceConnection)
{
$this->resourceConnection = $resourceConnection;
}
/**
* @param string $tableName
* @param string $columnName
* @return array
*/
public function describeTableData($tableName, $columnName = null)
{
$adapter = $this->resourceConnection->getConnection();
$cols = $columnName ?: '*';
$select = $adapter
->select()
->from($tableName, $cols);
return $columnName ? $adapter->fetchCol($select) : $adapter->fetchAll($select);
}
}
@@ -0,0 +1,182 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Deploy;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Module\ModuleList;
use Magento\Framework\Module\ModuleListInterface;
/**
* The purpose of this class is adding test modules files to Magento code base.
*/
class TestModuleManager
{
/**
* Name of file of DB XML declaration.
*/
const DECLARATIVE_FILE_NAME = "db_schema.xml";
/**
* Add test module files to Magento code base.
*
* @param string $moduleName
* @return void
* @throws \RuntimeException
*/
public function addModuleFiles($moduleName)
{
$moduleName = str_replace("Magento_", "", $moduleName);
$pathToCommittedTestModules = TESTS_MODULES_PATH . '/Magento/' . $moduleName;
$pathToInstalledMagentoInstanceModules = MAGENTO_MODULES_PATH . $moduleName;
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$pathToCommittedTestModules,
\RecursiveDirectoryIterator::FOLLOW_SYMLINKS
)
);
/** @var \SplFileInfo $file */
foreach ($iterator as $file) {
if (!$file->isDir()) {
$source = $file->getPathname();
$relativePath = substr($source, strlen($pathToCommittedTestModules));
$destination = $pathToInstalledMagentoInstanceModules . $relativePath;
$targetDir = dirname($destination);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
copy($source, $destination);
}
}
unset($iterator, $file);
// Register the modules under '_files/'
$pathPattern = $pathToInstalledMagentoInstanceModules . '/Test*/registration.php';
$files = glob($pathPattern, GLOB_NOSORT);
if ($files === false) {
throw new \RuntimeException('glob() returned error while searching in \'' . $pathPattern . '\'');
}
foreach ($files as $file) {
include $file;
}
}
/**
* Copy revision folder to main module
*
* @param string $moduleName
* @param string $revisionName
* @param string $dir
* @return void
*/
public function addRevision($moduleName, $revisionName, $dir)
{
$modulePath = str_replace("Magento_", "", $moduleName);
$folder = MAGENTO_MODULES_PATH . $modulePath;
$desiredPath = $folder . '/' . $dir;
$revisionPath = $folder . '/revisions/' . $revisionName . '/';
if (!is_dir($desiredPath)) {
mkdir($desiredPath, 0777, true);
}
rename($revisionPath, $desiredPath);
}
/**
* Update module version.
*
* @param string $moduleName Like Magento_TestSetupModule
* @param string $revisionName Folder name, like reviisions/revision_1/db_schema.xml
* @param string $fileName For example db_schema.xml
* @param string $fileDir For example etc or Setup
*/
public function updateRevision($moduleName, $revisionName, $fileName, $fileDir)
{
$modulePath = str_replace("Magento_", "", $moduleName);
$folder = MAGENTO_MODULES_PATH . $modulePath;
$oldFile = $folder . DIRECTORY_SEPARATOR . $fileDir . "/" . $fileName;
$revisionFile = MAGENTO_MODULES_PATH . $modulePath . "/revisions/" .
$revisionName . DIRECTORY_SEPARATOR . $fileName;
if (!file_exists($oldFile)) {
$dir = dirname($oldFile);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
touch($oldFile);
}
if (file_exists($revisionFile)) {
unlink($oldFile);
copy($revisionFile, $oldFile);
} else {
throw new \InvalidArgumentException("Old File or revision files paths are invalid");
}
}
/**
* Remove test module files to Magento code base.
*
* @param string $moduleName
* @return void
*/
public function removeModuleFiles($moduleName)
{
$modulePath = str_replace("Magento_", "", $moduleName);
$folder = MAGENTO_MODULES_PATH . $modulePath;
//remove test modules from magento codebase
if (is_dir($folder)) {
\Magento\Framework\Filesystem\Io\File::rmdirRecursive($folder);
}
}
/**
* There can be situation when config version of module can be cached
* So proposed to clean shared instance of
* @see ModuleList in order to achieve clean installation
*/
public function cleanModuleList()
{
/**
* @var \Magento\TestFramework\ObjectManager $objectManager
*/
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
$objectManager->removeSharedInstance(ModuleList::class);
$objectManager->removeSharedInstance(ModuleListInterface::class);
}
/**
* Update module files.
*
* @param string $moduleName
* @return void
*/
public function updateModuleFiles($moduleName)
{
$pathToCommittedTestModules = TESTS_MODULES_PATH . '/UpgradeScripts/' . $moduleName;
$pathToInstalledMagentoInstanceModules = MAGENTO_MODULES_PATH . $moduleName;
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$pathToCommittedTestModules,
\RecursiveDirectoryIterator::FOLLOW_SYMLINKS
)
);
/** @var \SplFileInfo $file */
foreach ($iterator as $file) {
if (!$file->isDir()) {
$source = $file->getPathname();
$relativePath = substr($source, strlen($pathToCommittedTestModules));
$destination = $pathToInstalledMagentoInstanceModules . $relativePath;
$targetDir = dirname($destination);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
copy($source, $destination);
}
}
}
}
@@ -0,0 +1,24 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\TestFramework\Mview;
/**
* Stub for \Magento\Framework\Mview\TriggerCleaner
*/
class DummyTriggerCleaner
{
/**
* Remove the outdated trigger from the system
*
* @return bool
*/
public function removeTriggers(): bool
{
return true;
}
}
@@ -0,0 +1,37 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework;
/**
* Encapsulates application installation, initialization and uninstall, add flag to skip database dump.
*
* Allow installation and uninstallation.
*/
class SetupApplication extends Application
{
/**
* {@inheritdoc}
*/
protected $dumpDb = false;
/**
* @var bool
*/
protected $canLoadArea = false;
/**
* @var bool
*/
protected $canInstallSequence = false;
/**
* {@inheritdoc}
*/
public function run()
{
throw new \Exception("Can't start application.");
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\TestCase;
/**
* This interface allows to add data to test case dynamically, for example from startTest listeners
* in order to reuse it later.
*/
interface MutableDataInterface
{
/**
* Set data providers data.
*
* @param array $data
* @return void
*/
public function setData(array $data);
/**
* Retrieve data injected dynamically in test case.
*
* @return array
*/
public function getData();
/**
* Revert data to default dataProviders data.
*
* @return void
*/
public function flushData();
}
@@ -0,0 +1,147 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\TestFramework\TestCase;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\ConnectionException;
use Magento\Framework\DB\Adapter\SqlVersionProvider;
use Magento\TestFramework\Annotation\DataProviderFromFile;
use Magento\TestFramework\Helper\Bootstrap;
use Zend_Db_Statement_Exception;
/**
* Instance of Setup test case. Used in order to tweak dataProviders functionality.
*/
class SetupTestCase extends \PHPUnit\Framework\TestCase implements MutableDataInterface
{
/**
* @var array
*/
private $data = [];
/**
* @var string
*/
private $dbKey;
/**
* @var SqlVersionProvider
*/
private $sqlVersionProvider;
/**
* @var ResourceConnection
*/
private ResourceConnection $resourceConnection;
/**
* @inheritDoc
*/
public function __construct(
$name = null,
array $data = [],
$dataName = '',
ResourceConnection $resourceConnection = null
) {
parent::__construct($name, $data, $dataName);
$objectManager = Bootstrap::getObjectManager();
$this->sqlVersionProvider = $objectManager->get(SqlVersionProvider::class);
$this->resourceConnection = $resourceConnection ?? $objectManager->get(ResourceConnection::class);
}
/**
* @inheritdoc
*/
public function setData(array $data)
{
$this->data = $data;
}
/**
* @inheritdoc
*/
public function flushData()
{
$this->data = [];
}
/**
* @inheritdoc
*/
public function getData()
{
if (array_key_exists($this->getDbKey(), $this->data)) {
return $this->data[$this->getDbKey()];
}
return $this->data[DataProviderFromFile::FALLBACK_VALUE];
}
/**
* Get database version.
*
* @return string
* @throws ConnectionException
*/
protected function getDatabaseVersion(): string
{
return $this->sqlVersionProvider->getSqlVersion();
}
/**
* Get db key to decide which file to use.
*
* @return string
* @throws ConnectionException
*/
private function getDbKey(): string
{
if ($this->dbKey) {
return $this->dbKey;
}
$this->dbKey = DataProviderFromFile::FALLBACK_VALUE;
foreach (DataProviderFromFile::POSSIBLE_SUFFIXES as $possibleVersion => $suffix) {
if ($this->sqlVersionProvider->isMysqlGte8029()) {
$this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MYSQL_8_0_29_VERSION];
break;
} elseif ($this->sqlVersionProvider->isMariaDBGte10427()) {
$this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MARIA_DB_10_4_27_VERSION];
break;
} elseif ($this->sqlVersionProvider->isMariaDBGte10611()) {
$this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MARIA_DB_10_6_11_VERSION];
break;
} elseif ($this->sqlVersionProvider->isMariaDBGte101111()) {
$this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MARIA_DB_10_11_11_VERSION];
break;
} elseif (strpos($this->getDatabaseVersion(), (string)$possibleVersion) !== false) {
$this->dbKey = $suffix;
break;
}
}
return $this->dbKey;
}
/**
* Checks if the DB connection Aurora RDS
*
* @param string $resource
* @return bool
*/
public function isUsingAuroraDb(string $resource = ResourceConnection::DEFAULT_CONNECTION): bool
{
try {
$this->resourceConnection->getConnection($resource)->query('SELECT AURORA_VERSION();');
return true;
} catch (Zend_Db_Statement_Exception $e) {
return false;
}
}
}
@@ -0,0 +1,30 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Workaround;
use Magento\TestFramework\Helper\Bootstrap;
/**
* Deployment config handler.
*
* @package Magento\TestFramework\Workaround
*/
class CacheClean
{
/**
* Start test.
*
* @return void
*/
public function endTest()
{
/** @var \Magento\Framework\App\Cache\Manager $cacheManager */
$cacheManager = Bootstrap::getObjectManager()->get(\Magento\Framework\App\Cache\Manager::class);
$types = $cacheManager->getAvailableTypes();
$cacheManager->clean($types);
}
}
@@ -0,0 +1,29 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\TestFramework\Workaround;
use Magento\TestFramework\Helper\Bootstrap;
/**
* Deployment config handler.
*
* @package Magento\TestFramework\Workaround
*/
class DeploymentConfig
{
/**
* Start test.
*
* @return void
*/
public function startTest()
{
/** @var \Magento\Framework\App\DeploymentConfig $deploymentConfig */
$deploymentConfig = Bootstrap::getObjectManager()->get(\Magento\Framework\App\DeploymentConfig::class);
$deploymentConfig->resetData();
}
}