View Javadoc

1   package com.atlassian.scheduler.core.tests;
2   
3   import com.atlassian.scheduler.cron.CronSyntaxException;
4   import com.atlassian.scheduler.cron.ErrorCode;
5   import org.junit.Test;
6   
7   import static com.atlassian.scheduler.cron.ErrorCode.INVALID_NAME;
8   import static org.junit.Assert.assertThat;
9   import static org.junit.Assert.fail;
10  
11  /**
12   * Quartz has so many stupid bugs in it (both in 1.x and 2.x) that we have little choice but to hack up
13   * the tests with a bunch of special cases.  This tries to encapsulate all of that ugliness.
14   *
15   * @since v1.4
16   */
17  public abstract class QuartzCronExpressionValidatorTest extends CronExpressionValidatorTest {
18      /**
19       * Exempts the Quartz validator from reporting error offsets correctly as long as it
20       * reports {@code -1}.
21       * <p>
22       * See {@link com.atlassian.scheduler.core.util.QuartzParseExceptionMapper} for the sad explanation.
23       * </p>
24       */
25      @Override
26      protected boolean verifyErrorOffset(int expected, CronSyntaxException cse) {
27          return cse.getErrorOffset() == -1 || super.verifyErrorOffset(expected, cse);
28      }
29  
30      /**
31       * Allow Quartz validator to fail to detect this error.
32       * <p>
33       * There are several cases where the Quartz cron expression parser forgives errors in the syntax that it
34       * really should not permit.
35       * </p>
36       */
37      @Override
38      protected void assertErrorQuartzExempt(String cronExpression, ErrorCode errorCode, String message, String value,
39                                             int errorOffset, String whyQuartzIsWrong) {
40          try {
41              validator.validate(cronExpression);
42              System.err.println("Quartz thinks '" + cronExpression + "' is valid: " + whyQuartzIsWrong);
43          } catch (CronSyntaxException cse) {
44              assertThat(cse, exception(cronExpression, errorCode, message, value, errorOffset));
45              fail("Quartz used to think '" + cronExpression + "' was valid, but now correctly identifies the problem!");
46          }
47      }
48  
49      /**
50       * Quartz's assumption that names are always 3 chars long without checking means that this causes
51       * more characters to get gobbled up as part of the name than should be.
52       */
53      @Override
54      @Test
55      public void testNameRangesWrongLength() {
56          assertInvalidNameDayOfWeek("0 0 0 ? 1 M-FRI", "M-F", 10);
57          assertInvalidNameDayOfWeek("0 0 0 ? 1 MO-FRI", "MO-", 10);
58          assertInvalidNameRange("0 0 0 ? 1 MON-F", 14);
59          assertInvalidNameRange("0 0 0 ? 1 MON-FR", 14);
60          assertInvalidName("0 0 0 WL 1 ?", "WL", 6);
61      }
62  
63      /**
64       * Quartz's assumption that names are always 3 chars long without checking means that this causes
65       * a {@code StringIndexOutOfBoundsException} and gets reported as a general failure.
66       * <p>
67       * We work backwards to try to tell what went wrong, but the logic for this is not perfect and
68       * we cannot tell what field was being checked at the time.  We can't assume it is always the
69       * day-of-week at the end of the expression, because Quartz tokenizes the expression into fields
70       * beforehand, so it could just as easily happen to the day-of-month.  All we can do is report
71       * {@code INVALID_NAME} to be as friendly/helpful as possible.
72       * </p><p>
73       * That's better than nothing, I guess, but the main test does the correct assertion.
74       * </p>
75       */
76      @Override
77      @Test
78      public void testTruncatedExpression() {
79          assertError("0 0 0 ? 1 NO", INVALID_NAME, "Invalid name: 'NO'", "NO", 10);
80      }
81  }