ConsoleWatcher.java

package net.morimekta.testing.junit4;

import net.morimekta.io.tty.TTY;
import net.morimekta.io.tty.TTYSize;
import net.morimekta.testing.console.Console;
import net.morimekta.testing.console.ConsoleManager;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;


/**
 * Printed output watcher rule.
 *
 * <pre>{@code
 * public class Test {
 *     {@literal@}Rule
 *     public ConsoleWatcher watcher = new ConsoleWatcher();
 *
 *     {@literal@}Test
 *     public void testOutput() {
 *         System.err.println("woot!");
 *
 *         assertThat(console.console().error(), is("woot!\n"));
 *     }
 * }
 * }</pre>
 * <p>
 * Note that this watcher hijacks the default {@link System#in},
 * {@link System#out} and {@link System#err} streams. This may cause problems
 * when other systems prints out to those streams and expects the output to
 * be present for others. In some cases maven may balk at this, but it seems
 * to pass silently (and successfully) in other cases.
 */
public class ConsoleWatcher
        extends TestWatcher {
    private final ConsoleManager manager = new ConsoleManager();

    private boolean started = false;

    private TTYSize defaultTerminalSize        = Console.DEFAULT_TERMINAL_SIZE;
    private boolean defaultInteractive         = false;
    private boolean defaultDumpOutputOnFailure = false;
    private boolean defaultDumpErrorOnFailure  = false;

    /**
     * @return The testing TTY
     */
    public TTY tty() {
        return manager.getTTY();
    }

    /**
     * @return The testing console.
     */
    public Console console() {
        return manager.getConsole();
    }

    /**
     * Set the current terminal size.
     *
     * @param rows Row count.
     * @param cols Column count.
     * @return The console watcher.
     */
    public ConsoleWatcher withTerminalSize(int rows, int cols) {
        var terminalSize = new TTYSize(rows, cols);
        if (started) {
            manager.setTerminalSize(terminalSize);
        } else {
            defaultTerminalSize = terminalSize;
        }
        return this;
    }

    /**
     * Set input mode to non-interactive. This makes the terminal no longer
     * behave like an interactive terminal (the default for ConsoleWatcher),
     * but as a wrapped shell script.
     *
     * @return The console watcher.
     */
    public ConsoleWatcher nonInteractive() {
        if (started) {
            manager.setInteractive(false);
        } else {
            defaultInteractive = false;
        }
        return this;
    }

    /**
     * Set input mode to interactive. This makes the terminal behave like an
     * interactive terminal (the default for ConsoleWatcher).
     *
     * @return The console watcher.
     */
    public ConsoleWatcher interactive() {
        if (started) {
            manager.setInteractive(true);
        } else {
            defaultInteractive = true;
        }
        return this;
    }

    /**
     * Dump stdout to error output on failure.
     *
     * @return The console watcher.
     */
    public ConsoleWatcher dumpOutputOnFailure() {
        if (started) {
            manager.setDumpOutputOnFailure(true);
            manager.setForkOutput(false);
        } else {
            defaultDumpOutputOnFailure = true;
        }
        return this;
    }

    /**
     * Dump stderr to error output on failure.
     *
     * @return The console watcher.
     */
    public ConsoleWatcher dumpErrorOnFailure() {
        if (started) {
            manager.setDumpErrorOnFailure(true);
            manager.setForkError(false);
        } else {
            defaultDumpErrorOnFailure = true;
        }
        return this;
    }

    /**
     * Dump both stdout and stderr to error output on failure.
     *
     * @return The console watcher.
     */
    public ConsoleWatcher dumpOnFailure() {
        dumpOutputOnFailure();
        dumpErrorOnFailure();
        return this;
    }

    @Override
    protected void starting(Description description) {
        started = true;
        manager.setTerminalSize(defaultTerminalSize);
        manager.setInteractive(defaultInteractive);
        manager.setDumpErrorOnFailure(defaultDumpErrorOnFailure);
        manager.setForkError(!defaultDumpErrorOnFailure);
        manager.setDumpOutputOnFailure(defaultDumpOutputOnFailure);
        manager.setForkOutput(!defaultDumpOutputOnFailure);
        manager.doBeforeEach();
    }

    @Override
    protected void failed(Throwable e, Description description) {
        manager.onTestFailed(description.getMethodName());
    }

    @Override
    protected void finished(Description description) {
        manager.doAfterEach();
    }
}