1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    * <p>
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    * <p>
9    * or (per the licensee's choosing)
10   * <p>
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling;
15  
16  import static ch.qos.logback.core.CoreConstants.DAILY_DATE_PATTERN;
17  import static ch.qos.logback.core.CoreConstants.STRICT_ISO8601_PATTERN;
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertTrue;
20  
21  import java.io.File;
22  import java.io.FileFilter;
23  import java.time.Instant;
24  import java.time.LocalDateTime;
25  import java.time.ZoneId;
26  import java.time.ZonedDateTime;
27  import java.time.format.DateTimeFormatter;
28  import java.util.ArrayList;
29  import java.util.Calendar;
30  import java.util.Collections;
31  import java.util.Comparator;
32  import java.util.Date;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Set;
36  import java.util.concurrent.atomic.LongAdder;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  import java.time.temporal.ChronoUnit;
40  
41  import ch.qos.logback.core.util.StatusPrinter;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Disabled;
44  import org.junit.jupiter.api.Test;
45  
46  import ch.qos.logback.core.CoreConstants;
47  import ch.qos.logback.core.pattern.SpacePadder;
48  import ch.qos.logback.core.rolling.helper.RollingCalendar;
49  import ch.qos.logback.core.rolling.testUtil.ScaffoldingForRollingTests;
50  import ch.qos.logback.core.status.testUtil.StatusChecker;
51  import ch.qos.logback.core.util.FileSize;
52  import ch.qos.logback.core.util.FixedRateInvocationGate;
53  
54  public class TimeBasedRollingWithArchiveRemoval_Test extends ScaffoldingForRollingTests {
55      String MONTHLY_DATE_PATTERN = "yyyy-MM";
56      String MONTHLY_CRONOLOG_DATE_PATTERN = "yyyy/MM";
57      final String DAILY_CRONOLOG_DATE_PATTERN = "yyyy/MM/dd";
58  
59      RollingFileAppender<Object> rfa = new RollingFileAppender<Object>();
60      TimeBasedRollingPolicy<Object> tbrp = new TimeBasedRollingPolicy<Object>();
61  
62      DateTimeFormatter STRICT_DATE_PARSER = DateTimeFormatter.ofPattern(STRICT_ISO8601_PATTERN);
63  
64      // by default tbfnatp is an instance of
65      // DefaultTimeBasedFileNamingAndTriggeringPolicy
66      TimeBasedFileNamingAndTriggeringPolicy<Object> tbfnatp = new DefaultTimeBasedFileNamingAndTriggeringPolicy<Object>();
67  
68      StatusChecker checker = new StatusChecker(context);
69  
70      static long MILLIS_IN_MINUTE = 60 * 1000;
71      static long MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE;
72      static long MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
73      static long MILLIS_IN_MONTH = (long) ((365.242199 / 12) * MILLIS_IN_DAY);
74      static int MONTHS_IN_YEAR = 12;
75  
76      public static final String DAILY_HOUR_PATTERN = "yyyy-MM-dd-HH";
77  
78      // Wed Mar 23 23:07:05 CET 2016
79      static final long WED_2016_03_23_T_230705_CET = 1458770825333L;
80      static final long THU_2016_03_17_T_230330_CET = 1458252210975L;
81  
82      int slashCount = 0;
83      int ticksPerPeriod = 216;
84  
85      ConfigParameters cp; // initialized in setup
86      FixedRateInvocationGate fixedRateInvocationGate = new FixedRateInvocationGate(ticksPerPeriod / 2);
87  
88      @BeforeEach
89      public void setUp() {
90          super.setUp();
91          this.cp = new ConfigParameters(currentTime);
92      }
93  
94      private int computeSlashCount(String datePattern) {
95          if (datePattern == null)
96              return 0;
97          else {
98              int count = 0;
99              for (int i = 0; i < datePattern.length(); i++) {
100                 char c = datePattern.charAt(i);
101                 if (c == '/')
102                     count++;
103             }
104             return count;
105         }
106     }
107 
108     // test that the number of files at the end of the test is same as the expected
109     // number taking into account end dates near the beginning of a new year.
110     // This test has been run in a loop with start date varying over two years with success.
111     @Test
112     public void monthlyRolloverOverManyPeriods() {
113         this.slashCount = computeSlashCount(MONTHLY_CRONOLOG_DATE_PATTERN);
114         int maxHistory = 2;
115         int simulatedNumberOfPeriods = 30;
116         String fileNamePattern = randomOutputDir + "/%d{" + MONTHLY_CRONOLOG_DATE_PATTERN + "}/clean.txt.zip";
117 
118         cp.maxHistory(maxHistory).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(simulatedNumberOfPeriods)
119                 .periodDurationInMillis(MILLIS_IN_MONTH);
120 
121         long startTime = currentTime;
122         long endTime = logOverMultiplePeriods(cp);
123         System.out.println("randomOutputDir:" + randomOutputDir);
124         System.out.println("start:" + startTime + ", end=" + endTime);
125         int differenceInMonths = RollingCalendar.diffInMonths(startTime, endTime);
126         System.out.println("differenceInMonths:" + differenceInMonths);
127         Calendar startTimeAsCalendar = Calendar.getInstance();
128         startTimeAsCalendar.setTimeInMillis(startTime);
129         int indexOfStartPeriod = startTimeAsCalendar.get(Calendar.MONTH);
130         boolean withExtraFolder = extraFolder(differenceInMonths, MONTHS_IN_YEAR, indexOfStartPeriod, maxHistory);
131 
132         checkFileCount(expectedCountWithFolders(maxHistory, withExtraFolder));
133     }
134 
135     long generateDailyRollover(ConfigParameters cp) {
136         this.slashCount = computeSlashCount(DAILY_DATE_PATTERN);
137         cp.fileNamePattern(randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt");
138         return logOverMultiplePeriods(cp);
139     }
140 
141     long generateDailyRolloverAndCheckFileCount(ConfigParameters cp) {
142         long millisAtEnd = generateDailyRollover(cp);
143         int periodBarriersCrossed = computeCrossedDayBarriers(currentTime, millisAtEnd);
144         // StatusPrinter.print(context);
145         checkFileCount(expectedCountWithoutFoldersWithInactivity(cp.maxHistory, periodBarriersCrossed,
146                 cp.startInactivity + cp.numInactivityPeriods));
147         return millisAtEnd;
148     }
149 
150     @Test
151     public void checkCrossedPeriodsWithDSTBarrier() {
152         long SAT_2016_03_26_T_230705_CET = WED_2016_03_23_T_230705_CET + 3 * CoreConstants.MILLIS_IN_ONE_DAY;
153         long MON_2016_03_28_T_000705_CET = SAT_2016_03_26_T_230705_CET + CoreConstants.MILLIS_IN_ONE_DAY;
154 
155         long result = computeCrossedDayBarriers(SAT_2016_03_26_T_230705_CET, MON_2016_03_28_T_000705_CET, "CET");
156         assertEquals(2, result);
157     }
158 
159     private int computeCrossedDayBarriers(long currentTime, long millisAtEnd) {
160         return computeCrossedDayBarriers(currentTime, millisAtEnd, null);
161     }
162 
163     private int computeCrossedDayBarriers(long currentTime, long millisAtEnd, String timeZoneID) {
164         ZoneId dateTimeZone = ZoneId.systemDefault();
165         if (timeZoneID != null) {
166             dateTimeZone = ZoneId.of(timeZoneID);
167         }
168 
169         Instant startInstant = Instant.ofEpochMilli(currentTime);
170         ZonedDateTime startZDT = startInstant.atZone(dateTimeZone);
171         // truncate to beginning of day as DAYS.between computes fully elapsed days
172         ZonedDateTime startZDT0 = startZDT.truncatedTo(ChronoUnit.DAYS);
173 
174         Instant endInstant = Instant.ofEpochMilli(millisAtEnd);
175         ZonedDateTime endZDT = endInstant.atZone(dateTimeZone);
176         // truncate to beginning of day as DAYS.between computes fully elapsed days
177         ZonedDateTime endZDT0 = endZDT.truncatedTo(ChronoUnit.DAYS);
178 
179         // computes fully elapsed days
180         long dayCount = ChronoUnit.DAYS.between(startZDT0, endZDT0);
181         return (int) dayCount;
182     }
183 
184     @Test
185     public void checkCleanupForBasicDailyRollover() {
186         cp.maxHistory(20).simulatedNumberOfPeriods(20 * 3).startInactivity(0).numInactivityPeriods(0);
187         generateDailyRolloverAndCheckFileCount(cp);
188     }
189 
190     @Test
191     public void checkCleanupForBasicDailyRolloverWithSizeCap() {
192         long bytesOutputPerPeriod = 15984;
193         int sizeInUnitsOfBytesPerPeriod = 2;
194 
195         cp.maxHistory(5).simulatedNumberOfPeriods(10)
196                 .sizeCap(sizeInUnitsOfBytesPerPeriod * bytesOutputPerPeriod + 1000);
197         generateDailyRollover(cp);
198         checkFileCount(sizeInUnitsOfBytesPerPeriod + 1);
199     }
200 
201     @Test
202     public void checkThatSmallTotalSizeCapLeavesAtLeastOneArhcive() {
203         long WED_2016_03_23_T_131345_CET = WED_2016_03_23_T_230705_CET - 10 * CoreConstants.MILLIS_IN_ONE_HOUR;
204 
205         // long bytesOutputPerPeriod = 15984;
206 
207         cp = new ConfigParameters(WED_2016_03_23_T_131345_CET);
208         final int verySmallCapSize = 1;
209         cp.maxHistory(5).simulatedNumberOfPeriods(3).sizeCap(verySmallCapSize);
210         generateDailyRollover(cp);
211         // StatusPrinter.print(context);
212         checkFileCountAtMost(1);
213 
214     }
215 
216     @Test
217     public void checkCleanupForBasicDailyRolloverWithMaxSize() {
218         cp.maxHistory(6).simulatedNumberOfPeriods(30).startInactivity(10).numInactivityPeriods(1);
219         generateDailyRolloverAndCheckFileCount(cp);
220     }
221 
222     // Since the duration of a month (in seconds) varies from month to month, tests
223     // with inactivity period must
224     // be conducted with daily rollover not monthly
225     @Test
226     public void checkCleanupForDailyRollover_15Periods() {
227         cp.maxHistory(5).simulatedNumberOfPeriods(15).startInactivity(6).numInactivityPeriods(3);
228         generateDailyRolloverAndCheckFileCount(cp);
229     }
230 
231     @Test
232     public void checkCleanupForDailyRolloverWithInactivity_30Periods() {
233         // / -------
234         cp.maxHistory(2).simulatedNumberOfPeriods(30).startInactivity(3).numInactivityPeriods(1);
235         generateDailyRolloverAndCheckFileCount(cp);
236     }
237 
238     @Test
239     public void checkCleanupForDailyRolloverWithInactivity_10Periods() {
240         this.currentTime = THU_2016_03_17_T_230330_CET;
241         cp.maxHistory(6).simulatedNumberOfPeriods(10).startInactivity(2).numInactivityPeriods(2);
242         generateDailyRolloverAndCheckFileCount(cp);
243     }
244 
245     @Test
246     public void checkCleanupForDailyRolloverWithSecondPhase() {
247         slashCount = computeSlashCount(DAILY_DATE_PATTERN);
248         int maxHistory = 5;
249         String fileNamePattern = randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt";
250 
251         ConfigParameters cp0 = new ConfigParameters(currentTime).maxHistory(maxHistory).fileNamePattern(fileNamePattern)
252                 .simulatedNumberOfPeriods(maxHistory * 2);
253         long endTime = logOverMultiplePeriods(cp0);
254 
255         ConfigParameters cp1 = new ConfigParameters(endTime + MILLIS_IN_DAY * 10).maxHistory(maxHistory)
256                 .fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(maxHistory);
257         logOverMultiplePeriods(cp1);
258         checkFileCount(expectedCountWithoutFolders(maxHistory));
259     }
260 
261     @Test
262     public void dailyRolloverWithCronologPattern() {
263         this.slashCount = computeSlashCount(DAILY_CRONOLOG_DATE_PATTERN);
264         String fileNamePattern = randomOutputDir + "/%d{" + DAILY_CRONOLOG_DATE_PATTERN + "}/clean.txt.zip";
265         cp.maxHistory(8).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(8 * 3);
266         logOverMultiplePeriods(cp);
267         int expectedDirMin = 9 + slashCount;
268         int expectDirMax = expectedDirMin + 1 + 1;
269         expectedFileAndDirCount(9, expectedDirMin, expectDirMax);
270     }
271 
272     @Test
273     public void dailySizeBasedRolloverWithoutCap() {
274         SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object>();
275         //sizeAndTimeBasedFNATP.invocationGate = fixedRateInvocationGate;
276 
277         sizeAndTimeBasedFNATP.setMaxFileSize(new FileSize(10000));
278         tbfnatp = sizeAndTimeBasedFNATP;
279         this.slashCount = computeSlashCount(DAILY_DATE_PATTERN);
280         String fileNamePattern = randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}-clean.%i.zip";
281         cp.maxHistory(5).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(5 * 4);
282         logOverMultiplePeriods(cp);
283         checkPatternCompliance(5 + 1 + slashCount, "\\d{4}-\\d{2}-\\d{2}-clean(\\.\\d)(.zip)?");
284     }
285 
286     @Test
287     public void dailySizeBasedRolloverWithSizeCap() {
288         SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object>();
289 
290         long fileSize = 3400;
291         int expectedFileCount = 10;
292         long sizeCap = expectedFileCount * fileSize;
293         sizeAndTimeBasedFNATP.setMaxFileSize(new FileSize(fileSize));
294         tbfnatp = sizeAndTimeBasedFNATP;
295         this.slashCount = computeSlashCount(DAILY_DATE_PATTERN);
296 
297         // 2016-03-05 00:14:39 CET
298         //long simulatedTime = 1457133279186L;
299         long simulatedTime = getSimulatedTimeFromString("2016-03-05T00:14:39,186");
300 
301         ConfigParameters params = new ConfigParameters(simulatedTime);
302         String fileNamePattern = randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}-clean.%i";
303         params.maxHistory(60).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(10).sizeCap(sizeCap);
304         logOverMultiplePeriods(params);
305 
306         List<File> foundFiles = findFilesByPattern("\\d{4}-\\d{2}-\\d{2}-clean(\\.\\d)");
307         Collections.sort(foundFiles, new Comparator<File>() {
308             public int compare(File f0, File f1) {
309                 String s0 = f0.getName().toString();
310                 String s1 = f1.getName().toString();
311                 return s0.compareTo(s1);
312             }
313         });
314 
315         StatusPrinter.print(context);
316         foundFiles.forEach(f -> System.out.println(""+f+ " "+f.length()));
317         LongAdder la = new LongAdder();
318         foundFiles.forEach(f -> la.add(f.length()));
319         System.out.println("Sum: "+la.sum());
320 
321         // adding fileSize to sizeCap may make sense
322         assertTrue(la.sum() < sizeCap);
323 
324         checkFileCount(expectedFileCount + 1);
325     }
326 
327     /**
328      * The returned millis is based on local time
329      * @param dateStr
330      * @return
331      */
332     private long getSimulatedTimeFromString(String dateStr) {
333         LocalDateTime localDateTime = LocalDateTime.parse(dateStr, STRICT_DATE_PARSER);
334         long simulatedTime = localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
335         return simulatedTime;
336     }
337 
338     @Test
339     public void dailyChronologSizeBasedRollover() {
340         SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object> sizeAndTimeBasedFileNamingAndTriggeringPolicy = new SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object>();
341         sizeAndTimeBasedFileNamingAndTriggeringPolicy.setMaxFileSize(new FileSize(10000));
342         //sizeAndTimeBasedFileNamingAndTriggeringPolicy.invocationGate = fixedRateInvocationGate;
343         tbfnatp = sizeAndTimeBasedFileNamingAndTriggeringPolicy;
344         slashCount = 1;
345         String fileNamePattern = randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}/clean.%i.zip";
346         cp.maxHistory(5).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(5 * 3);
347         logOverMultiplePeriods(cp);
348         checkDirPatternCompliance(6);
349     }
350 
351     @Test
352     public void dailyChronologSizeBasedRolloverWithSecondPhase() {
353         SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object> sizeAndTimeBasedFileNamingAndTriggeringPolicy = new SizeAndTimeBasedFileNamingAndTriggeringPolicy<Object>();
354         sizeAndTimeBasedFileNamingAndTriggeringPolicy.setMaxFileSize(new FileSize(10000));
355         //sizeAndTimeBasedFileNamingAndTriggeringPolicy.invocationGate = fixedRateInvocationGate;
356         tbfnatp = sizeAndTimeBasedFileNamingAndTriggeringPolicy;
357         this.slashCount = 1;
358         String fileNamePattern = randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}/clean.%i";
359         int maxHistory = 5;
360         cp.maxHistory(maxHistory).fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(3);
361         long endTime = logOverMultiplePeriods(cp);
362 
363         int simulatedNumberOfPeriods = maxHistory * 4;
364         ConfigParameters cp1 = new ConfigParameters(endTime + MILLIS_IN_DAY * 7).maxHistory(maxHistory)
365                 .fileNamePattern(fileNamePattern).simulatedNumberOfPeriods(simulatedNumberOfPeriods);
366         logOverMultiplePeriods(cp1);
367         checkDirPatternCompliance(maxHistory + 1);
368     }
369 
370     void logTwiceAndStop(long currentTime, String fileNamePattern, int maxHistory, long durationInMillis) {
371         ConfigParameters params = new ConfigParameters(currentTime).fileNamePattern(fileNamePattern)
372                 .maxHistory(maxHistory);
373         buildRollingFileAppender(params, DO_CLEAN_HISTORY_ON_START);
374         rfa.doAppend("Hello ----------------------------------------------------------" + new Date(currentTime));
375         currentTime += durationInMillis / 2;
376         add(tbrp.compressionFuture);
377         add(tbrp.cleanUpFuture);
378         waitForJobsToComplete();
379         tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);
380         rfa.doAppend("Hello ----------------------------------------------------------" + new Date(currentTime));
381         rfa.stop();
382     }
383 
384     // LOGBACK-1562
385     @Test
386     public void cleanHistoryOnStartWithHourPattern() {
387         long simulatedTime = WED_2016_03_23_T_230705_CET;
388         String fileNamePattern = randomOutputDir + "clean-%d{" + DAILY_HOUR_PATTERN + "}.txt";
389         int maxHistory = 3;
390         for (int i = 0; i <= 5; i++) {
391             logTwiceAndStop(simulatedTime, fileNamePattern, maxHistory, MILLIS_IN_HOUR);
392             simulatedTime += MILLIS_IN_HOUR;
393         }
394         checkFileCount(expectedCountWithoutFolders(maxHistory));
395     }
396 
397     @Disabled
398     @Test
399     // this test assumes a high degree of collisions in the archived files. Every 24
400     // hours, the archive
401     // belonging to the previous day will be overwritten. Given that logback goes 14
402     // days (336 hours) in history
403     // to clean files on start up, it is bound to delete more recent files. It is
404     // not logback's responsibility
405     // to cater for such degenerate cases.
406     public void cleanHistoryOnStartWithHourPatternWithCollisions() {
407         long now = this.currentTime;
408         String fileNamePattern = randomOutputDir + "clean-%d{HH}.txt";
409         int maxHistory = 3;
410         for (int i = 0; i <= 5; i++) {
411             logTwiceAndStop(now, fileNamePattern, maxHistory, MILLIS_IN_DAY);
412             now = now + MILLIS_IN_HOUR;
413         }
414         checkFileCount(expectedCountWithoutFolders(maxHistory));
415     }
416 
417     @Test
418     public void cleanHistoryOnStartWithDayPattern() {
419         long simulatedTime = WED_2016_03_23_T_230705_CET;
420         String fileNamePattern = randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt";
421         int maxHistory = 3;
422         for (int i = 0; i <= 5; i++) {
423             logTwiceAndStop(simulatedTime, fileNamePattern, maxHistory, MILLIS_IN_DAY);
424             simulatedTime += MILLIS_IN_DAY;
425         }
426         checkFileCount(expectedCountWithoutFolders(maxHistory));
427     }
428 
429     @Test
430     public void cleanHistoryOnStartWithHourDayPattern() {
431         long simulatedTime = WED_2016_03_23_T_230705_CET;
432         String fileNamePattern = randomOutputDir + "clean-%d{yyyy-MM-dd-HH}.txt";
433         int maxHistory = 3;
434         for (int i = 0; i <= 5; i++) {
435             logTwiceAndStop(simulatedTime, fileNamePattern, maxHistory, MILLIS_IN_HOUR);
436             simulatedTime += MILLIS_IN_HOUR;
437         }
438         checkFileCount(expectedCountWithoutFolders(maxHistory));
439     }
440 
441     int expectedCountWithoutFolders(int maxHistory) {
442         return maxHistory + 1;
443     }
444 
445     int expectedCountWithFolders(int maxHistory, boolean withExtraFolder) {
446         int numLogFiles = (maxHistory + 1);
447         int numLogFilesAndFolders = numLogFiles * 2;
448         int result = numLogFilesAndFolders + slashCount;
449         if (withExtraFolder)
450             result += 1;
451         return result;
452     }
453 
454     void buildRollingFileAppender(ConfigParameters cp, boolean cleanHistoryOnStart) {
455         rfa.setContext(context);
456         rfa.setEncoder(encoder);
457         tbrp.setContext(context);
458         tbrp.setFileNamePattern(cp.fileNamePattern);
459         tbrp.setMaxHistory(cp.maxHistory);
460         tbrp.setTotalSizeCap(new FileSize(cp.sizeCap));
461         tbrp.setParent(rfa);
462         tbrp.setCleanHistoryOnStart(cleanHistoryOnStart);
463         tbrp.timeBasedFileNamingAndTriggeringPolicy = tbfnatp;
464         tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(cp.simulatedTime);
465         tbrp.start();
466         rfa.setRollingPolicy(tbrp);
467         rfa.start();
468     }
469 
470     boolean DO_CLEAN_HISTORY_ON_START = true;
471     boolean DO_NOT_CLEAN_HISTORY_ON_START = false;
472 
473     long logOverMultiplePeriods(ConfigParameters cp) {
474 
475         buildRollingFileAppender(cp, DO_NOT_CLEAN_HISTORY_ON_START);
476 
477         int runLength = cp.simulatedNumberOfPeriods * ticksPerPeriod;
478         int startInactivityIndex = cp.startInactivity * ticksPerPeriod;
479         int endInactivityIndex = startInactivityIndex + cp.numInactivityPeriods * ticksPerPeriod;
480         long tickDuration = cp.periodDurationInMillis / ticksPerPeriod;
481 
482         for (int i = 0; i <= runLength; i++) {
483             Date currentDate = new Date(tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
484             if (i < startInactivityIndex || i > endInactivityIndex) {
485                 rfa.doAppend(buildMessageString(currentDate, i));
486             }
487 
488             tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(
489                     addTime(tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime(), tickDuration));
490 
491             add(tbrp.compressionFuture);
492             add(tbrp.cleanUpFuture);
493             waitForJobsToComplete();
494         }
495 
496         try {
497             Thread.sleep(100);
498         } catch (InterruptedException e) {
499             // TODO Auto-generated catch block
500             e.printStackTrace();
501         }
502         rfa.stop();
503 
504         // System.out.println("Current time at end of loop: "+new
505         // Date(tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime()));
506         return tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime();
507     }
508 
509     private static String buildMessageString(Date currentDate, int i) {
510         StringBuilder sb = new StringBuilder("Hello");
511         String currentDateStr = currentDate.toString();
512         String iAsString = Integer.toString(i);
513         sb.append(currentDateStr);
514         SpacePadder.spacePad(sb, 68 + (3 - iAsString.length() - currentDateStr.length() - CoreConstants.LINE_SEPARATOR_LEN));
515         sb.append(iAsString);
516         return sb.toString();
517     }
518 
519     void fillWithChar(StringBuffer sb, char c, int count) {
520         for (int i = 0; i < count; i++) {
521             sb.append(c);
522         }
523     }
524 
525     boolean extraFolder(int numPeriods, int periodsPerEra, int beginPeriod, int maxHistory) {
526         int valueOfLastMonth = ((beginPeriod) + numPeriods) % periodsPerEra;
527         return (valueOfLastMonth < maxHistory);
528     }
529 
530     long addTime(long time, long timeToWait) {
531         return time + timeToWait;
532     }
533 
534     void expectedFileAndDirCount(int expectedFileAndDirCount, int expectedDirCountMin, int expectedDirCountMax) {
535         File dir = new File(randomOutputDir);
536         List<File> fileList = new ArrayList<File>();
537         findFilesInFolderRecursivelyByPatterMatch(dir, fileList, "clean");
538         List<File> dirList = new ArrayList<File>();
539         findAllFoldersInFolderRecursively(dir, dirList);
540         String msg = "expectedDirCountMin=" + expectedDirCountMin + ", expectedDirCountMax=" + expectedDirCountMax
541                 + " actual value=" + dirList.size();
542         assertTrue(expectedDirCountMin <= dirList.size() && dirList.size() <= expectedDirCountMax, msg);
543     }
544 
545     void checkFileCount(int expectedCount) {
546         File dir = new File(randomOutputDir);
547         List<File> fileList = new ArrayList<File>();
548         findAllDirsOrStringContainsFilesRecursively(dir, fileList, "clean");
549         assertEquals(expectedCount, fileList.size());
550     }
551 
552     void checkFileCountAtMost(int expectedCount) {
553         File dir = new File(randomOutputDir);
554         List<File> fileList = new ArrayList<File>();
555         findAllDirsOrStringContainsFilesRecursively(dir, fileList, "clean");
556         int fileListSize = fileList.size();
557 
558         assertTrue(fileListSize <= expectedCount, "file list size " + fileListSize + ", expectedCount=" + expectedCount);
559     }
560 
561     int expectedCountWithoutFoldersWithInactivity(int maxHistory, int totalPeriods, int endOfInactivity) {
562         int availableHistory = (totalPeriods + 1) - endOfInactivity;
563         int actualHistory = Math.min(availableHistory, maxHistory + 1);
564         return actualHistory;
565     }
566 
567     void genericFindMatching(final FileMatchFunction matchFunc, File dir, List<File> fileList, final String pattern,
568                              boolean includeDirs) {
569         if (dir.isDirectory()) {
570             File[] matchArray = dir.listFiles(new FileFilter() {
571                 public boolean accept(File f) {
572                     return f.isDirectory() || matchFunc.match(f, pattern);
573                 }
574             });
575             for (File f : matchArray) {
576                 if (f.isDirectory()) {
577                     if (includeDirs)
578                         fileList.add(f);
579                     genericFindMatching(matchFunc, f, fileList, pattern, includeDirs);
580                 } else
581                     fileList.add(f);
582             }
583         }
584     }
585 
586     private void findAllFoldersInFolderRecursively(File dir, List<File> fileList) {
587         FileMatchFunction alwaysFalse = new FileMatchFunction() {
588             public boolean match(File f, String pattern) {
589                 return false;
590             }
591         };
592         genericFindMatching(alwaysFalse, dir, fileList, null, true);
593     }
594 
595     private void findAllDirsOrStringContainsFilesRecursively(File dir, List<File> fileList, String pattern) {
596         FileMatchFunction matchFunction = new FileMatchFunction() {
597             public boolean match(File f, String pattern) {
598                 return f.getName().contains(pattern);
599             }
600         };
601         genericFindMatching(matchFunction, dir, fileList, pattern, true);
602     }
603 
604     void findFilesInFolderRecursivelyByPatterMatch(File dir, List<File> fileList, String pattern) {
605         FileMatchFunction matchByPattern = new FileMatchFunction() {
606             public boolean match(File f, String pattern) {
607                 return f.getName().matches(pattern);
608             }
609         };
610         genericFindMatching(matchByPattern, dir, fileList, pattern, false);
611     }
612 
613     Set<String> groupByClass(List<File> fileList, String regex) {
614         Pattern p = Pattern.compile(regex);
615         Set<String> set = new HashSet<String>();
616         for (File f : fileList) {
617             String n = f.getName();
618             Matcher m = p.matcher(n);
619             m.matches();
620             int begin = m.start(1);
621             String reduced = n.substring(0, begin);
622             set.add(reduced);
623         }
624         return set;
625     }
626 
627     void checkPatternCompliance(int expectedClassCount, String regex) {
628         Set<String> set = findFilesByPatternClass(regex);
629         assertEquals(expectedClassCount, set.size());
630     }
631 
632     private List<File> findFilesByPattern(String regex) {
633         File dir = new File(randomOutputDir);
634         List<File> fileList = new ArrayList<File>();
635         findFilesInFolderRecursivelyByPatterMatch(dir, fileList, regex);
636         return fileList;
637     }
638 
639     private Set<String> findFilesByPatternClass(String regex) {
640         List<File> fileList = findFilesByPattern(regex);
641         Set<String> set = groupByClass(fileList, regex);
642         return set;
643     }
644 
645     void checkDirPatternCompliance(int expectedClassCount) {
646         File dir = new File(randomOutputDir);
647         List<File> fileList = new ArrayList<File>();
648         findAllFoldersInFolderRecursively(dir, fileList);
649         for (File f : fileList) {
650             assertTrue(f.list().length >= 1);
651         }
652         assertEquals(expectedClassCount, fileList.size());
653     }
654 }