001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.oscache.util;
006:
007: import junit.framework.Test;
008: import junit.framework.TestCase;
009: import junit.framework.TestSuite;
010:
011: import java.text.ParseException;
012: import java.text.SimpleDateFormat;
013:
014: import java.util.*;
015:
016: /**
017: *
018: * @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
019: * @author $Author: larst $
020: * @version $Revision: 385 $
021: */
022: public class TestFastCronParser extends TestCase {
023: public TestFastCronParser(String str) {
024: super (str);
025: }
026:
027: /**
028: * This methods returns the name of this test class to JUnit
029: * <p>
030: * @return The name of this class
031: */
032: public static Test suite() {
033: return new TestSuite(TestFastCronParser.class);
034: }
035:
036: /**
037: * Tests to see if the cron class can calculate the previous matching
038: * time correctly in various circumstances
039: */
040: public void testEvaluations() {
041: // Minute tests
042: cronCall("01/01/2003 0:00", "45 * * * *", "31/12/2002 23:45",
043: false);
044: cronCall("01/01/2003 0:00", "45-47,48,49 * * * *",
045: "31/12/2002 23:49", false);
046: cronCall("01/01/2003 0:00", "2/5 * * * *", "31/12/2002 23:57",
047: false);
048:
049: // Hour tests
050: cronCall("20/12/2003 10:00", "* 3/4 * * *", "20/12/2003 07:59",
051: false);
052: cronCall("20/12/2003 0:00", "* 3 * * *", "19/12/2003 03:59",
053: false);
054:
055: // Day of month tests
056: cronCall("07/01/2003 0:00", "30 * 1 * *", "01/01/2003 23:30",
057: false);
058: cronCall("01/01/2003 0:00", "10 * 22 * *", "22/12/2002 23:10",
059: false);
060: cronCall("01/01/2003 0:00", "30 23 19 * *", "19/12/2002 23:30",
061: false);
062: cronCall("01/01/2003 0:00", "30 23 21 * *", "21/12/2002 23:30",
063: false);
064: cronCall("01/01/2003 0:01", "* * 21 * *", "21/12/2002 23:59",
065: false);
066: cronCall("10/07/2003 0:00", "* * 30,31 * *",
067: "30/06/2003 23:59", false);
068:
069: // Test month rollovers for months with 28,29,30 and 31 days
070: cronCall("01/03/2002 0:11", "* * * 2 *", "28/02/2002 23:59",
071: false);
072: cronCall("01/03/2004 0:44", "* * * 2 *", "29/02/2004 23:59",
073: false);
074: cronCall("01/04/2002 0:00", "* * * 3 *", "31/03/2002 23:59",
075: false);
076: cronCall("01/05/2002 0:00", "* * * 4 *", "30/04/2002 23:59",
077: false);
078:
079: // Other month tests (including year rollover)
080: cronCall("01/01/2003 5:00", "10 * * 6 *", "30/06/2002 23:10",
081: false);
082: cronCall("01/01/2003 5:00", "10 * * February,April-Jun *",
083: "30/06/2002 23:10", false);
084: cronCall("01/01/2003 0:00", "0 12 1 6 *", "01/06/2002 12:00",
085: false);
086: cronCall("11/09/1988 14:23", "* 12 1 6 *", "01/06/1988 12:59",
087: false);
088: cronCall("11/03/1988 14:23", "* 12 1 6 *", "01/06/1987 12:59",
089: false);
090: cronCall("11/03/1988 14:23", "* 2,4-8,15 * 6 *",
091: "30/06/1987 15:59", false);
092: cronCall(
093: "11/03/1988 14:23",
094: "20 * * january,FeB,Mar,april,May,JuNE,July,Augu,SEPT-October,Nov,DECEM *",
095: "11/03/1988 14:20", false);
096:
097: // Day of week tests
098: cronCall("26/06/2003 10:00", "30 6 * * 0", "22/06/2003 06:30",
099: false);
100: cronCall("26/06/2003 10:00", "30 6 * * sunday",
101: "22/06/2003 06:30", false);
102: cronCall("26/06/2003 10:00", "30 6 * * SUNDAY",
103: "22/06/2003 06:30", false);
104: cronCall("23/06/2003 0:00", "1 12 * * 2", "17/06/2003 12:01",
105: false);
106: cronCall("23/06/2003 0:00", "* * * * 3,0,4",
107: "22/06/2003 23:59", false);
108: cronCall("23/06/2003 0:00", "* * * * 5", "20/06/2003 23:59",
109: false);
110: cronCall("02/06/2003 18:30", "0 12 * * 2", "27/05/2003 12:00",
111: false);
112: cronCall("02/06/2003 18:30", "0 12 * * Tue,Thurs-Sat,2",
113: "31/05/2003 12:00", false);
114: cronCall("02/06/2003 18:30",
115: "0 12 * * Mon-tuesday,wed,THURS-FRiday,Sat-SUNDAY",
116: "02/06/2003 12:00", false);
117:
118: // Leap year tests
119: cronCall("01/03/2003 12:00", "1 12 * * *", "28/02/2003 12:01",
120: false); // non-leap year
121: cronCall("01/03/2004 12:00", "1 12 * * *", "29/02/2004 12:01",
122: false); // leap year
123: cronCall("01/03/2003 12:00", "1 23 * * 0", "23/02/2003 23:01",
124: false); // non-leap year
125: cronCall("01/03/2004 12:00", "1 23 * * 0", "29/02/2004 23:01",
126: false); // leap year
127: cronCall("01/03/2003 12:00", "* * 29 2 *", "29/02/2000 23:59",
128: false); // Find the previous leap-day
129: cronCall("01/02/2003 12:00", "* * 29 2 *", "29/02/2000 23:59",
130: false); // Find the previous leap-day
131: cronCall("01/02/2004 12:00", "* * 29 2 *", "29/02/2000 23:59",
132: false); // Find the previous leap-day
133:
134: // Interval and range tests
135: cronCall("20/12/2003 10:00", "* */4 * * *", "20/12/2003 08:59",
136: false);
137: cronCall("20/12/2003 10:00", "* 3/2 * * *", "20/12/2003 09:59",
138: false);
139: cronCall("20/12/2003 10:00", "1-30/5 10-20/3 * jan-aug/2 *",
140: "31/07/2003 19:26", false);
141: cronCall("20/12/2003 10:00", "20-25,27-30/2 10/8 * * *",
142: "19/12/2003 18:29", false);
143: }
144:
145: /**
146: * Tests a range of invalid cron expressions
147: */
148: public void testInvalidExpressionParsing() {
149: FastCronParser parser = new FastCronParser();
150:
151: try {
152: parser.setCronExpression(null);
153: fail("An IllegalArgumentException should have been thrown");
154: } catch (IllegalArgumentException e) {
155: // Expected
156: } catch (ParseException e) {
157: fail("Expected an IllegalArgumentException but received a ParseException instead");
158: }
159:
160: /**
161: * Not enough tokens
162: */
163: cronCall("01/01/2003 0:00", "", "", true);
164: cronCall("01/01/2003 0:00", "8 * 8/1 *", "", true);
165:
166: /**
167: * Invalid syntax
168: */
169: cronCall("01/01/2003 0:00", "* invalid * * *", "", true);
170: cronCall("01/01/2003 0:00", "* -1 * * *", "", true);
171: cronCall("01/01/2003 0:00", "* * 20 * 0", "", true);
172: cronCall("01/01/2003 0:00", "* * 5-6-7 * *", "", true);
173: cronCall("01/01/2003 0:00", "* * 5/6-7 * *", "", true);
174: cronCall("01/01/2003 0:00", "* * 5-* * *", "", true);
175: cronCall("01/01/2003 0:00", "* * 5-6* * *", "", true);
176: cronCall("01/01/2003 0:00", "* * * * Mo", "", true);
177: cronCall("01/01/2003 0:00", "* * * jxxx *", "", true);
178: cronCall("01/01/2003 0:00", "* * * juxx *", "", true);
179: cronCall("01/01/2003 0:00", "* * * fbr *", "", true);
180: cronCall("01/01/2003 0:00", "* * * mch *", "", true);
181: cronCall("01/01/2003 0:00", "* * * mAh *", "", true);
182: cronCall("01/01/2003 0:00", "* * * arl *", "", true);
183: cronCall("01/01/2003 0:00", "* * * Spteber *", "", true);
184: cronCall("01/01/2003 0:00", "* * * otber *", "", true);
185: cronCall("01/01/2003 0:00", "* * * nvemtber *", "", true);
186: cronCall("01/01/2003 0:00", "* * * Dcmber *", "", true);
187: cronCall("01/01/2003 0:00", "* * * * mnday", "", true);
188: cronCall("01/01/2003 0:00", "* * * * tsdeday", "", true);
189: cronCall("01/01/2003 0:00", "* * * * wdnesday", "", true);
190: cronCall("01/01/2003 0:00", "* * * * frday", "", true);
191: cronCall("01/01/2003 0:00", "* * * * sdhdatr", "", true);
192:
193: /**
194: * Values out of range
195: */
196: cronCall("01/01/2003 0:00", "* * 0 * *", "", true);
197: cronCall("01/01/2003 0:00", "* 50 * * *", "", true);
198: cronCall("01/01/2003 0:00", "* * * 1-20 *", "", true);
199: cronCall("01/01/2003 0:00", "* * 0-20 * *", "", true);
200: cronCall("01/01/2003 0:00", "* * 1-40 * *", "", true);
201: cronCall("01/01/2003 0:00", "* * * 1 8", "", true);
202: cronCall("01/01/2003 0:00", "* * 0/3 * *", "", true);
203: cronCall("01/01/2003 0:00", "* * 30 2 *", "", true); // 30th Feb doesn't ever exist!
204: cronCall("01/01/2003 0:00", "* * 31 4 *", "", true); // 31st April doesn't ever exist!
205: }
206:
207: /**
208: * This tests the performance of the cron parsing engine. Note that it may take
209: * a couple of minutes o run - by default this test is disabled. Comment out the
210: * <code>return</code> statement at the start of this method to enable the
211: * benchmarking.
212: */
213: public void testPerformance() {
214: if (true) {
215: // return; // Comment out this line to benchmark
216: }
217:
218: SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
219: Date date = null;
220:
221: try {
222: date = sdf.parse("21/01/2003 16:27");
223: } catch (ParseException e) {
224: fail("Failed to parse date. Please check your unit test code!");
225: }
226:
227: Calendar calendar = Calendar.getInstance(TimeZone
228: .getTimeZone("GMT"));
229: calendar.setTime(date);
230:
231: long baseTime = calendar.getTimeInMillis();
232:
233: long time = 0;
234:
235: try {
236: // Give HotSpot a chance to warm up
237: iterate("28 17 22 02 *", baseTime, time, 10000, true);
238:
239: // Number of iterations to test
240: int count = 1000000;
241:
242: // Test the best-case scenario
243: long bestCaseTime = iterate("* * * * *", baseTime, time,
244: count, true);
245: System.out.println("Best case with parsing took "
246: + bestCaseTime + "ms for " + count
247: + " iterations. (" + (bestCaseTime / (float) count)
248: + "ms per call)");
249:
250: // Test a near worst-case scenario
251: long worstCaseTime = iterate(
252: "0-59,0-13,2,3,4,5 17-19 22-23,22,23 2,3 *",
253: baseTime, time, count, true);
254: System.out.println("Worst case with parsing took "
255: + worstCaseTime + "ms for " + count
256: + " iterations. ("
257: + (worstCaseTime / (float) count) + "ms per call)");
258:
259: // Test the best-case scenario without parsing the expression on each iteration
260: bestCaseTime = iterate("* * * * *", baseTime, time, count,
261: false);
262: System.out.println("Best case without parsing took "
263: + bestCaseTime + "ms for " + count
264: + " iterations. (" + (bestCaseTime / (float) count)
265: + "ms per call)");
266:
267: // Test a near worst-case scenario without parsing the expression on each iteration
268: worstCaseTime = iterate(
269: "0-59,0-13,2,3,4,5 17-19 22-23,22,23 2,3 *",
270: baseTime, time, count, false);
271: System.out.println("Worst case without parsing took "
272: + worstCaseTime + "ms for " + count
273: + " iterations. ("
274: + (worstCaseTime / (float) count) + "ms per call)");
275: } catch (ParseException e) {
276: }
277: }
278:
279: /**
280: * Tests that a range of valid cron expressions get parsed correctly.
281: */
282: public void testValidExpressionParsing() {
283: FastCronParser parser;
284:
285: // Check the default constructor
286: parser = new FastCronParser();
287: assertNull(parser.getCronExpression());
288:
289: try {
290: parser = new FastCronParser("* * * * *");
291: assertEquals("* * * * *", parser.getCronExpression()); // Should be the same as what we gave it
292: assertEquals("* * * * *", parser.getExpressionSummary());
293:
294: parser.setCronExpression("0 * * * *");
295: assertEquals("0 * * * *", parser.getCronExpression()); // Should be the same as what we gave it
296: assertEquals("0 * * * *", parser.getExpressionSummary());
297:
298: parser.setCronExpression("5 10 * * 1,4,6");
299: assertEquals("5 10 * * 1,4,6", parser
300: .getExpressionSummary());
301:
302: parser
303: .setCronExpression("0,5-20,4-15,24-27 0 * 2-4,5,6-3 *"); // Overlapping ranges, backwards ranges
304: assertEquals(
305: "0,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,24,25,26,27 0 * 2,3,4,5,6 *",
306: parser.getExpressionSummary());
307: } catch (ParseException e) {
308: e.printStackTrace();
309: fail("Cron expression should have been valid: " + e);
310: }
311: }
312:
313: /**
314: * Makes a call to the FastCronParser.
315: *
316: * @param dateStr The date string to use as the base date. The format must be
317: * <code>"dd/MM/yyyy HH:mm"</code>.
318: * @param cronExpr The cron expression to test.
319: * @param result The expected result. This should be a date in the same format
320: * as <code>dateStr</code>.
321: * @param expectException Pass in <code>true</code> if the {@link FastCronParser} is
322: * expected to throw a <code>ParseException</code>.
323: */
324: private void cronCall(String dateStr, String cronExpr,
325: String result, boolean expectException) {
326: SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
327: Date date = null;
328:
329: try {
330: date = sdf.parse(dateStr);
331: } catch (ParseException e) {
332: fail("Failed to parse date " + dateStr
333: + ". Please check your unit test code!");
334: }
335:
336: Calendar calendar = Calendar.getInstance();
337: calendar.setTime(date);
338:
339: long baseTime = calendar.getTimeInMillis();
340: FastCronParser parser = null;
341:
342: try {
343: parser = new FastCronParser(cronExpr);
344:
345: if (expectException) {
346: fail("Should have received a ParseException while parsing "
347: + cronExpr);
348: }
349:
350: long time = parser.getTimeBefore(baseTime);
351: assertEquals(result, sdf.format(new Date(time)));
352: } catch (ParseException e) {
353: if (!expectException) {
354: fail("Unexpected ParseException while parsing "
355: + cronExpr + ": " + e);
356: }
357: }
358: }
359:
360: /**
361: * Used by the benchmarking
362: */
363: private long iterate(String cronExpr, long baseTime, long time,
364: int count, boolean withParse) throws ParseException {
365: long startTime = System.currentTimeMillis();
366:
367: if (withParse) {
368: FastCronParser parser = new FastCronParser();
369:
370: for (int i = 0; i < count; i++) {
371: parser.setCronExpression(cronExpr);
372: time = parser.getTimeBefore(baseTime);
373: }
374: } else {
375: FastCronParser parser = new FastCronParser(cronExpr);
376:
377: for (int i = 0; i < count; i++) {
378: time = parser.getTimeBefore(baseTime);
379: }
380: }
381:
382: long endTime = System.currentTimeMillis();
383: long duration = (endTime - startTime);
384: duration += (time - time); // Use the time variable to prevent it getting optimized away
385: return duration;
386: }
387: }
|