com.linecorp.armeria.common.stream.AbstractStreamMessageTest.java Source code

Java tutorial

Introduction

Here is the source code for com.linecorp.armeria.common.stream.AbstractStreamMessageTest.java

Source

/*
 * Copyright 2017 LINE Corporation
 *
 * LINE Corporation 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 com.linecorp.armeria.common.stream;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.common.util.Exceptions;
import com.linecorp.armeria.unsafe.ByteBufHttpData;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledHeapByteBuf;
import io.netty.channel.DefaultEventLoop;
import io.netty.channel.EventLoop;

@SuppressWarnings("unchecked") // Allow using the same tests for writers and non-writers
public abstract class AbstractStreamMessageTest {

    static final List<Integer> TEN_INTEGERS = IntStream.range(0, 10).boxed().collect(toImmutableList());

    private static EventLoop eventLoop;

    @BeforeClass
    public static void startEventLoop() {
        eventLoop = new DefaultEventLoop();
    }

    @AfterClass
    public static void stopEventLoop() {
        eventLoop.shutdownGracefully().syncUninterruptibly();
    }

    EventLoop eventLoop() {
        return eventLoop;
    }

    abstract <T> StreamMessage<T> newStream(List<T> inputs);

    List<Integer> streamValues() {
        return TEN_INTEGERS;
    }

    private List<Integer> result;
    private volatile boolean completed;
    private volatile Throwable error;

    @Before
    public void reset() {
        result = new ArrayList<>();
        completed = false;
    }

    @Test
    public void full_writeFirst() throws Exception {
        StreamMessage<Integer> stream = newStream(streamValues());
        writeTenIntegers(stream);
        stream.subscribe(new ResultCollectingSubscriber() {
            @Override
            public void onSubscribe(Subscription s) {
                s.request(Long.MAX_VALUE);
            }
        });
        assertSuccess();
    }

    @Test
    public void full_writeAfter() throws Exception {
        StreamMessage<Integer> stream = newStream(streamValues());
        stream.subscribe(new ResultCollectingSubscriber() {
            @Override
            public void onSubscribe(Subscription s) {
                s.request(Long.MAX_VALUE);
            }
        });
        writeTenIntegers(stream);
        assertSuccess();
    }

    // Verifies that re-entrancy into onNext, e.g. calling onNext -> request -> onNext, is not allowed, as per
    // reactive streams spec 3.03. If it were allowed, the order of processing would be incorrect and the test
    // would fail.
    @Test
    public void flowControlled_writeThenDemandThenProcess() throws Exception {
        StreamMessage<Integer> stream = newStream(streamValues());
        writeTenIntegers(stream);
        stream.subscribe(new ResultCollectingSubscriber() {
            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription s) {
                subscription = s;
                subscription.request(1);
            }

            @Override
            public void onNext(Integer value) {
                subscription.request(1);
                super.onNext(value);
            }
        });
        assertSuccess();
    }

    @Test
    public void flowControlled_writeThenDemandThenProcess_eventLoop() throws Exception {
        StreamMessage<Integer> stream = newStream(streamValues());
        writeTenIntegers(stream);
        eventLoop().submit(() -> stream.subscribe(new ResultCollectingSubscriber() {
            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription s) {
                subscription = s;
                subscription.request(1);
            }

            @Override
            public void onNext(Integer value) {
                subscription.request(1);
                super.onNext(value);
            }
        }, eventLoop())).syncUninterruptibly();
        assertSuccess();
    }

    @Test
    public void flowControlled_writeThenProcessThenDemand() throws Exception {
        StreamMessage<Integer> stream = newStream(streamValues());
        writeTenIntegers(stream);
        stream.subscribe(new ResultCollectingSubscriber() {
            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription s) {
                subscription = s;
                subscription.request(1);
            }

            @Override
            public void onNext(Integer value) {
                super.onNext(value);
                subscription.request(1);
            }
        });
        assertSuccess();
    }

    @Test
    public void releaseOnConsumption_ByteBuf() throws Exception {
        final ByteBuf buf = newPooledBuffer();
        StreamMessage<ByteBuf> stream = newStream(ImmutableList.of(buf));

        if (stream instanceof StreamWriter) {
            ((StreamWriter<ByteBuf>) stream).write(buf);
            ((StreamWriter<?>) stream).close();
        }
        assertThat(buf.refCnt()).isEqualTo(1);

        stream.subscribe(new Subscriber<ByteBuf>() {
            @Override
            public void onSubscribe(Subscription subscription) {
                subscription.request(1);
            }

            @Override
            public void onNext(ByteBuf o) {
                assertThat(o).isNotSameAs(buf);
                assertThat(o).isInstanceOf(UnpooledHeapByteBuf.class);
                assertThat(o.refCnt()).isEqualTo(1);
                assertThat(buf.refCnt()).isZero();
            }

            @Override
            public void onError(Throwable throwable) {
                Exceptions.throwUnsafely(throwable);
            }

            @Override
            public void onComplete() {
                completed = true;
            }
        });
        await().untilAsserted(() -> assertThat(completed).isTrue());
    }

    @Test
    public void releaseOnConsumption_HttpData() throws Exception {
        final ByteBufHttpData data = new ByteBufHttpData(newPooledBuffer(), false);
        StreamMessage<ByteBufHolder> stream = newStream(ImmutableList.of(data));

        if (stream instanceof StreamWriter) {
            ((StreamWriter<ByteBufHolder>) stream).write(data);
            ((StreamWriter<?>) stream).close();
        }
        assertThat(data.refCnt()).isEqualTo(1);

        stream.subscribe(new Subscriber<ByteBufHolder>() {
            @Override
            public void onSubscribe(Subscription subscription) {
                subscription.request(1);
            }

            @Override
            public void onNext(ByteBufHolder o) {
                assertThat(o).isNotSameAs(data);
                assertThat(o).isInstanceOf(ByteBufHttpData.class);
                assertThat(o.content()).isInstanceOf(UnpooledHeapByteBuf.class);
                assertThat(o.refCnt()).isEqualTo(1);
                assertThat(data.refCnt()).isZero();
            }

            @Override
            public void onError(Throwable throwable) {
                Exceptions.throwUnsafely(throwable);
            }

            @Override
            public void onComplete() {
                completed = true;
            }
        });
        await().untilAsserted(() -> assertThat(completed).isTrue());
    }

    @Test
    public void releaseWithZeroDemand() {
        final ByteBufHttpData data = new ByteBufHttpData(newPooledBuffer(), true);
        StreamMessage<Object> stream = newStream(ImmutableList.of(data));
        if (stream instanceof StreamWriter) {
            ((StreamWriter) stream).write(data);
        }
        stream.subscribe(new Subscriber<Object>() {
            @Override
            public void onSubscribe(Subscription subscription) {
                // Cancel the subscription when the demand is 0.
                subscription.cancel();
            }

            @Override
            public void onNext(Object o) {
                fail();
            }

            @Override
            public void onError(Throwable throwable) {
                fail();
            }

            @Override
            public void onComplete() {
                fail();
            }
        }, true);

        await().untilAsserted(() -> assertThat(stream.isOpen()).isFalse());
        await().untilAsserted(() -> assertThat(data.refCnt()).isZero());
    }

    @Test
    public void releaseWithZeroDemandAndClosedStream() {
        final ByteBufHttpData data = new ByteBufHttpData(newPooledBuffer(), true);
        StreamMessage<Object> stream = newStream(ImmutableList.of(data));
        if (stream instanceof StreamWriter) {
            ((StreamWriter) stream).write(data);
            ((StreamWriter) stream).close();
        }

        stream.subscribe(new Subscriber<Object>() {
            @Override
            public void onSubscribe(Subscription subscription) {
                // Cancel the subscription when the demand is 0.
                subscription.cancel();
            }

            @Override
            public void onNext(Object o) {
                fail();
            }

            @Override
            public void onError(Throwable throwable) {
                fail();
            }

            @Override
            public void onComplete() {
                fail();
            }
        }, true);

        await().untilAsserted(() -> assertThat(stream.isOpen()).isFalse());
        await().untilAsserted(() -> assertThat(data.refCnt()).isZero());
    }

    private void assertSuccess() {
        await().untilAsserted(() -> assertThat(completed).isTrue());
        assertThat(error).isNull();
        assertThat(result).containsExactlyElementsOf(streamValues());
    }

    private abstract class ResultCollectingSubscriber implements Subscriber<Integer> {

        @Override
        public void onNext(Integer value) {
            result.add(value);
        }

        @Override
        public void onComplete() {
            completed = true;
        }

        @Override
        public void onError(Throwable t) {
            error = t;
        }
    }

    protected static ByteBuf newPooledBuffer() {
        return PooledByteBufAllocator.DEFAULT.buffer().writeByte(0);
    }

    private void writeTenIntegers(StreamMessage<Integer> stream) {
        if (stream instanceof StreamWriter) {
            StreamWriter<Integer> writer = (StreamWriter<Integer>) stream;
            streamValues().forEach(writer::write);
            writer.close();
        }
    }
}