diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce45b2c1..319828d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,15 @@ This option allow to run all tasks regardless of configured run time,
part of issue [#11](https://github.com/lavary/crunz/issues/11) -
PR [#120](https://github.com/lavary/crunz/pull/120) by [@PabloKowalczyk](https://github.com/PabloKowalczyk)
+- `--task=TASK-NUMBER` option to `schedule:run` command.
+This option allow to run only one specific task,
+part of issue [#11](https://github.com/lavary/crunz/issues/11) -
+PR [#129](https://github.com/lavary/crunz/pull/129) by [@PabloKowalczyk](https://github.com/PabloKowalczyk)
+
+### Changed
+- Tasks in `schedule:list` is sorted by filename,
+PR [#129](https://github.com/lavary/crunz/pull/129) by [@PabloKowalczyk](https://github.com/PabloKowalczyk)
+
## 1.7.3 - 2018-06-15
### Fixed
diff --git a/config/services.xml b/config/services.xml
index 472d73cd..d45e2593 100644
--- a/config/services.xml
+++ b/config/services.xml
@@ -14,6 +14,7 @@
+
+
+
taskCollection = $taskCollection;
$this->configuration = $configuration;
$this->eventRunner = $eventRunner;
$this->taskTimezone = $taskTimezone;
+ $this->scheduleFactory = $scheduleFactory;
parent::__construct();
}
@@ -70,6 +75,13 @@ protected function configure()
InputOption::VALUE_NONE,
'Run all tasks regardless of configured run time.'
)
+ ->addOption(
+ 'task',
+ 't',
+ InputOption::VALUE_REQUIRED,
+ 'Which task to run. Provide task number from schedule:list command.',
+ null
+ )
->setHelp('This command starts the Crunz event runner.');
}
@@ -80,11 +92,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
$this->arguments = $input->getArguments();
$this->options = $input->getOptions();
+ $task = $this->options['task'];
$files = $this->taskCollection
->all($this->arguments['source'])
;
- if (!count($files)) {
+ if (!\count($files)) {
$output->writeln('No task found! Please check your source path.');
return 0;
@@ -92,7 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
// List of schedules
$schedules = [];
-
$tasksTimezone = $this->taskTimezone
->timezoneForComparisons()
;
@@ -103,20 +115,39 @@ protected function execute(InputInterface $input, OutputInterface $output)
continue;
}
- if (false === $this->options['force']) {
- // We keep the events which are due and dismiss the rest.
- $schedule->events(
- $schedule->dueEvents(
- $tasksTimezone
- )
- );
- }
-
- if (count($schedule->events())) {
+ if (\count($schedule->events())) {
$schedules[] = $schedule;
}
}
+ // Is specified task should be invoked?
+ if (null !== $task) {
+ $schedules = $this->scheduleFactory
+ ->singleTaskSchedule(TaskNumber::fromString($task), ...$schedules);
+ }
+
+ $schedules = \array_map(
+ function (Schedule $schedule) use ($tasksTimezone) {
+ if (false === $this->options['force']) {
+ // We keep the events which are due and dismiss the rest.
+ $schedule->events(
+ $schedule->dueEvents(
+ $tasksTimezone
+ )
+ );
+ }
+
+ return $schedule;
+ },
+ $schedules
+ );
+ $schedules = \array_filter(
+ $schedules,
+ function (Schedule $schedule) {
+ return \count($schedule->events());
+ }
+ );
+
if (!count($schedules)) {
$output->writeln('No event is due!');
diff --git a/src/Exception/TaskNotExistException.php b/src/Exception/TaskNotExistException.php
new file mode 100644
index 00000000..f8f88d5a
--- /dev/null
+++ b/src/Exception/TaskNotExistException.php
@@ -0,0 +1,7 @@
+events();
+ },
+ $schedules
+ );
+
+ $flattenEvents = \array_merge(...$events);
+
+ if (!isset($flattenEvents[$taskNumber->asArrayIndex()])) {
+ $tasksCount = \count($flattenEvents);
+ throw new TaskNotExistException("Task with id '{$taskNumber->asInt()}' not found. Last task id is '{$tasksCount}'.");
+ }
+
+ $event = $flattenEvents[$taskNumber->asArrayIndex()];
+
+ $schedule = new Schedule();
+ $schedule->events([$event]);
+
+ return [$schedule];
+ }
+}
diff --git a/src/Task/Collection.php b/src/Task/Collection.php
index b5c86d4d..1fa1c3aa 100644
--- a/src/Task/Collection.php
+++ b/src/Task/Collection.php
@@ -37,6 +37,7 @@ public function all($source)
$iterator = $this->finder
->files()
->name("*{$suffix}")
+ ->sortByName()
->in($source)
;
diff --git a/src/Task/TaskNumber.php b/src/Task/TaskNumber.php
new file mode 100644
index 00000000..b694e638
--- /dev/null
+++ b/src/Task/TaskNumber.php
@@ -0,0 +1,64 @@
+number = $number;
+ }
+
+ /**
+ * @param $value string
+ *
+ * @return TaskNumber
+ *
+ * @throws WrongTaskNumberException
+ */
+ public static function fromString($value)
+ {
+ if (!\is_string($value)) {
+ throw new WrongTaskNumberException('Passed task number is not string.');
+ }
+
+ if (!\is_numeric($value)) {
+ throw new WrongTaskNumberException("Task number '{$value}' is not numeric.");
+ }
+
+ $number = (int) $value;
+
+ return new self($number);
+ }
+
+ /**
+ * @return int
+ */
+ public function asInt()
+ {
+ return $this->number;
+ }
+
+ /**
+ * @return int
+ */
+ public function asArrayIndex()
+ {
+ return $this->number - 1;
+ }
+}
diff --git a/tests/Unit/Console/Command/ScheduleRunCommandTest.php b/tests/Unit/Console/Command/ScheduleRunCommandTest.php
index 709d0ce6..f7edb198 100644
--- a/tests/Unit/Console/Command/ScheduleRunCommandTest.php
+++ b/tests/Unit/Console/Command/ScheduleRunCommandTest.php
@@ -4,6 +4,7 @@
use Crunz\Configuration\Configuration;
use Crunz\Console\Command\ScheduleRunCommand;
+use Crunz\Event;
use Crunz\EventRunner;
use Crunz\Schedule;
use Crunz\Task\Collection;
@@ -21,7 +22,7 @@ public function forceRunAllTasks()
{
$filename = $this->createTaskFile($this->taskContent());
- $mockInput = $this->mockInput(['force' => true]);
+ $mockInput = $this->mockInput(['force' => true, 'task' => null]);
$mockOutput = $this->createMock(OutputInterface::class);
$mockTaskCollection = $this->mockTaskCollection($filename);
$mockEventRunner = $this->mockEventRunner($mockOutput);
@@ -30,7 +31,8 @@ public function forceRunAllTasks()
$mockTaskCollection,
$this->createMock(Configuration::class),
$mockEventRunner,
- $this->createMock(Timezone::class)
+ $this->createMock(Timezone::class),
+ $this->createMock(Schedule\ScheduleFactory::class)
);
$command->run(
@@ -39,6 +41,48 @@ public function forceRunAllTasks()
);
}
+ /** @test */
+ public function runSpecificTask()
+ {
+ $filename1 = $this->createTaskFile($this->phpVersionTaskContent());
+ $filename2 = $this->createTaskFile($this->phpVersionTaskContent());
+
+ $mockInput = $this->mockInput(['force' => false, 'task' => '1']);
+ $mockOutput = $this->createMock(OutputInterface::class);
+ $mockTaskCollection = $this->mockTaskCollection($filename1, $filename2);
+ $mockEventRunner = $this->mockEventRunner($mockOutput);
+
+ $command = new ScheduleRunCommand(
+ $mockTaskCollection,
+ $this->createMock(Configuration::class),
+ $mockEventRunner,
+ $this->mockTimezoneProvider(),
+ $this->mockScheduleFactory()
+ );
+
+ $command->run(
+ $mockInput,
+ $mockOutput
+ );
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|Schedule\ScheduleFactory
+ */
+ private function mockScheduleFactory()
+ {
+ $mockEvent = $this->createMock(Event::class);
+ $mockSchedule = $this->createConfiguredMock(Schedule::class, ['events' => [$mockEvent]]);
+ $mockScheduleFactory = $this->createMock(Schedule\ScheduleFactory::class);
+ $mockScheduleFactory
+ ->expects($this->once())
+ ->method('singleTaskSchedule')
+ ->willReturn([$mockSchedule])
+ ;
+
+ return $mockScheduleFactory;
+ }
+
private function mockEventRunner(OutputInterface $output)
{
$mockEventRunner = $this->createMock(EventRunner::class);
@@ -56,7 +100,7 @@ function ($schedules) {
return $isArray
&& 1 === $count
&& $schedule instanceof Schedule
- ;
+ ;
}
)
)
@@ -70,28 +114,36 @@ private function mockInput(array $options)
$mockInput = $this->createMock(InputInterface::class);
$mockInput
->method('getOptions')
- ->willReturn(
- [
- 'force' => true,
- ]
- )
+ ->willReturn($options)
;
return $mockInput;
}
- private function mockTaskCollection($taskFile)
+ /**
+ * @return Timezone|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private function mockTimezoneProvider()
+ {
+ $timeZone = new \DateTimeZone('UTC');
+
+ return $this->createConfiguredMock(Timezone::class, ['timezoneForComparisons' => $timeZone]);
+ }
+
+ private function mockTaskCollection(...$taskFiles)
{
- $mockFileInfo = $this->createMock(SplFileInfo::class);
$mockTaskCollection = $this->createMock(Collection::class);
+ $mocksFileInfo = \array_map(
+ function ($taskFile) {
+ return $this->createConfiguredMock(SplFileInfo::class, ['getRealPath' => $taskFile]);
+ },
+ $taskFiles
+ );
+
$mockTaskCollection
->method('all')
- ->willReturn([$mockFileInfo])
- ;
- $mockFileInfo
- ->method('getRealPath')
- ->willReturn($taskFile)
+ ->willReturn($mocksFileInfo)
;
return $mockTaskCollection;
@@ -123,6 +175,24 @@ private function taskContent()
->skip(function () {return true;})
;
+return $schedule;
+PHP;
+ }
+
+ private function phpVersionTaskContent()
+ {
+ return <<<'PHP'
+run('php -v')
+ ->everyMinute()
+ ->description('Show PHP version')
+;
+
return $schedule;
PHP;
}
diff --git a/tests/Unit/Schedule/ScheduleFactoryTest.php b/tests/Unit/Schedule/ScheduleFactoryTest.php
new file mode 100644
index 00000000..20bf00be
--- /dev/null
+++ b/tests/Unit/Schedule/ScheduleFactoryTest.php
@@ -0,0 +1,44 @@
+events([$event1, $event2]);
+
+ $schedules = $factory->singleTaskSchedule(TaskNumber::fromString('1'), $schedule);
+ $firstSchedule = \reset($schedules);
+
+ $this->assertSame([$event1], $firstSchedule->events());
+ }
+
+ /** @test */
+ public function singleTaskScheduleThrowsExceptionOnWrongTaskNumber()
+ {
+ $factory = new ScheduleFactory();
+
+ $event1 = new Event(1, 'php -v');
+ $schedule = new Schedule();
+ $schedule->events([$event1]);
+
+ $this->expectException(TaskNotExistException::class);
+ $this->expectExceptionMessage("Task with id '2' not found. Last task id is '1'.");
+
+ $factory->singleTaskSchedule(TaskNumber::fromString('2'), $schedule);
+ }
+}
diff --git a/tests/Unit/Task/TaskNumberTest.php b/tests/Unit/Task/TaskNumberTest.php
new file mode 100644
index 00000000..61187383
--- /dev/null
+++ b/tests/Unit/Task/TaskNumberTest.php
@@ -0,0 +1,80 @@
+expectException(WrongTaskNumberException::class);
+ $this->expectExceptionMessage('Passed task number is not string.');
+
+ TaskNumber::fromString($value);
+ }
+
+ /**
+ * @test
+ * @dataProvider nonNumericProvider
+ */
+ public function taskNumberCanNotBeNonNumericString($value)
+ {
+ $this->expectException(WrongTaskNumberException::class);
+ $this->expectExceptionMessage("Task number '{$value}' is not numeric.");
+
+ TaskNumber::fromString($value);
+ }
+
+ /**
+ * @test
+ * @dataProvider numericValueProvider
+ */
+ public function taskNumberCanBeCreatedWithNumericStringValue($value, $expectedNumber)
+ {
+ $taskNumber = TaskNumber::fromString($value);
+
+ $this->assertSame($expectedNumber, $taskNumber->asInt());
+ }
+
+ /** @test */
+ public function arrayIndexIsOneStepLower()
+ {
+ $taskNumber = TaskNumber::fromString('14');
+
+ $this->assertSame(13, $taskNumber->asArrayIndex());
+ }
+
+ public function nonStringValueProvider()
+ {
+ yield 'null' => [null];
+ yield 'float' => [3.14];
+ yield 'int' => [7];
+ yield 'array' => [[]];
+ yield 'object' => [new \stdClass()];
+ }
+
+ public function numericValueProvider()
+ {
+ yield 'int' => [
+ '155',
+ 155,
+ ];
+ yield 'float' => [
+ '3.14',
+ 3,
+ ];
+ }
+
+ public function nonNumericProvider()
+ {
+ yield 'chars' => ['abc'];
+ yield 'charsWithNumber' => ['1a2b3'];
+ }
+}