1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j;
19  
20  import junit.framework.TestCase;
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.FileInputStream;
26  import java.text.SimpleDateFormat;
27  import java.util.Calendar;
28  import java.util.Date;
29  import org.apache.log4j.util.Compare;
30  
31  /**
32     Exhaustive test of the DailyRollingFileAppender compute algorithm.
33  
34     @author Ceki Gülcü
35     @author Curt Arnold
36   */
37  public class DRFATestCase extends TestCase {
38  
39      /**
40       * Create new test.
41       * @param name test name.
42       */
43    public DRFATestCase(final String name) {
44      super(name);
45    }
46  
47      /**
48       * Reset configuration after every test.
49       */
50    public void tearDown() {
51        LogManager.resetConfiguration();
52    }
53  
54      /**
55       * Test prediction of check period.
56       */
57    public
58    void testComputeCheckPeriod() {
59      DailyRollingFileAppender drfa = new DailyRollingFileAppender();
60      drfa.setName("testComputeCheckPeriod");
61      drfa.setDatePattern("yyyy-MM-dd.'log'");
62      drfa.activateOptions();
63  
64      drfa.computeCheckPeriod();
65      assertEquals(drfa.computeCheckPeriod(),
66           DailyRollingFileAppender.TOP_OF_DAY);
67  
68      drfa.setDatePattern("yyyy-MM-dd mm.'log'");
69      assertEquals(drfa.computeCheckPeriod(),
70           DailyRollingFileAppender.TOP_OF_MINUTE);
71  
72      drfa.setDatePattern("yyyy-MM-dd a.'log'");
73      assertEquals(drfa.computeCheckPeriod(),
74           DailyRollingFileAppender.HALF_DAY);
75  
76      drfa.setDatePattern("yyyy-MM-dd HH.'log'");
77      assertEquals(drfa.computeCheckPeriod(),
78           DailyRollingFileAppender.TOP_OF_HOUR);
79  
80      drfa.setDatePattern("yyyy-MM.'log'");
81      assertEquals(drfa.computeCheckPeriod(),
82           DailyRollingFileAppender.TOP_OF_MONTH);
83  
84      drfa.setDatePattern("'log'HH'log'");
85      assertEquals(drfa.computeCheckPeriod(),
86           DailyRollingFileAppender.TOP_OF_HOUR);
87    }
88  
89  
90      /**
91       *   Test of RollingCalendar.
92       */
93    public
94    void testRC1() {
95      RollingCalendar rc = new RollingCalendar();
96      rc.setType(DailyRollingFileAppender.TOP_OF_DAY);
97  
98      Calendar c = Calendar.getInstance();
99  
100     // jan, mar, may, july, aug, oct, dec have 31 days
101     int [] M31 = {0,2,4,6,7,9,11};
102 
103     for(int i = 0; i < M31.length; i ++) {
104       for(int d = 1; d <=31; d++) {
105     for(int h = 0; h < 23; h++) {
106       c.clear();
107       c.set(Calendar.YEAR, 20);
108       c.set(Calendar.MONTH, Calendar.JANUARY + M31[i]);
109       c.set(Calendar.DAY_OF_MONTH, d);
110       c.set(Calendar.HOUR_OF_DAY, h);
111       c.set(Calendar.MINUTE, 10);
112       c.set(Calendar.SECOND, 10);
113       c.set(Calendar.MILLISECOND, 88);
114 
115       c.setTime(rc.getNextCheckDate(c.getTime()));
116       if(d == 31) {
117         assertEquals(c.get(Calendar.MONTH),(Calendar.JANUARY+M31[i]+1)%12);
118         assertEquals(c.get(Calendar.DAY_OF_MONTH), 1);
119       } else {
120         assertEquals(c.get(Calendar.MONTH), Calendar.JANUARY+M31[i]);
121         assertEquals(c.get(Calendar.DAY_OF_MONTH), d+1);
122       }
123       assertEquals(c.get(Calendar.HOUR_OF_DAY), 0);
124       assertEquals(c.get(Calendar.MINUTE), 0);
125       assertEquals(c.get(Calendar.SECOND), 0);
126       assertEquals(c.get(Calendar.MILLISECOND), 0);
127     }
128       }
129     }
130   }
131 
132     /**
133      * RollingCalendar test.
134      */
135   public
136   void testRC2() {
137     RollingCalendar rc = new RollingCalendar();
138 
139     rc.setType(DailyRollingFileAppender.TOP_OF_HOUR);
140 
141     Calendar c = Calendar.getInstance();
142 
143     // jan, mar, may, july, aug, oct, dec have 31 days
144     int [] M31 = {0,2,4,6,7,9,11};
145 
146     for(int i = 0; i < M31.length; i ++) {
147       System.out.println("Month = "+(M31[i]+1));
148       for(int d = 1; d <= 31; d++) {
149     for(int h = 0; h < 23; h++) {
150       for(int m = 0; m <= 59; m++) {
151         c.clear();
152         c.set(Calendar.YEAR, 20);
153         c.set(Calendar.MONTH, Calendar.JANUARY + M31[i]);
154         c.set(Calendar.DAY_OF_MONTH, d);
155         c.set(Calendar.HOUR_OF_DAY, h);
156         c.set(Calendar.MINUTE, m);
157         c.set(Calendar.SECOND, 12);
158         c.set(Calendar.MILLISECOND, 88);
159 
160         boolean dltState0 = c.getTimeZone().inDaylightTime(c.getTime());
161         c.setTime(rc.getNextCheckDate(c.getTime()));
162         boolean dltState1 = c.getTimeZone().inDaylightTime(c.getTime());
163 
164         assertEquals(c.get(Calendar.MILLISECOND), 0);
165         assertEquals(c.get(Calendar.SECOND), 0);
166         assertEquals(c.get(Calendar.MINUTE), 0);
167 
168         if(dltState0 == dltState1) {
169           assertEquals(c.get(Calendar.HOUR_OF_DAY), (h+1)%24);
170         } else {
171           // returning to standard time
172           if(dltState0) {
173         assertEquals(c.get(Calendar.HOUR_OF_DAY), h);
174           } else { // switching to day light saving time
175         //System.err.println("m="+m+", h="+h+", d="+d+", i="+i);
176         //if(h==2) {
177         // System.err.println(c);
178         //}
179         //assertEquals(c.get(Calendar.HOUR_OF_DAY), (h+2)%24);
180           }
181         }
182 
183         if(h == 23) {
184           assertEquals(c.get(Calendar.DAY_OF_MONTH), (d+1)%32);
185           if(d == 31) {
186         assertEquals(c.get(Calendar.MONTH),
187                  (Calendar.JANUARY+M31[i]+1)%12);
188           } else {
189         assertEquals(c.get(Calendar.MONTH),
190                  Calendar.JANUARY+M31[i]);
191           }
192         } else {
193           assertEquals(c.get(Calendar.DAY_OF_MONTH), d);
194           assertEquals(c.get(Calendar.MONTH), Calendar.JANUARY+M31[i]);
195         }
196       }
197     }
198       }
199     }
200   }
201 
202     /**
203      * RollingCalendar test.
204      */
205   public
206   void testRC3() {
207     RollingCalendar rc = new RollingCalendar();
208 
209     rc.setType(DailyRollingFileAppender.TOP_OF_MINUTE);
210 
211     int[] S = {0, 1, 5, 10, 21, 30, 59};
212     int[] M = {0, 1, 5, 10, 21, 30, 59};
213     Calendar c = Calendar.getInstance();
214 
215     // jan, mar, may, july, aug, oct, dec have 31 days
216     int [] M31 = {2,9,0,4,6,7,11};
217 
218     for(int i = 0; i < M31.length; i ++) {
219       System.out.println("Month = "+(M31[i]+1));
220       for(int d = 1; d <= 31; d++) {
221     for(int h = 0; h < 23; h++) {
222       for(int m = 0; m < M.length; m++) {
223         for(int s = 0; s < S.length; s++) {
224           c.clear();
225           c.set(Calendar.YEAR, 20);
226           c.set(Calendar.MONTH, Calendar.JANUARY + M31[i]);
227           c.set(Calendar.DAY_OF_MONTH, d);
228           c.set(Calendar.HOUR_OF_DAY, h);
229           c.set(Calendar.MINUTE, M[m]);
230           c.set(Calendar.SECOND, S[s]);
231           c.set(Calendar.MILLISECOND, 88);
232           c.add(Calendar.MILLISECOND, 1);
233 
234           boolean dltState0 = c.getTimeZone().inDaylightTime(c.getTime());
235 
236           c.setTime(rc.getNextCheckDate(c.getTime()));
237           c.add(Calendar.MILLISECOND, 0);
238           boolean dltState1 = c.getTimeZone().inDaylightTime(c.getTime());
239 
240           assertEquals(c.get(Calendar.MILLISECOND), 0);
241           assertEquals(c.get(Calendar.SECOND), 0);
242           assertEquals(c.get(Calendar.MINUTE), (M[m]+1)%60);
243 
244           if(M[m] == 59) {
245         if(dltState0 == dltState1) {
246           assertEquals(c.get(Calendar.HOUR_OF_DAY), (h+1)%24);
247         }
248         if(h == 23) {
249           assertEquals(c.get(Calendar.DAY_OF_MONTH), (d+1)%32);
250           if(d == 31) {
251               assertEquals(c.get(Calendar.MONTH),
252                  (Calendar.JANUARY+M31[i]+1)%12);
253           } else {
254             assertEquals(c.get(Calendar.MONTH),
255                  Calendar.JANUARY+M31[i]);
256           }
257         } else {
258           assertEquals(c.get(Calendar.DAY_OF_MONTH), d);
259         }
260           } else {
261         // allow discrepancies only if we are switching from std to dls time
262         if(c.get(Calendar.HOUR_OF_DAY) != h) {
263           c.add(Calendar.HOUR_OF_DAY, +1);
264           boolean dltState2 = c.getTimeZone().inDaylightTime(c.getTime());
265           if(dltState1 == dltState2) {
266             fail("No switch");
267           }
268         }
269         assertEquals(c.get(Calendar.DAY_OF_MONTH), d);
270         assertEquals(c.get(Calendar.MONTH), Calendar.JANUARY+M31[i]);
271           }
272         }
273       }
274     }
275       }
276     }
277   }
278 
279 
280     /**
281      * Common test code for 3 parameter constructor.
282      *
283      * @throws IOException if IOException during test.
284      */
285    public void test3Param(final String datePattern,
286                           final String filename) throws IOException {
287        Layout layout = new SimpleLayout();
288        DailyRollingFileAppender appender =
289                new DailyRollingFileAppender(layout, filename, datePattern);
290        assertEquals(datePattern, appender.getDatePattern());
291        Logger root = Logger.getRootLogger();
292        root.addAppender(appender);
293        root.info("Hello, World");
294        assertTrue(new File(filename).exists());
295     }
296 
297     /**
298      * Creates an appender with an unrecognized top-of-year pattern.
299      *
300      * @throws IOException if IOException during test.
301      */
302     public void testTopOfYear() throws IOException {
303         try {
304             test3Param("'.'yyyy", "output/drfa_topOfYear.log");
305             fail("Expected illegal state exception.");
306         } catch(IllegalStateException ex) {
307             assertNotNull(ex);
308         }
309     }
310 
311     /**
312      * Creates an appender with a top-of-month pattern.
313      *
314      * @throws IOException if IOException during test.
315      */
316     public void testTopOfMonth() throws IOException {
317         test3Param("'.'yyyy-MM", "output/drfa_topOfMonth.log");
318     }
319 
320 
321     /**
322      * Creates an appender with a top-of-week pattern.
323      *
324      * @throws IOException if IOException during test.
325      */
326     public void testTopOfWeek() throws IOException {
327         test3Param("'.'yyyy-w", "output/drfa_topOfWeek.log");
328     }
329 
330     /**
331      * Creates an appender with a top-of-day pattern.
332      *
333      * @throws IOException if IOException during test.
334      */
335     public void testTopOfDay() throws IOException {
336         test3Param("'.'yyyy-MM-dd", "output/drfa_topOfDay.log");
337     }
338 
339 
340     /**
341      * Creates an appender with a half day pattern.
342      *
343      * @throws IOException if IOException during test.
344      */
345     public void testHalfDay() throws IOException {
346         test3Param("'.'yyyy-MM-dd-a", "output/drfa_halfDay.log");
347     }
348 
349     /**
350      * Creates an appender with a top-of-hour pattern.
351      *
352      * @throws IOException if IOException during test.
353      */
354     public void testTopOfHour() throws IOException {
355         test3Param("'.'yyyy-MM-dd-HH", "output/drfa_topOfHour.log");
356     }
357 
358     /**
359      * Creates an appender with a top-of-day pattern.
360      *
361      * @throws IOException if IOException during test.
362      */
363     public void testTopOfMinute() throws IOException {
364         test3Param("'.'yyyy-MM-dd-HH-mm", "output/drfa_topOfMinute.log");
365     }
366 
367     /**
368      * Attempts to rollOver with no date pattern set.
369      *
370      * @throws IOException if IOException during test.
371      */
372     public void testRolloverNoPattern() throws IOException {
373         Layout layout = new SimpleLayout();
374         DailyRollingFileAppender appender =
375                 new DailyRollingFileAppender(layout, "output/drfa_nopattern.log", null);
376 
377         VectorErrorHandler errorHandler = new VectorErrorHandler();
378         appender.setErrorHandler(errorHandler);
379         appender.rollOver();
380         assertEquals(1, errorHandler.size());
381         assertEquals("Missing DatePattern option in rollOver().",
382                 errorHandler.getMessage(0));
383     }
384 
385     /**
386      * Tests rollOver with a minute periodicity.
387      *
388      * @throws IOException
389      * @throws InterruptedException
390      */
391     public void testMinuteRollover() throws IOException, InterruptedException {
392         Layout layout = new SimpleLayout();
393         String filename = "output/drfa_minuteRollover.log";
394         String pattern = "'.'yyyy-MM-dd-HH-mm";
395 
396         DailyRollingFileAppender appender =
397                 new DailyRollingFileAppender(layout,
398                         filename,
399                         pattern);
400         Logger root = Logger.getRootLogger();
401         root.addAppender(appender);
402         File firstFile =
403                 new File(filename + new SimpleDateFormat(pattern).format(new Date()));
404         root.info("Hello, World");
405         //
406         //   create a file by that name so it has to be deleted
407         //       on rollover
408         firstFile.createNewFile();
409         assertTrue(firstFile.exists());
410         assertEquals(0, firstFile.length());
411 
412         Calendar cal = Calendar.getInstance();
413         long now = cal.getTime().getTime();
414         cal.set(Calendar.SECOND, 3);
415         cal.set(Calendar.MILLISECOND, 0);
416         cal.add(Calendar.MINUTE, 1);
417         long until = cal.getTime().getTime();
418         Thread.sleep(until - now);
419         root.info("Hello, World");
420         assertTrue(firstFile.exists());
421         assertTrue(firstFile.length() > 0);
422 
423     }
424 
425     /**
426      * Naive append method to combine rollover fragments.
427      * @param combined stream to which source is appended.
428      * @param source stream containing bytes to append.
429      * @param buf byte array to use in transfer.
430      * @throws IOException if io error during operation.
431      */
432     private static void append(final FileOutputStream combined,
433                                final FileInputStream source,
434                                final byte[] buf) throws IOException {
435         int count1 = source.read(buf);
436         if (count1 > 0) {
437             combined.write(buf, 0, count1);
438         }
439         source.close();
440     }
441 
442     /**
443      * Tests rollOver when log file is unabled to be renamed.
444      * See bug 43374.
445      *
446      * @throws IOException if io error.
447      * @throws InterruptedException if test interrupted while waiting for the start of the next minute.
448      */
449     public void testBlockedRollover() throws IOException, InterruptedException {
450         Layout layout = new SimpleLayout();
451         String filename = "output/drfa_blockedRollover.log";
452         String pattern = "'.'yyyy-MM-dd-HH-mm";
453 
454 
455         Date start = new Date();
456         DailyRollingFileAppender appender =
457                 new DailyRollingFileAppender(layout,
458                         filename,
459                         pattern);
460         appender.setAppend(false);
461         Logger root = Logger.getRootLogger();
462         root.addAppender(appender);
463         //
464         //   open next two anticipated rollover file names
465         //
466         File block1 = new File(filename + new SimpleDateFormat(pattern).format(start));
467         File block2 = new File(filename + new SimpleDateFormat(pattern).format(
468                 new Date(start.getTime() + 60000)));
469         FileOutputStream os1 = new FileOutputStream(block1);
470         FileOutputStream os2 = new FileOutputStream(block2);
471         root.info("Prior to rollover");
472         //
473         //   sleep until three seconds into next minute
474         //
475         Thread.sleep(63000 - (start.getTime() % 60000));
476         //
477         //  should trigger failed rollover
478         //
479         root.info("Rollover attempt while blocked");
480         os1.close();
481         os2.close();
482         root.info("Message after block removed");
483         appender.close();
484         //
485         //   combine base file and potential rollovers
486         //      since rollover may or may not have been blocked
487         //      depending on platform.
488         //
489         String combinedFilename = "output/drfa_blockedRollover.combined";
490         FileOutputStream combined = new FileOutputStream(combinedFilename);
491         byte[] buf = new byte[500];
492         append(combined, new FileInputStream(block1), buf);
493         append(combined, new FileInputStream(block2), buf);
494         append(combined, new FileInputStream(filename), buf);
495         combined.close();
496         assertTrue(Compare.compare(combinedFilename,
497                 "witness/drfa_blockedRollover.log"));
498     }
499 
500     /** Check that the computed rollover period for a pattern containing a week as the finest unit is set to be
501      * a week.  Due to a locale mismatch this was incorrect in non-English locales.  See bug 40888.
502      *
503      */
504     public void testWeeklyRollover() {
505         DailyRollingFileAppender drfa = new DailyRollingFileAppender();
506     	drfa.setDatePattern("'.'yyyy-ww");
507 		int checkPeriod = drfa.computeCheckPeriod();
508 		assertEquals(DailyRollingFileAppender.TOP_OF_WEEK, checkPeriod);
509     }
510 
511 
512 }