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,109 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\MessageQueue\Model;
use Magento\Framework\MessageQueue\Consumer;
use Magento\Framework\MessageQueue\ConsumerFactory;
use Magento\Framework\MessageQueue\EnvelopeFactory;
use Magento\Framework\MessageQueue\QueueInterface;
use Magento\MysqlMq\Model\QueueManagement;
use Magento\MysqlMq\Model\ResourceModel\Queue;
use Magento\TestFramework\ObjectManager;
use PHPUnit\Framework\TestCase;
/**
* Tests the different cases of consumers running by Consumer processor
*/
class ConsumerTest extends TestCase
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var Consumer
*/
private $model;
/**
* @var Queue
*/
private $queueResource;
/**
* @inheritdoc
*/
protected function setUp(): void
{
$this->objectManager = ObjectManager::getInstance();
/** @var ConsumerFactory $factory */
$factory = $this->objectManager->get(ConsumerFactory::class);
$this->model = $factory->get('demoConsumerWithConnectionException');
$this->queueResource = $this->objectManager->get(Queue::class);
}
/**
* Test if after connection exception and retry
* message doesn't have success status but still has status in progress
*
* @return void
*/
public function testRunWithException(): void
{
/** @var EnvelopeFactory $envelopFactory */
$envelopFactory = $this->objectManager->get(EnvelopeFactory::class);
$messageBody = '{"name":"test"}';
$topicName = 'demo.connection.exception';
$queueName = 'queue-connection-exception';
$envelope = $envelopFactory->create(['body' => $messageBody, 'properties' => ['topic_name' => $topicName]]);
/** @var QueueInterface $queue */
$queue = $this->objectManager->create(
\Magento\MysqlMq\Model\Driver\Queue::class,
['queueName' => $queueName]
);
$queue->push($envelope);
$messages = $this->queueResource->getMessages($queueName, 1);
$envelope = $envelopFactory->create(['body' => $messageBody, 'properties' => $messages[0]]);
$this->model->process(1);
$queue->reject($envelope);
$this->model->process(1);
$message = $this->getLastMessage($queueName);
$this->assertEquals(QueueManagement::MESSAGE_STATUS_IN_PROGRESS, $message['status']);
}
/**
* Return last message by queue name
*
* @param string $queueName
* @return array
*/
private function getLastMessage(string $queueName)
{
$connection = $this->queueResource->getConnection();
$select = $connection->select()
->from(
['queue_message' => $this->queueResource->getTable('queue_message')],
[]
)->join(
['queue_message_status' => $this->queueResource->getTable('queue_message_status')],
'queue_message.id = queue_message_status.message_id',
[
QueueManagement::MESSAGE_QUEUE_RELATION_ID => 'id',
QueueManagement::MESSAGE_STATUS => 'status',
]
)->join(
['queue' => $this->queueResource->getTable('queue')],
'queue.id = queue_message_status.queue_id',
[QueueManagement::MESSAGE_QUEUE_NAME => 'name']
)->where('queue.name = ?', $queueName)
->order(['queue_message_status.id DESC']);
return $connection->fetchRow($select);
}
}
@@ -0,0 +1,280 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\MessageQueue\Model\Cron;
use Magento\Framework\App\Config\ReinitableConfigInterface;
use Magento\Framework\App\DeploymentConfig;
use Magento\Framework\App\DeploymentConfig\FileReader;
use Magento\Framework\App\DeploymentConfig\Writer;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Config\File\ConfigFilePool;
use Magento\Framework\Filesystem;
use Magento\Framework\Lock\Backend\Database;
use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\MessageQueue\Consumer\ConfigInterface as ConsumerConfigInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\ShellInterface;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests the different cases of consumers running by ConsumersRunner
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ConsumersRunnerTest extends TestCase
{
/**
* @var ObjectManagerInterface
*/
private $objectManager;
/**
* Consumer config provider
*
* @var ConsumerConfigInterface
*/
private $consumerConfig;
/**
* @var LockManagerInterface
*/
private $lockManager;
/**
* @var FileReader
*/
private $reader;
/**
* @var ConsumersRunner
*/
private $consumersRunner;
/**
* @var Filesystem
*/
private $filesystem;
/**
* @var ConfigFilePool
*/
private $configFilePool;
/**
* @var ReinitableConfigInterface
*/
private $appConfig;
/**
* @var ShellInterface|MockObject
*/
private $shellMock;
/**
* @var array
*/
private $config;
/**
* @inheritdoc
*/
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->shellMock = $this->getMockBuilder(ShellInterface::class)
->getMockForAbstractClass();
$resourceConnection = $this->objectManager->create(ResourceConnection::class);
$deploymentConfig = $this->objectManager->create(DeploymentConfig::class);
// create object with new otherwise dummy locker is created because of di.xml preference for integration tests
$this->lockManager = new Database($resourceConnection, $deploymentConfig);
$this->consumerConfig = $this->objectManager->get(ConsumerConfigInterface::class);
$this->reader = $this->objectManager->get(FileReader::class);
$this->filesystem = $this->objectManager->get(Filesystem::class);
$this->configFilePool = $this->objectManager->get(ConfigFilePool::class);
$this->appConfig = $this->objectManager->get(ReinitableConfigInterface::class);
$this->consumersRunner = $this->objectManager->create(
ConsumersRunner::class,
['shellBackground' => $this->shellMock]
);
$this->config = $this->loadConfig();
$this->shellMock->expects($this->any())
->method('execute')
->willReturnCallback(
function ($command, $arguments) {
$command = vsprintf($command, $arguments);
$params = Bootstrap::getInstance()->getAppInitParams();
$params['MAGE_DIRS']['base']['path'] = BP;
$params = 'INTEGRATION_TEST_PARAMS="' . urldecode(http_build_query($params)) . '"';
$command = str_replace('bin/magento', 'dev/tests/integration/bin/magento', $command);
$command = $params . ' ' . $command;
return exec("{$command} >/dev/null &"); //phpcs:ignore
}
);
}
/**
* @param string $specificConsumer
* @param int $maxMessage
* @param string $command
* @param array $expectedArguments
*
* @return void
* @dataProvider runDataProvider
*/
public function testArgumentMaxMessages(
string $specificConsumer,
int $maxMessage,
string $command,
array $expectedArguments
) {
$config = $this->config;
$config['cron_consumers_runner'] = ['consumers' => [$specificConsumer], 'max_messages' => $maxMessage];
$this->writeConfig($config);
$this->shellMock->expects($this->any())
->method('execute')
->with($command, $expectedArguments);
$this->consumersRunner->run();
}
/**
* @return array
*/
public function runDataProvider()
{
return [
[
'specificConsumer' => 'exportProcessor',
'max_messages' => 10,
'command' => PHP_BINARY . ' ' . BP . '/bin/magento queue:consumers:start %s %s %s',
'expectedArguments' => ['exportProcessor', '--single-thread', '--max-messages=10'],
],
[
'specificConsumer' => 'exportProcessor',
'max_messages' => 5000,
'command' => PHP_BINARY . ' ' . BP . '/bin/magento queue:consumers:start %s %s %s',
'expectedArguments' => ['exportProcessor', '--single-thread', '--max-messages=100'],
],
];
}
/**
* Tests running of specific consumer and his re-running when it is working
*
* @return void
*/
public function testSpecificConsumerAndRerun()
{
$specificConsumer = 'exportProcessor';
$config = $this->config;
$config['cron_consumers_runner'] = ['consumers' => [$specificConsumer], 'max_messages' => 0];
$config['queue'] = ['only_spawn_when_message_available' => 0];
$this->writeConfig($config);
$this->reRunConsumersAndCheckLocks($specificConsumer);
$this->reRunConsumersAndCheckLocks($specificConsumer);
$this->assertTrue($this->lockManager->isLocked(md5($specificConsumer))); //phpcs:ignore
}
/**
* @param string $specificConsumer
* @return void
*/
private function reRunConsumersAndCheckLocks($specificConsumer)
{
$this->consumersRunner->run();
sleep(20);
foreach ($this->consumerConfig->getConsumers() as $consumer) {
$consumerName = $consumer->getName();
if ($consumerName === $specificConsumer) {
$this->assertTrue($this->lockManager->isLocked(md5($consumerName))); //phpcs:ignore
} else {
$this->assertFalse($this->lockManager->isLocked(md5($consumerName))); //phpcs:ignore
}
}
}
/**
* Tests disabling cron job which runs consumers
*
* @return void
*/
public function testCronJobDisabled()
{
$config = $this->config;
$config['cron_consumers_runner'] = ['cron_run' => false];
$this->writeConfig($config);
$this->consumersRunner->run();
sleep(20);
foreach ($this->consumerConfig->getConsumers() as $consumer) {
$this->assertFalse($this->lockManager->isLocked(md5($consumer->getName()))); //phpcs:ignore
}
}
/**
* @return array
*/
private function loadConfig()
{
return $this->reader->load(ConfigFilePool::APP_ENV);
}
/**
* @param array $config
* @return void
*/
private function writeConfig(array $config)
{
/** @var Writer $writer */
$writer = $this->objectManager->get(Writer::class);
$writer->saveConfig([ConfigFilePool::APP_ENV => $config]);
}
/**
* @inheritdoc
*/
protected function tearDown(): void
{
foreach ($this->consumerConfig->getConsumers() as $consumer) {
foreach ($this->getConsumerProcessIds($consumer->getName()) as $consumerProcessId) {
exec("kill {$consumerProcessId}"); //phpcs:ignore
}
}
$this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile(
$this->configFilePool->getPath(ConfigFilePool::APP_ENV),
"<?php\n return array();\n"
);
$this->writeConfig($this->config);
$this->appConfig->reinit();
}
/**
* Get Consumer ProcessIds
*
* @param string $consumer
* @return string[]
*/
private function getConsumerProcessIds($consumer)
{
//phpcs:ignore
exec("ps ax | grep -v grep | grep 'queue:consumers:start {$consumer}' | awk '{print $1}'", $output);
return $output;
}
}
@@ -0,0 +1,66 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\MessageQueue\Model\Plugin\ResourceModel;
use Magento\TestFramework\Event\Magento;
class LockTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \Magento\Framework\MessageQueue\LockInterface
*/
protected $lock;
/**
* @var \Magento\Framework\MessageQueue\Lock\WriterInterface
*/
protected $writer;
/**
* @var \Magento\Framework\MessageQueue\Lock\ReaderInterface
*/
protected $reader;
protected function setUp(): void
{
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
$this->lock = $this->objectManager->get(\Magento\Framework\MessageQueue\LockInterface::class);
$this->writer = $this->objectManager->get(\Magento\Framework\MessageQueue\Lock\WriterInterface::class);
$this->reader = $this->objectManager->get(\Magento\Framework\MessageQueue\Lock\ReaderInterface::class);
}
/**
* Test to ensure Queue Lock Table is cleared when maintenance mode transitions from on to off.
*
* @return void
*/
public function testLockClearedByMaintenanceModeOff()
{
/** @var $maintenanceMode \Magento\Framework\App\MaintenanceMode */
$maintenanceMode = $this->objectManager->get(\Magento\Framework\App\MaintenanceMode::class);
// md5() here is not for cryptographic use.
// phpcs:ignore Magento2.Security.InsecureFunction
$code = md5('consumer.name-1');
$this->lock->setMessageCode($code);
$this->writer->saveLock($this->lock);
$this->reader->read($this->lock, $code);
$id = $this->lock->getId();
$maintenanceMode->set(true);
$maintenanceMode->set(false);
$this->reader->read($this->lock, $code);
$emptyId = $this->lock->getId();
$this->assertGreaterThanOrEqual('1', $id);
$this->assertEmpty($emptyId);
}
}
@@ -0,0 +1,28 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\MessageQueue\Model\ResourceModel;
use Magento\Framework\MessageQueue\LockInterface;
use Magento\TestFramework\ObjectManager;
use PHPUnit\Framework\TestCase;
/**
* Covers Lock resource model test cases
*/
class LockTest extends TestCase
{
public function testSaveLock()
{
$objectManager = ObjectManager::getInstance();
/** @var Lock $resourceModel */
$resourceModel = $objectManager->get(Lock::class);
$lock = $objectManager->create(LockInterface::class);
$resourceModel->saveLock($lock);
self::assertNotEquals(null, $lock->getId());
}
}