Enabling more websocket tests

This commit is contained in:
Joakim Erdfelt 2017-06-21 07:56:48 -07:00
parent 21b1ecef7b
commit 31705767f0
3 changed files with 213 additions and 224 deletions

View File

@ -179,7 +179,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
private void close(CloseInfo closeInfo, FrameCallback callback) private void close(CloseInfo closeInfo, FrameCallback callback)
{ {
connectionState.onClosing(); // move to CLOSING state (always) connectionState.onClosing(); // always move to (at least) the CLOSING state (might already be past it, which is ok)
if (closeSent.compareAndSet(false, true)) if (closeSent.compareAndSet(false, true))
{ {
@ -192,7 +192,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Close Frame Previously Sent: ignoring: {} [{}]", closeInfo, callback); LOG.debug("Close Frame Previously Sent: ignoring: {} [{}]", closeInfo, callback);
callback.succeed(); callback.fail(new WebSocketException("Already closed"));
} }
} }

View File

@ -1,139 +1,141 @@
// //
// ======================================================================== // ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials // All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0 // are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution. // and Apache License v2.0 which accompanies this distribution.
// //
// The Eclipse Public License is available at // The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html // http://www.eclipse.org/legal/epl-v10.html
// //
// The Apache License v2.0 is available at // The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php // http://www.opensource.org/licenses/apache2.0.php
// //
// You may elect to redistribute this code under either of these licenses. // You may elect to redistribute this code under either of these licenses.
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.websocket.tests; package org.eclipse.jetty.websocket.tests;
import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.CloseInfo;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
public abstract class AbstractTrackingEndpoint<T> public abstract class AbstractTrackingEndpoint<T>
{ {
public final Logger LOG; public final Logger LOG;
public T session; public T session;
public CountDownLatch openLatch = new CountDownLatch(1); public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch closeLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1);
public CountDownLatch errorLatch = new CountDownLatch(1); public CountDownLatch errorLatch = new CountDownLatch(1);
public AtomicReference<CloseInfo> closeInfo = new AtomicReference<>(); public AtomicReference<CloseInfo> closeInfo = new AtomicReference<>();
public AtomicReference<Throwable> error = new AtomicReference<>(); public AtomicReference<Throwable> closeStack = new AtomicReference<>();
public AtomicReference<Throwable> error = new AtomicReference<>();
public AbstractTrackingEndpoint(String id)
{ public AbstractTrackingEndpoint(String id)
LOG = Log.getLogger(this.getClass().getName() + "." + id); {
LOG.debug("init"); LOG = Log.getLogger(this.getClass().getName() + "." + id);
} LOG.debug("init");
}
public void assertCloseInfo(String prefix, int expectedCloseStatusCode, Matcher<? super String> reasonMatcher) throws InterruptedException
{ public void assertCloseInfo(String prefix, int expectedCloseStatusCode, Matcher<? super String> reasonMatcher) throws InterruptedException
CloseInfo close = closeInfo.get(); {
assertThat(prefix + " close info", close, Matchers.notNullValue()); CloseInfo close = closeInfo.get();
assertThat(prefix + " received close code", close.getStatusCode(), Matchers.is(expectedCloseStatusCode)); assertThat(prefix + " close info", close, Matchers.notNullValue());
assertThat(prefix + " received close reason", close.getReason(), reasonMatcher); assertThat(prefix + " received close code", close.getStatusCode(), Matchers.is(expectedCloseStatusCode));
} assertThat(prefix + " received close reason", close.getReason(), reasonMatcher);
}
public void assertErrorEvent(String prefix, Matcher<Throwable> throwableMatcher, Matcher<? super String> messageMatcher)
{ public void assertErrorEvent(String prefix, Matcher<Throwable> throwableMatcher, Matcher<? super String> messageMatcher)
assertThat(prefix + " error event type", error.get(), throwableMatcher); {
assertThat(prefix + " error event message", error.get().getMessage(), messageMatcher); assertThat(prefix + " error event type", error.get(), throwableMatcher);
} assertThat(prefix + " error event message", error.get().getMessage(), messageMatcher);
}
public void assertNoErrorEvents(String prefix)
{ public void assertNoErrorEvents(String prefix)
assertTrue(prefix + " error event should not have occurred", error.get() == null); {
} assertTrue(prefix + " error event should not have occurred", error.get() == null);
}
public void assertNotClosed(String prefix)
{ public void assertNotClosed(String prefix)
assertTrue(prefix + " close event should not have occurred: got " + closeInfo.get(), closeLatch.getCount() > 0); {
} assertTrue(prefix + " close event should not have occurred: got " + closeInfo.get(), closeLatch.getCount() > 0);
}
public void assertNotOpened(String prefix)
{ public void assertNotOpened(String prefix)
assertTrue(prefix + " open event should not have occurred", openLatch.getCount() > 0); {
} assertTrue(prefix + " open event should not have occurred", openLatch.getCount() > 0);
}
public void awaitCloseEvent(String prefix) throws InterruptedException
{ public void awaitCloseEvent(String prefix) throws InterruptedException
assertTrue(prefix + " onClose event", closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); {
} assertTrue(prefix + " onClose event", closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void awaitOpenEvent(String prefix) throws InterruptedException
{ public void awaitOpenEvent(String prefix) throws InterruptedException
assertTrue(prefix + " onOpen event", openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); {
} assertTrue(prefix + " onOpen event", openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void awaitErrorEvent(String prefix) throws InterruptedException
{ public void awaitErrorEvent(String prefix) throws InterruptedException
assertTrue(prefix + " onError event", errorLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); {
} assertTrue(prefix + " onError event", errorLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
protected void onWSOpen(T session)
{ protected void onWSOpen(T session)
this.session = session; {
if (LOG.isDebugEnabled()) this.session = session;
{ if (LOG.isDebugEnabled())
LOG.debug("onWSOpen()"); {
} LOG.debug("onWSOpen()");
this.openLatch.countDown(); }
} this.openLatch.countDown();
}
protected void onWSClose(int statusCode, String reason)
{ protected void onWSClose(int statusCode, String reason)
if(LOG.isDebugEnabled()) {
{ if(LOG.isDebugEnabled())
LOG.debug("onWSClose({}, {})", statusCode, reason); {
} LOG.debug("onWSClose({}, {})", statusCode, reason);
CloseInfo close = new CloseInfo(statusCode, reason); }
if (closeInfo.compareAndSet(null, close) == false) CloseInfo close = new CloseInfo(statusCode, reason);
{ if (closeInfo.compareAndSet(null, close) == false)
LOG.warn("onClose should only happen once - Original Close: " + closeInfo.get()); {
LOG.warn("onClose should only happen once - Extra/Excess Close: " + close); LOG.warn("onClose should only happen once - Original Close: " + closeInfo.get(), closeStack.get());
fail("onClose should only happen once!"); LOG.warn("onClose should only happen once - Extra/Excess Close: " + close, new Throwable("extra/excess"));
} fail("onClose should only happen once!");
this.closeLatch.countDown(); }
} closeStack.compareAndSet(null, new Throwable("original"));
this.closeLatch.countDown();
protected void onWSError(Throwable cause) }
{
if(LOG.isDebugEnabled()) protected void onWSError(Throwable cause)
{ {
LOG.debug("onWSError()", cause); if(LOG.isDebugEnabled())
} {
assertThat("Error must have value", cause, notNullValue()); LOG.debug("onWSError()", cause);
if (error.compareAndSet(null, cause) == false) }
{ assertThat("Error must have value", cause, notNullValue());
LOG.warn("onError should only happen once - Original Cause", error.get()); if (error.compareAndSet(null, cause) == false)
LOG.warn("onError should only happen once - Extra/Excess Cause", cause); {
fail("onError should only happen once!"); LOG.warn("onError should only happen once - Original Cause", error.get());
} LOG.warn("onError should only happen once - Extra/Excess Cause", cause);
this.errorLatch.countDown(); fail("onError should only happen once!");
} }
} this.errorLatch.countDown();
}
}

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.websocket.tests.client; package org.eclipse.jetty.websocket.tests.client;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -26,7 +25,6 @@ import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.IOException; import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI; import java.net.URI;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
@ -51,7 +49,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.api.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketFrame;
@ -62,7 +60,6 @@ import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession; import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestName; import org.junit.rules.TestName;
@ -70,52 +67,52 @@ import org.junit.rules.TestName;
public class ClientCloseTest public class ClientCloseTest
{ {
private static final Logger LOG = Log.getLogger(ClientCloseTest.class); private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
@Rule @Rule
public TestName testname = new TestName(); public TestName testname = new TestName();
@Rule @Rule
public TestTracker tt = new TestTracker(); public TestTracker tt = new TestTracker();
private UntrustedWSServer server; private UntrustedWSServer server;
private WebSocketClient client; private WebSocketClient client;
private void confirmConnection(TrackingEndpoint clientSocket, Future<Session> clientFuture, UntrustedWSSession serverSession) throws Exception private void confirmConnection(TrackingEndpoint clientSocket, Future<Session> clientFuture, UntrustedWSSession serverSession) throws Exception
{ {
// Wait for client connect on via future // Wait for client connect on via future
Session clientSession = clientFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); Session clientSession = clientFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
clientSession.getRemote().setBatchMode(BatchMode.OFF); clientSession.getRemote().setBatchMode(BatchMode.OFF);
// Wait for client connect via client websocket // Wait for client connect via client websocket
assertThat("Client WebSocket is Open", clientSocket.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true)); assertThat("Client WebSocket is Open", clientSocket.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true));
UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint(); UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint();
// Send message from client to server // Send message from client to server
final String echoMsg = "echo-test"; final String echoMsg = "echo-test";
Future<Void> testFut = clientSocket.getRemote().sendStringByFuture(echoMsg); Future<Void> testFut = clientSocket.getRemote().sendStringByFuture(echoMsg);
// Wait for send future // Wait for send future
testFut.get(30, TimeUnit.SECONDS); testFut.get(30, TimeUnit.SECONDS);
// Read Frame on server side // Read Frame on server side
WebSocketFrame frame = serverEndpoint.framesQueue.poll(10, TimeUnit.SECONDS); WebSocketFrame frame = serverEndpoint.framesQueue.poll(10, TimeUnit.SECONDS);
assertThat("Server received frame", frame.getOpCode(), is(OpCode.TEXT)); assertThat("Server received frame", frame.getOpCode(), is(OpCode.TEXT));
assertThat("Server received frame payload", frame.getPayloadAsUTF8(), is(echoMsg)); assertThat("Server received frame payload", frame.getPayloadAsUTF8(), is(echoMsg));
// Server send echo reply // Server send echo reply
serverEndpoint.getRemote().sendString(echoMsg); serverEndpoint.getRemote().sendString(echoMsg);
// Wait for received echo // Wait for received echo
String incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS); String incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS);
// Verify received message // Verify received message
assertThat("Received message", incomingMessage, is(echoMsg)); assertThat("Received message", incomingMessage, is(echoMsg));
// Verify that there are no errors // Verify that there are no errors
assertThat("Error events", clientSocket.error.get(), nullValue()); assertThat("Error events", clientSocket.error.get(), nullValue());
} }
public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP
{ {
@Override @Override
@ -133,16 +130,16 @@ public class ClientCloseTest
}; };
} }
} }
public static class TestEndPoint extends SocketChannelEndPoint public static class TestEndPoint extends SocketChannelEndPoint
{ {
public AtomicBoolean congestedFlush = new AtomicBoolean(false); public AtomicBoolean congestedFlush = new AtomicBoolean(false);
public TestEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler) public TestEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
{ {
super((SocketChannel) channel, selector, key, scheduler); super((SocketChannel) channel, selector, key, scheduler);
} }
@Override @Override
public boolean flush(ByteBuffer... buffers) throws IOException public boolean flush(ByteBuffer... buffers) throws IOException
{ {
@ -152,7 +149,7 @@ public class ClientCloseTest
return flushed; return flushed;
} }
} }
@Before @Before
public void startClient() throws Exception public void startClient() throws Exception
{ {
@ -161,123 +158,113 @@ public class ClientCloseTest
client.addBean(httpClient); client.addBean(httpClient);
client.start(); client.start();
} }
@Before @Before
public void startServer() throws Exception public void startServer() throws Exception
{ {
server = new UntrustedWSServer(); server = new UntrustedWSServer();
server.registerWebSocket("/noclose", (req, resp) -> new WebSocketAdapter()
{
@Override
public void onWebSocketClose(int statusCode, String reason)
{
try
{
getSession().disconnect();
}
catch (IOException ignore)
{
}
}
});
server.start(); server.start();
} }
@After @After
public void stopClient() throws Exception public void stopClient() throws Exception
{ {
client.stop(); client.stop();
} }
@After @After
public void stopServer() throws Exception public void stopServer() throws Exception
{ {
server.stop(); server.stop();
} }
@Test @Test
@Ignore("Not working yet")
public void testNetworkCongestion() throws Exception public void testNetworkCongestion() throws Exception
{ {
// Set client timeout // Set client timeout
final int timeout = 1000; final int timeout = 1000;
client.setMaxIdleTimeout(timeout); client.setMaxIdleTimeout(timeout);
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname); URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>(); CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut); server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects // Client connects
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri); Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
try
// client confirms connection via echo
confirmConnection(clientSocket, clientConnectFuture, serverSession);
// client sends BIG frames (until it cannot write anymore)
// server must not read (for test purpose, in order to congest connection)
// when write is congested, client enqueue close frame
// client initiate write, but write never completes
EndPoint endp = clientSocket.getJettyEndPoint();
assertThat("EndPoint is testable", endp, instanceOf(TestEndPoint.class));
TestEndPoint testendp = (TestEndPoint) endp;
char msg[] = new char[10240];
int writeCount = 0;
long writeSize = 0;
int i = 0;
while (!testendp.congestedFlush.get())
{ {
int z = i - ((i / 26) * 26);
char c = (char) ('a' + z); // client sends frames big enough to break through network layer caching, until it cannot write anymore.
Arrays.fill(msg, c); // server must not read (for test purpose, in order to congest connection)
clientSocket.getRemote().sendStringByFuture(String.valueOf(msg)); // when write is congested, client enqueue close frame
writeCount++; // client initiate write, but write never completes
writeSize += msg.length; EndPoint endp = clientSocket.getJettyEndPoint();
assertThat("EndPoint is testable", endp, instanceOf(TestEndPoint.class));
TestEndPoint testendp = (TestEndPoint) endp;
char msg[] = new char[10240];
int writeCount = 0;
long writeSize = 0;
int i = 0;
while (!testendp.congestedFlush.get())
{
int z = i - ((i / 26) * 26);
char c = (char) ('a' + z);
Arrays.fill(msg, c);
clientSocket.getRemote().sendStringByFuture(String.valueOf(msg));
writeCount++;
writeSize += msg.length;
}
LOG.info("Wrote {} frames totalling {} bytes of payload before congestion kicked in", writeCount, writeSize);
// Verify timeout error
clientSocket.awaitErrorEvent("Client");
clientSocket.assertErrorEvent("Client", instanceOf(WebSocketTimeoutException.class), containsString("Idle Timeout"));
}
finally
{
clientSession.close();
} }
LOG.info("Wrote {} frames totalling {} bytes of payload before congestion kicked in", writeCount, writeSize);
// Verify timeout error
clientSocket.assertErrorEvent("Client", instanceOf(SocketTimeoutException.class), anything());
} }
@Test(timeout = 5000L) @Test(timeout = 5000L)
public void testStopLifecycle() throws Exception public void testStopLifecycle() throws Exception
{ {
// Set client timeout // Set client timeout
final int timeout = 1000; final int timeout = 1000;
client.setMaxIdleTimeout(timeout); client.setMaxIdleTimeout(timeout);
int clientCount = 3; int clientCount = 3;
TrackingEndpoint clientSockets[] = new TrackingEndpoint[clientCount]; TrackingEndpoint clientSockets[] = new TrackingEndpoint[clientCount];
UntrustedWSSession serverSessions[] = new UntrustedWSSession[clientCount]; UntrustedWSSession serverSessions[] = new UntrustedWSSession[clientCount];
// Connect Multiple Clients // Connect Multiple Clients
for (int i = 0; i < clientCount; i++) for (int i = 0; i < clientCount; i++)
{ {
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname).resolve(Integer.toString(i)); URI wsUri = server.getUntrustedWsUri(this.getClass(), testname).resolve(Integer.toString(i));
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>(); CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut); server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client Request Upgrade // Client Request Upgrade
clientSockets[i] = new TrackingEndpoint(testname.getMethodName() + "[" + i + "]"); clientSockets[i] = new TrackingEndpoint(testname.getMethodName() + "[" + i + "]");
Future<Session> clientConnectFuture = client.connect(clientSockets[i], wsUri); Future<Session> clientConnectFuture = client.connect(clientSockets[i], wsUri);
// Server accepts connection // Server accepts connection
serverSessions[i] = serverSessionFut.get(10, TimeUnit.SECONDS); serverSessions[i] = serverSessionFut.get(10, TimeUnit.SECONDS);
// client confirms connection via echo // client confirms connection via echo
confirmConnection(clientSockets[i], clientConnectFuture, serverSessions[i]); confirmConnection(clientSockets[i], clientConnectFuture, serverSessions[i]);
} }
// client lifecycle stop // client lifecycle stop
// every open client should send a close frame // every open client should send a close frame
client.stop(); client.stop();
// clients send close frames (code 1001, shutdown) // clients send close frames (code 1001, shutdown)
for (int i = 0; i < clientCount; i++) for (int i = 0; i < clientCount; i++)
{ {
@ -286,7 +273,7 @@ public class ClientCloseTest
serverEndpoint.awaitCloseEvent("Server"); serverEndpoint.awaitCloseEvent("Server");
serverEndpoint.assertCloseInfo("Server", StatusCode.SHUTDOWN, containsString("Shutdown")); serverEndpoint.assertCloseInfo("Server", StatusCode.SHUTDOWN, containsString("Shutdown"));
} }
// clients disconnect // clients disconnect
for (int i = 0; i < clientCount; i++) for (int i = 0; i < clientCount; i++)
{ {