fi.jumi.core.stdout.OutputCapturerTest.java Source code

Java tutorial

Introduction

Here is the source code for fi.jumi.core.stdout.OutputCapturerTest.java

Source

// Copyright  2011-2013, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package fi.jumi.core.stdout;

import com.google.common.base.Throwables;
import fi.jumi.core.util.ConcurrencyUtil;
import org.apache.commons.io.output.WriterOutputStream;
import org.junit.*;
import org.junit.rules.Timeout;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.CountDownLatch;

import static fi.jumi.core.util.ConcurrencyUtil.*;
import static org.fest.assertions.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

public class OutputCapturerTest {

    private static final int TIMEOUT = 1000;

    @Rule
    public final Timeout timeout = new Timeout(TIMEOUT);

    private final StringWriter printedToOut = new StringWriter();
    private final StringWriter printedToErr = new StringWriter();
    private final OutputStream realOut = new WriterOutputStream(printedToOut);
    private final OutputStream realErr = new WriterOutputStream(printedToErr);

    private final OutputCapturer capturer = new OutputCapturer(realOut, realErr, Charset.defaultCharset());

    // basic capturing

    @Test
    public void passes_through_stdout_to_the_real_stdout() {
        capturer.out().print("foo");

        assertThat("stdout", printedToOut.toString(), is("foo"));
        assertThat("stderr", printedToErr.toString(), is(""));
    }

    @Test
    public void passes_through_stderr_to_the_real_stderr() {
        capturer.err().print("foo");

        assertThat("stdout", printedToOut.toString(), is(""));
        assertThat("stderr", printedToErr.toString(), is("foo"));
    }

    @Test
    public void captures_stdout() {
        OutputListenerSpy listener = new OutputListenerSpy();

        capturer.captureTo(listener);
        capturer.out().print("foo");

        assertThat(listener.out).as("stdout").containsExactly("foo");
        assertThat(listener.err).as("stderr").containsExactly();
    }

    @Test
    public void captures_stderr() {
        OutputListenerSpy listener = new OutputListenerSpy();

        capturer.captureTo(listener);
        capturer.err().print("foo");

        assertThat(listener.out).as("stdout").containsExactly();
        assertThat(listener.err).as("stderr").containsExactly("foo");
    }

    @Test
    public void single_byte_prints_are_also_captured_and_passed_through() {
        OutputListenerSpy listener = new OutputListenerSpy();
        capturer.captureTo(listener);

        capturer.out().write('.');

        assertThat(printedToOut.toString(), is("."));
        assertThat(listener.out).containsExactly(".");
    }

    @Test
    public void after_starting_a_new_capture_all_new_events_go_to_the_new_output_listener() {
        OutputListenerSpy listener1 = new OutputListenerSpy();
        OutputListenerSpy listener2 = new OutputListenerSpy();

        capturer.captureTo(listener1);
        capturer.captureTo(listener2);
        capturer.out().print("foo");

        assertThat(listener1.out).containsExactly();
        assertThat(listener2.out).containsExactly("foo");
    }

    @Test
    public void starting_a_new_capture_does_not_require_installing_a_new_PrintStream_to_SystemOut() {
        OutputListenerSpy listener = new OutputListenerSpy();

        PrintStream out = capturer.out();
        capturer.captureTo(listener);
        out.print("foo");

        assertThat(listener.out).containsExactly("foo");
    }

    // concurrency

    @Test
    public void concurrent_captures_are_isolated_from_each_other() throws Exception {
        CountDownLatch barrier = new CountDownLatch(2);
        OutputListenerSpy listener1 = new OutputListenerSpy();
        OutputListenerSpy listener2 = new OutputListenerSpy();

        runConcurrently(() -> {
            capturer.captureTo(listener1);
            sync(barrier);
            capturer.out().print("from thread 1");
        }, () -> {
            capturer.captureTo(listener2);
            sync(barrier);
            capturer.out().print("from thread 2");
        });

        assertThat(listener1.out).containsExactly("from thread 1");
        assertThat(listener2.out).containsExactly("from thread 2");
    }

