You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, over a month ago, I opened #26964, and it was pretty promptly resolved... for that one specific cron expression. I pulled in that bugfix (along with the rest of 5.3.8), and more or less immediately found another cron line that is still broken in the exact same way: As you move forward in time, the next match of a cron expression moves backwards, which it should never ever do.
Instead of just attaching another bug proof with the particular bad cron expression, here is an entire bug proof generator that will give you an infinite number of similarly broken cron lines, with test dates, until this issue is actually fixed.
packagecom.example;
importorg.junit.jupiter.api.Test;
importorg.springframework.scheduling.support.CronExpression;
importjava.time.LocalDateTime;
importjava.time.ZoneOffset;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.Random;
importjava.util.function.BiFunction;
/** * @author Scott Albertine */publicclassCronExpressionBugProof {
privatestaticfinalRandomR = newRandom();
privatestaticfinalintTWO_YEARS_IN_SECONDS = 63113852;
privatestaticfinalLocalDateTimeEPOCH = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC);
privatestaticfinalList<BiFunction<Integer, Integer, String>> CRON_SEGMENT_GENERATORS = newArrayList<>();
static {
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::numberSegment);
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::star);
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::starOverNumber);
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::numberDashNumber);
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::numberCommaNumber);
CRON_SEGMENT_GENERATORS.add(CronExpressionBugProof::numberHashNumber);
}
privatestaticCronExpressionrandomCronExpression() {
while (true) { //there are bad cron expressions, skip past themStringcronLine = randomCronLine();
try {
//this will throw IllegalArgumentException on some bad cron lines, but not all of themCronExpressioncronExpression = CronExpression.parse(cronLine);
//this will check that the cron line has at least one instance since the epoch//to filter out stuff like "every february 30th"if (cronExpression.next(EPOCH) == null) {
continue;
}
returncronExpression;
} catch (IllegalArgumentExceptionignored) {
}
}
}
privatestaticStringrandomCronLine() {
returnrandomCronSegment(0, 59) + ' ' + //secondsrandomCronSegment(0, 59) + ' ' + //minutesrandomCronSegment(0, 23) + ' ' + //hoursrandomCronSegment(1, 28) + ' ' + //daysrandomCronSegment(1, 12) + ' ' + //monthsrandomWeekdaySegment(); //day of week
}
privatestaticStringrandomCronSegment(intmin, intmax) {
returnCRON_SEGMENT_GENERATORS.get(R.nextInt(5)).apply(min, max); //don't use numberHashNumber
}
privatestaticStringrandomWeekdaySegment() {
returnCRON_SEGMENT_GENERATORS.get(R.nextInt(6)).apply(0, 6); //include numberHashNumber
}
privatestaticStringnumberSegment(Integermin, Integermax) {
returnInteger.toString(R.nextInt((max - min) + 1) + min);
}
privatestaticStringstar(Integermin, Integermax) {
return"*";
}
privatestaticStringstarOverNumber(Integermin, Integermax) {
return"*/" + (R.nextInt((max - min) / 2) + 2);
}
privatestaticStringnumberDashNumber(Integermin, Integermax) {
intnum1 = R.nextInt((max - min) + 1) + min;
intnum2 = R.nextInt((max - min) + 1) + min;
if (num1 == num2) {
returnInteger.toString(num1);
}
return (num1 > num2) ? (num2 + "-" + num1)
: (num1 + "-" + num2);
}
privatestaticStringnumberCommaNumber(Integermin, Integermax) {
intnum1 = R.nextInt((max - min) + 1) + min;
intnum2 = R.nextInt((max - min) + 1) + min;
if (num1 == num2) {
returnInteger.toString(num1);
}
return (num1 > num2) ? (num2 + "," + num1)
: (num1 + "," + num2);
}
privatestaticStringnumberHashNumber(Integermin, Integermax) {
intnum1 = R.nextInt((max - min) + 1) + min;
intnum2 = R.nextInt(4) + 1;
returnnum1 + "#" + num2;
}
@TestpublicvoidproveCronExpressionIsStillBroken() {
//pick 1000 random times in the next 2 yearsLocalDateTimenow = LocalDateTime.now(ZoneOffset.UTC);
inttimesToTest = 1000;
List<LocalDateTime> times = newArrayList<>();
for (inti = 0; i < timesToTest; i++) {
times.add(now.plusSeconds(R.nextInt(TWO_YEARS_IN_SECONDS)));
}
//make sure they're sorted, so we can know that the firstResult and secondResult below should be in chronological order tootimes.sort(null);
//test up to 1000 cron linesfor (intc = 0; c < 1000; c++) {
CronExpressioncronExpression = randomCronExpression(); //pick a random cron expression//iterate through each pair of times to testfor (intt = 0; t < (timesToTest - 1); t++) {
LocalDateTimefirstTime = times.get(t);
LocalDateTimesecondTime = times.get(t + 1);
LocalDateTimefirstResult = cronExpression.next(firstTime);
LocalDateTimesecondResult = cronExpression.next(secondTime);
if (firstResult.isAfter(secondResult)) { //check for pairs of results that aren't in the right orderSystem.out.println("Insane CronExpression: " + cronExpression);
System.out.println("It thinks the next match of " + firstTime + " is " + firstResult);
System.out.println("And the next match of " + secondTime + " is " + secondResult);
System.out.println("Even though " + firstResult + " is after " + secondResult);
System.out.println("Cron lines tested: " + c);
thrownewRuntimeException("Insane CronExpression found.");
}
}
}
}
}
This is by no means an exhaustive test. However, when I run it, I get output like the following:
Insane CronExpression: 4,21 12,58 * */6 3 *
It thinks the next match of 2022-02-28T13:32:03.032096 is 2022-03-07T00:12:04
And the next match of 2022-03-01T22:44:31.032096 is 2022-03-01T22:58:04
Even though 2022-03-07T00:12:04 is after 2022-03-01T22:58:04
Cron lines tested: 27
I've never made it to 50 cron lines without finding one that's broken, and I've run this dozens of times.
The text was updated successfully, but these errors were encountered:
Thank you for providing that test, it has proven quite valuable for fixing two issues in CronExpression. The fixes are in 76b1c0f (I put the wrong github issue in the commit message by accident, so there is no link from this issue). With fixes for those two in, I am now able to run your tests multiple times without running into exceptions.
Feel free to try a recent snapshot, and see if you can find any other remaining issues. It would be good to squash them all in 5.3.9.
Hi, over a month ago, I opened #26964, and it was pretty promptly resolved... for that one specific cron expression. I pulled in that bugfix (along with the rest of 5.3.8), and more or less immediately found another cron line that is still broken in the exact same way: As you move forward in time, the next match of a cron expression moves backwards, which it should never ever do.
Instead of just attaching another bug proof with the particular bad cron expression, here is an entire bug proof generator that will give you an infinite number of similarly broken cron lines, with test dates, until this issue is actually fixed.
This is by no means an exhaustive test. However, when I run it, I get output like the following:
I've never made it to 50 cron lines without finding one that's broken, and I've run this dozens of times.
The text was updated successfully, but these errors were encountered: