TimeBasedRollingTest.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.rolling;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.function.UnaryOperator;

import ch.qos.logback.core.rolling.testUtil.ParentScaffoldingForRollingTests;
import ch.qos.logback.core.rolling.testUtil.ScaffoldingForRollingTests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import ch.qos.logback.core.encoder.EchoEncoder;
import ch.qos.logback.core.testUtil.EnvUtilForTests;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * A rather exhaustive set of tests. Tests include leaving the file option
 * blank, or setting it, with and without compression, and tests with or without
 * stopping/restarting the RollingFileAppender.
 * <p>
 * The regression tests log a few times using a RollingFileAppender. Then, they
 * predict the names of the files which should be generated and compare them
 * with witness files.
 * <p>
 * 
 * <pre>
 *                Compression     file option    Stop/Restart
 *     Test1      NO              BLANK           NO
 *     Test2      YES             BLANK           NO
 *     Test3      NO              BLANK           YES
 *     Test4      NO              SET             YES
 *     Test5      NO              SET             NO
 *     Test6      YES             SET             NO
 * </pre>
 *
 * @author Ceki G&uuml;lc&uuml;
 */
public class TimeBasedRollingTest extends ScaffoldingForRollingTests {

    static public final String DATE_PATTERN_WITH_SECONDS = "yyyy-MM-dd_HH_mm_ss";
    static final int NO_RESTART = 0;
    static final int WITH_RESTART = 1;
    static final int WITH_RESTART_AND_LONG_WAIT = 2000;

    static final boolean FILE_OPTION_SET = true;
    static final boolean FILE_OPTION_BLANK = false;

    RollingFileAppender<Object> rfa1 = new RollingFileAppender<Object>();
    TimeBasedRollingPolicy<Object> tbrp1 = new TimeBasedRollingPolicy<Object>();

    RollingFileAppender<Object> rfa2 = new RollingFileAppender<Object>();
    TimeBasedRollingPolicy<Object> tbrp2 = new TimeBasedRollingPolicy<Object>();

    EchoEncoder<Object> encoder = new EchoEncoder<Object>();

    RolloverChecker rolloverChecker;

    @BeforeEach
    @Override
    public void setUp() {
        super.setUp();
    }

    @AfterEach
    public void tearDown() {
    }

    void initRFA(RollingFileAppender<Object> rfa, String filename) {
        rfa.setContext(context);
        rfa.setEncoder(encoder);
        if (filename != null) {
            rfa.setFile(filename);
        }
    }

    void initTRBP(RollingFileAppender<Object> rfa, TimeBasedRollingPolicy<Object> tbrp, String filenamePattern,
            long givenTime) {
        tbrp.setContext(context);
        tbrp.setFileNamePattern(filenamePattern);
        tbrp.setParent(rfa);
        tbrp.timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<Object>();
        tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(givenTime);
        rfa.setRollingPolicy(tbrp);
        tbrp.start();
        rfa.start();
    }

    void genericTest(String testId, String patternPrefix, String compressionSuffix,
            UnaryOperator<String> filenameFunction, int waitDuration) throws IOException {

        String fileName = filenameFunction.apply(testId);
        // String fileName = fileOptionIsSet ? testId2FileName(testId) : null;

        initRFA(rfa1, fileName);

        String fileNamePatternStr = randomOutputDir + patternPrefix + "-%d{" + DATE_PATTERN_WITH_SECONDS + "}"
                + compressionSuffix;

        initTRBP(rfa1, tbrp1, fileNamePatternStr, currentTime);

        // compute the current filename
        addExpectedFileName_ByDate(fileNamePatternStr, getMillisOfCurrentPeriodsStart());

        incCurrentTime(1100);
        tbrp1.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);

