schedule monitor

This commit is contained in:
2021-03-30 11:31:10 +00:00
parent 40ed5dd5a5
commit 39b0a8c372
66 changed files with 3443 additions and 36 deletions

View File

@@ -0,0 +1,42 @@
name: Tests
on: [push]
jobs:
phpunit73:
name: "Tests on PHP 7.3"
runs-on: ubuntu-latest
container:
image: lorisleiva/laravel-docker:7.3
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache dependencies
uses: actions/cache@v1
with:
path: /composer/cache/files
key: dependencies-composer-${{ hashFiles('composer.json') }}
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run tests
run: phpunit
phpunit74:
name: "Tests on PHP 7.4"
runs-on: ubuntu-latest
container:
image: lorisleiva/laravel-docker:7.4
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache dependencies
uses: actions/cache@v1
with:
path: /composer/cache/files
key: dependencies-composer-${{ hashFiles('composer.json') }}
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run tests
run: phpunit

View File

@@ -0,0 +1,4 @@
vendor
composer.lock
.phpunit.result.cache
build

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Loris Leiva
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,31 @@
# CRON Translator
⏰️ Makes CRON expressions human-readable
![intro-rounded](https://user-images.githubusercontent.com/3642397/60768671-7d6c7100-a0be-11e9-8cee-8a8d2780d76f.png)
## Installation
```sh
composer require lorisleiva/cron-translator
```
## Usage
```php
use Lorisleiva\CronTranslator\CronTranslator;
CronTranslator::translate('* * * * *'); // => Every minute
CronTranslator::translate('30 22 * * *'); // => Every day at 10:30pm
CronTranslator::translate('0 16 * * 1'); // => Every Monday at 4:00pm
CronTranslator::translate('0 0 1 1 *'); // => Every year on January the 1st at 12:00am
CronTranslator::translate('0 0 1 * *'); // => The 1st of every month at 12:00am
CronTranslator::translate('0 * * * 1'); // => Once an hour on Mondays
CronTranslator::translate('* 1-20 * * *'); // => Every minute 20 hours a day
CronTranslator::translate('0,30 * * * *'); // => Twice an hour
CronTranslator::translate('0 1-5 * * *'); // => 5 times a day
CronTranslator::translate('0 1 1-5 * *'); // => 5 days a month at 1:00am
CronTranslator::translate('*/2 * * * *'); // => Every 2 minutes
CronTranslator::translate('* 1/3 2 * *'); // => Every minute of every 3 hours on the 2nd of every month
CronTranslator::translate('1-3/5 * * * *'); // => 3 times every 5 minutes
CronTranslator::translate('1,2 0 */2 1,2 *'); // => Twice an hour every 2 days 2 months a year at 12am
```

View File

@@ -0,0 +1,33 @@
{
"name": "lorisleiva/cron-translator",
"description": "Makes CRON expressions human-readable",
"keywords": [
"cron",
"expression",
"human"
],
"homepage": "https://github.com/lorisleiva/cron-translator",
"license": "MIT",
"authors": [
{
"name": "Loris LEIVA",
"email": "loris.leiva@gmail.com",
"homepage": "https://lorisleiva.com"
}
],
"require-dev": {
"phpunit/phpunit" : "^8.0"
},
"autoload": {
"psr-4": {
"Lorisleiva\\CronTranslator\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Lorisleiva\\CronTranslator\\Tests\\": "tests"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,13 @@
<?php
namespace Lorisleiva\CronTranslator;
use Exception;
class CronParsingException extends Exception
{
public function __construct($cron)
{
parent::__construct("Failed to parse the following CRON expression: {$cron}");
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Lorisleiva\CronTranslator;
class CronTranslator
{
private static $extendedMap = [
'@yearly' => '0 0 1 1 *',
'@annually' => '0 0 1 1 *',
'@monthly' => '0 0 1 * *',
'@weekly' => '0 0 * * 0',
'@daily' => '0 0 * * *',
'@hourly' => '0 * * * *'
];
public static function translate($cron)
{
if (isset(self::$extendedMap[$cron])) {
$cron = self::$extendedMap[$cron];
}
try {
$fields = static::parseFields($cron);
$orderedFields = static::orderFields($fields);
$fieldsAsObject = static::getFieldsAsObject($fields);
$translations = array_map(function ($field) use ($fieldsAsObject) {
return $field->translate($fieldsAsObject);
}, $orderedFields);
return ucfirst(implode(' ', array_filter($translations)));
} catch (\Throwable $th) {
throw new CronParsingException($cron);
}
}
protected static function parseFields($cron)
{
$fields = explode(' ', $cron);
return [
new MinutesField($fields[0]),
new HoursField($fields[1]),
new DaysOfMonthField($fields[2]),
new MonthsField($fields[3]),
new DaysOfWeekField($fields[4]),
];
}
protected static function orderFields($fields)
{
// Group fields by CRON types.
$onces = static::filterType($fields, 'Once');
$everys = static::filterType($fields, 'Every');
$incrementsAndMultiples = static::filterType($fields, 'Increment', 'Multiple');
// Decide whether to keep one or zero CRON type "Every".
$firstEvery = reset($everys)->position ?? PHP_INT_MIN;
$firstIncrementOrMultiple = reset($incrementsAndMultiples)->position ?? PHP_INT_MAX;
$numberOfEverysKept = $firstIncrementOrMultiple < $firstEvery ? 0 : 1;
// Mark fields that will not be displayed as dropped.
// This allows other fields to check whether some
// information is missing and adapt their translation.
foreach (array_slice($everys, $numberOfEverysKept) as $field) {
$field->dropped = true;
}
return array_merge(
// Place one or zero "Every" field at the beginning.
array_slice($everys, 0, $numberOfEverysKept),
// Place all "Increment" and "Multiple" fields in the middle.
$incrementsAndMultiples,
// Finish with the "Once" fields reversed (i.e. from months to minutes).
array_reverse($onces)
);
}
protected static function filterType($fields, ...$types)
{
return array_filter($fields, function ($field) use ($types) {
return $field->hasType(...$types);
});
}
protected static function getFieldsAsObject($fields)
{
return (object) [
'minute' => $fields[0],
'hour' => $fields[1],
'day' => $fields[2],
'month' => $fields[3],
'weekday' => $fields[4],
];
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Lorisleiva\CronTranslator;
class CronType
{
const TYPES = [
'Every', 'Increment', 'Multiple', 'Once',
];
public $type;
public $value;
public $count;
public $increment;
private function __construct($type, $value = null, $count = null, $increment = null)
{
$this->type = $type;
$this->value = $value;
$this->count = $count;
$this->increment = $increment;
}
public static function every()
{
return new static('Every');
}
public static function increment($increment, $count = 1)
{
return new static('Increment', null, $count, $increment);
}
public static function multiple($count)
{
return new static('Multiple', null, $count);
}
public static function once($value)
{
return new static('Once', $value);
}
public static function parse($expression)
{
// Parse "*".
if ($expression === '*') {
return static::every();
}
// Parse fixed values like "1".
if (preg_match("/^[0-9]+$/", $expression)) {
return static::once((int) $expression);
}
// Parse multiple selected values like "1,2,5".
if (preg_match("/^[0-9]+(,[0-9]+)+$/", $expression)) {
return static::multiple(count(explode(',', $expression)));
}
// Parse ranges of selected values like "1-5".
if (preg_match("/^([0-9]+)\-([0-9]+)$/", $expression, $matches)) {
$count = $matches[2] - $matches[1] + 1;
return $count > 1
? static::multiple($count)
: static::once((int) $matches[1]);
}
// Parse incremental expressions like "*/2", "1-4/10" or "1,3/4".
if (preg_match("/(.+)\/([0-9]+)$/", $expression, $matches)) {
$range = static::parse($matches[1]);
if ($range->hasType('Once', 'Every')) {
return static::Increment($matches[2]);
}
if ($range->hasType('Multiple')) {
return static::Increment($matches[2], $range->count);
}
}
// Unsupported expressions throw exceptions.
throw new CronParsingException($expression);
}
public function hasType()
{
return in_array($this->type, func_get_args());
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Lorisleiva\CronTranslator;
class DaysOfMonthField extends Field
{
public $position = 2;
public function translateEvery($fields)
{
if ($fields->weekday->hasType('Once')) {
return "every {$fields->weekday->format()}";
}
return 'every day';
}
public function translateIncrement()
{
if ($this->count > 1) {
return "{$this->count} days out of {$this->increment}";
}
return "every {$this->increment} days";
}
public function translateMultiple()
{
return "{$this->count} days a month";
}
public function translateOnce($fields)
{
if ($fields->month->hasType('Once')) {
return; // MonthsField adapts to "On January the 1st".
}
if ($fields->month->hasType('Every') && ! $fields->month->dropped) {
return; // MonthsField adapts to "The 1st of every month".
}
if ($fields->month->hasType('Every') && $fields->month->dropped) {
return 'on the ' . $this->format() . ' of every month';
}
return 'on the ' . $this->format();
}
public function format()
{
if (in_array($this->value, [1, 21, 31])) {
return $this->value . 'st';
}
if (in_array($this->value, [2, 22])) {
return $this->value . 'nd';
}
if (in_array($this->value, [3, 23])) {
return $this->value . 'rd';
}
return $this->value . 'th';
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Lorisleiva\CronTranslator;
class DaysOfWeekField extends Field
{
public $position = 4;
public function translateEvery()
{
return 'every year';
}
public function translateIncrement()
{
if ($this->count > 1) {
return "{$this->count} days of the week out of {$this->increment}";
}
return "every {$this->increment} days of the week";
}
public function translateMultiple()
{
return "{$this->count} days a week";
}
public function translateOnce($fields)
{
if ($fields->day->hasType('Every') && ! $fields->day->dropped) {
return; // DaysOfMonthField adapts to "Every Sunday".
}
return "on {$this->format()}s";
}
public function format()
{
if ($this->value < 0 || $this->value > 7) {
throw new \Exception();
}
return [
0 => 'Sunday',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday',
][$this->value];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Lorisleiva\CronTranslator;
abstract class Field
{
public $expression;
public $type;
public $value;
public $count;
public $increment;
public $dropped = false;
public $position;
public function __construct($expression)
{
$this->expression = $expression;
$cronType = CronType::parse($expression);
$this->type = $cronType->type;
$this->value = $cronType->value;
$this->count = $cronType->count;
$this->increment = $cronType->increment;
}
public function translate($fields)
{
foreach (CronType::TYPES as $type) {
if ($this->hasType($type) && method_exists($this, "translate{$type}")) {
return $this->{"translate{$type}"}($fields);
}
}
}
public function hasType()
{
return in_array($this->type, func_get_args());
}
public function times($count)
{
switch ($count) {
case 1: return 'once';
case 2: return 'twice';
default: return "{$count} times";
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Lorisleiva\CronTranslator;
class HoursField extends Field
{
public $position = 1;
public function translateEvery($fields)
{
if ($fields->minute->hasType('Once')) {
return 'once an hour';
}
return 'every hour';
}
public function translateIncrement($fields)
{
if ($fields->minute->hasType('Once')) {
return $this->times($this->count) . " every {$this->increment} hours";
}
if ($this->count > 1) {
return "{$this->count} hours out of {$this->increment}";
}
if ($fields->minute->hasType('Every')) {
return "of every {$this->increment} hours";
}
return "every {$this->increment} hours";
}
public function translateMultiple($fields)
{
if ($fields->minute->hasType('Once')) {
return $this->times($this->count) . " a day";
}
return "{$this->count} hours a day";
}
public function translateOnce($fields)
{
return 'at ' . $this->format(
$fields->minute->hasType('Once') ? $fields->minute : null
);
}
public function format($minute = null)
{
$amOrPm = $this->value < 12 ? 'am' : 'pm';
$hour = $this->value === 0 ? 12 : $this->value;
$hour = $hour > 12 ? $hour - 12 : $hour;
return $minute
? "{$hour}:{$minute->format()}{$amOrPm}"
: "{$hour}{$amOrPm}";
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Lorisleiva\CronTranslator;
class MinutesField extends Field
{
public $position = 0;
public function translateEvery()
{
return 'every minute';
}
public function translateIncrement()
{
if ($this->count > 1) {
return $this->times($this->count) . " every {$this->increment} minutes";
}
return "every {$this->increment} minutes";
}
public function translateMultiple()
{
return $this->times($this->count) . " an hour";
}
public function format()
{
return ($this->value < 10 ? '0' : '') . $this->value;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Lorisleiva\CronTranslator;
class MonthsField extends Field
{
public $position = 3;
public function translateEvery($fields)
{
if ($fields->day->hasType('Once')) {
return 'the ' . $fields->day->format() . ' of every month';
}
return 'every month';
}
public function translateIncrement()
{
if ($this->count > 1) {
return "{$this->count} months out of {$this->increment}";
}
return "every {$this->increment} months";
}
public function translateMultiple()
{
return "{$this->count} months a year";
}
public function translateOnce($fields)
{
if ($fields->day->hasType('Once')) {
return "on {$this->format()} the {$fields->day->format()}";
}
return "on {$this->format()}";
}
public function format()
{
if ($this->value < 1 || $this->value > 12) {
throw new \Exception();
}
return [
1 => 'January',
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December',
][$this->value];
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Lorisleiva\CronTranslator\Tests;
class CronTranslatorTest extends TestCase
{
/** @test */
public function it_translates_expressions_with_every_and_once()
{
// All 32 (2^5) combinations of Every/Once.
$this->assertCronTranslateTo('Every minute', '* * * * *');
$this->assertCronTranslateTo('Every minute on Sundays', '* * * * 0');
$this->assertCronTranslateTo('Every minute on January', '* * * 1 *');
$this->assertCronTranslateTo('Every minute on Sundays on January', '* * * 1 0');
$this->assertCronTranslateTo('Every minute on the 1st of every month', '* * 1 * *');
$this->assertCronTranslateTo('Every minute on Sundays on the 1st of every month', '* * 1 * 0');
$this->assertCronTranslateTo('Every minute on January the 1st', '* * 1 1 *');
$this->assertCronTranslateTo('Every minute on Sundays on January the 1st', '* * 1 1 0');
$this->assertCronTranslateTo('Every minute at 12am', '* 0 * * *');
$this->assertCronTranslateTo('Every minute on Sundays at 12am', '* 0 * * 0');
$this->assertCronTranslateTo('Every minute on January at 12am', '* 0 * 1 *');
$this->assertCronTranslateTo('Every minute on Sundays on January at 12am', '* 0 * 1 0');
$this->assertCronTranslateTo('Every minute on the 1st of every month at 12am', '* 0 1 * *');
$this->assertCronTranslateTo('Every minute on Sundays on the 1st of every month at 12am', '* 0 1 * 0');
$this->assertCronTranslateTo('Every minute on January the 1st at 12am', '* 0 1 1 *');
$this->assertCronTranslateTo('Every minute on Sundays on January the 1st at 12am', '* 0 1 1 0');
$this->assertCronTranslateTo('Once an hour', '0 * * * *');
$this->assertCronTranslateTo('Once an hour on Sundays', '0 * * * 0');
$this->assertCronTranslateTo('Once an hour on January', '0 * * 1 *');
$this->assertCronTranslateTo('Once an hour on Sundays on January', '0 * * 1 0');
$this->assertCronTranslateTo('Once an hour on the 1st of every month', '0 * 1 * *');
$this->assertCronTranslateTo('Once an hour on Sundays on the 1st of every month', '0 * 1 * 0');
$this->assertCronTranslateTo('Once an hour on January the 1st', '0 * 1 1 *');
$this->assertCronTranslateTo('Once an hour on Sundays on January the 1st', '0 * 1 1 0');
$this->assertCronTranslateTo('Every day at 12:00am', '0 0 * * *');
$this->assertCronTranslateTo('Every Sunday at 12:00am', '0 0 * * 0');
$this->assertCronTranslateTo('Every day on January at 12:00am', '0 0 * 1 *');
$this->assertCronTranslateTo('Every Sunday on January at 12:00am', '0 0 * 1 0');
$this->assertCronTranslateTo('The 1st of every month at 12:00am', '0 0 1 * *');
$this->assertCronTranslateTo('The 1st of every month on Sundays at 12:00am', '0 0 1 * 0');
$this->assertCronTranslateTo('Every year on January the 1st at 12:00am', '0 0 1 1 *');
$this->assertCronTranslateTo('On Sundays on January the 1st at 12:00am', '0 0 1 1 0');
// More realistic examples.
$this->assertCronTranslateTo('Every year on January the 1st at 12:00pm', '0 12 1 1 *');
$this->assertCronTranslateTo('Every minute on Mondays at 3pm', '* 15 * * 1');
$this->assertCronTranslateTo('Every minute on January the 3rd', '* * 3 1 *');
$this->assertCronTranslateTo('Every minute on Mondays on April', '* * * 4 1');
$this->assertCronTranslateTo('On Mondays on April the 22nd at 3:10pm', '10 15 22 4 1');
// Paparazzi examples.
$this->assertCronTranslateTo('Every day at 10:00pm', '0 22 * * *');
$this->assertCronTranslateTo('Every day at 9:00am', '0 9 * * *');
$this->assertCronTranslateTo('Every Monday at 4:00pm', '0 16 * * 1');
$this->assertCronTranslateTo('Every year on January the 1st at 12:00am', '0 0 1 1 *');
$this->assertCronTranslateTo('The 1st of every month at 12:00am', '0 0 1 * *');
}
/** @test */
public function it_translate_expressions_with_multiple()
{
$this->assertCronTranslateTo('Every minute 2 hours a day', '* 8,18 * * *');
$this->assertCronTranslateTo('Every minute 3 hours a day', '* 8,18,20 * * *');
$this->assertCronTranslateTo('Every minute 20 hours a day', '* 1-20 * * *');
$this->assertCronTranslateTo('Twice an hour', '0,30 * * * *');
$this->assertCronTranslateTo('Twice an hour 5 hours a day', '0,30 1-5 * * *');
$this->assertCronTranslateTo('5 times a day', '0 1-5 * * *');
$this->assertCronTranslateTo('Every minute 5 hours a day', '* 1-5 * * *');
$this->assertCronTranslateTo('5 days a month at 1:00am', '0 1 1-5 * *');
$this->assertCronTranslateTo('5 days a month 2 months a year at 1:00am', '0 1 1-5 5,6 *');
$this->assertCronTranslateTo('2 months a year on the 5th at 1:00am', '0 1 5 5,6 *');
$this->assertCronTranslateTo('The 5th of every month 4 days a week at 1:00am', '0 1 5 * 1-4');
}
/** @test */
public function it_translate_expressions_with_increment()
{
$this->assertCronTranslateTo('Every 2 minutes', '*/2 * * * *');
$this->assertCronTranslateTo('Every 2 minutes', '1/2 * * * *');
$this->assertCronTranslateTo('Twice every 4 minutes', '1,3/4 * * * *');
$this->assertCronTranslateTo('3 times every 5 minutes', '1-3/5 * * * *');
$this->assertCronTranslateTo('Every 2 minutes at 2pm', '*/2 14 * * *');
$this->assertCronTranslateTo('Once an hour every 2 days', '0 * */2 * *');
$this->assertCronTranslateTo('Every minute every 2 days', '* * */2 * *');
$this->assertCronTranslateTo('Once every 2 hours', '0 */2 * * *');
$this->assertCronTranslateTo('Twice every 5 hours', '0 1,2/5 * * *');
$this->assertCronTranslateTo('Every minute 2 hours out of 5', '* 1,2/5 * * *');
$this->assertCronTranslateTo('Every day every 4 months at 12:00am', '0 0 * */4 *');
}
/** @test */
public function it_adds_junctions_to_certain_combinations_of_cron_types()
{
$this->assertCronTranslateTo('Every minute of every 2 hours', '* */2 * * *');
$this->assertCronTranslateTo('Every minute of every 3 hours on the 2nd of every month', '* 1/3 2 * *');
}
/** @test */
public function it_converts_ranges_of_one_into_once_cron_types()
{
$this->assertCronTranslateTo('Every minute at 8am', '* 8-8 * * *');
$this->assertCronTranslateTo('Every minute on January', '* * * 1-1 *');
}
/** @test */
public function it_handles_extended_cron_syntax()
{
$this->assertCronTranslateTo('Once an hour', '@hourly');
$this->assertCronTranslateTo('Every day at 12:00am', '@daily');
$this->assertCronTranslateTo('Every Sunday at 12:00am', '@weekly');
$this->assertCronTranslateTo('The 1st of every month at 12:00am', '@monthly');
$this->assertCronTranslateTo('Every year on January the 1st at 12:00am', '@yearly');
$this->assertCronTranslateTo('Every year on January the 1st at 12:00am', '@annually');
}
/** @test */
public function it_returns_parsing_errors_when_something_goes_wrong()
{
$this->assertCronThrowsParsingError('I_AM_NOT_A_CRON_EXPRESSION');
$this->assertCronThrowsParsingError('A * * * *');
$this->assertCronThrowsParsingError('1,2-3 * * * *');
$this->assertCronThrowsParsingError('1/2/3 * * * *');
$this->assertCronThrowsParsingError('* * * 0 *');
$this->assertCronThrowsParsingError('* * * 13 *');
$this->assertCronThrowsParsingError('* * * * 8');
}
/**
* @skip
* @doesNotPerformAssertions
*/
public function result_generator()
{
$this->generateCombinationsFromMatrix([
['*', '0', '1,2', '*/2'],
['*', '0', '1,2', '*/2'],
['*', '1', '1,2', '*/2'],
['*', '1', '1,2', '*/2'],
['*', '0', '1,2', '*/2'],
]);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Lorisleiva\CronTranslator\Tests;
use Lorisleiva\CronTranslator\CronTranslator;
use PHPUnit\Framework\TestCase as BaseTestCase;
use Lorisleiva\CronTranslator\CronParsingException;
class TestCase extends BaseTestCase
{
public function assertCronTranslateTo($expected, $actual)
{
$this->assertEquals($expected, CronTranslator::translate($actual));
}
public function assertCronThrowsParsingError($cron)
{
try {
CronTranslator::translate($cron);
} catch (CronParsingException $expression) {
return $this->addToAssertionCount(1);
}
$this->fail("Expected CronParsingError exception for [$cron]");
}
public function generateCombinationsFromMatrix($matrix)
{
function combinations($matrix, $acc = []) {
if (empty($matrix)) {
return [implode(' ', $acc)];
}
$current = array_shift($matrix);
$results = [];
foreach ($current as $value) {
$results[] = combinations($matrix, array_merge($acc, [$value]));
}
return array_merge(...$results);
}
foreach (combinations($matrix) as $cron) {
echo "\n" . $cron . "\t=> " . CronTranslator::translate($cron);
}
}
}