// 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 net.sf.cglib.proxy.Factory; import org.apache.commons.io.output.NullOutputStream; import org.junit.*; import org.junit.rules.Timeout; import java.io.*; import java.nio.charset.Charset; import static fi.jumi.core.util.ConcurrencyUtil.runConcurrently; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public class SynchronizedPrintStreamTest { @Rule public final Timeout timeout = new Timeout(1000); private final Object lock = new Object(); @Test public void synchronizes_all_methods_on_the_lock_given_as_parameter() { SpyOutputStream spy = new SpyOutputStream(); PrintStream printStream = SynchronizedPrintStream.create(spy, Charset.defaultCharset(), lock); printStream.println("foo"); assertThat("was called", spy.wasCalled, is(true)); assertThat("used the lock", spy.lockWasHeldByCurrentThread, is(true)); } /** * For example {@link Throwable#printStackTrace} does this, we must be careful to always acquire a lock on the * monitor of the PrintStream first, before all other locks. */ @Test public void does_not_deadlock_if_somebody_locks_in_the_PrintStream_externally() throws Exception { final int ITERATIONS = 10; PrintStream printStream = SynchronizedPrintStream.create(new NullOutputStream(), Charset.defaultCharset(), lock); // will fail with a test timeout if a deadlock happens runConcurrently(() -> { // what Thread.printStackTrace() would do synchronized (printStream) { for (int i = 0; i < ITERATIONS; i++) { Thread.yield(); printStream.print("X"); } } }, () -> { // what a normal printer would do for (int i = 0; i < ITERATIONS; i++) { Thread.yield(); printStream.print("X"); } }); } @Test public void the_class_name_in_stack_traces_gives_a_hint_of_who_generated_the_proxy_class() { PrintStream printStream = SynchronizedPrintStream.create(new NullOutputStream(), Charset.defaultCharset(), lock); assertThat(printStream.getClass().getName(), startsWith(SynchronizedPrintStream.class.getName())); } @Test public void does_not_expose_the_CGLIB_Factory_interface() { PrintStream printStream = SynchronizedPrintStream.create(new NullOutputStream(), Charset.defaultCharset(), lock); assertThat(printStream, not(instanceOf(Factory.class))); } private class SpyOutputStream extends OutputStream { boolean wasCalled = false; boolean lockWasHeldByCurrentThread = false; @Override public void write(int b) { wasCalled = true; lockWasHeldByCurrentThread = Thread.holdsLock(lock); } } }