FileAppenderResilience_AS_ROOT_Test.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;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import ch.qos.logback.core.testUtil.EnvUtilForTests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;

import ch.qos.logback.core.encoder.EchoEncoder;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.util.ResilienceUtil;
import ch.qos.logback.core.util.StatusPrinter;
import org.junit.jupiter.api.Test;

@Disabled
public class FileAppenderResilience_AS_ROOT_Test {

    static String MOUNT_POINT = "/mnt/loop/";

    static String LONG_STR = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    static String PATH_LOOPFS_SCRIPT = "/home/ceki/java/logback/logback-core/src/test/loopfs.sh";

    enum LoopFSCommand {
        setup, shake, teardown;
    }

    Context context = new ContextBase();
    int diff = RandomUtil.getPositiveInt();
    String outputDirStr = MOUNT_POINT + "resilience-" + diff + "/";
    String logfileStr = outputDirStr + "output.log";

    FileAppender<Object> fa = new FileAppender<Object>();

    static boolean isConformingHost() {
        return EnvUtilForTests.isLocalHostNameInList(new String[] { "haro" });
    }

    @BeforeEach
    public void setUp() throws IOException, InterruptedException {
        if (!isConformingHost()) {
            return;
        }
        Process p = runLoopFSScript(LoopFSCommand.setup);
        p.waitFor();

        dump("/tmp/loopfs.log");

        fa.setContext(context);
        File outputDir = new File(outputDirStr);
        outputDir.mkdirs();
        System.out.println("FileAppenderResilienceTest output dir [" + outputDirStr + "]");

        fa.setName("FILE");
        fa.setEncoder(new EchoEncoder<Object>());
        fa.setFile(logfileStr);
        fa.start();
    }

    void dump(String file) throws IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            int r;
            while ((r = fis.read()) != -1) {
                char c = (char) r;
                System.out.print(c);
            }
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    @AfterEach
    public void tearDown() throws IOException, InterruptedException {
        if (!isConformingHost()) {
            return;
        }
        StatusPrinter.print(context);
        fa.stop();
        Process p = runLoopFSScript(LoopFSCommand.teardown);
        p.waitFor();
        System.out.println("Tearing down");
    }

    static int TOTAL_DURATION = 5000;
    static int NUM_STEPS = 500;
    static int DELAY = TOTAL_DURATION / NUM_STEPS;

    @Test
    public void go() throws IOException, InterruptedException {
        if (!isConformingHost()) {
            return;
        }
        Process p = runLoopFSScript(LoopFSCommand.shake);
        for (int i = 0; i < NUM_STEPS; i++) {
            fa.append(String.valueOf(i) + LONG_STR);
            Thread.sleep(DELAY);
        }
        p.waitFor();
        // the external script has the file system ready for IO 50% of the time
        double bestCase = 0.5;
        ResilienceUtil.countLines(logfileStr, "^(\\d{1,3}) x*$");
        System.out.println("Done go");
    }

    // the loopfs script is tightly coupled with the host machine
    // it needs to be Unix, with sudo privileges granted to the script
    Process runLoopFSScript(LoopFSCommand cmd) throws IOException, InterruptedException {
        // causing a NullPointerException is better than locking the whole
        // machine which the next operation can and will do.
        if (!isConformingHost()) {
            return null;
        }
        ProcessBuilder pb = new ProcessBuilder();
        pb.command("/usr/bin/sudo", PATH_LOOPFS_SCRIPT, cmd.toString());
        return pb.start();
    }
}