/*
* The MIT License
*
* Copyright (c) 2004-2010, InfraDNA, Inc.
*
* 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 hudson.scheduler;
import antlr.ANTLRException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import static org.junit.Assert.*;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.Url;
import static java.util.Calendar.MONDAY;
import java.util.List;
/**
* @author Kohsuke Kawaguchi
*/
public class CronTabTest {
@Test
public void test1() throws ANTLRException {
new CronTab("@yearly");
new CronTab("@weekly");
new CronTab("@midnight");
new CronTab("@monthly");
new CronTab("0 0 * 1-10/3 *");
}
@Test
public void testCeil1() throws Exception {
CronTab x = new CronTab("0,30 * * * *");
Calendar c = new GregorianCalendar(2000,2,1,1,10);
compare(new GregorianCalendar(2000,2,1,1,30),x.ceil(c));
// roll up test
c = new GregorianCalendar(2000,2,1,1,40);
compare(new GregorianCalendar(2000,2,1,2, 0),x.ceil(c));
}
@Test
public void testCeil2() throws Exception {
// make sure that lower fields are really reset correctly
CronTab x = new CronTab("15,45 3 * * *");
Calendar c = new GregorianCalendar(2000,2,1,2,30);
compare(new GregorianCalendar(2000,2,1,3,15),x.ceil(c));
}
@Test
public void testCeil3() throws Exception {
// conflict between DoM and DoW. In this we need to find a day that's the first day of a month and Sunday
CronTab x = new CronTab("0 0 1 * 0");
Calendar c = new GregorianCalendar(2010,0,1,15,55);
// the first such day in 2010 is Aug 1st
compare(new GregorianCalendar(2010,7,1,0,0),x.ceil(c));
}
@Test(timeout = 1000)
@Issue("JENKINS-12357")
public void testCeil3_DoW7() throws Exception {
// similar to testCeil3, but DoW=7 may stuck in an infinite loop
CronTab x = new CronTab("0 0 1 * 7");
Calendar c = new GregorianCalendar(2010,0,1,15,55);
// the first such day in 2010 is Aug 1st
compare(new GregorianCalendar(2010, 7, 1, 0, 0), x.ceil(c));
}
/**
* Verifies that HUDSON-8656 never crops up again.
*/
@Url("http://issues.hudson-ci.org/browse/HUDSON-8656")
@Test
public void testCeil4() throws ANTLRException {
final Calendar cal = Calendar.getInstance(new Locale("de", "de"));
cal.set(2011, 0, 16, 0, 0, 0); // Sunday, Jan 16th 2011, 00:00
final String cronStr = "0 23 * * 1-5"; // execute on weekdays @23:00
final CronTab cron = new CronTab(cronStr);
final Calendar next = cron.ceil(cal);
final Calendar expectedDate = Calendar.getInstance();
expectedDate.set(2011, 0, 17, 23, 0, 0); // Expected next: Monday, Jan 17th 2011, 23:00
assertEquals(expectedDate.get(Calendar.HOUR), next.get(Calendar.HOUR));
assertEquals(expectedDate.get(Calendar.MINUTE), next.get(Calendar.MINUTE));
assertEquals(expectedDate.get(Calendar.YEAR), next.get(Calendar.YEAR));
assertEquals(expectedDate.get(Calendar.MONTH), next.get(Calendar.MONTH));
assertEquals(expectedDate.get(Calendar.DAY_OF_MONTH), next.get(Calendar.DAY_OF_MONTH)); // FAILS: is Monday, Jan 10th, 23:00
}
/**
* Verifies that HUDSON-8656 never crops up again.
*/
@Url("http://issues.hudson-ci.org/browse/HUDSON-8656")
@Test
public void testCeil5() throws ANTLRException {
final Calendar cal = Calendar.getInstance(new Locale("de", "at"));
cal.set(2011, 0, 16, 0, 0, 0); // Sunday, Jan 16th 2011, 00:00
final String cronStr = "0 23 * * 1-5"; // execute on weekdays @23:00
final CronTab cron = new CronTab(cronStr);
final Calendar next = cron.ceil(cal);
final Calendar expectedDate = Calendar.getInstance();
expectedDate.set(2011, 0, 17, 23, 0, 0); // Expected next: Monday, Jan 17th 2011, 23:00
assertEquals(expectedDate.get(Calendar.HOUR), next.get(Calendar.HOUR));
assertEquals(expectedDate.get(Calendar.MINUTE), next.get(Calendar.MINUTE));
assertEquals(expectedDate.get(Calendar.YEAR), next.get(Calendar.YEAR));
assertEquals(expectedDate.get(Calendar.MONTH), next.get(Calendar.MONTH));
assertEquals(expectedDate.get(Calendar.DAY_OF_MONTH), next.get(Calendar.DAY_OF_MONTH)); // FAILS: is Monday, Jan 10th, 23:00
}
@Test
public void testFloor1() throws Exception {
CronTab x = new CronTab("30 * * * *");
Calendar c = new GregorianCalendar(2000,2,1,1,40);
compare(new GregorianCalendar(2000,2,1,1,30),x.floor(c));
// roll down test
c = new GregorianCalendar(2000,2,1,1,10);
compare(new GregorianCalendar(2000,2,1,0,30),x.floor(c));
}
@Test
public void testFloor2() throws Exception {
// make sure that lower fields are really reset correctly
CronTab x = new CronTab("15,45 3 * * *");
Calendar c = new GregorianCalendar(2000,2,1,4,30);
compare(new GregorianCalendar(2000,2,1,3,45),x.floor(c));
}
@Test
public void testFloor3() throws Exception {
// conflict between DoM and DoW. In this we need to find a day that's the first day of a month and Sunday in 2010
CronTab x = new CronTab("0 0 1 * 0");
Calendar c = new GregorianCalendar(2011,0,1,15,55);
// the last such day in 2010 is Aug 1st
compare(new GregorianCalendar(2010,7,1,0,0),x.floor(c));
}
@Issue("JENKINS-8401")
@Test
public void testFloor4() throws Exception {
// conflict between DoM and DoW. In this we need to find a day that's the first day of a month and Sunday in 2010
CronTab x = new CronTab("0 0 1 * 0");
Calendar c = new GregorianCalendar(2011,0,1,15,55);
c.setFirstDayOfWeek(MONDAY);
// the last such day in 2010 is Aug 1st
GregorianCalendar answer = new GregorianCalendar(2010, 7, 1, 0, 0);
answer.setFirstDayOfWeek(MONDAY);
compare(answer,x.floor(c));
}
@Test public void checkSanity() throws Exception {
assertEquals(null, new CronTab("@hourly").checkSanity());
assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* * * * *", "H * * * *"), new CronTab("* * * * *").checkSanity());
assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("*/1 * * * *", "H * * * *"), new CronTab("*/1 * * * *").checkSanity());
assertEquals(null, new CronTab("H H(0-2) * * *", Hash.from("stuff")).checkSanity());
assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* 0 * * *", "H 0 * * *"), new CronTab("* 0 * * *").checkSanity());
assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* 6,18 * * *", "H 6,18 * * *"), new CronTab("* 6,18 * * *").checkSanity());
// dubious; could be improved:
assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* * 3 * *", "H * 3 * *"), new CronTab("* * 3 * *").checkSanity());
// promote hashes:
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H/15 * * * *", "*/15 * * * *"), new CronTab("*/15 * * * *").checkSanity());
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H/15 * * * *", "0,15,30,45 * * * *"), new CronTab("0,15,30,45 * * * *").checkSanity());
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H * * * *", "0 * * * *"), new CronTab("0 * * * *").checkSanity());
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H * * * *", "5 * * * *"), new CronTab("5 * * * *").checkSanity());
// if the user specifically asked for 3:00 AM, probably we should stick to 3:00–3:59
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H 3 * * *", "0 3 * * *"), new CronTab("0 3 * * *").checkSanity());
assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H 22 * * 6", "00 22 * * 6"), new CronTab("00 22 * * 6").checkSanity());
assertEquals(null, new CronTab("H/15 * 1 1 *").checkSanity());
assertEquals(null, new CronTab("0 3 H/15 * *").checkSanity());
assertEquals(Messages.CronTab_short_cycles_in_the_day_of_month_field_w(), new CronTab("0 3 H/3 * *").checkSanity());
assertEquals(Messages.CronTab_short_cycles_in_the_day_of_month_field_w(), new CronTab("0 3 */5 * *").checkSanity());
}
/**
* Humans can't easily see difference in two {@link Calendar}s, do help the diagnosis by using {@link DateFormat}.
*/
private void compare(Calendar expected, Calendar actual) {
DateFormat f = DateFormat.getDateTimeInstance();
assertEquals(f.format(expected.getTime()), f.format(actual.getTime()));
}
@Test
public void testHash1() throws Exception {
CronTab x = new CronTab("H H(5-8) H/3 H(1-10)/4 *",new Hash() {
public int next(int n) {
return n-1;
}
});
assertEquals("59;", bitset(x.bits[0]));
assertEquals("8;", bitset(x.bits[1]));
assertEquals("3;6;9;12;15;18;21;24;27;", bitset(x.bits[2]));
assertEquals("4;8;", bitset(x.bits[3]));
}
private static String bitset(long bits) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < 64; i++) {
if ((bits & 1L << i) != 0) {
b.append(i).append(';');
}
}
return b.toString();
}
@Test
public void testHash2() throws Exception {
CronTab x = new CronTab("H H(5-8) H/3 H(1-10)/4 *",new Hash() {
public int next(int n) {
return 1;
}
});
assertEquals("1;", bitset(x.bits[0]));
assertEquals("6;", bitset(x.bits[1]));
assertEquals("2;5;8;11;14;17;20;23;26;", bitset(x.bits[2]));
assertEquals("2;6;10;", bitset(x.bits[3]));
}
@Test public void hashedMinute() throws Exception {
long t = new GregorianCalendar(2013, 2, 21, 16, 21).getTimeInMillis();
compare(new GregorianCalendar(2013, 2, 21, 17, 56), new CronTab("H 17 * * *", Hash.from("stuff")).ceil(t));
compare(new GregorianCalendar(2013, 2, 21, 16, 56), new CronTab("H * * * *", Hash.from("stuff")).ceil(t));
compare(new GregorianCalendar(2013, 2, 21, 16, 56), new CronTab("@hourly", Hash.from("stuff")).ceil(t));
compare(new GregorianCalendar(2013, 2, 21, 17, 20), new CronTab("@hourly", Hash.from("junk")).ceil(t));
compare(new GregorianCalendar(2013, 2, 22, 13, 56), new CronTab("H H(12-13) * * *", Hash.from("stuff")).ceil(t));
}
@Test public void hashSkips() throws Exception {
compare(new GregorianCalendar(2013, 2, 21, 16, 26), new CronTab("H/15 * * * *", Hash.from("stuff")).ceil(new GregorianCalendar(2013, 2, 21, 16, 21)));
compare(new GregorianCalendar(2013, 2, 21, 16, 41), new CronTab("H/15 * * * *", Hash.from("stuff")).ceil(new GregorianCalendar(2013, 2, 21, 16, 31)));
compare(new GregorianCalendar(2013, 2, 21, 16, 56), new CronTab("H/15 * * * *", Hash.from("stuff")).ceil(new GregorianCalendar(2013, 2, 21, 16, 42)));
compare(new GregorianCalendar(2013, 2, 21, 17, 11), new CronTab("H/15 * * * *", Hash.from("stuff")).ceil(new GregorianCalendar(2013, 2, 21, 16, 59)));
compare(new GregorianCalendar(2013, 2, 21, 0, 2), new CronTab("H(0-15)/3 * * * *", Hash.from("junk")).ceil(new GregorianCalendar(2013, 2, 21, 0, 0)));
compare(new GregorianCalendar(2013, 2, 21, 0, 2), new CronTab("H(0-3)/4 * * * *", Hash.from("junk")).ceil(new GregorianCalendar(2013, 2, 21, 0, 0)));
compare(new GregorianCalendar(2013, 2, 21, 1, 2), new CronTab("H(0-3)/4 * * * *", Hash.from("junk")).ceil(new GregorianCalendar(2013, 2, 21, 0, 5)));
try {
compare(new GregorianCalendar(2013, 2, 21, 0, 0), new CronTab("H(0-3)/15 * * * *", Hash.from("junk")).ceil(new GregorianCalendar(2013, 2, 21, 0, 0)));
fail();
} catch (ANTLRException x) {
// good
}
}
@Test public void repeatedHash() throws Exception {
CronTabList tabs = CronTabList.create("H * * * *\nH * * * *", Hash.from("seed"));
List<Integer> times = new ArrayList<Integer>();
for (int i = 0; i < 60; i++) {
if (tabs.check(new GregorianCalendar(2013, 3, 3, 11, i, 0))) {
times.add(i);
}
}
assertEquals("[35, 56]", times.toString());
}
@Test public void rangeBoundsCheckOK() throws Exception {
new CronTab("H(0-59) H(0-23) H(1-31) H(1-12) H(0-7)");
}
@Test public void rangeBoundsCheckFailHour() throws Exception {
try {
new CronTab("H H(12-24) * * *");
fail();
} catch (ANTLRException e) {
// ok
}
}
@Test public void rangeBoundsCheckFailMinute() throws Exception {
try {
new CronTab("H(33-66) * * * *");
fail();
} catch (ANTLRException e) {
// ok
}
}
@Issue("JENKINS-9283")
@Test public void testTimezone() throws Exception {
CronTabList tabs = CronTabList.create("TZ=Australia/Sydney\nH * * * *\nH * * * *", Hash.from("seed"));
List<Integer> times = new ArrayList<Integer>();
for (int i = 0; i < 60; i++) {
if (tabs.check(new GregorianCalendar(2013, 3, 3, 11, i, 0))) {
times.add(i);
}
}
assertEquals("[35, 56]", times.toString());
}
}