Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.runtime.io.network.netty; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; import io.netty.channel.embedded.EmbeddedChannel; import org.junit.Assert; import org.apache.flink.core.memory.DataInputView; import org.apache.flink.core.memory.DataOutputView; import org.apache.flink.core.memory.MemorySegment; import org.apache.flink.runtime.event.task.AbstractEvent; import org.apache.flink.runtime.io.network.Buffer; import org.apache.flink.runtime.io.network.BufferRecycler; import org.apache.flink.runtime.io.network.Envelope; import org.apache.flink.runtime.io.network.bufferprovider.BufferAvailabilityListener; import org.apache.flink.runtime.io.network.bufferprovider.BufferProvider; import org.apache.flink.runtime.io.network.bufferprovider.BufferProviderBroker; import org.apache.flink.runtime.io.network.bufferprovider.BufferProvider.BufferAvailabilityRegistration; import org.apache.flink.runtime.io.network.channels.ChannelID; import org.apache.flink.runtime.io.network.netty.InboundEnvelopeDecoder; import org.apache.flink.runtime.io.network.netty.OutboundEnvelopeEncoder; import org.apache.flink.runtime.jobgraph.JobID; import org.junit.Before; import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Random; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class InboundEnvelopeDecoderTest { @Mock private BufferProvider bufferProvider; @Mock private BufferProviderBroker bufferProviderBroker; @Before public void initMocks() throws IOException { MockitoAnnotations.initMocks(this); } @Test public void testBufferStaging() throws Exception { final InboundEnvelopeDecoder decoder = new InboundEnvelopeDecoder(this.bufferProviderBroker); final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), decoder); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); // -------------------------------------------------------------------- Envelope[] envelopes = nextEnvelopes(3, true); ByteBuf buf = encode(ch, envelopes); when(this.bufferProvider .registerBufferAvailabilityListener(Matchers.<BufferAvailabilityListener>anyObject())) .thenReturn(BufferAvailabilityRegistration.SUCCEEDED_REGISTERED); Buffer buffer = allocBuffer(envelopes[2].getBuffer().size()); when(this.bufferProvider.requestBuffer(anyInt())).thenReturn(null, null, buffer, null); // -------------------------------------------------------------------- // slices: [0] => full envelope, [1] => half envelope, [2] => remaining half + full envelope ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + envelopes[0].getBuffer().size(), OutboundEnvelopeEncoder.HEADER_SIZE + envelopes[1].getBuffer().size() / 2); // 1. no buffer available, incoming slice contains all data int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0]); Assert.assertEquals(refCount + 1, slices[0].refCnt()); Assert.assertFalse(ch.config().isAutoRead()); // notify of available buffer (=> bufferAvailable() callback does return a buffer // of the current network buffer size; the decoder needs to adjust its size to the // requested size decoder.bufferAvailable(allocBuffer(envelopes[0].getBuffer().size() * 2)); ch.runPendingTasks(); Assert.assertEquals(refCount - 1, slices[0].refCnt()); Assert.assertTrue(ch.config().isAutoRead()); decodeAndVerify(ch, envelopes[0]); // 2. no buffer available, incoming slice does NOT contain all data refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1]); Assert.assertEquals(refCount + 1, slices[1].refCnt()); Assert.assertFalse(ch.config().isAutoRead()); decoder.bufferAvailable(allocBuffer()); ch.runPendingTasks(); Assert.assertEquals(refCount - 1, slices[1].refCnt()); Assert.assertTrue(ch.config().isAutoRead()); decodeAndVerify(ch); // 3. buffer available refCount = slices[2].refCnt(); decodeAndVerify(ch, slices[2], envelopes[1], envelopes[2]); Assert.assertEquals(refCount - 1, slices[2].refCnt()); Assert.assertTrue(ch.config().isAutoRead()); Assert.assertEquals(1, buf.refCnt()); buf.release(); } @Test public void testBufferStagingStagedBufferException() throws Exception { final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), new InboundEnvelopeDecoder(this.bufferProviderBroker)); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); // -------------------------------------------------------------------- ByteBuf buf = encode(ch, nextEnvelope(true)); when(this.bufferProvider.requestBuffer(anyInt())).thenReturn(null); when(this.bufferProvider .registerBufferAvailabilityListener(Matchers.<BufferAvailabilityListener>anyObject())) .thenReturn(BufferAvailabilityRegistration.SUCCEEDED_REGISTERED); // -------------------------------------------------------------------- int refCount = buf.refCnt(); decodeAndVerify(ch, buf); Assert.assertFalse(ch.config().isAutoRead()); Assert.assertEquals(refCount + 1, buf.refCnt()); try { decodeAndVerify(ch, buf); Assert.fail("Expected IllegalStateException not thrown"); } catch (IllegalStateException e) { // expected exception } buf.release(); } @Test public void testBufferAvailabilityRegistrationBufferAvailable() throws Exception { final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), new InboundEnvelopeDecoder(this.bufferProviderBroker)); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); // -------------------------------------------------------------------- Envelope[] envelopes = new Envelope[] { nextEnvelope(true), nextEnvelope() }; when(this.bufferProvider .registerBufferAvailabilityListener(Matchers.<BufferAvailabilityListener>anyObject())) .thenReturn(BufferAvailabilityRegistration.FAILED_BUFFER_AVAILABLE); when(this.bufferProvider.requestBuffer(anyInt())).thenReturn(null) .thenReturn(allocBuffer(envelopes[0].getBuffer().size())); // -------------------------------------------------------------------- ByteBuf buf = encode(ch, envelopes); decodeAndVerify(ch, buf, envelopes); Assert.assertEquals(0, buf.refCnt()); } @Test public void testBufferAvailabilityRegistrationBufferPoolDestroyedSkipBytes() throws Exception { final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), new InboundEnvelopeDecoder(this.bufferProviderBroker)); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); when(this.bufferProvider.requestBuffer(anyInt())).thenReturn(null); when(this.bufferProvider .registerBufferAvailabilityListener(Matchers.<BufferAvailabilityListener>anyObject())) .thenReturn(BufferAvailabilityRegistration.FAILED_BUFFER_POOL_DESTROYED); // -------------------------------------------------------------------- Envelope[] envelopes = new Envelope[] { nextEnvelope(true), nextEnvelope(), nextEnvelope() }; Envelope[] expectedEnvelopes = new Envelope[] { envelopes[1], envelopes[2] }; ByteBuf buf = encode(ch, envelopes); int bufferSize = envelopes[0].getBuffer().size(); // -------------------------------------------------------------------- // 1) skip in current buffer only // -------------------------------------------------------------------- { // skip last bytes in current buffer ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + bufferSize); int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0]); Assert.assertEquals(refCount - 1, slices[0].refCnt()); refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1], expectedEnvelopes); Assert.assertEquals(refCount - 1, slices[1].refCnt()); } { // skip bytes in current buffer, leave last 16 bytes from next envelope ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + bufferSize + 16); int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0]); Assert.assertEquals(refCount - 1, slices[0].refCnt()); refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1], expectedEnvelopes); Assert.assertEquals(refCount - 1, slices[1].refCnt()); } { // skip bytes in current buffer, then continue with full envelope from same buffer ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + bufferSize + OutboundEnvelopeEncoder.HEADER_SIZE); int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0], expectedEnvelopes[0]); Assert.assertEquals(refCount - 1, slices[0].refCnt()); refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1], expectedEnvelopes[1]); Assert.assertEquals(refCount - 1, slices[1].refCnt()); } // -------------------------------------------------------------------- // 2) skip in current and next buffer // -------------------------------------------------------------------- { // skip bytes in current buffer, then continue to skip last 32 bytes in next buffer ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + bufferSize - 32); int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0]); Assert.assertEquals(refCount - 1, slices[0].refCnt()); refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1], expectedEnvelopes); Assert.assertEquals(refCount - 1, slices[1].refCnt()); } { // skip bytes in current buffer, then continue to skip in next two buffers ByteBuf[] slices = slice(buf, OutboundEnvelopeEncoder.HEADER_SIZE + bufferSize - 32, 16); int refCount = slices[0].refCnt(); decodeAndVerify(ch, slices[0]); Assert.assertEquals(refCount - 1, slices[0].refCnt()); refCount = slices[1].refCnt(); decodeAndVerify(ch, slices[1]); Assert.assertEquals(refCount - 1, slices[1].refCnt()); refCount = slices[2].refCnt(); decodeAndVerify(ch, slices[2], expectedEnvelopes); Assert.assertEquals(refCount - 1, slices[2].refCnt()); } // ref count should be 1, because slices shared the ref count Assert.assertEquals(1, buf.refCnt()); } @Test public void testEncodeDecode() throws Exception { final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), new InboundEnvelopeDecoder(this.bufferProviderBroker)); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); when(this.bufferProvider.requestBuffer(anyInt())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { // fulfill the buffer request return allocBuffer((Integer) invocation.getArguments()[0]); } }); // -------------------------------------------------------------------- Envelope[] envelopes = new Envelope[] { nextEnvelope(0), nextEnvelope(2), nextEnvelope(32768), nextEnvelope(3782, new TestEvent1(34872527)), nextEnvelope(88, new TestEvent1(8749653), new TestEvent1(365345)), nextEnvelope(0, new TestEvent2(34563456), new TestEvent1(598432), new TestEvent2(976293845)), nextEnvelope(23) }; ByteBuf buf = encode(ch, envelopes); // 1. complete ByteBuf as input int refCount = buf.retain().refCnt(); decodeAndVerify(ch, buf, envelopes); Assert.assertEquals(refCount - 1, buf.refCnt()); // 2. random slices buf.readerIndex(0); ByteBuf[] slices = randomSlices(buf); ch.writeInbound((Object[]) slices); for (ByteBuf slice : slices) { Assert.assertEquals(1, slice.refCnt()); } decodeAndVerify(ch, envelopes); buf.release(); } @Test public void testEncodeDecodeRandomEnvelopes() throws Exception { final InboundEnvelopeDecoder decoder = new InboundEnvelopeDecoder(this.bufferProviderBroker); final EmbeddedChannel ch = new EmbeddedChannel(new OutboundEnvelopeEncoder(), decoder); when(this.bufferProviderBroker.getBufferProvider(anyJobId(), anyChannelId())) .thenReturn(this.bufferProvider); when(this.bufferProvider.requestBuffer(anyInt())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { // fulfill the buffer request with the requested size return allocBuffer((Integer) invocation.getArguments()[0]); } }); Random randomAnswerSource = new Random(RANDOM_SEED); RandomBufferRequestAnswer randomBufferRequestAnswer = new RandomBufferRequestAnswer(randomAnswerSource); RandomBufferAvailabilityRegistrationAnswer randomBufferAvailabilityRegistrationAnswer = new RandomBufferAvailabilityRegistrationAnswer( randomAnswerSource, randomBufferRequestAnswer); when(this.bufferProvider.requestBuffer(anyInt())).thenAnswer(randomBufferRequestAnswer); when(this.bufferProvider .registerBufferAvailabilityListener(Matchers.<BufferAvailabilityListener>anyObject())) .thenAnswer(randomBufferAvailabilityRegistrationAnswer); // -------------------------------------------------------------------- Envelope[] envelopes = nextRandomEnvelopes(1024); ByteBuf buf = encode(ch, envelopes); ByteBuf[] slices = randomSlices(buf); for (ByteBuf slice : slices) { int refCount = slice.refCnt(); ch.writeInbound(slice); // registered BufferAvailabilityListener => call bufferAvailable(buffer) while (randomBufferAvailabilityRegistrationAnswer.isRegistered()) { randomBufferAvailabilityRegistrationAnswer.unregister(); Assert.assertFalse(ch.config().isAutoRead()); Assert.assertEquals(refCount + 1, slice.refCnt()); // return a buffer of max size => decoder needs to limit buffer size decoder.bufferAvailable(allocBuffer(MAX_BUFFER_SIZE)); ch.runPendingTasks(); } Assert.assertEquals(refCount - 1, slice.refCnt()); Assert.assertTrue(ch.config().isAutoRead()); } Envelope[] expected = randomBufferAvailabilityRegistrationAnswer.removeSkippedEnvelopes(envelopes); decodeAndVerify(ch, expected); Assert.assertEquals(1, buf.refCnt()); buf.release(); } // ======================================================================== // helpers // ======================================================================== private final static long RANDOM_SEED = 520346508276087l; private final static Random random = new Random(RANDOM_SEED); private final static int[] BUFFER_SIZES = new int[] { 8192, 16384, 32768 }; private final static int MAX_BUFFER_SIZE = BUFFER_SIZES[2]; private final static int MAX_NUM_EVENTS = 5; private final static int MAX_SLICE_SIZE = MAX_BUFFER_SIZE / 3; private final static int MIN_SLICE_SIZE = 1; private final static BufferRecycler RECYCLER = mock(BufferRecycler.class); // ------------------------------------------------------------------------ // envelopes // ------------------------------------------------------------------------ private static Buffer allocBuffer() { return allocBuffer(MAX_BUFFER_SIZE); } private static Buffer allocBuffer(int bufferSize) { return spy(new Buffer(new MemorySegment(new byte[bufferSize]), bufferSize, RECYCLER)); } private Envelope nextEnvelope() { return nextEnvelope(false, false); } private Envelope nextEnvelope(boolean withBuffer) { return nextEnvelope(withBuffer, false); } private Envelope nextEnvelope(int bufferSize, AbstractEvent... events) { Envelope env = new Envelope(random.nextInt(), new JobID(), new ChannelID()); if (bufferSize > 0) { byte[] data = new byte[bufferSize]; random.nextBytes(data); env.setBuffer(spy(new Buffer(new MemorySegment(data), bufferSize, RECYCLER))); } if (events != null && events.length > 0) { env.serializeEventList(Arrays.asList(events)); } return env; } private Envelope nextEnvelope(boolean withBuffer, boolean withEvents) { int bufferSize = 0; AbstractEvent[] events = null; if (withBuffer) { bufferSize = BUFFER_SIZES[random.nextInt(BUFFER_SIZES.length)]; } if (withEvents) { events = new AbstractEvent[random.nextInt(MAX_NUM_EVENTS) + 1]; for (int i = 0; i < events.length; i++) { events[i] = (random.nextBoolean() ? new TestEvent1(random.nextLong()) : new TestEvent2(random.nextLong())); } } return nextEnvelope(bufferSize, events); } private Envelope[] nextEnvelopes(int numEnvelopes, boolean withBuffer) { Envelope[] envelopes = new Envelope[numEnvelopes]; for (int i = 0; i < numEnvelopes; i++) { envelopes[i] = nextEnvelope(withBuffer, false); } return envelopes; } private Envelope[] nextRandomEnvelopes(int numEnvelopes) { Envelope[] envelopes = new Envelope[numEnvelopes]; for (int i = 0; i < numEnvelopes; i++) { envelopes[i] = nextEnvelope(random.nextBoolean(), random.nextBoolean()); } return envelopes; } // ------------------------------------------------------------------------ // channel encode/decode // ------------------------------------------------------------------------ private static ByteBuf encode(EmbeddedChannel ch, Envelope... envelopes) { for (Envelope env : envelopes) { ch.writeOutbound(env); if (env.getBuffer() != null) { verify(env.getBuffer(), times(1)).recycleBuffer(); } } CompositeByteBuf encodedEnvelopes = new CompositeByteBuf(ByteBufAllocator.DEFAULT, false, envelopes.length); ByteBuf buf; while ((buf = (ByteBuf) ch.readOutbound()) != null) { encodedEnvelopes.addComponent(buf); } return encodedEnvelopes.writerIndex(encodedEnvelopes.capacity()); } private static void decodeAndVerify(EmbeddedChannel ch, ByteBuf buf, Envelope... expectedEnvelopes) { ch.writeInbound(buf); decodeAndVerify(ch, expectedEnvelopes); } private static void decodeAndVerify(EmbeddedChannel ch, Envelope... expectedEnvelopes) { if (expectedEnvelopes == null) { Assert.assertNull(ch.readInbound()); } else { for (Envelope expected : expectedEnvelopes) { Envelope actual = (Envelope) ch.readInbound(); if (actual == null) { Assert.fail("No inbound envelope available, but expected one"); } assertEqualEnvelopes(expected, actual); } } } private static void assertEqualEnvelopes(Envelope expected, Envelope actual) { Assert.assertTrue(expected.getSequenceNumber() == actual.getSequenceNumber() && expected.getJobID().equals(actual.getJobID()) && expected.getSource().equals(actual.getSource())); if (expected.getBuffer() == null) { Assert.assertNull(actual.getBuffer()); } else { Assert.assertNotNull(actual.getBuffer()); ByteBuffer expectedByteBuffer = expected.getBuffer().getMemorySegment().wrap(0, expected.getBuffer().size()); ByteBuffer actualByteBuffer = actual.getBuffer().getMemorySegment().wrap(0, actual.getBuffer().size()); Assert.assertEquals(0, expectedByteBuffer.compareTo(actualByteBuffer)); } if (expected.getEventsSerialized() == null) { Assert.assertNull(actual.getEventsSerialized()); } else { Assert.assertNotNull(actual.getEventsSerialized()); // this is needed, because the encoding of the byte buffer // alters the state of the buffer expected.getEventsSerialized().clear(); List<? extends AbstractEvent> expectedEvents = expected.deserializeEvents(); List<? extends AbstractEvent> actualEvents = actual.deserializeEvents(); Assert.assertEquals(expectedEvents.size(), actualEvents.size()); for (int i = 0; i < expectedEvents.size(); i++) { AbstractEvent expectedEvent = expectedEvents.get(i); AbstractEvent actualEvent = actualEvents.get(i); Assert.assertEquals(expectedEvent.getClass(), actualEvent.getClass()); Assert.assertEquals(expectedEvent, actualEvent); } } } private static ByteBuf[] randomSlices(ByteBuf buf) { List<Integer> sliceSizes = new LinkedList<Integer>(); if (buf.readableBytes() < MIN_SLICE_SIZE) { throw new IllegalStateException("Buffer to slice is smaller than required minimum slice size"); } int available = buf.readableBytes() - MIN_SLICE_SIZE; while (available > 0) { int size = Math.min(available, Math.max(MIN_SLICE_SIZE, random.nextInt(MAX_SLICE_SIZE) + 1)); available -= size; sliceSizes.add(size); } int[] slices = new int[sliceSizes.size()]; for (int i = 0; i < sliceSizes.size(); i++) { slices[i] = sliceSizes.get(i); } return slice(buf, slices); } /** * Returns slices with the specified sizes of the given buffer. * <p/> * When given n indexes, n+1 slices will be returned: * <ul> * <li>0 - sliceSizes[0]</li> * <li>sliceSizes[0] - sliceSizes[1]</li> * <li>...</li> * <li>sliceSizes[n-1] - buf.capacity()</li> * </ul> * * @return slices with the specified sizes of the given buffer */ private static ByteBuf[] slice(ByteBuf buf, int... sliceSizes) { if (sliceSizes.length == 0) { throw new IllegalStateException("Need to provide at least one slice size"); } int numSlices = sliceSizes.length; // transform slice sizes to buffer indexes for (int i = 1; i < numSlices; i++) { sliceSizes[i] += sliceSizes[i - 1]; } for (int i = 0; i < sliceSizes.length - 1; i++) { if (sliceSizes[i] >= sliceSizes[i + 1] || sliceSizes[i] <= 0 || sliceSizes[i] >= buf.capacity()) { throw new IllegalStateException( String.format("Slice size %s are off for %s", Arrays.toString(sliceSizes), buf)); } } ByteBuf[] slices = new ByteBuf[numSlices + 1]; // slice at slice indexes slices[0] = buf.slice(0, sliceSizes[0]).retain(); for (int i = 1; i < numSlices; i++) { slices[i] = buf.slice(sliceSizes[i - 1], sliceSizes[i] - sliceSizes[i - 1]).retain(); } slices[numSlices] = buf.slice(sliceSizes[numSlices - 1], buf.capacity() - sliceSizes[numSlices - 1]) .retain(); return slices; } // ------------------------------------------------------------------------ // mocking // ------------------------------------------------------------------------ private static JobID anyJobId() { return Matchers.anyObject(); } private static ChannelID anyChannelId() { return Matchers.anyObject(); } // these following two Answer classes are quite ugly, but they allow to implement a randomized // test of encoding and decoding envelopes private static class RandomBufferRequestAnswer implements Answer<Buffer> { private final Random random; private boolean forced; private RandomBufferRequestAnswer(Random random) { this.random = random; } @Override public Buffer answer(InvocationOnMock invocation) throws Throwable { if (this.forced) { Buffer toReturn = allocBuffer((Integer) invocation.getArguments()[0]); this.forced = false; return toReturn; } return this.random.nextBoolean() ? allocBuffer((Integer) invocation.getArguments()[0]) : null; } public void forceBufferAvailable() { this.forced = true; } } private static class RandomBufferAvailabilityRegistrationAnswer implements Answer<BufferAvailabilityRegistration> { private final Random random; private final RandomBufferRequestAnswer bufferRequestAnswer; private boolean isRegistered = false; private int numSkipped; private RandomBufferAvailabilityRegistrationAnswer(Random random, RandomBufferRequestAnswer bufferRequestAnswer) { this.random = random; this.bufferRequestAnswer = bufferRequestAnswer; } @Override public BufferAvailabilityRegistration answer(InvocationOnMock invocation) throws Throwable { if (this.random.nextBoolean()) { this.isRegistered = true; return BufferAvailabilityRegistration.SUCCEEDED_REGISTERED; } else if (this.random.nextBoolean()) { this.bufferRequestAnswer.forceBufferAvailable(); return BufferAvailabilityRegistration.FAILED_BUFFER_AVAILABLE; } else { this.numSkipped++; return BufferAvailabilityRegistration.FAILED_BUFFER_POOL_DESTROYED; } } public Envelope[] removeSkippedEnvelopes(Envelope[] envelopes) { this.random.setSeed(RANDOM_SEED); Envelope[] envelopesWithoutSkipped = new Envelope[envelopes.length - this.numSkipped]; int numEnvelopes = 0; for (Envelope env : envelopes) { if (env.getBuffer() != null) { // skip envelope if returned FAILED_BUFFER_POOL_DESTROYED if (!this.random.nextBoolean() && !this.random.nextBoolean() && !this.random.nextBoolean()) { continue; } } envelopesWithoutSkipped[numEnvelopes++] = env; } return envelopesWithoutSkipped; } public boolean isRegistered() { return this.isRegistered; } public void unregister() { this.isRegistered = false; } } // ------------------------------------------------------------------------ public static final class TestEvent1 extends AbstractEvent { private long id; public TestEvent1() { } public TestEvent1(long id) { this.id = id; } @Override public void write(DataOutputView out) throws IOException { out.writeLong(id); } @Override public void read(DataInputView in) throws IOException { id = in.readLong(); } @Override public boolean equals(Object obj) { return obj.getClass() == TestEvent1.class && ((TestEvent1) obj).id == this.id; } @Override public int hashCode() { return ((int) id) ^ ((int) (id >>> 32)); } @Override public String toString() { return "TestEvent1 (" + id + ")"; } } public static final class TestEvent2 extends AbstractEvent { private long id; public TestEvent2() { } public TestEvent2(long id) { this.id = id; } @Override public void write(DataOutputView out) throws IOException { out.writeLong(id); } @Override public void read(DataInputView in) throws IOException { id = in.readLong(); } @Override public boolean equals(Object obj) { return obj.getClass() == TestEvent2.class && ((TestEvent2) obj).id == this.id; } @Override public int hashCode() { return ((int) id) ^ ((int) (id >>> 32)); } @Override public String toString() { return "TestEvent2 (" + id + ")"; } } }