netty5/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionTest....

708 lines
28 KiB
Java

/*
* Copyright 2014 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.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.local.LocalHandler;
import io.netty.handler.codec.http2.Http2Connection.Endpoint;
import io.netty.handler.codec.http2.Http2Stream.State;
import io.netty.util.concurrent.Promise;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static java.lang.Integer.MAX_VALUE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DefaultHttp2Connection}.
*/
public class DefaultHttp2ConnectionTest {
private DefaultHttp2Connection server;
private DefaultHttp2Connection client;
private static EventLoopGroup group;
@Mock
private Http2Connection.Listener clientListener;
@Mock
private Http2Connection.Listener clientListener2;
@BeforeAll
public static void beforeClass() {
group = new MultithreadEventLoopGroup(2, LocalHandler.newFactory());
}
@AfterAll
public static void afterClass() {
group.shutdownGracefully();
}
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
server = new DefaultHttp2Connection(true);
client = new DefaultHttp2Connection(false);
client.addListener(clientListener);
doAnswer((Answer<Void>) invocation -> {
assertNotNull(client.stream(((Http2Stream) invocation.getArgument(0)).id()));
return null;
}).when(clientListener).onStreamClosed(any(Http2Stream.class));
doAnswer((Answer<Void>) invocation -> {
assertNull(client.stream(((Http2Stream) invocation.getArgument(0)).id()));
return null;
}).when(clientListener).onStreamRemoved(any(Http2Stream.class));
}
@Test
public void getStreamWithoutStreamShouldReturnNull() {
assertNull(server.stream(100));
}
@Test
public void removeAllStreamsWithEmptyStreams() throws InterruptedException {
testRemoveAllStreams();
}
@Test
public void removeAllStreamsWithJustOneLocalStream() throws Exception {
client.local().createStream(3, false);
testRemoveAllStreams();
}
@Test
public void removeAllStreamsWithJustOneRemoveStream() throws Exception {
client.remote().createStream(2, false);
testRemoveAllStreams();
}
@Test
public void removeAllStreamsWithManyActiveStreams() throws Exception {
Endpoint<Http2RemoteFlowController> remote = client.remote();
Endpoint<Http2LocalFlowController> local = client.local();
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
local.createStream(c, false);
remote.createStream(s, false);
}
testRemoveAllStreams();
}
@Test
public void removeIndividualStreamsWhileCloseDoesNotNPE() throws Exception {
final Http2Stream streamA = client.local().createStream(3, false);
final Http2Stream streamB = client.remote().createStream(2, false);
doAnswer((Answer<Void>) invocation -> {
streamA.close();
streamB.close();
return null;
}).when(clientListener2).onStreamClosed(any(Http2Stream.class));
try {
client.addListener(clientListener2);
testRemoveAllStreams();
} finally {
client.removeListener(clientListener2);
}
}
@Test
public void removeAllStreamsWhileIteratingActiveStreams() throws Exception {
final Endpoint<Http2RemoteFlowController> remote = client.remote();
final Endpoint<Http2LocalFlowController> local = client.local();
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
local.createStream(c, false);
remote.createStream(s, false);
}
final Promise<Void> promise = group.next().newPromise();
final CountDownLatch latch = new CountDownLatch(client.numActiveStreams());
client.forEachActiveStream(stream -> {
promise.asFuture().addListener(future -> {
latch.countDown();
});
client.close(promise);
return true;
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void removeAllStreamsWhileIteratingActiveStreamsAndExceptionOccurs()
throws Exception {
final Endpoint<Http2RemoteFlowController> remote = client.remote();
final Endpoint<Http2LocalFlowController> local = client.local();
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
local.createStream(c, false);
remote.createStream(s, false);
}
final Promise<Void> promise = group.next().newPromise();
final CountDownLatch latch = new CountDownLatch(1);
try {
client.forEachActiveStream(stream -> {
// This close call is basically a noop, because the following statement will throw an exception.
client.close(promise);
// Do an invalid operation while iterating.
remote.createStream(3, false);
return true;
});
} catch (Http2Exception ignored) {
promise.asFuture().addListener(future -> {
latch.countDown();
});
client.close(promise);
}
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void goAwayReceivedShouldCloseStreamsGreaterThanLastStream() throws Exception {
Http2Stream stream1 = client.local().createStream(3, false);
Http2Stream stream2 = client.local().createStream(5, false);
Http2Stream remoteStream = client.remote().createStream(4, false);
assertEquals(State.OPEN, stream1.state());
assertEquals(State.OPEN, stream2.state());
client.goAwayReceived(3, 8, null);
assertEquals(State.OPEN, stream1.state());
assertEquals(State.CLOSED, stream2.state());
assertEquals(State.OPEN, remoteStream.state());
assertEquals(3, client.local().lastStreamKnownByPeer());
assertEquals(5, client.local().lastStreamCreated());
// The remote endpoint must not be affected by a received GOAWAY frame.
assertEquals(-1, client.remote().lastStreamKnownByPeer());
assertEquals(State.OPEN, remoteStream.state());
}
@Test
public void goAwaySentShouldCloseStreamsGreaterThanLastStream() throws Exception {
Http2Stream stream1 = server.remote().createStream(3, false);
Http2Stream stream2 = server.remote().createStream(5, false);
Http2Stream localStream = server.local().createStream(4, false);
server.goAwaySent(3, 8, null);
assertEquals(State.OPEN, stream1.state());
assertEquals(State.CLOSED, stream2.state());
assertEquals(3, server.remote().lastStreamKnownByPeer());
assertEquals(5, server.remote().lastStreamCreated());
// The local endpoint must not be affected by a sent GOAWAY frame.
assertEquals(-1, server.local().lastStreamKnownByPeer());
assertEquals(State.OPEN, localStream.state());
}
@Test
public void serverCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.local().createStream(2, false);
assertEquals(2, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(1, server.numActiveStreams());
assertEquals(2, server.local().lastStreamCreated());
stream = server.local().createStream(4, true);
assertEquals(4, stream.id());
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(2, server.numActiveStreams());
assertEquals(4, server.local().lastStreamCreated());
stream = server.remote().createStream(3, true);
assertEquals(3, stream.id());
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(3, server.numActiveStreams());
assertEquals(3, server.remote().lastStreamCreated());
stream = server.remote().createStream(5, false);
assertEquals(5, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(4, server.numActiveStreams());
assertEquals(5, server.remote().lastStreamCreated());
}
@Test
public void clientCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = client.remote().createStream(2, false);
assertEquals(2, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(1, client.numActiveStreams());
assertEquals(2, client.remote().lastStreamCreated());
stream = client.remote().createStream(4, true);
assertEquals(4, stream.id());
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(2, client.numActiveStreams());
assertEquals(4, client.remote().lastStreamCreated());
assertTrue(stream.isHeadersReceived());
stream = client.local().createStream(3, true);
assertEquals(3, stream.id());
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(3, client.numActiveStreams());
assertEquals(3, client.local().lastStreamCreated());
assertTrue(stream.isHeadersSent());
stream = client.local().createStream(5, false);
assertEquals(5, stream.id());
assertEquals(State.OPEN, stream.state());
assertEquals(4, client.numActiveStreams());
assertEquals(5, client.local().lastStreamCreated());
}
@Test
public void serverReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream pushStream = server.local().reservePushStream(2, stream);
assertEquals(2, pushStream.id());
assertEquals(State.RESERVED_LOCAL, pushStream.state());
assertEquals(1, server.numActiveStreams());
assertEquals(2, server.local().lastStreamCreated());
}
@Test
public void clientReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
Http2Stream pushStream = server.local().reservePushStream(4, stream);
assertEquals(4, pushStream.id());
assertEquals(State.RESERVED_LOCAL, pushStream.state());
assertEquals(1, server.numActiveStreams());
assertEquals(4, server.local().lastStreamCreated());
}
@Test
public void serverRemoteIncrementAndGetStreamShouldSucceed() throws Http2Exception {
incrementAndGetStreamShouldSucceed(server.remote());
}
@Test
public void serverLocalIncrementAndGetStreamShouldSucceed() throws Http2Exception {
incrementAndGetStreamShouldSucceed(server.local());
}
@Test
public void clientRemoteIncrementAndGetStreamShouldSucceed() throws Http2Exception {
incrementAndGetStreamShouldSucceed(client.remote());
}
@Test
public void clientLocalIncrementAndGetStreamShouldSucceed() throws Http2Exception {
incrementAndGetStreamShouldSucceed(client.local());
}
@Test
public void serverRemoteIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
incrementAndGetStreamShouldRespectOverflow(server.remote(), MAX_VALUE);
}
@Test
public void serverLocalIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
incrementAndGetStreamShouldRespectOverflow(server.local(), MAX_VALUE - 1);
}
@Test
public void clientRemoteIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
incrementAndGetStreamShouldRespectOverflow(client.remote(), MAX_VALUE - 1);
}
@Test
public void clientLocalIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
incrementAndGetStreamShouldRespectOverflow(client.local(), MAX_VALUE);
}
@Test
public void clientLocalCreateStreamExhaustedSpace() throws Http2Exception {
client.local().createStream(MAX_VALUE, true);
Http2Exception expected = assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
client.local().createStream(MAX_VALUE, true);
}
});
assertEquals(Http2Error.REFUSED_STREAM, expected.error());
assertEquals(Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN, expected.shutdownHint());
}
@Test
public void newStreamBehindExpectedShouldThrow() throws Http2Exception {
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.local().createStream(0, true);
}
});
}
@Test
public void newStreamNotForServerShouldThrow() throws Http2Exception {
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.local().createStream(11, true);
}
});
}
@Test
public void newStreamNotForClientShouldThrow() throws Http2Exception {
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
client.local().createStream(10, true);
}
});
}
@Test
public void createShouldThrowWhenMaxAllowedStreamsOpenExceeded() throws Http2Exception {
server.local().maxActiveStreams(0);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.local().createStream(2, true);
}
});
}
@Test
public void serverCreatePushShouldFailOnRemoteEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
server = new DefaultHttp2Connection(true, 0);
server.remote().maxActiveStreams(1);
final Http2Stream requestStream = server.remote().createStream(3, false);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.remote().reservePushStream(2, requestStream);
}
});
}
@Test
public void clientCreatePushShouldFailOnRemoteEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
client = new DefaultHttp2Connection(false, 0);
client.remote().maxActiveStreams(1);
final Http2Stream requestStream = client.remote().createStream(2, false);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
client.remote().reservePushStream(4, requestStream);
}
});
}
@Test
public void serverCreatePushShouldSucceedOnLocalEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
server = new DefaultHttp2Connection(true, 0);
server.local().maxActiveStreams(1);
Http2Stream requestStream = server.remote().createStream(3, false);
assertNotNull(server.local().reservePushStream(2, requestStream));
}
@Test
public void reserveWithPushDisallowedShouldThrow() throws Http2Exception {
final Http2Stream stream = server.remote().createStream(3, true);
server.remote().allowPushTo(false);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.local().reservePushStream(2, stream);
}
});
}
@Test
public void goAwayReceivedShouldDisallowLocalCreation() throws Http2Exception {
server.goAwayReceived(0, 1L, Unpooled.EMPTY_BUFFER);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.local().createStream(3, true);
}
});
}
@Test
public void goAwayReceivedShouldAllowRemoteCreation() throws Http2Exception {
server.goAwayReceived(0, 1L, Unpooled.EMPTY_BUFFER);
server.remote().createStream(3, true);
}
@Test
public void goAwaySentShouldDisallowRemoteCreation() throws Http2Exception {
server.goAwaySent(0, 1L, Unpooled.EMPTY_BUFFER);
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
server.remote().createStream(2, true);
}
});
}
@Test
public void goAwaySentShouldAllowLocalCreation() throws Http2Exception {
server.goAwaySent(0, 1L, Unpooled.EMPTY_BUFFER);
server.local().createStream(2, true);
}
@Test
public void closeShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
stream.close();
assertEquals(State.CLOSED, stream.state());
assertEquals(0, server.numActiveStreams());
}
@Test
public void closeLocalWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, false);
stream.closeLocalSide();
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
assertEquals(1, server.numActiveStreams());
}
@Test
public void closeRemoteWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, false);
stream.closeRemoteSide();
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertEquals(1, server.numActiveStreams());
}
@Test
public void closeOnlyOpenSideShouldClose() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, true);
stream.closeLocalSide();
assertEquals(State.CLOSED, stream.state());
assertEquals(0, server.numActiveStreams());
}
@SuppressWarnings("NumericOverflow")
@Test
public void localStreamInvalidStreamIdShouldThrow() {
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
client.local().createStream(MAX_VALUE + 2, false);
}
});
}
@SuppressWarnings("NumericOverflow")
@Test
public void remoteStreamInvalidStreamIdShouldThrow() {
assertThrows(Http2Exception.class, new Executable() {
@Override
public void execute() throws Throwable {
client.remote().createStream(MAX_VALUE + 1, false);
}
});
}
/**
* We force {@link #clientListener} methods to all throw a {@link RuntimeException} and verify the following:
* <ol>
* <li>all listener methods are called for both {@link #clientListener} and {@link #clientListener2}</li>
* <li>{@link #clientListener2} is notified after {@link #clientListener}</li>
* <li>{@link #clientListener2} methods are all still called despite {@link #clientListener}'s
* method throwing a {@link RuntimeException}</li>
* </ol>
*/
@Test
public void listenerThrowShouldNotPreventOtherListenersFromBeingNotified() throws Http2Exception {
final boolean[] calledArray = new boolean[128];
// The following setup will ensure that clientListener throws exceptions, and marks a value in an array
// such that clientListener2 will verify that is is set or fail the test.
int methodIndex = 0;
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamAdded(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamAdded(any(Http2Stream.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamActive(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamActive(any(Http2Stream.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamHalfClosed(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamHalfClosed(any(Http2Stream.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamClosed(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamClosed(any(Http2Stream.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamRemoved(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamRemoved(any(Http2Stream.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
.when(clientListener).onStreamAdded(any(Http2Stream.class));
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
.when(clientListener2).onStreamAdded(any(Http2Stream.class));
// Now we add clientListener2 and exercise all listener functionality
try {
client.addListener(clientListener2);
Http2Stream stream = client.local().createStream(3, false);
verify(clientListener).onStreamAdded(any(Http2Stream.class));
verify(clientListener2).onStreamAdded(any(Http2Stream.class));
verify(clientListener).onStreamActive(any(Http2Stream.class));
verify(clientListener2).onStreamActive(any(Http2Stream.class));
Http2Stream reservedStream = client.remote().reservePushStream(2, stream);
verify(clientListener, never()).onStreamActive(streamEq(reservedStream));
verify(clientListener2, never()).onStreamActive(streamEq(reservedStream));
reservedStream.open(false);
verify(clientListener).onStreamActive(streamEq(reservedStream));
verify(clientListener2).onStreamActive(streamEq(reservedStream));
stream.closeLocalSide();
verify(clientListener).onStreamHalfClosed(any(Http2Stream.class));
verify(clientListener2).onStreamHalfClosed(any(Http2Stream.class));
stream.close();
verify(clientListener).onStreamClosed(any(Http2Stream.class));
verify(clientListener2).onStreamClosed(any(Http2Stream.class));
verify(clientListener).onStreamRemoved(any(Http2Stream.class));
verify(clientListener2).onStreamRemoved(any(Http2Stream.class));
client.goAwaySent(client.connectionStream().id(), Http2Error.INTERNAL_ERROR.code(), Unpooled.EMPTY_BUFFER);
verify(clientListener).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
verify(clientListener2).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
client.goAwayReceived(client.connectionStream().id(),
Http2Error.INTERNAL_ERROR.code(), Unpooled.EMPTY_BUFFER);
verify(clientListener).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
verify(clientListener2).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
} finally {
client.removeListener(clientListener2);
}
}
private void testRemoveAllStreams() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final Promise<Void> promise = group.next().newPromise();
promise.asFuture().addListener(future -> {
latch.countDown();
});
client.close(promise);
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
private static void incrementAndGetStreamShouldRespectOverflow(final Endpoint<?> endpoint, int streamId) {
assertTrue(streamId > 0);
try {
endpoint.createStream(streamId, true);
streamId = endpoint.incrementAndGetNextStreamId();
} catch (Throwable t) {
fail(t);
}
assertTrue(streamId < 0);
final int finalStreamId = streamId;
assertThrows(Http2NoMoreStreamIdsException.class, new Executable() {
@Override
public void execute() throws Throwable {
endpoint.createStream(finalStreamId, true);
}
});
}
private static void incrementAndGetStreamShouldSucceed(Endpoint<?> endpoint) throws Http2Exception {
Http2Stream streamA = endpoint.createStream(endpoint.incrementAndGetNextStreamId(), true);
Http2Stream streamB = endpoint.createStream(streamA.id() + 2, true);
Http2Stream streamC = endpoint.createStream(endpoint.incrementAndGetNextStreamId(), true);
assertEquals(streamB.id() + 2, streamC.id());
endpoint.createStream(streamC.id() + 2, true);
}
private static final class ListenerExceptionThrower implements Answer<Void> {
private static final RuntimeException FAKE_EXCEPTION = new RuntimeException("Fake Exception");
private final boolean[] array;
private final int index;
ListenerExceptionThrower(boolean[] array, int index) {
this.array = array;
this.index = index;
}
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
array[index] = true;
throw FAKE_EXCEPTION;
}
}
private static final class ListenerVerifyCallAnswer implements Answer<Void> {
private final boolean[] array;
private final int index;
ListenerVerifyCallAnswer(boolean[] array, int index) {
this.array = array;
this.index = index;
}
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertTrue(array[index]);
return null;
}
}
@SuppressWarnings("unchecked")
private static <T> T streamEq(T stream) {
return (T) (stream == null ? ArgumentMatchers.<Http2Stream>isNull() : eq(stream));
}
}