Skip to content

Commit

Permalink
ExplainCommand: --id option
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Mar 11, 2024
1 parent b32d54a commit 52e5eb8
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 5 deletions.
11 changes: 9 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ use Orisai\Scheduler\Command\WorkerCommand;

$app = new Application();
$app->addCommands([
new ExplainCommand(),
new ExplainCommand($scheduler),
new ListCommand($scheduler),
new RunCommand($scheduler),
new RunJobCommand($scheduler),
Expand Down Expand Up @@ -630,6 +630,7 @@ List all scheduled jobs (in `expression / second (timezone) [id] name... next-du
- use `--timezone` (or `-tz`) to display times in specified timezone instead of one used by application
- e.g. `--tz=UTC`
- use `--explain` to explain whole expression, including [seconds](#seconds) and [timezones](#timezones)
- [Explain command](#explain-command) with `--id` parameter can be used to explain specific job

### Worker command

Expand All @@ -646,7 +647,13 @@ Run scheduler repeatedly, once every minute

Explain cron expression syntax

`bin/console scheduler:explain`
```shell
bin/console scheduler:explain
bin/console scheduler:explain --id="job id"
```

- use `--id=<id>` option to explain specific job
- [List command](#list-command) with `--explain` parameter can be used to explain all jobs

## Lazy loading

Expand Down
89 changes: 89 additions & 0 deletions src/Command/ExplainCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,41 @@

namespace Orisai\Scheduler\Command;

use DateTimeZone;
use Orisai\Clock\SystemClock;
use Orisai\CronExpressionExplainer\CronExpressionExplainer;
use Orisai\CronExpressionExplainer\DefaultCronExpressionExplainer;
use Orisai\Scheduler\Job\JobSchedule;
use Orisai\Scheduler\Scheduler;
use Psr\Clock\ClockInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function assert;
use function is_string;

final class ExplainCommand extends Command
{

private Scheduler $scheduler;

private CronExpressionExplainer $explainer;

private ClockInterface $clock;

public function __construct(
Scheduler $scheduler,
?CronExpressionExplainer $explainer = null,
?ClockInterface $clock = null
)
{
parent::__construct();
$this->scheduler = $scheduler;
$this->explainer = $explainer ?? new DefaultCronExpressionExplainer();

Check warning on line 36 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "Coalesce": --- Original +++ New @@ @@ { parent::__construct(); $this->scheduler = $scheduler; - $this->explainer = $explainer ?? new DefaultCronExpressionExplainer(); + $this->explainer = new DefaultCronExpressionExplainer() ?? $explainer; $this->clock = $clock ?? new SystemClock(); } public static function getDefaultName() : string

Check warning on line 36 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "Coalesce": --- Original +++ New @@ @@ { parent::__construct(); $this->scheduler = $scheduler; - $this->explainer = $explainer ?? new DefaultCronExpressionExplainer(); + $this->explainer = new DefaultCronExpressionExplainer() ?? $explainer; $this->clock = $clock ?? new SystemClock(); } public static function getDefaultName() : string
$this->clock = $clock ?? new SystemClock();
}

public static function getDefaultName(): string
{
return 'scheduler:explain';
Expand All @@ -19,13 +47,74 @@ public static function getDefaultDescription(): string
return 'Explain cron expression';
}

protected function configure(): void
{
/** @infection-ignore-all */
parent::configure();
$this->addOption('id', null, InputOption::VALUE_REQUIRED, 'ID of job to explain');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$id = $this->validateIdOption($input);

if ($id !== null) {
return $this->explainJobWithId($id, $output);
}

$this->explainSyntax($output);

return 0;
}

private function validateIdOption(InputInterface $input): ?string
{
$id = $input->getOption('id');
assert(is_string($id) || $id === null);

return $id;
}

private function explainJobWithId(string $id, OutputInterface $output): int
{
$jobSchedules = $this->scheduler->getJobSchedules();
$jobSchedule = $jobSchedules[$id] ?? null;

if ($jobSchedule === null) {
$output->writeln("<error>Job with id '$id' does not exist.</error>");

return 1;
}

$output->writeln($this->explainer->explain(
$jobSchedule->getExpression()->getExpression(),
$jobSchedule->getRepeatAfterSeconds(),
$this->computeTimeZone($jobSchedule, $this->clock->now()->getTimezone()),
));

return 0;
}

private function computeTimeZone(JobSchedule $jobSchedule, DateTimeZone $renderedTimeZone): ?DateTimeZone
{
$timeZone = $jobSchedule->getTimeZone();
$clockTimeZone = $this->clock->now()->getTimezone();

if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone !== null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "NotIdentical": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone === null && $renderedTimeZone->getName() === $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "LogicalAnd": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone === null || $renderedTimeZone->getName() !== $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone !== null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "NotIdentical": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone === null && $renderedTimeZone->getName() === $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {

Check warning on line 103 in src/Command/ExplainCommand.php

View workflow job for this annotation

GitHub Actions / Test for mutants (ubuntu-latest, 8.1)

Escaped Mutant for Mutator "LogicalAnd": --- Original +++ New @@ @@ { $timeZone = $jobSchedule->getTimeZone(); $clockTimeZone = $this->clock->now()->getTimezone(); - if ($timeZone === null && $renderedTimeZone->getName() !== $clockTimeZone->getName()) { + if ($timeZone === null || $renderedTimeZone->getName() !== $clockTimeZone->getName()) { $timeZone = $clockTimeZone; } if ($timeZone === null) {
$timeZone = $clockTimeZone;
}

if ($timeZone === null) {
return null;
}

if ($timeZone->getName() === $renderedTimeZone->getName()) {
return null;
}

return $timeZone;
}

private function explainSyntax(OutputInterface $output): void
{
$output->writeln(
Expand Down
121 changes: 118 additions & 3 deletions tests/Unit/Command/ExplainCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

namespace Tests\Orisai\Scheduler\Unit\Command;

use Closure;
use Cron\CronExpression;
use DateTimeZone;
use Orisai\Clock\FrozenClock;
use Orisai\Scheduler\Command\ExplainCommand;
use Orisai\Scheduler\Job\CallbackJob;
use Orisai\Scheduler\SimpleScheduler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Tests\Orisai\Scheduler\Doubles\CallbackList;
use function array_map;
use function explode;
use function implode;
use function putenv;
use function rtrim;
use const PHP_EOL;

Expand All @@ -17,10 +23,12 @@ final class ExplainCommandTest extends TestCase

public function testBasicExplain(): void
{
$command = new ExplainCommand();
$clock = new FrozenClock(1, new DateTimeZone('Europe/Prague'));
$scheduler = new SimpleScheduler(null, null, null, $clock);

$command = new ExplainCommand($scheduler);
$tester = new CommandTester($command);

putenv('COLUMNS=80');
$code = $tester->execute([]);

self::assertSame(
Expand Down Expand Up @@ -62,6 +70,113 @@ public function testBasicExplain(): void
- seconds - repeat job every n seconds
- timezone - run only when cron expression matches within given timezone

MSG,
implode(
PHP_EOL,
array_map(
static fn (string $s): string => rtrim($s),
explode(PHP_EOL, $tester->getDisplay()),
),
),
);
self::assertSame($command::SUCCESS, $code);
}

public function testExplainId(): void
{
$clock = new FrozenClock(1, new DateTimeZone('Europe/Prague'));
$scheduler = new SimpleScheduler(null, null, null, $clock);

$cbs = new CallbackList();
$scheduler->addJob(
new CallbackJob(Closure::fromCallable([$cbs, 'job1'])),
new CronExpression('* * * * *'),
'one',
0,
new DateTimeZone('Europe/Prague'),
);
$scheduler->addJob(
new CallbackJob(Closure::fromCallable([$cbs, 'job1'])),
new CronExpression('*/30 7-15 * * 1-5'),
'two',
0,
new DateTimeZone('America/New_York'),
);
$scheduler->addJob(
new CallbackJob(Closure::fromCallable($cbs)),
new CronExpression('* * * 4 *'),
'three',
10,
);

$command = new ExplainCommand($scheduler, null, $clock);
$tester = new CommandTester($command);

$code = $tester->execute([
'--id' => 'non-existent',
]);

self::assertSame(
<<<'MSG'
Job with id 'non-existent' does not exist.

MSG,
implode(
PHP_EOL,
array_map(
static fn (string $s): string => rtrim($s),
explode(PHP_EOL, $tester->getDisplay()),
),
),
);
self::assertSame($command::FAILURE, $code);

$code = $tester->execute([
'--id' => 'one',
]);

self::assertSame(
<<<'MSG'
At every minute.

MSG,
implode(
PHP_EOL,
array_map(
static fn (string $s): string => rtrim($s),
explode(PHP_EOL, $tester->getDisplay()),
),
),
);
self::assertSame($command::SUCCESS, $code);

$code = $tester->execute([
'--id' => 'two',
]);

self::assertSame(
<<<'MSG'
At every 30th minute past every hour from 7 through 15 on every day-of-week from Monday through Friday in America/New_York time zone.

MSG,
implode(
PHP_EOL,
array_map(
static fn (string $s): string => rtrim($s),
explode(PHP_EOL, $tester->getDisplay()),
),
),
);
self::assertSame($command::SUCCESS, $code);

$code = $tester->execute([
'--id' => 'three',
]);

self::assertSame(
<<<'MSG'
At every 10 seconds in April.

MSG,
implode(
PHP_EOL,
Expand Down
5 changes: 5 additions & 0 deletions tools/phpstan.baseline.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$expression of method Orisai\\\\CronExpressionExplainer\\\\CronExpressionExplainer\\:\\:explain\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: ../src/Command/ExplainCommand.php

-
message: "#^Parameter \\#1 \\$expression of method Orisai\\\\CronExpressionExplainer\\\\CronExpressionExplainer\\:\\:explain\\(\\) expects string, string\\|null given\\.$#"
count: 1
Expand Down

0 comments on commit 52e5eb8

Please sign in to comment.