diff --git a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..2835244599d38943fea5b2162814bbd6b9edb32e --- /dev/null +++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Console\Command; + +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Crontab\TasksProviderInterface; +use Magento\Framework\Exception\LocalizedException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Console\Cli; +use Symfony\Component\Console\Input\InputOption; + +/** + * CronInstallCommand installs Magento cron tasks + */ +class CronInstallCommand extends Command +{ + /** + * @var CrontabManagerInterface + */ + private $crontabManager; + + /** + * @var TasksProviderInterface + */ + private $tasksProvider; + + /** + * @param CrontabManagerInterface $crontabManager + * @param TasksProviderInterface $tasksProvider + */ + public function __construct( + CrontabManagerInterface $crontabManager, + TasksProviderInterface $tasksProvider + ) { + $this->crontabManager = $crontabManager; + $this->tasksProvider = $tasksProvider; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('cron:install') + ->setDescription('Generates and installs crontab for current user') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force install tasks'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($this->crontabManager->getTasks() && !$input->getOption('force')) { + $output->writeln('<error>Crontab has already been generated and saved</error>'); + return Cli::RETURN_FAILURE; + } + + try { + $this->crontabManager->saveTasks($this->tasksProvider->getTasks()); + } catch (LocalizedException $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + return Cli::RETURN_FAILURE; + } + + $output->writeln('<info>Crontab has been generated and saved</info>'); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php b/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..11c666a556f899e9d7c6358c09291ee9df904998 --- /dev/null +++ b/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Console\Command; + +use Magento\Framework\Crontab\CrontabManagerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; + +/** + * CronRemoveCommand removes Magento cron tasks + */ +class CronRemoveCommand extends Command +{ + /** + * @var CrontabManagerInterface + */ + private $crontabManager; + + /** + * @param CrontabManagerInterface $crontabManager + */ + public function __construct(CrontabManagerInterface $crontabManager) + { + $this->crontabManager = $crontabManager; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('cron:remove') + ->setDescription('Removes tasks from crontab'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $this->crontabManager->removeTasks(); + } catch (LocalizedException $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + return Cli::RETURN_FAILURE; + } + + $output->writeln('<info>Magento cron tasks have been removed</info>'); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php b/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..09949cf78dc8f267fe2bb668d91f2b28032ed8c1 --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Test\Unit\Console\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Cron\Console\Command\CronInstallCommand; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Crontab\TasksProviderInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +class CronInstallCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CrontabManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $crontabManagerMock; + + /** + * @var TasksProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tasksProviderMock; + + /** + * @var CommandTester + */ + private $commandTester; + + /** + * @return void + */ + protected function setUp() + { + $this->crontabManagerMock = $this->getMockBuilder(CrontabManagerInterface::class) + ->getMockForAbstractClass(); + $this->tasksProviderMock = $this->getMockBuilder(TasksProviderInterface::class) + ->getMockForAbstractClass(); + + $this->commandTester = new CommandTester( + new CronInstallCommand($this->crontabManagerMock, $this->tasksProviderMock) + ); + } + + /** + * @return void + */ + public function testExecuteAlreadyInstalled() + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn([['* * * * * /bin/php /var/run.php']]); + $this->tasksProviderMock->expects($this->never()) + ->method('getTasks'); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Crontab has already been generated and saved' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } + + /** + * @return void + */ + public function testExecuteWithException() + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->tasksProviderMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->crontabManagerMock->expects($this->once()) + ->method('saveTasks') + ->willThrowException(new LocalizedException(new Phrase('Some error'))); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Some error' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } + + /** + * @param array $existingTasks + * @param array $options + * @return void + * @dataProvider executeDataProvider + */ + public function testExecute($existingTasks, $options) + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn($existingTasks); + $this->tasksProviderMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->crontabManagerMock->expects($this->once()) + ->method('saveTasks') + ->with([]); + + $this->commandTester->execute($options); + $this->assertEquals( + 'Crontab has been generated and saved' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_SUCCESS, $this->commandTester->getStatusCode()); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + ['existingTasks' => [], 'options' => []], + ['existingTasks' => ['* * * * * /bin/php /var/www/run.php'], 'options' => ['-f'=> true]] + ]; + } +} diff --git a/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php b/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cd017b26d758578482bffa137a3f221650619bae --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Test\Unit\Console\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Cron\Console\Command\CronRemoveCommand; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +class CronRemoveCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CrontabManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $crontabManagerMock; + + /** + * @var CommandTester + */ + private $commandTester; + + /** + * @return void + */ + protected function setUp() + { + $this->crontabManagerMock = $this->getMockBuilder(CrontabManagerInterface::class) + ->getMockForAbstractClass(); + + $this->commandTester = new CommandTester( + new CronRemoveCommand($this->crontabManagerMock) + ); + } + + /** + * @return void + */ + public function testExecute() + { + $this->crontabManagerMock->expects($this->once()) + ->method('RemoveTasks'); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Magento cron tasks have been removed' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_SUCCESS, $this->commandTester->getStatusCode()); + } + + /** + * @return void + */ + public function testExecuteFailed() + { + $this->crontabManagerMock->expects($this->once()) + ->method('RemoveTasks') + ->willThrowException(new LocalizedException(new Phrase('Some error'))); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Some error' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } +} diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml index d5624e96765c58bd699eebc29c14c7fe76037b7a..6abc9096f24012309a3b56cb0b84912d3a8d1e26 100644 --- a/app/code/Magento/Cron/etc/di.xml +++ b/app/code/Magento/Cron/etc/di.xml @@ -8,6 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Cron\Model\ConfigInterface" type="Magento\Cron\Model\Config" /> <preference for="Magento\Framework\Shell\CommandRendererInterface" type="Magento\Framework\Shell\CommandRenderer" /> + <preference for="Magento\Framework\Crontab\CrontabManagerInterface" type="Magento\Framework\Crontab\CrontabManager" /> + <preference for="Magento\Framework\Crontab\TasksProviderInterface" type="Magento\Framework\Crontab\TasksProvider" /> <type name="Magento\Config\Model\Config\Structure\Converter"> <plugin name="cron_backend_config_structure_converter_plugin" type="Magento\Cron\Model\Backend\Config\Structure\Converter" /> </type> @@ -28,6 +30,8 @@ <arguments> <argument name="commands" xsi:type="array"> <item name="cronCommand" xsi:type="object">Magento\Cron\Console\Command\CronCommand</item> + <item name="cronInstall" xsi:type="object">Magento\Cron\Console\Command\CronInstallCommand</item> + <item name="cronRemove" xsi:type="object">Magento\Cron\Console\Command\CronRemoveCommand</item> </argument> </arguments> </type> @@ -38,4 +42,24 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Crontab\CrontabManager"> + <arguments> + <argument name="shell" xsi:type="object">Magento\Framework\App\Shell</argument> + </arguments> + </type> + <type name="Magento\Framework\Crontab\TasksProviderInterface"> + <arguments> + <argument name="tasks" xsi:type="array"> + <item name="cronMagento" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}bin/magento cron:run | grep -v "Ran jobs by schedule" >> {magentoLog}magento.cron.log</item> + </item> + <item name="cronUpdate" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}update/cron.php >> {magentoLog}update.cron.log</item> + </item> + <item name="cronSetup" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}bin/magento setup:cron:run >> {magentoLog}setup.cron.log</item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php new file mode 100644 index 0000000000000000000000000000000000000000..a60bd9d2cb512ac0000941657e76db1f0be20b85 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php @@ -0,0 +1,199 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +use Magento\Framework\ShellInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; + +/** + * Manager works with cron tasks + */ +class CrontabManager implements CrontabManagerInterface +{ + /** + * @var ShellInterface + */ + private $shell; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @param ShellInterface $shell + * @param Filesystem $filesystem + */ + public function __construct( + ShellInterface $shell, + Filesystem $filesystem + ) { + $this->shell = $shell; + $this->filesystem = $filesystem; + } + + /** + * {@inheritdoc} + */ + public function getTasks() + { + $this->checkSupportedOs(); + $content = $this->getCrontabContent(); + $pattern = '!(' . self::TASKS_BLOCK_START . ')(.*?)(' . self::TASKS_BLOCK_END . ')!s'; + + if (preg_match($pattern, $content, $matches)) { + $tasks = trim($matches[2], PHP_EOL); + $tasks = explode(PHP_EOL, $tasks); + return $tasks; + } + + return []; + } + + /** + * {@inheritdoc} + */ + public function saveTasks(array $tasks) + { + $this->checkSupportedOs(); + $baseDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); + $logDir = $this->filesystem->getDirectoryRead(DirectoryList::LOG)->getAbsolutePath(); + + if (!$tasks) { + throw new LocalizedException(new Phrase('List of tasks is empty')); + } + + foreach ($tasks as $key => $task) { + if (empty($task['expression'])) { + $tasks[$key]['expression'] = '* * * * *'; + } + + if (empty($task['command'])) { + throw new LocalizedException(new Phrase('Command should not be empty')); + } + + $tasks[$key]['command'] = str_replace( + ['{magentoRoot}', '{magentoLog}'], + [$baseDir, $logDir], + $task['command'] + ); + } + + $content = $this->getCrontabContent(); + $content = $this->cleanMagentoSection($content); + $content = $this->generateSection($content, $tasks); + + $this->save($content); + } + + /** + * {@inheritdoc} + * @throws LocalizedException + */ + public function removeTasks() + { + $this->checkSupportedOs(); + $content = $this->getCrontabContent(); + $content = $this->cleanMagentoSection($content); + $this->save($content); + } + + /** + * Generate Magento Tasks Section + * + * @param string $content + * @param array $tasks + * @return string + */ + private function generateSection($content, $tasks = []) + { + if ($tasks) { + $content .= self::TASKS_BLOCK_START . PHP_EOL; + foreach ($tasks as $task) { + $content .= $task['expression'] . ' ' . PHP_BINARY . ' '. $task['command'] . PHP_EOL; + } + $content .= self::TASKS_BLOCK_END . PHP_EOL; + } + + return $content; + } + + /** + * Clean Magento Tasks Section in crontab content + * + * @param string $content + * @return string + */ + private function cleanMagentoSection($content) + { + $content = preg_replace( + '!' . preg_quote(self::TASKS_BLOCK_START) . '.*?' . preg_quote(self::TASKS_BLOCK_END . PHP_EOL) . '!s', + '', + $content + ); + + return $content; + } + + /** + * Get crontab content without Magento Tasks Section + * + * In case of some exceptions the empty content is returned + * + * @return string + */ + private function getCrontabContent() + { + try { + $content = (string)$this->shell->execute('crontab -l'); + } catch (LocalizedException $e) { + return ''; + } + + return $content; + } + + /** + * Save crontab + * + * @param string $content + * @return void + * @throws LocalizedException + */ + private function save($content) + { + $content = str_replace('%', '%%', $content); + + try { + $this->shell->execute('echo "' . $content . '" | crontab -'); + } catch (LocalizedException $e) { + throw new LocalizedException( + new Phrase('Error during saving of crontab: %1', [$e->getPrevious()->getMessage()]), + $e + ); + } + } + + /** + * Check that OS is supported + * + * If OS is not supported then no possibility to work with crontab + * + * @return void + * @throws LocalizedException + */ + private function checkSupportedOs() + { + if (stripos(PHP_OS, 'WIN') === 0) { + throw new LocalizedException( + new Phrase('Your operation system is not supported, you cannot work with crontab') + ); + } + } +} diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c00ab41d8b873a46dc27abbb1af37a23b7e7fd86 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +use Magento\Framework\Exception\LocalizedException; + +interface CrontabManagerInterface +{ + /**#@+ + * Constants for wrapping Magento section in crontab + */ + const TASKS_BLOCK_START = '#~ MAGENTO START'; + const TASKS_BLOCK_END = '#~ MAGENTO END'; + /**#@-*/ + + /** + * Get list of Magento Tasks + * + * @return array + * @throws LocalizedException + */ + public function getTasks(); + + /** + * Save Magento Tasks to crontab + * + * @param array $tasks + * @return void + * @throws LocalizedException + */ + public function saveTasks(array $tasks); + + /** + * Remove Magento Tasks form crontab + * + * @return void + * @throws LocalizedException + */ + public function removeTasks(); +} diff --git a/lib/internal/Magento/Framework/Crontab/README.md b/lib/internal/Magento/Framework/Crontab/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bfbf194715dc8f9f359137ecb79be5a1adbd5c5e --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/README.md @@ -0,0 +1,12 @@ +Library for working with crontab + +The library has the next interfaces: +* CrontabManagerInterface +* TasksProviderInterface + +*CrontabManagerInterface* provides working with crontab: +* *getTasks* - get list of Magento cron tasks from crontab +* *saveTasks* - save Magento cron tasks to crontab +* *removeTasks* - remove Magento cron tasks from crontab + +*TasksProviderInterface* has only one method *getTasks*. This interface provides transportation the list of tasks from DI \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Crontab/TasksProvider.php b/lib/internal/Magento/Framework/Crontab/TasksProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..94524fd2bbd19b4395a8ba36daaa0f43c2b6c648 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/TasksProvider.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +/** + * TasksProvider collects list of tasks + */ +class TasksProvider implements TasksProviderInterface +{ + /** + * @var array + */ + private $tasks = []; + + /** + * @param array $tasks + */ + public function __construct(array $tasks = []) + { + $this->tasks = $tasks; + } + + /** + * {@inheritdoc} + */ + public function getTasks() + { + return $this->tasks; + } +} diff --git a/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php b/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bb02c30797be4cbf015c5858ac1c88825bde54a7 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +interface TasksProviderInterface +{ + /** + * Get list of tasks + * + * @return array + */ + public function getTasks(); +} diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7cafd386c629a4b48350d5ca4937b58fd49da4b6 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php @@ -0,0 +1,333 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab\Test\Unit; + +use Magento\Framework\Crontab\CrontabManager; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\ShellInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverPool; + +class CrontabManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ShellInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $shellMock; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + + /** + * @var CrontabManager + */ + private $crontabManager; + + /** + * @return void + */ + protected function setUp() + { + $this->shellMock = $this->getMockBuilder(ShellInterface::class) + ->getMockForAbstractClass(); + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalClone() + ->disableOriginalConstructor() + ->getMock(); + + $this->crontabManager = new CrontabManager($this->shellMock, $this->filesystemMock); + } + + /** + * @return void + */ + public function testGetTasksNoCrontab() + { + $exception = new \Exception('crontab: no crontab for user'); + $localizedException = new LocalizedException(new Phrase('Some error'), $exception); + + $this->shellMock->expects($this->once()) + ->method('execute') + ->with('crontab -l', []) + ->willThrowException($localizedException); + + $this->assertEquals([], $this->crontabManager->getTasks()); + } + + /** + * @param string $content + * @param array $tasks + * @return void + * @dataProvider getTasksDataProvider + */ + public function testGetTasks($content, $tasks) + { + $this->shellMock->expects($this->once()) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($content); + + $this->assertEquals($tasks, $this->crontabManager->getTasks()); + } + + /** + * @return array + */ + public function getTasksDataProvider() + { + return [ + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'tasks' => ['* * * * * /bin/php /var/www/magento/bin/magento cron:run'], + ], + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'tasks' => [ + '* * * * * /bin/php /var/www/magento/bin/magento cron:run', + '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run', + ], + ], + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL, + 'tasks' => [], + ], + [ + 'content' => '', + 'tasks' => [], + ], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Shell error + */ + public function testRemoveTasksWithException() + { + $exception = new \Exception('Shell error'); + $localizedException = new LocalizedException(new Phrase('Some error'), $exception); + + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn(''); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "" | crontab -', []) + ->willThrowException($localizedException); + + $this->crontabManager->removeTasks(); + } + + /** + * @param string $contentBefore + * @param string $contentAfter + * @return void + * @dataProvider removeTasksDataProvider + */ + public function testRemoveTasks($contentBefore, $contentAfter) + { + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($contentBefore); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "' . $contentAfter . '" | crontab -', []); + + $this->crontabManager->removeTasks(); + } + + /** + * @return array + */ + public function removeTasksDataProvider() + { + return [ + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '', + 'contentAfter' => '' + ], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage List of tasks is empty + */ + public function testSaveTasksWithEmptyTasksList() + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->crontabManager->saveTasks([]); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Command should not be empty + */ + public function testSaveTasksWithoutCommand() + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->crontabManager->saveTasks([ + 'myCron' => ['expression' => '* * * * *'] + ]); + } + + /** + * @param array $tasks + * @param string $content + * @param string $contentToSave + * @return void + * @dataProvider saveTasksDataProvider + */ + public function testSaveTasks($tasks, $content, $contentToSave) + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($content); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "' . $contentToSave . '" | crontab -', []); + + $this->crontabManager->saveTasks($tasks); + } + + /** + * @return array + */ + public function saveTasksDataProvider() + { + $content = '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL; + + return [ + [ + 'tasks' => [ + ['expression' => '* * * * *', 'command' => 'run.php'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * ' . PHP_BINARY . ' run.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + [ + 'tasks' => [ + ['expression' => '1 2 3 4 5', 'command' => 'run.php'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '1 2 3 4 5 ' . PHP_BINARY . ' run.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + [ + 'tasks' => [ + ['command' => '{magentoRoot}run.php >> {magentoLog}cron.log'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php >>' + . ' /var/www/magento2/var/log/cron.log' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..80be47cda5ef4973ad1999968dd636da0bd1757a --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php @@ -0,0 +1,34 @@ +<?php + +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab\Test\Unit; + +use Magento\Framework\Crontab\TasksProvider; + +class TasksProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return void + */ + public function testTasksProviderEmpty() + { + /** @var $tasksProvider $tasksProvider */ + $tasksProvider = new TasksProvider(); + $this->assertSame([], $tasksProvider->getTasks()); + } + + public function testTasksProvider() + { + $tasks = [ + 'magentoCron' => ['expressin' => '* * * * *', 'command' => 'bin/magento cron:run'], + 'magentoSetup' => ['command' => 'bin/magento setup:cron:run'], + ]; + + /** @var $tasksProvider $tasksProvider */ + $tasksProvider = new TasksProvider($tasks); + $this->assertSame($tasks, $tasksProvider->getTasks()); + } +}