package com.yammer.telemetry.tracing; import com.google.common.collect.ImmutableList; import org.junit.After; import org.junit.Test; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; public class SpanSinkRegistryTest { @After public void resetRegistry() { SpanSinkRegistry.clear(); } @Test public void testStartsEmpty() { assertFalse(SpanSinkRegistry.getSpanSinks().iterator().hasNext()); } @Test public void testAddOne() { SpanSink sink = mock(SpanSink.class); SpanSinkRegistry.register(sink); ImmutableList<SpanSink> sinks = ImmutableList.copyOf(SpanSinkRegistry.getSpanSinks()); assertEquals(1, sinks.size()); assertTrue(sinks.contains(sink)); } @Test public void testAddTwo() { SpanSink sinkOne = mock(SpanSink.class); SpanSink sinkTwo = mock(SpanSink.class); assertFalse(sinkOne.equals(sinkTwo)); SpanSinkRegistry.register(sinkOne); SpanSinkRegistry.register(sinkTwo); ImmutableList<SpanSink> sinks = ImmutableList.copyOf(SpanSinkRegistry.getSpanSinks()); assertEquals(2, sinks.size()); assertEquals(ImmutableList.of(sinkOne, sinkTwo), sinks); } @Test public void testIsClearable() { SpanSink sink = mock(SpanSink.class); SpanSinkRegistry.register(sink); assertTrue(SpanSinkRegistry.getSpanSinks().iterator().hasNext()); SpanSinkRegistry.clear(); assertFalse(SpanSinkRegistry.getSpanSinks().iterator().hasNext()); } // todo - yeuch. @Test(timeout = 5000) public void testRaceCondition() throws InterruptedException { int parties = 2; final AtomicInteger barrierGenerations = new AtomicInteger(); final CyclicBarrier barrier = new CyclicBarrier(parties, new Runnable() { @Override public void run() { int generation = barrierGenerations.incrementAndGet(); } }); final ArrayBlockingQueue<Throwable> caught = new ArrayBlockingQueue<>(1); final AtomicBoolean running = new AtomicBoolean(true); // We start 2 threads with a cyclic barrier repeatedly and wait a collision with at least one long endBy = System.currentTimeMillis() + 5000; for (int i = 0; i < parties; i++) { new CyclicBarrierThread(barrier, caught, running, endBy).start(); } //noinspection ThrowableResultOfMethodCallIgnored assertEquals("Failed to add new SpanSink, concurrent add", caught.poll(5000, TimeUnit.MILLISECONDS).getMessage()); int sinks = ImmutableList.copyOf(SpanSinkRegistry.getSpanSinks()).size(); int totalAdds = barrierGenerations.get() * 2; assertTrue(sinks + " < " + totalAdds + "?", totalAdds > sinks); } private static class CyclicBarrierThread extends Thread { private final CyclicBarrier barrier; private final ArrayBlockingQueue<Throwable> caught; private final AtomicBoolean running; private final long endBy; public CyclicBarrierThread(CyclicBarrier barrier, ArrayBlockingQueue<Throwable> caught, AtomicBoolean running, long endBy) { this.barrier = barrier; this.caught = caught; this.running = running; this.endBy = endBy; } @Override public void run() { // we have an upper bound on how long we run... while (!timedOut() && running.get()) { try { barrier.await(); SpanSinkRegistry.register(mock(SpanSink.class)); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } catch (Throwable t) { caught.offer(t); running.compareAndSet(true, false); // allow other threads to end } } } private boolean timedOut() { return System.currentTimeMillis() > endBy; } } }