aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 19:41:32 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 20:29:02 +0200
commita3f1e6872f4f768d14899a1e70bbdc14f32e478d (patch)
treeb2daf0c81724f31ee190f5d63eb42988086dabf2 /subprojects/language-web
parentfix: nullary model initialization (diff)
downloadrefinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.gz
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.zst
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.zip
feat: improve semantics error reporting
Also makes model seeds cancellable to reduce server load during semantic analysis.
Diffstat (limited to 'subprojects/language-web')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java99
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java (renamed from subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java)2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java3
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java43
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java44
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java3
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java9
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java8
9 files changed, 196 insertions, 28 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
new file mode 100644
index 00000000..aa14f39d
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.reasoning.seed.Seed;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.viatra.runtime.CancellationToken;
15
16import java.util.Set;
17
18class CancellableSeed<T> implements Seed<T> {
19 private final CancellationToken cancellationToken;
20 private final Seed<T> seed;
21
22 private CancellableSeed(CancellationToken cancellationToken, Seed<T> seed) {
23 this.cancellationToken = cancellationToken;
24 this.seed = seed;
25 }
26
27 @Override
28 public int arity() {
29 return seed.arity();
30 }
31
32 @Override
33 public Class<T> valueType() {
34 return seed.valueType();
35 }
36
37 @Override
38 public T reducedValue() {
39 return seed.reducedValue();
40 }
41
42 @Override
43 public T get(Tuple key) {
44 return seed.get(key);
45 }
46
47 @Override
48 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
49 return new CancellableCursor<>(cancellationToken, seed.getCursor(defaultValue, nodeCount));
50 }
51
52 public static ModelSeed wrap(CancellationToken cancellationToken, ModelSeed modelSeed) {
53 var builder = ModelSeed.builder(modelSeed.getNodeCount());
54 for (var partialSymbol : modelSeed.getSeededSymbols()) {
55 wrap(cancellationToken, (PartialSymbol<?, ?>) partialSymbol, modelSeed, builder);
56 }
57 return builder.build();
58 }
59
60 private static <A, C> void wrap(CancellationToken cancellationToken, PartialSymbol<A, C> partialSymbol,
61 ModelSeed originalModelSeed, ModelSeed.Builder builder) {
62 var originalSeed = originalModelSeed.getSeed(partialSymbol);
63 builder.seed(partialSymbol, new CancellableSeed<>(cancellationToken, originalSeed));
64 }
65
66 private record CancellableCursor<T>(CancellationToken cancellationToken, Cursor<Tuple, T> cursor)
67 implements Cursor<Tuple, T> {
68 @Override
69 public Tuple getKey() {
70 return cursor.getKey();
71 }
72
73 @Override
74 public T getValue() {
75 return cursor.getValue();
76 }
77
78 @Override
79 public boolean isTerminated() {
80 return cursor.isTerminated();
81 }
82
83 @Override
84 public boolean move() {
85 cancellationToken.checkCancelled();
86 return cursor.move();
87 }
88
89 @Override
90 public boolean isDirty() {
91 return cursor.isDirty();
92 }
93
94 @Override
95 public Set<AnyVersionedMap> getDependingMaps() {
96 return cursor.getDependingMaps();
97 }
98 }
99}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
index ce34ef6c..ff592e93 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
@@ -5,5 +5,5 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8public record SemanticsErrorResult(String error) implements SemanticsResult { 8public record SemanticsInternalErrorResult(String error) implements SemanticsResult {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
new file mode 100644
index 00000000..644bd179
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.validation.ValidationResult;
9
10import java.util.List;
11
12public record SemanticsIssuesResult(List<ValidationResult.Issue> issues) implements SemanticsResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
index 92639578..a2e19a2f 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
@@ -7,5 +7,6 @@ package tools.refinery.language.web.semantics;
7 7
8import org.eclipse.xtext.web.server.IServiceResult; 8import org.eclipse.xtext.web.server.IServiceResult;
9 9
10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult, SemanticsErrorResult { 10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult,
11 SemanticsInternalErrorResult, SemanticsIssuesResult {
11} 12}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
index 39191162..56b2cbc1 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -5,9 +5,11 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8import com.google.gson.JsonObject;
8import com.google.inject.Inject; 9import com.google.inject.Inject;
9import com.google.inject.Provider; 10import com.google.inject.Provider;
10import com.google.inject.Singleton; 11import com.google.inject.Singleton;
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.eclipse.xtext.web.server.model.AbstractCachedService; 15import org.eclipse.xtext.web.server.model.AbstractCachedService;
@@ -19,6 +21,7 @@ import org.slf4j.LoggerFactory;
19import tools.refinery.language.model.problem.Problem; 21import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.web.xtext.server.push.PushWebDocument; 22import tools.refinery.language.web.xtext.server.push.PushWebDocument;
21 23
24import java.util.List;
22import java.util.concurrent.*; 25import java.util.concurrent.*;
23 26
24@Singleton 27@Singleton
@@ -34,7 +37,12 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
34 @Inject 37 @Inject
35 private ValidationService validationService; 38 private ValidationService validationService;
36 39
37 private final ExecutorService executorService = Executors.newCachedThreadPool(); 40 private ExecutorService executorService;
41
42 @Inject
43 public void setExecutorServiceProvider(ExecutorServiceProvider provider) {
44 executorService = provider.get(this.getClass().getName());
45 }
38 46
39 @Override 47 @Override
40 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) { 48 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
@@ -42,12 +50,15 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
42 if (LOG.isTraceEnabled()) { 50 if (LOG.isTraceEnabled()) {
43 start = System.currentTimeMillis(); 51 start = System.currentTimeMillis();
44 } 52 }
45 var problem = getProblem(doc, cancelIndicator); 53 if (hasError(doc, cancelIndicator)) {
46 if (problem == null) {
47 return null; 54 return null;
48 } 55 }
56 var problem = getProblem(doc);
57 if (problem == null) {
58 return new SemanticsSuccessResult(List.of(), new JsonObject());
59 }
49 var worker = workerProvider.get(); 60 var worker = workerProvider.get();
50 worker.setProblem(problem,cancelIndicator); 61 worker.setProblem(problem, cancelIndicator);
51 var future = executorService.submit(worker); 62 var future = executorService.submit(worker);
52 SemanticsResult result = null; 63 SemanticsResult result = null;
53 try { 64 try {
@@ -58,11 +69,19 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
58 Thread.currentThread().interrupt(); 69 Thread.currentThread().interrupt();
59 } catch (ExecutionException e) { 70 } catch (ExecutionException e) {
60 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause()); 71 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause());
61 throw new IllegalStateException(e); 72 LOG.debug("Error while computing semantics", e);
73 if (e.getCause() instanceof Error error) {
74 throw error;
75 }
76 String message = e.getMessage();
77 if (message == null) {
78 message = "Partial interpretation error";
79 }
80 return new SemanticsInternalErrorResult(message);
62 } catch (TimeoutException e) { 81 } catch (TimeoutException e) {
63 future.cancel(true); 82 future.cancel(true);
64 LOG.trace("Semantics service timeout", e); 83 LOG.trace("Semantics service timeout", e);
65 return new SemanticsErrorResult("Partial interpretation timed out"); 84 return new SemanticsInternalErrorResult("Partial interpretation timed out");
66 } 85 }
67 if (LOG.isTraceEnabled()) { 86 if (LOG.isTraceEnabled()) {
68 long end = System.currentTimeMillis(); 87 long end = System.currentTimeMillis();
@@ -72,17 +91,17 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
72 return result; 91 return result;
73 } 92 }
74 93
75 @Nullable 94 private boolean hasError(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
76 private Problem getProblem(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
77 if (!(doc instanceof PushWebDocument pushDoc)) { 95 if (!(doc instanceof PushWebDocument pushDoc)) {
78 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc); 96 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc);
79 } 97 }
80 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true); 98 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true);
81 boolean hasError = validationResult.getIssues().stream() 99 return validationResult.getIssues().stream()
82 .anyMatch(issue -> "error".equals(issue.getSeverity())); 100 .anyMatch(issue -> "error".equals(issue.getSeverity()));
83 if (hasError) { 101 }
84 return null; 102
85 } 103 @Nullable
104 private Problem getProblem(IXtextWebDocument doc) {
86 var contents = doc.getResource().getContents(); 105 var contents = doc.getResource().getContents();
87 if (contents.isEmpty()) { 106 if (contents.isEmpty()) {
88 return null; 107 return null;
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 25589260..43d0238c 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -8,13 +8,19 @@ package tools.refinery.language.web.semantics;
8import com.google.gson.JsonArray; 8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject; 9import com.google.gson.JsonObject;
10import com.google.inject.Inject; 10import com.google.inject.Inject;
11import org.eclipse.emf.common.util.Diagnostic;
12import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.slf4j.Logger; 15import org.eclipse.xtext.validation.CheckType;
14import org.slf4j.LoggerFactory; 16import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
17import org.eclipse.xtext.validation.IDiagnosticConverter;
18import org.eclipse.xtext.validation.Issue;
19import org.eclipse.xtext.web.server.validation.ValidationResult;
15import tools.refinery.language.model.problem.Problem; 20import tools.refinery.language.model.problem.Problem;
16import tools.refinery.language.semantics.model.ModelInitializer; 21import tools.refinery.language.semantics.model.ModelInitializer;
17import tools.refinery.language.semantics.model.SemanticsUtils; 22import tools.refinery.language.semantics.model.SemanticsUtils;
23import tools.refinery.language.semantics.model.TracedException;
18import tools.refinery.store.model.Model; 24import tools.refinery.store.model.Model;
19import tools.refinery.store.model.ModelStore; 25import tools.refinery.store.model.ModelStore;
20import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 26import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
@@ -22,17 +28,19 @@ import tools.refinery.store.reasoning.ReasoningAdapter;
22import tools.refinery.store.reasoning.ReasoningStoreAdapter; 28import tools.refinery.store.reasoning.ReasoningStoreAdapter;
23import tools.refinery.store.reasoning.literal.Concreteness; 29import tools.refinery.store.reasoning.literal.Concreteness;
24import tools.refinery.store.reasoning.representation.PartialRelation; 30import tools.refinery.store.reasoning.representation.PartialRelation;
31import tools.refinery.store.reasoning.translator.TranslationException;
25import tools.refinery.store.representation.TruthValue; 32import tools.refinery.store.representation.TruthValue;
26import tools.refinery.store.tuple.Tuple; 33import tools.refinery.store.tuple.Tuple;
27import tools.refinery.viatra.runtime.CancellationToken; 34import tools.refinery.viatra.runtime.CancellationToken;
28 35
36import java.util.ArrayList;
29import java.util.Arrays; 37import java.util.Arrays;
30import java.util.List; 38import java.util.List;
31import java.util.TreeMap; 39import java.util.TreeMap;
32import java.util.concurrent.Callable; 40import java.util.concurrent.Callable;
33 41
34class SemanticsWorker implements Callable<SemanticsResult> { 42class SemanticsWorker implements Callable<SemanticsResult> {
35 private static final Logger LOG = LoggerFactory.getLogger(SemanticsWorker.class); 43 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError";
36 44
37 @Inject 45 @Inject
38 private SemanticsUtils semanticsUtils; 46 private SemanticsUtils semanticsUtils;
@@ -41,6 +49,9 @@ class SemanticsWorker implements Callable<SemanticsResult> {
41 private OperationCanceledManager operationCanceledManager; 49 private OperationCanceledManager operationCanceledManager;
42 50
43 @Inject 51 @Inject
52 private IDiagnosticConverter diagnosticConverter;
53
54 @Inject
44 private ModelInitializer initializer; 55 private ModelInitializer initializer;
45 56
46 private Problem problem; 57 private Problem problem;
@@ -71,15 +82,17 @@ class SemanticsWorker implements Callable<SemanticsResult> {
71 cancellationToken.checkCancelled(); 82 cancellationToken.checkCancelled();
72 var store = builder.build(); 83 var store = builder.build();
73 cancellationToken.checkCancelled(); 84 cancellationToken.checkCancelled();
74 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); 85 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed);
86 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed);
75 cancellationToken.checkCancelled(); 87 cancellationToken.checkCancelled();
76 var partialInterpretation = getPartialInterpretation(initializer, model); 88 var partialInterpretation = getPartialInterpretation(initializer, model);
77 89
78 return new SemanticsSuccessResult(nodeTrace, partialInterpretation); 90 return new SemanticsSuccessResult(nodeTrace, partialInterpretation);
79 } catch (RuntimeException e) { 91 } catch (TracedException e) {
80 LOG.debug("Error while computing semantics", e); 92 return getTracedErrorResult(e.getSourceElement(), e.getMessage());
81 var message = e.getMessage(); 93 } catch (TranslationException e) {
82 return new SemanticsErrorResult(message == null ? "Partial interpretation error" : e.getMessage()); 94 var sourceElement = initializer.getInverseTrace(e.getPartialSymbol());
95 return getTracedErrorResult(sourceElement, e.getMessage());
83 } 96 }
84 } 97 }
85 98
@@ -130,4 +143,19 @@ class SemanticsWorker implements Callable<SemanticsResult> {
130 json.add(value.toString()); 143 json.add(value.toString());
131 return json; 144 return json;
132 } 145 }
146
147 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
148 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
149 return new SemanticsInternalErrorResult(message);
150 }
151 var diagnostic = new FeatureBasedDiagnostic(Diagnostic.ERROR, message, sourceElement, null, 0,
152 CheckType.EXPENSIVE, DIAGNOSTIC_ID);
153 var xtextIssues = new ArrayList<Issue>();
154 diagnosticConverter.convertValidatorDiagnostic(diagnostic, xtextIssues::add);
155 var issues = xtextIssues.stream()
156 .map(issue -> new ValidationResult.Issue(issue.getMessage(), "error", issue.getLineNumber(),
157 issue.getColumn(), issue.getOffset(), issue.getLength()))
158 .toList();
159 return new SemanticsIssuesResult(issues);
160 }
133} 161}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 1542c694..2d43fb26 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -53,10 +53,9 @@ public class PushWebDocument extends XtextWebDocument {
53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
54 CancelIndicator cancelIndicator, boolean logCacheMiss) { 54 CancelIndicator cancelIndicator, boolean logCacheMiss) {
55 var serviceClass = service.getClass(); 55 var serviceClass = service.getClass();
56 var previousResult = precomputedServices.get(serviceClass);
57 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss); 56 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
58 precomputedServices.put(serviceClass, result); 57 precomputedServices.put(serviceClass, result);
59 if (result != null && !result.equals(previousResult)) { 58 if (result != null) {
60 notifyPrecomputationListeners(serviceName, result); 59 notifyPrecomputationListeners(serviceName, result);
61 } 60 }
62 } 61 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 99ca5420..889a55cb 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -93,7 +93,7 @@ class ProblemWebSocketServletIntegrationTest {
93 clientSocket.waitForTestResult(); 93 clientSocket.waitForTestResult();
94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); 94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
95 var responses = clientSocket.getResponses(); 95 var responses = clientSocket.getResponses();
96 assertThat(responses, hasSize(7)); 96 assertThat(responses, hasSize(8));
97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); 97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
98 assertThat(responses.get(1), startsWith( 98 assertThat(responses.get(1), startsWith(
99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," + 99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," +
@@ -108,7 +108,10 @@ class ProblemWebSocketServletIntegrationTest {
108 assertThat(responses.get(5), startsWith( 108 assertThat(responses.get(5), startsWith(
109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," + 109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," +
110 "\"push\":{\"regions\":[")); 110 "\"push\":{\"regions\":["));
111 assertThat(responses.get(6), startsWith( 111 assertThat(responses.get(6), equalTo(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"validate\"," +
113 "\"push\":{\"issues\":[]}}"));
114 assertThat(responses.get(7), startsWith(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," + 115 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," +
113 "\"push\":{")); 116 "\"push\":{"));
114 } 117 }
@@ -130,7 +133,7 @@ class ProblemWebSocketServletIntegrationTest {
130 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}", 133 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}",
131 Callback.NOOP 134 Callback.NOOP
132 ); 135 );
133 case 7 -> session.close(); 136 case 8 -> session.close();
134 } 137 }
135 } 138 }
136 } 139 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index b7142506..22ce1b47 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith; 18import org.junit.jupiter.api.extension.ExtendWith;
19import org.mockito.ArgumentCaptor; 19import org.mockito.ArgumentCaptor;
20import org.mockito.junit.jupiter.MockitoExtension; 20import org.mockito.junit.jupiter.MockitoExtension;
21import tools.refinery.language.web.semantics.SemanticsService;
21import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; 22import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider;
22import tools.refinery.language.web.tests.ProblemWebInjectorProvider; 23import tools.refinery.language.web.tests.ProblemWebInjectorProvider;
23import tools.refinery.language.web.xtext.server.ResponseHandler; 24import tools.refinery.language.web.xtext.server.ResponseHandler;
@@ -59,11 +60,16 @@ class TransactionExecutorTest {
59 @Inject 60 @Inject
60 private AwaitTerminationExecutorServiceProvider executorServices; 61 private AwaitTerminationExecutorServiceProvider executorServices;
61 62
63 @Inject
64 private SemanticsService semanticsService;
65
62 private TransactionExecutor transactionExecutor; 66 private TransactionExecutor transactionExecutor;
63 67
64 @BeforeEach 68 @BeforeEach
65 void beforeEach() { 69 void beforeEach() {
66 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry); 70 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry);
71 // Manually re-create the semantics analysis thread pool if it was disposed by the previous test.
72 semanticsService.setExecutorServiceProvider(executorServices);
67 } 73 }
68 74
69 @Test 75 @Test
@@ -95,7 +101,7 @@ class TransactionExecutorTest {
95 "0"))); 101 "0")));
96 102
97 var captor = newCaptor(); 103 var captor = newCaptor();
98 verify(responseHandler, times(3)).onResponse(captor.capture()); 104 verify(responseHandler, times(4)).onResponse(captor.capture());
99 var newStateId = getStateId("bar", captor.getAllValues().get(0)); 105 var newStateId = getStateId("bar", captor.getAllValues().get(0));
100 assertHighlightingResponse(newStateId, captor.getAllValues().get(1)); 106 assertHighlightingResponse(newStateId, captor.getAllValues().get(1));
101 } 107 }