/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Anders Wisch
*
* 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.
*/
package cron;
import static cron.DateTimes.midnight;
import static cron.DateTimes.nearestWeekday;
import static cron.DateTimes.now;
import static cron.DateTimes.nthOfMonth;
import static cron.DateTimes.startOfHour;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.time.DayOfWeek;
import java.time.Month;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class CronExpressionTest {
public static final CronExpression.Parser withSecondsField = CronExpression.parser().withSecondsField(true);
private CronExpression expression;
@Test
public void testHashCode() {
assertEquals(CronExpression.daily().hashCode(), CronExpression.parse("@daily").hashCode());
assertEquals(CronExpression.daily().hashCode(), CronExpression.parse("@midnight").hashCode());
assertEquals(CronExpression.hourly().hashCode(), CronExpression.parse("@hourly").hashCode());
assertEquals(CronExpression.monthly().hashCode(), CronExpression.parse("@monthly").hashCode());
assertEquals(CronExpression.weekly().hashCode(), CronExpression.parse("@weekly").hashCode());
assertEquals(CronExpression.yearly().hashCode(), CronExpression.parse("@annually").hashCode());
assertEquals(CronExpression.yearly().hashCode(), CronExpression.parse("@yearly").hashCode());
assertEquals(
CronExpression.parse("0 0 ? * 5#3,2#2").hashCode(),
CronExpression.parse("0 0 ? * 5#3,2#2").hashCode());
assertEquals(
withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010").hashCode(),
withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010").hashCode());
}
@Test
public void testEquals() {
assertEquals(CronExpression.daily(), CronExpression.parse("@daily"));
assertEquals(CronExpression.daily(), CronExpression.parse("@midnight"));
assertEquals(CronExpression.hourly(), CronExpression.parse("@hourly"));
assertEquals(CronExpression.monthly(), CronExpression.parse("@monthly"));
assertEquals(CronExpression.weekly(), CronExpression.parse("@weekly"));
assertEquals(CronExpression.yearly(), CronExpression.parse("@annually"));
assertEquals(CronExpression.yearly(), CronExpression.parse("@yearly"));
assertEquals(
CronExpression.parse("0 0 ? * 5#3,2#2"),
CronExpression.parse("0 0 ? * 5#3,2#2"));
assertEquals(
withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"),
withSecondsField.parse("0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"));
}
@Test
public void illegalCharacter() {
try {
expression = CronExpression.parse("0 0 4X * *");
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertEquals("Bad character 'X' at position 5 in string: 0 0 4X * *", e.getMessage());
}
}
@Test
public void disallowBothDayFields() {
try {
expression = CronExpression.parser().allowBothDayFields(false).parse("0 0 1 * 5L");
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertEquals("Day of month and day of week may not both be specified", e.getMessage());
}
}
@Test
public void nearestWeekdayWithoutNumber() {
try {
expression = CronExpression.parse("0 0 W * *");
} catch (IllegalArgumentException e) {
assertEquals("Bad character 'W' in day of month field: W", e.getMessage());
}
}
@Test
public void nearestWeekdayOfMonth() {
expression = CronExpression.parse("0 0 5W * *");
List<ZonedDateTime> times = new ArrayList<>();
ZonedDateTime t = DateTimes.startOfYear();
int year = t.getYear();
do {
times.add(nearestWeekday(t.withDayOfMonth(5)));
t = t.plusMonths(1);
} while (year == t.getYear());
assertMatchesAll(times);
}
@Test
public void nearestFriday() {
ZonedDateTime t = now().truncatedTo(ChronoUnit.DAYS).with(DayOfWeek.SATURDAY);
expression = CronExpression.parse("0 0 " + t.getDayOfMonth() + "W * *");
assertMatches(t.minusDays(1));
}
@Test
public void nearestMonday() {
ZonedDateTime t = now().truncatedTo(ChronoUnit.DAYS).with(DayOfWeek.SUNDAY);
expression = CronExpression.parse("0 0 " + t.getDayOfMonth() + "W * *");
assertMatches(t.plusDays(1));
}
@Test
public void nonMatchingNth() {
expression = CronExpression.parse("0 0 ? * 2#2");
List<ZonedDateTime> times = new ArrayList<>();
ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
int year = t.getYear();
while (t.getYear() == year) {
times.add(nthOfMonth(t, DayOfWeek.TUESDAY, 1));
t = t.plusMonths(1);
}
for (ZonedDateTime time : times)
assertFalse(expression.matches(time));
}
@Test
public void multipleNth() {
expression = CronExpression.parse("0 0 ? * 5#3,2#2");
List<ZonedDateTime> times = new ArrayList<>();
ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
int year = t.getYear();
while (t.getYear() == year) {
times.add(nthOfMonth(t, DayOfWeek.FRIDAY, 3));
times.add(nthOfMonth(t, DayOfWeek.TUESDAY, 2));
t = t.plusMonths(1);
}
assertMatchesAll(times);
}
@Test
public void thirdFriday() {
String string = "0 0 ? * 5#3";
List<ZonedDateTime> times = new ArrayList<>();
ZonedDateTime t = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
int year = t.getYear();
while (t.getYear() == year) {
times.add(nthOfMonth(t, DayOfWeek.FRIDAY, 3));
t = t.plusMonths(1);
}
expression = CronExpression.parse(string);
assertMatchesAll(times);
}
@Test
public void reboot() {
expression = CronExpression.parse("@reboot");
ZonedDateTime now = now();
assertTrue(expression.matches(now));
assertFalse(expression.matches(now));
}
@Test
public void minuteFullRangeExplicit() {
expression = CronExpression.parse("0-59 * * * * *");
ZonedDateTime time = startOfHour();
int hour = time.getHour();
do {
assertMatches(time);
time = time.plusMinutes(1);
} while (time.getHour() == hour);
}
@Test
public void minuteRestrictedRange() {
expression = CronExpression.parse("10-20 * * * * *");
int first = 10, last = 20;
ZonedDateTime time = startOfHour();
int hour = time.getHour();
do {
int minute = time.getMinute();
assertEquals(first <= minute && minute <= last, expression.matches(time));
time = time.plusMinutes(1);
} while (time.getHour() == hour);
}
@Test
public void minuteFullRangeMod() {
expression = CronExpression.parse("*/5 * * * * *");
ZonedDateTime time = startOfHour();
int hour = time.getHour();
do {
int minute = time.getMinute();
assertEquals(minute % 5 == 0, expression.matches(time));
time = time.plusMinutes(1);
} while (time.getHour() == hour);
}
@Test
public void minuteRestrictedRangeMod() {
expression = CronExpression.parse("10-20/5 * * * * *");
int first = 10, last = 20;
ZonedDateTime time = startOfHour();
int hour = time.getHour();
do {
int minute = time.getMinute();
assertEquals(first <= minute && minute <= last && minute % 5 == 0, expression.matches(time));
time = time.plusMinutes(1);
} while (time.getHour() == hour);
}
@Test
public void yearly() {
expression = CronExpression.yearly();
assertYearly();
}
@Test
public void monthly() {
expression = CronExpression.monthly();
assertMonthly();
}
@Test
public void weekly() {
expression = CronExpression.weekly();
assertWeekly();
}
@Test
public void daily() {
expression = CronExpression.daily();
assertDaily();
}
@Test
public void hourly() {
expression = CronExpression.hourly();
assertHourly();
}
@Test
public void yearlyKeyword() {
expression = CronExpression.parse("@yearly");
assertYearly();
}
@Test
public void annualKeyword() {
expression = CronExpression.parse("@annually");
assertYearly();
}
@Test
public void monthlyKeyword() {
expression = CronExpression.parse("@monthly");
assertMonthly();
}
@Test
public void weeklyKeyword() {
expression = CronExpression.parse("@weekly");
assertWeekly();
}
@Test
public void dailyKeyword() {
expression = CronExpression.parse("@daily");
assertDaily();
}
@Test
public void hourlyKeyword() {
expression = CronExpression.parse("@hourly");
assertHourly();
}
@Test
public void invalid() {
assertFalse(CronExpression.isValid(null));
assertFalse(CronExpression.isValid(""));
assertFalse(CronExpression.isValid("a"));
assertFalse(CronExpression.isValid("0 0 1 * X"));
assertFalse(CronExpression.isValid("0 0 1 * 1X"));
}
@Test
public void invalidDueToSecondsField() {
assertTrue(CronExpression.isValid("0 0 1 * 1"));
assertFalse(CronExpression.parser().allowBothDayFields(false).isValid("0 0 1 * 1"));
}
private void assertWeekly() {
for (int week = 1; week <= 52; week++) {
assertMatches(midnight().withDayOfYear(7 * week).with(DayOfWeek.MONDAY).minusDays(1));
}
}
private void assertDaily() {
for (int day = 1; day <= 365; day++) {
assertMatches(midnight().withDayOfYear(day));
}
}
private void assertMonthly() {
for (Month month : Month.values()) {
assertMatches(midnight().with(month).withDayOfMonth(1));
}
}
private void assertHourly() {
for (int day = 1; day <= 365; day++) {
for (int hour = 0; hour <= 23; hour++) {
assertMatches(midnight().withDayOfYear(day).withHour(hour));
}
}
}
private void assertYearly() {
assertMatches(midnight().withDayOfYear(1));
}
private static final String formatString = "m H d M E yyyy";
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatString);
private void assertMatchesAll(List<ZonedDateTime> times) {
for (ZonedDateTime time : times)
assertMatches(time);
}
private void assertMatches(ZonedDateTime time) {
assertTrue(
time.format(formatter).toUpperCase() + " doesn't match expression: " + expression,
expression.matches(time)
);
}
}