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