        for (int i = 0; i < 3; i++) {
            rfa1.doAppend("Hello---" + i);
            addExpectedFileNamedIfItsTime_ByDate(fileNamePatternStr);
            incCurrentTime(500);
            tbrp1.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);
            add(tbrp1.compressionFuture);
            add(tbrp1.cleanUpFuture);
        }
        rfa1.stop();
        waitForJobsToComplete();

        if (waitDuration != NO_RESTART) {
            doRestart(testId, patternPrefix, filenameFunction, waitDuration);
        }
        waitForJobsToComplete();

        massageExpectedFilesToCorresponToCurrentTarget(testId, filenameFunction);
        // StatusPrinter.print(context);
        rolloverChecker.check(expectedFilenameList);
    }

    void defaultTest(String testId, String patternPrefix, String compressionSuffix,
            UnaryOperator<String> filenameFunction, int waitDuration) throws IOException {
        boolean withCompression = compressionSuffix.length() > 0;
        rolloverChecker = new DefaultRolloverChecker(testId, withCompression, compressionSuffix);
        genericTest(testId, patternPrefix, compressionSuffix, filenameFunction, waitDuration);
    }

    void doRestart(String testId, String patternPart, UnaryOperator<String> filenameFunction, int waitDuration) {
        // change the timestamp of the currently actively file
        File activeFile = new File(rfa1.getFile());
        activeFile.setLastModified(currentTime);

        incCurrentTime(waitDuration);

        String filePatternStr = randomOutputDir + patternPart + "-%d{" + DATE_PATTERN_WITH_SECONDS + "}";

        String fileName = filenameFunction.apply(testId);
        initRFA(rfa2, fileName);
        initTRBP(rfa2, tbrp2, filePatternStr, currentTime);
        for (int i = 0; i < 3; i++) {
            rfa2.doAppend("World---" + i);
            addExpectedFileNamedIfItsTime_ByDate(filePatternStr);
            incCurrentTime(100);
            tbrp2.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);
            add(tbrp2.compressionFuture);
            add(tbrp1.cleanUpFuture);
        }
        rfa2.stop();
    }

    @Test
    public void noCompression_FileBlank_NoRestart_1() throws IOException {
        defaultTest("test1", "test1", "", this::nullFileName, NO_RESTART);
    }

    @Test
    public void withCompression_FileBlank_NoRestart_2() throws IOException {
        defaultTest("test2", "test2", ".gz", this::nullFileName, NO_RESTART);
    }

    @Test
    public void noCompression_FileBlank_StopRestart_3() throws IOException {
        defaultTest("test3", "test3", "", this::nullFileName, WITH_RESTART);
    }

    @Test
    public void noCompression_FileSet_StopRestart_4() throws IOException {
        defaultTest("test4", "test4", "", this::testId2FileName, WITH_RESTART);
    }

    @Test
    public void noCompression_FileSet_StopRestart_WithLongWait_4B() throws IOException {
        defaultTest("test4B", "test4B", "", this::testId2FileName, WITH_RESTART_AND_LONG_WAIT);
    }

    @Test
    public void noCompression_FileSet_NoRestart_5() throws IOException {
        defaultTest("test5", "test5", "", this::testId2FileName, NO_RESTART);
    }

    @Test
    public void withCompression_FileSet_NoRestart_6() throws IOException {
        defaultTest("test6", "test6", ".gz", this::testId2FileName, NO_RESTART);
    }

    // LOGBACK-168
    @Test
    public void withMissingTargetDirWithCompression() throws IOException {
        defaultTest("test7", "%d{yyyy-MM-dd, aux}/test7", ".gz", this::testId2FileName, NO_RESTART);
    }

    @Test
    public void withMissingTargetDirWithZipCompression() throws IOException {
        defaultTest("test8", "%d{yyyy-MM-dd, aux}/test8", ".zip", this::testId2FileName, NO_RESTART);
    }

    @Test
    public void failed_rename() throws IOException {
        if (!EnvUtilForTests.isWindows())
            return;

        String testId = "failed_rename";
        FileOutputStream fos = null;
        try {
            String fileName = testId2FileName(testId);
            File file = new File(fileName);
            file.getParentFile().mkdirs();

            fos = new FileOutputStream(fileName);

            rolloverChecker = new ZRolloverChecker(testId);
            genericTest(testId, "failed_rename", "", this::testId2FileName, NO_RESTART);
            rolloverChecker.check(expectedFilenameList);

        } finally {
            StatusPrinter.print(context);
            if (fos != null)
                fos.close();
        }
    }

//    @Test
//    public void failed_rename2() throws IOException {
//
//        String testId = "failed_rename";
//        try {
//            String fileName = testId2FileName(testId);
//
//            
//            rolloverChecker = new ZRolloverChecker(testId);
//            genericTest(testId, "test10", ".gz", this::testId2FileName, NO_RESTART);
//            rolloverChecker.check(expectedFilenameList);
//
//        } finally {
//            StatusPrinter.print(context);
//
//        }
//    }

}