aboutsummaryrefslogtreecommitdiffstats
path: root/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-24 14:34:39 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:11 +0100
commite90f0cd525c619b1d0e21bfcd7466a99853ee710 (patch)
tree91e8ed0bd2599a099dd67cc4e6132c375333b453 /language-web
parenttest(web): websockets fixes and tests (diff)
downloadrefinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.tar.gz
refinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.tar.zst
refinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.zip
test(web): more websocket integration tests
Diffstat (limited to 'language-web')
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java2
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java232
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java (renamed from language-web/src/test/java/tools/refinery/language/web/xtext/servlet/AwaitTerminationExecutorServiceProvider.java)2
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java (renamed from language-web/src/test/java/tools/refinery/language/web/ProblemWebInjectorProvider.java)5
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java (renamed from language-web/src/test/java/tools/refinery/language/web/xtext/servlet/RestartableCachedThreadPool.java)2
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java98
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java3
7 files changed, 238 insertions, 106 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
index cde7278f..ffd903d0 100644
--- a/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
+++ b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
@@ -73,7 +73,7 @@ public class ServerLauncher {
73 problemServletHolder.setInitParameter(XtextWebSocketServlet.ALLOWED_ORIGINS_INIT_PARAM, 73 problemServletHolder.setInitParameter(XtextWebSocketServlet.ALLOWED_ORIGINS_INIT_PARAM,
74 allowedOriginsString); 74 allowedOriginsString);
75 } 75 }
76 handler.addServlet(problemServletHolder, "/xtext-service/*"); 76 handler.addServlet(problemServletHolder, "/xtext-service");
77 JettyWebSocketServletContainerInitializer.configure(handler, null); 77 JettyWebSocketServletContainerInitializer.configure(handler, null);
78 } 78 }
79 79
diff --git a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 60581b5c..5ccd155f 100644
--- a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -3,25 +3,25 @@ package tools.refinery.language.web;
3import static org.hamcrest.MatcherAssert.assertThat; 3import static org.hamcrest.MatcherAssert.assertThat;
4import static org.hamcrest.Matchers.equalTo; 4import static org.hamcrest.Matchers.equalTo;
5import static org.hamcrest.Matchers.hasSize; 5import static org.hamcrest.Matchers.hasSize;
6import static org.hamcrest.Matchers.instanceOf;
6import static org.hamcrest.Matchers.startsWith; 7import static org.hamcrest.Matchers.startsWith;
7import static org.junit.jupiter.api.Assertions.fail; 8import static org.junit.jupiter.api.Assertions.assertThrows;
8 9
9import java.io.IOException; 10import java.io.IOException;
10import java.net.InetSocketAddress; 11import java.net.InetSocketAddress;
11import java.net.URI; 12import java.net.URI;
12import java.time.Duration; 13import java.util.concurrent.CompletableFuture;
13import java.util.ArrayList; 14import java.util.concurrent.CompletionException;
14import java.util.List;
15 15
16import org.eclipse.jetty.http.HttpHeader;
17import org.eclipse.jetty.http.HttpStatus;
16import org.eclipse.jetty.server.Server; 18import org.eclipse.jetty.server.Server;
17import org.eclipse.jetty.servlet.ServletContextHandler; 19import org.eclipse.jetty.servlet.ServletContextHandler;
20import org.eclipse.jetty.servlet.ServletHolder;
18import org.eclipse.jetty.websocket.api.Session; 21import org.eclipse.jetty.websocket.api.Session;
19import org.eclipse.jetty.websocket.api.StatusCode; 22import org.eclipse.jetty.websocket.api.StatusCode;
20import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
21import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
22import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
23import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
24import org.eclipse.jetty.websocket.api.annotations.WebSocket; 23import org.eclipse.jetty.websocket.api.annotations.WebSocket;
24import org.eclipse.jetty.websocket.api.exceptions.UpgradeException;
25import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; 25import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
26import org.eclipse.jetty.websocket.client.WebSocketClient; 26import org.eclipse.jetty.websocket.client.WebSocketClient;
27import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; 27import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
@@ -30,13 +30,17 @@ import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento;
30import org.junit.jupiter.api.AfterEach; 30import org.junit.jupiter.api.AfterEach;
31import org.junit.jupiter.api.BeforeEach; 31import org.junit.jupiter.api.BeforeEach;
32import org.junit.jupiter.api.Test; 32import org.junit.jupiter.api.Test;
33import org.junit.jupiter.params.ParameterizedTest;
34import org.junit.jupiter.params.provider.ValueSource;
33 35
36import tools.refinery.language.web.tests.WebSocketIntegrationTestClient;
37import tools.refinery.language.web.xtext.servlet.XtextStatusCode;
34import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; 38import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet;
35 39
36class ProblemWebSocketServletIntegrationTest { 40class ProblemWebSocketServletIntegrationTest {
37 private static int SERVER_PORT = 28080; 41 private static int SERVER_PORT = 28080;
38 42
39 private static long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis(); 43 private static String SERVLET_URI = "/xtext-service";
40 44
41 private GlobalStateMemento stateBeforeInjectorCreation; 45 private GlobalStateMemento stateBeforeInjectorCreation;
42 46
@@ -45,128 +49,156 @@ class ProblemWebSocketServletIntegrationTest {
45 private WebSocketClient client; 49 private WebSocketClient client;
46 50
47 @BeforeEach 51 @BeforeEach
48 void startServer() throws Exception { 52 void beforeEach() throws Exception {
49 stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); 53 stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState();
50 server = new Server(new InetSocketAddress(SERVER_PORT));
51 var handler = new ServletContextHandler();
52 handler.addServlet(ProblemWebSocketServlet.class, "/xtext-service/*");
53 JettyWebSocketServletContainerInitializer.configure(handler, null);
54 server.setHandler(handler);
55 server.start();
56 client = new WebSocketClient(); 54 client = new WebSocketClient();
57 client.start(); 55 client.start();
58 } 56 }
59 57
60 @AfterEach 58 @AfterEach
61 void stopServer() throws Exception { 59 void afterEach() throws Exception {
62 client.stop(); 60 client.stop();
63 server.stop(); 61 client = null;
62 if (server != null) {
63 server.stop();
64 server = null;
65 }
64 stateBeforeInjectorCreation.restoreGlobalState(); 66 stateBeforeInjectorCreation.restoreGlobalState();
67 stateBeforeInjectorCreation = null;
65 } 68 }
66 69
67 @Test 70 @Test
68 void updateTest() throws IOException { 71 void updateTest() {
72 startServer(null);
69 var clientSocket = new UpdateTestClient(); 73 var clientSocket = new UpdateTestClient();
70 var upgradeRequest = new ClientUpgradeRequest(); 74 var session = connect(clientSocket, null, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1);
71 upgradeRequest.setSubProtocols(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1);
72 var sessionFuture = client.connect(clientSocket, URI.create("ws://localhost:" + SERVER_PORT + "/xtext-service"),
73 upgradeRequest);
74 var session = sessionFuture.join();
75 assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), 75 assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(),
76 equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); 76 equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1));
77 clientSocket.waitForTestResult(); 77 clientSocket.waitForTestResult();
78 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
79 var responses = clientSocket.getResponses();
80 assertThat(responses, hasSize(5));
81 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
82 assertThat(responses.get(1), startsWith(
83 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\",\"push\":{\"regions\":["));
84 assertThat(responses.get(2), equalTo(
85 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\",\"push\":{\"issues\":[]}}"));
86 assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}"));
87 assertThat(responses.get(4), startsWith(
88 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\",\"push\":{\"regions\":["));
78 } 89 }
79 90
80 @WebSocket 91 @WebSocket
81 public static class UpdateTestClient { 92 public static class UpdateTestClient extends WebSocketIntegrationTestClient {
82 private boolean finished = false; 93 @Override
83 94 protected void arrange(Session session, int responsesReceived) throws IOException {
84 private Object lock = new Object(); 95 switch (responsesReceived) {
96 case 0 -> session.getRemote().sendString(
97 "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}");
98 case 3 -> session.getRemote().sendString(
99 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"class Car.\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}");
100 case 5 -> session.close();
101 }
102 }
103 }
85 104
86 private Throwable error; 105 @Test
106 void badSubProtocolTest() {
107 startServer(null);
108 var clientSocket = new CloseImmediatelyTestClient();
109 var session = connect(clientSocket, null, "<invalid sub-protocol>");
110 assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), equalTo(null));
111 clientSocket.waitForTestResult();
112 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
113 }
87 114
88 private int closeStatusCode; 115 @WebSocket
116 public static class CloseImmediatelyTestClient extends WebSocketIntegrationTestClient {
117 @Override
118 protected void arrange(Session session, int responsesReceived) throws IOException {
119 session.close();
120 }
121 }
89 122
90 private String closeReason; 123 @Test
124 void subProtocolNegotiationTest() {
125 startServer(null);
126 var clientSocket = new CloseImmediatelyTestClient();
127 var session = connect(clientSocket, null, "<invalid sub-protocol>", XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1);
128 assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(),
129 equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1));
130 clientSocket.waitForTestResult();
131 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
132 }
91 133
92 private List<String> responses = new ArrayList<>(); 134 @Test
135 void invalidJsonTest() {
136 startServer(null);
137 var clientSocket = new InvalidJsonTestClient();
138 connect(clientSocket, null, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1);
139 clientSocket.waitForTestResult();
140 assertThat(clientSocket.getCloseStatusCode(), equalTo(XtextStatusCode.INVALID_JSON));
141 }
93 142
94 @OnWebSocketConnect 143 @WebSocket
95 public void onConnect(Session session) { 144 public static class InvalidJsonTestClient extends WebSocketIntegrationTestClient {
96 try { 145 @Override
97 session.getRemote().sendString( 146 protected void arrange(Session session, int responsesReceived) throws IOException {
98 "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); 147 session.getRemote().sendString("<invalid json>");
99 } catch (IOException e) {
100 finishedWithError(e);
101 }
102 } 148 }
149 }
103 150
104 @OnWebSocketClose 151 @ParameterizedTest(name = "Origin: {0}")
105 public void onClose(int statusCode, String reason) { 152 @ValueSource(strings = { "https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE" })
106 closeStatusCode = statusCode; 153 void validOriginTest(String origin) {
107 closeReason = reason; 154 startServer("https://refinery.example;https://refinery.example:443");
108 testFinished(); 155 var clientSocket = new CloseImmediatelyTestClient();
109 } 156 connect(clientSocket, origin, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1);
157 clientSocket.waitForTestResult();
158 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
159 }
110 160
111 @OnWebSocketError 161 @Test
112 public void onError(Throwable error) { 162 void invalidOriginTest() {
113 finishedWithError(error); 163 startServer("https://refinery.example;https://refinery.example:443");
114 } 164 var clientSocket = new CloseImmediatelyTestClient();
165 var exception = assertThrows(CompletionException.class,
166 () -> connect(clientSocket, "https://invalid.example", XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1));
167 var innerException = exception.getCause();
168 assertThat(innerException, instanceOf(UpgradeException.class));
169 assertThat(((UpgradeException) innerException).getResponseStatusCode(), equalTo(HttpStatus.FORBIDDEN_403));
170 }
115 171
116 @OnWebSocketMessage 172 private void startServer(String allowedOrigins) {
117 public void onMessage(Session session, String message) { 173 server = new Server(new InetSocketAddress(SERVER_PORT));
118 try { 174 var handler = new ServletContextHandler();
119 responses.add(message); 175 var holder = new ServletHolder(ProblemWebSocketServlet.class);
120 switch (responses.size()) { 176 if (allowedOrigins != null) {
121 case 3 -> session.getRemote().sendString( 177 holder.setInitParameter(ProblemWebSocketServlet.ALLOWED_ORIGINS_INIT_PARAM, allowedOrigins);
122 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"class Car.\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}");
123 case 5 -> session.close();
124 }
125 } catch (IOException e) {
126 finishedWithError(e);
127 }
128 } 178 }
129 179 handler.addServlet(holder, SERVLET_URI);
130 private void finishedWithError(Throwable t) { 180 JettyWebSocketServletContainerInitializer.configure(handler, null);
131 error = t; 181 server.setHandler(handler);
132 testFinished(); 182 try {
183 server.start();
184 } catch (Exception e) {
185 throw new RuntimeException("Failed to start websocket server");
133 } 186 }
187 }
134 188
135 private void testFinished() { 189 private Session connect(Object webSocketClient, String origin, String... subProtocols) {
136 synchronized (lock) { 190 var upgradeRequest = new ClientUpgradeRequest();
137 finished = true; 191 if (origin != null) {
138 lock.notify(); 192 upgradeRequest.setHeader(HttpHeader.ORIGIN.name(), origin);
139 }
140 } 193 }
141 194 upgradeRequest.setSubProtocols(subProtocols);
142 public void waitForTestResult() { 195 CompletableFuture<Session> sessionFuture;
143 synchronized (lock) { 196 try {
144 if (!finished) { 197 sessionFuture = client.connect(webSocketClient, URI.create("ws://localhost:" + SERVER_PORT + SERVLET_URI),
145 try { 198 upgradeRequest);
146 lock.wait(TIMEOUT_MILLIS); 199 } catch (IOException e) {
147 } catch (InterruptedException e) { 200 throw new AssertionError("Unexpected exception while connection to websocket", e);
148 fail("Unexpected InterruptedException", e);
149 }
150 }
151 }
152 if (!finished) {
153 fail("Test still not finished after timeout");
154 }
155 if (error != null) {
156 fail("Unexpected exception in websocket thread", error);
157 }
158 if (closeStatusCode != StatusCode.NORMAL) {
159 fail("Abnormal close status " + closeStatusCode + ": " + closeReason);
160 }
161 assertThat(responses, hasSize(5));
162 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
163 assertThat(responses.get(1), startsWith(
164 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\",\"push\":{\"regions\":["));
165 assertThat(responses.get(2), equalTo(
166 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\",\"push\":{\"issues\":[]}}"));
167 assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}"));
168 assertThat(responses.get(4), startsWith(
169 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\",\"push\":{\"regions\":["));
170 } 201 }
202 return sessionFuture.join();
171 } 203 }
172} 204}
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/AwaitTerminationExecutorServiceProvider.java b/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java
index 25bcec37..b70d0ed5 100644
--- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/AwaitTerminationExecutorServiceProvider.java
+++ b/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java
@@ -1,4 +1,4 @@
1package tools.refinery.language.web.xtext.servlet; 1package tools.refinery.language.web.tests;
2 2
3import java.util.ArrayList; 3import java.util.ArrayList;
4import java.util.List; 4import java.util.List;
diff --git a/language-web/src/test/java/tools/refinery/language/web/ProblemWebInjectorProvider.java b/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
index 2db4590e..43c12faa 100644
--- a/language-web/src/test/java/tools/refinery/language/web/ProblemWebInjectorProvider.java
+++ b/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
@@ -1,4 +1,4 @@
1package tools.refinery.language.web; 1package tools.refinery.language.web.tests;
2 2
3import org.eclipse.xtext.ide.ExecutorServiceProvider; 3import org.eclipse.xtext.ide.ExecutorServiceProvider;
4import org.eclipse.xtext.util.DisposableRegistry; 4import org.eclipse.xtext.util.DisposableRegistry;
@@ -9,7 +9,8 @@ import com.google.inject.Injector;
9 9
10import tools.refinery.language.ide.ProblemIdeModule; 10import tools.refinery.language.ide.ProblemIdeModule;
11import tools.refinery.language.tests.ProblemInjectorProvider; 11import tools.refinery.language.tests.ProblemInjectorProvider;
12import tools.refinery.language.web.xtext.servlet.AwaitTerminationExecutorServiceProvider; 12import tools.refinery.language.web.ProblemWebModule;
13import tools.refinery.language.web.ProblemWebSetup;
13 14
14public class ProblemWebInjectorProvider extends ProblemInjectorProvider { 15public class ProblemWebInjectorProvider extends ProblemInjectorProvider {
15 16
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/RestartableCachedThreadPool.java b/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
index 02ef38e2..1468273d 100644
--- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/RestartableCachedThreadPool.java
+++ b/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
@@ -1,4 +1,4 @@
1package tools.refinery.language.web.xtext.servlet; 1package tools.refinery.language.web.tests;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.List; 4import java.util.List;
diff --git a/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java b/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java
new file mode 100644
index 00000000..49464d27
--- /dev/null
+++ b/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java
@@ -0,0 +1,98 @@
1package tools.refinery.language.web.tests;
2
3import static org.junit.jupiter.api.Assertions.fail;
4
5import java.io.IOException;
6import java.time.Duration;
7import java.util.ArrayList;
8import java.util.List;
9
10import org.eclipse.jetty.websocket.api.Session;
11import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
12import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
13import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
14import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
15
16public abstract class WebSocketIntegrationTestClient {
17 private static long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis();
18
19 private boolean finished = false;
20
21 private Object lock = new Object();
22
23 private Throwable error;
24
25 private int closeStatusCode;
26
27 private List<String> responses = new ArrayList<>();
28
29 public int getCloseStatusCode() {
30 return closeStatusCode;
31 }
32
33 public List<String> getResponses() {
34 return responses;
35 }
36
37 @OnWebSocketConnect
38 public void onConnect(Session session) {
39 arrangeAndCatchErrors(session);
40 }
41
42 private void arrangeAndCatchErrors(Session session) {
43 try {
44 arrange(session, responses.size());
45 } catch (Exception e) {
46 finishedWithError(e);
47 }
48 }
49
50 protected abstract void arrange(Session session, int responsesReceived) throws IOException;
51
52 @OnWebSocketClose
53 public void onClose(int statusCode, String reason) {
54 closeStatusCode = statusCode;
55 testFinished();
56 }
57
58 @OnWebSocketError
59 public void onError(Throwable error) {
60 finishedWithError(error);
61 }
62
63 @OnWebSocketMessage
64 public void onMessage(Session session, String message) {
65 responses.add(message);
66 arrangeAndCatchErrors(session);
67 }
68
69 private void finishedWithError(Throwable t) {
70 error = t;
71 testFinished();
72 }
73
74 private void testFinished() {
75 synchronized (lock) {
76 finished = true;
77 lock.notify();
78 }
79 }
80
81 public void waitForTestResult() {
82 synchronized (lock) {
83 if (!finished) {
84 try {
85 lock.wait(TIMEOUT_MILLIS);
86 } catch (InterruptedException e) {
87 fail("Unexpected InterruptedException", e);
88 }
89 }
90 }
91 if (!finished) {
92 fail("Test still not finished after timeout");
93 }
94 if (error != null) {
95 fail("Unexpected exception in websocket thread", error);
96 }
97 }
98}
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index 2d3f45d6..0892954b 100644
--- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -26,7 +26,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
26 26
27import com.google.inject.Inject; 27import com.google.inject.Inject;
28 28
29import tools.refinery.language.web.ProblemWebInjectorProvider; 29import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider;
30import tools.refinery.language.web.tests.ProblemWebInjectorProvider;
30import tools.refinery.language.web.xtext.server.ResponseHandler; 31import tools.refinery.language.web.xtext.server.ResponseHandler;
31import tools.refinery.language.web.xtext.server.ResponseHandlerException; 32import tools.refinery.language.web.xtext.server.ResponseHandlerException;
32import tools.refinery.language.web.xtext.server.TransactionExecutor; 33import tools.refinery.language.web.xtext.server.TransactionExecutor;