diff --git a/app/code/Magento/Cron/Console/Command/CronCommand.php b/app/code/Magento/Cron/Console/Command/CronCommand.php
index 46b87e536187d..21ff041a8d5a4 100644
--- a/app/code/Magento/Cron/Console/Command/CronCommand.php
+++ b/app/code/Magento/Cron/Console/Command/CronCommand.php
@@ -3,7 +3,6 @@
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Cron\Console\Command;
use Symfony\Component\Console\Command\Command;
@@ -94,7 +93,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
}
/** @var \Magento\Framework\App\Cron $cronObserver */
- $cronObserver = $objectManager->create('Magento\Framework\App\Cron', ['parameters' => $params]);
+ $cronObserver = $objectManager->create(\Magento\Framework\App\Cron::class, ['parameters' => $params]);
$cronObserver->launch();
$output->writeln('' . 'Ran jobs by schedule.' . '');
}
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 0000000000000..84f948249db52
--- /dev/null
+++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php
@@ -0,0 +1,79 @@
+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('Crontab has already been generated and saved');
+ return Cli::RETURN_FAILURE;
+ }
+
+ try {
+ $this->crontabManager->saveTasks($this->tasksProvider->getTasks());
+ } catch (LocalizedException $e) {
+ $output->writeln('' . $e->getMessage() . '');
+ return Cli::RETURN_FAILURE;
+ }
+
+ $output->writeln('Crontab has been generated and saved');
+
+ 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 0000000000000..36ec73dc4fec8
--- /dev/null
+++ b/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php
@@ -0,0 +1,62 @@
+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('' . $e->getMessage() . '');
+ return Cli::RETURN_FAILURE;
+ }
+
+ $output->writeln('Magento cron tasks have been removed');
+
+ return Cli::RETURN_SUCCESS;
+ }
+}
diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml
index 0f7897c58b8fe..d571fa263116c 100644
--- a/app/code/Magento/Cron/etc/di.xml
+++ b/app/code/Magento/Cron/etc/di.xml
@@ -8,6 +8,8 @@
+
+
DefaultScopeReader
@@ -33,6 +35,8 @@
- Magento\Cron\Console\Command\CronCommand
+ - Magento\Cron\Console\Command\CronInstallCommand
+ - Magento\Cron\Console\Command\CronRemoveCommand
@@ -43,4 +47,24 @@
+
+
+ Magento\Framework\App\Shell
+
+
+
+
+
+ -
+
- {magentoRoot}bin/magento cron:run | grep -v "Ran jobs by schedule" >> {magentoLog}magento.cron.log
+
+ -
+
- {magentoRoot}update/cron.php >> {magentoLog}update.cron.log
+
+ -
+
- {magentoRoot}bin/magento setup:cron:run >> {magentoLog}setup.cron.log
+
+
+
+
diff --git a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php
index 1d220071d91f8..42a54f78ef961 100644
--- a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php
+++ b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php
@@ -88,6 +88,11 @@ public function create(
$appendComment,
$comment->getIsVisibleOnFront()
);
+
+ if ($appendComment) {
+ $shipment->setCustomerNote($comment->getComment());
+ $shipment->setCustomerNoteNotify($appendComment);
+ }
}
return $shipment;
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php
index bf334d8c4e733..e310e0cbe49ee 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php
@@ -92,7 +92,7 @@ protected function setUp()
$this->shipmentMock = $this->getMockBuilder(ShipmentInterface::class)
->disableOriginalConstructor()
- ->setMethods(['addComment', 'addTrack'])
+ ->setMethods(['addComment', 'addTrack', 'setCustomerNote', 'setCustomerNoteNotify'])
->getMockForAbstractClass();
$this->hydratorPoolMock = $this->getMockBuilder(HydratorPool::class)
@@ -166,7 +166,7 @@ public function testCreate()
if ($appendComment) {
$comment = "New comment!";
$visibleOnFront = true;
- $this->commentMock->expects($this->once())
+ $this->commentMock->expects($this->exactly(2))
->method('getComment')
->willReturn($comment);
@@ -178,6 +178,10 @@ public function testCreate()
->method('addComment')
->with($comment, $appendComment, $visibleOnFront)
->willReturnSelf();
+
+ $this->shipmentMock->expects($this->once())
+ ->method('setCustomerNoteNotify')
+ ->with(true);
}
$this->assertEquals(
diff --git a/app/code/Magento/Store/etc/frontend/di.xml b/app/code/Magento/Store/etc/frontend/di.xml
index 3e3ee0f1c5cf8..20945ea04e651 100644
--- a/app/code/Magento/Store/etc/frontend/di.xml
+++ b/app/code/Magento/Store/etc/frontend/di.xml
@@ -18,7 +18,7 @@
-
- Magento\Framework\App\Router\Base
- false
- - 20
+ - 30
-
- Magento\Framework\App\Router\DefaultRouter
diff --git a/app/code/Magento/UrlRewrite/etc/frontend/di.xml b/app/code/Magento/UrlRewrite/etc/frontend/di.xml
index a33595382c2a9..6b879c3f72afc 100644
--- a/app/code/Magento/UrlRewrite/etc/frontend/di.xml
+++ b/app/code/Magento/UrlRewrite/etc/frontend/di.xml
@@ -12,7 +12,7 @@
-
- Magento\UrlRewrite\Controller\Router
- false
- - 40
+ - 20
diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php
new file mode 100644
index 0000000000000..61150af7f07aa
--- /dev/null
+++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php
@@ -0,0 +1,224 @@
+shell = $shell;
+ $this->filesystem = $filesystem;
+ }
+
+ /**
+ * @return string
+ */
+ private function getTasksBlockStart()
+ {
+ $tasksBlockStart = self::TASKS_BLOCK_START;
+ if (defined('BP')) {
+ $tasksBlockStart .= ' ' . md5(BP);
+ }
+ return $tasksBlockStart;
+ }
+
+ /**
+ * @return string
+ */
+ private function getTasksBlockEnd()
+ {
+ $tasksBlockEnd = self::TASKS_BLOCK_END;
+ if (defined('BP')) {
+ $tasksBlockEnd .= ' ' . md5(BP);
+ }
+ return $tasksBlockEnd;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTasks()
+ {
+ $this->checkSupportedOs();
+ $content = $this->getCrontabContent();
+ $pattern = '!(' . $this->getTasksBlockStart() . ')(.*?)(' . $this->getTasksBlockEnd() . ')!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)
+ {
+ if (!$tasks) {
+ throw new LocalizedException(new Phrase('List of tasks is empty'));
+ }
+
+ $this->checkSupportedOs();
+ $baseDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath();
+ $logDir = $this->filesystem->getDirectoryRead(DirectoryList::LOG)->getAbsolutePath();
+
+ 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 .= $this->getTasksBlockStart() . PHP_EOL;
+ foreach ($tasks as $task) {
+ $content .= $task['expression'] . ' ' . PHP_BINARY . ' ' . $task['command'] . PHP_EOL;
+ }
+ $content .= $this->getTasksBlockEnd() . 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($this->getTasksBlockStart()) . '.*?'
+ . preg_quote($this->getTasksBlockEnd() . 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 operating system is not supported to work with this command')
+ );
+ }
+ }
+}
diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php
new file mode 100644
index 0000000000000..c924d6049affe
--- /dev/null
+++ b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php
@@ -0,0 +1,47 @@
+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 0000000000000..e9ab9eb649f2b
--- /dev/null
+++ b/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php
@@ -0,0 +1,20 @@
+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 . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . 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 . ' ' . md5(BP) . 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 . ' ' . md5(BP) . 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 . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL,
+ 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL
+ ],
+ [
+ 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . 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 . ' ' . md5(BP) . 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->never())
+ ->method('getAbsolutePath');
+ $logDirMock = $this->getMockBuilder(ReadInterface::class)
+ ->getMockForAbstractClass();
+ $logDirMock->expects($this->never())
+ ->method('getAbsolutePath');
+
+ $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 . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL;
+
+ return [
+ [
+ 'tasks' => [
+ ['expression' => '* * * * *', 'command' => 'run.php']
+ ],
+ 'content' => $content,
+ 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * ' . PHP_BINARY . ' run.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . 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 . ' ' . md5(BP) . PHP_EOL
+ . '1 2 3 4 5 ' . PHP_BINARY . ' run.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . 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 . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php >>'
+ . ' /var/www/magento2/var/log/cron.log' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL,
+ ],
+ [
+ 'tasks' => [
+ ['command' => '{magentoRoot}run.php % cron:run | grep -v "Ran \'jobs\' by schedule"']
+ ],
+ 'content' => $content,
+ 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL
+ . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php'
+ . ' %% cron:run | grep -v \"Ran \'jobs\' by schedule\"' . PHP_EOL
+ . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . 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 0000000000000..d3149641b5d9c
--- /dev/null
+++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php
@@ -0,0 +1,33 @@
+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());
+ }
+}