    @Test
    public void captures_what_is_printed_in_spawned_threads() throws Exception {
        OutputListenerSpy listener = new OutputListenerSpy();

        capturer.captureTo(listener);
        runConcurrently(() -> {
            capturer.out().print("from spawned thread");
        });

        assertThat(listener.out).containsExactly("from spawned thread");
    }

    @Test
    public void when_spawned_threads_print_something_after_the_capture_ends_they_are_still_include_in_the_original_capture()
            throws InterruptedException {
        CountDownLatch beforeFinished = new CountDownLatch(2);
        CountDownLatch afterFinished = new CountDownLatch(2);
        OutputListenerSpy capture1 = new OutputListenerSpy();
        OutputListenerSpy capture2 = new OutputListenerSpy();

        capturer.captureTo(capture1);
        Thread t = startThread(() -> {
            capturer.out().print("before capture finished");
            sync(beforeFinished);
            sync(afterFinished);
            capturer.out().print("after capture finished");
        });
        sync(beforeFinished);
        capturer.captureTo(capture2);
        sync(afterFinished);
        t.join();

        assertThat(capture1.out).containsExactly("before capture finished", "after capture finished");
        assertThat(capture2.out).containsExactly();
    }

    /**
     * {@link PrintStream} synchronizes all its operations on itself, but since {@link PrintStream#println} does two
     * calls to the underlying {@link OutputStream} (or if the printed text is longer than all the internal buffers),
     * it's possible for stdout and stderr to get interleaved.
     */
    @Test
    public void printing_to_stdout_and_stderr_concurrently() throws Exception {
        final int ITERATIONS = 30;
        CombinedOutput combinedOutput = new CombinedOutput();
        capturer.captureTo(combinedOutput);

        runConcurrently(() -> {
            for (int i = 0; i < ITERATIONS; i++) {
                capturer.out().println("O");
            }
        }, () -> {
            for (int i = 0; i < ITERATIONS; i++) {
                capturer.err().println("E");
            }
        });

        assertThat(combinedOutput.toString()).matches("(O\\r?\\n|E\\r?\\n)+");
    }

    /**
     * {@link Throwable#printStackTrace} synchronizes on {@code System.err}, but it can still interleave with something
     * that is printed to {@code System.out}. We can fix that by synchronizing all printing on {@code System.err}, but
     * only in one direction; the output from {@code Throwable.printStackTrace(System.out)} may still interleave with
     * printing to {@code System.err}.
     */
    @Test
    public void printing_a_stack_trace_to_stderr_and_normally_to_stdout_concurrently() throws Exception {
        CountDownLatch isPrintingToOut = new CountDownLatch(1);
        CountDownLatch hasPrintedStackTrace = new CountDownLatch(1);
        Exception exception = new Exception("dummy exception");
        CombinedOutput combinedOutput = new CombinedOutput();
        capturer.captureTo(combinedOutput);

        runConcurrently(() -> {
            await(isPrintingToOut);
            exception.printStackTrace(capturer.err());
            hasPrintedStackTrace.countDown();
        }, () -> {
            while (hasPrintedStackTrace.getCount() > 0) {
                capturer.out().println("*garbage*");
                isPrintingToOut.countDown();
            }
        });

        assertThat(combinedOutput.toString(), containsString(Throwables.getStackTraceAsString(exception)));
    }

    // helpers

    private static void sync(CountDownLatch barrier) {
        ConcurrencyUtil.sync(barrier, TIMEOUT);
    }

    private static void await(CountDownLatch barrier) {
        ConcurrencyUtil.await(barrier, TIMEOUT);
    }

    private static class OutputListenerSpy implements OutputListener {
        public List<String> out = Collections.synchronizedList(new ArrayList<String>());
        public List<String> err = Collections.synchronizedList(new ArrayList<String>());

        @Override
        public void out(String text) {
            out.add(text);
        }

        @Override
        public void err(String text) {
            err.add(text);
        }
    }

    private static class CombinedOutput implements OutputListener {
        private final StringBuffer sb = new StringBuffer();

        @Override
        public void out(String text) {
            sb.append(text);
        }

        @Override
        public void err(String text) {
            sb.append(text);
        }

        @Override
        public String toString() {
            return sb.toString();
        }
    }
}