org.apache.flink.runtime.io.network.netty.InboundEnvelopeDecoderTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flink.runtime.io.network.netty.InboundEnvelopeDecoderTest.java

Source

/**
 * 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 + ")";
        }
    }
}