netty5/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2StreamChannelBootstrapTest.java
Chris Vest 0cb4cc4e49
Make Promise not extend Future (#11634)
Motivation:
We wish to separate these two into clearer write/read interfaces.
In particular, we don't want to be able to add listeners to promises, because it makes it easy to add them out of order.
We can't prevent it entirely, because any promise can be freely converted to a future where listeners can be added.
We can, however, discourage this in the API.

Modification:
The Promise interface no longer extends the Future interface.
Numerous changes to make the project compile and its tests run.

Result:
Clearer separation of concerns in the code.
2021-09-02 10:46:54 +02:00

154 lines
6.3 KiB
Java

/*
* Copyright 2020 The Netty Project
*
* The Netty Project 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:
*
* https://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 io.netty.handler.codec.http2;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalHandler;
import io.netty.channel.local.LocalServerChannel;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.hamcrest.core.IsInstanceOf;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import static io.netty.handler.codec.http2.Http2FrameCodecBuilder.forClient;
import static io.netty.handler.codec.http2.Http2FrameCodecBuilder.forServer;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class Http2StreamChannelBootstrapTest {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(Http2StreamChannelBootstrapTest.class);
private volatile Channel serverConnectedChannel;
@Test
public void testStreamIsNotCreatedIfParentConnectionIsClosedConcurrently() throws Exception {
EventLoopGroup group = null;
Channel serverChannel = null;
Channel clientChannel = null;
try {
final CountDownLatch serverChannelLatch = new CountDownLatch(1);
group = new MultithreadEventLoopGroup(LocalHandler.newFactory());
LocalAddress serverAddress = new LocalAddress(getClass().getName());
ServerBootstrap sb = new ServerBootstrap()
.channel(LocalServerChannel.class)
.group(group)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
serverConnectedChannel = ch;
ch.pipeline().addLast(forServer().build(), newMultiplexedHandler());
serverChannelLatch.countDown();
}
});
serverChannel = sb.bind(serverAddress).get();
Bootstrap cb = new Bootstrap()
.channel(LocalChannel.class)
.group(group)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(forClient().build(), newMultiplexedHandler());
}
});
clientChannel = cb.connect(serverAddress).get();
assertTrue(serverChannelLatch.await(3, SECONDS));
Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(clientChannel);
final Promise<Http2StreamChannel> promise = clientChannel.executor().newPromise();
clientChannel.close().sync();
bootstrap.open(promise);
ExecutionException exception = assertThrows(ExecutionException.class, new Executable() {
@Override
public void execute() throws Throwable {
promise.asFuture().get(3, SECONDS);
}
});
assertThat(exception.getCause(), IsInstanceOf.instanceOf(ClosedChannelException.class));
} finally {
safeClose(clientChannel);
safeClose(serverConnectedChannel);
safeClose(serverChannel);
if (group != null) {
group.shutdownGracefully(0, 3, SECONDS);
}
}
}
private static Http2MultiplexHandler newMultiplexedHandler() {
return new Http2MultiplexHandler(new ChannelInitializer<Http2StreamChannel>() {
@Override
protected void initChannel(Http2StreamChannel ch) {
// noop
}
});
}
private static void safeClose(Channel channel) {
if (channel != null) {
try {
channel.close().syncUninterruptibly();
} catch (Exception e) {
logger.error(e);
}
}
}
@Test
public void open0FailsPromiseOnHttp2MultiplexHandlerError() {
Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(mock(Channel.class));
Http2MultiplexHandler handler = new Http2MultiplexHandler(mock(ChannelHandler.class));
EventExecutor executor = mock(EventExecutor.class);
when(executor.inEventLoop()).thenReturn(true);
ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
when(ctx.executor()).thenReturn(executor);
when(ctx.handler()).thenReturn(handler);
Promise<Http2StreamChannel> promise = new DefaultPromise<>(mock(EventExecutor.class));
bootstrap.open0(ctx, promise);
assertThat(promise.isDone(), is(true));
assertThat(promise.cause(), is(instanceOf(IllegalStateException.class)));
}
}