Commit b397dfb518e101558790127b5e7ad9a7e7aef34f
1 parent
f3757ad1
js-script-engine-api: js sync calls replaced with completely async calls (CE API only)
Showing
7 changed files
with
82 additions
and
128 deletions
@@ -390,13 +390,13 @@ public class RuleChainController extends BaseController { | @@ -390,13 +390,13 @@ public class RuleChainController extends BaseController { | ||
390 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); | 390 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); |
391 | switch (scriptType) { | 391 | switch (scriptType) { |
392 | case "update": | 392 | case "update": |
393 | - output = msgToOutput(engine.executeUpdate(inMsg)); | 393 | + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); |
394 | break; | 394 | break; |
395 | case "generate": | 395 | case "generate": |
396 | output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); | 396 | output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); |
397 | break; | 397 | break; |
398 | case "filter": | 398 | case "filter": |
399 | - boolean result = engine.executeFilter(inMsg); | 399 | + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
400 | output = Boolean.toString(result); | 400 | output = Boolean.toString(result); |
401 | break; | 401 | break; |
402 | case "switch": | 402 | case "switch": |
@@ -404,11 +404,11 @@ public class RuleChainController extends BaseController { | @@ -404,11 +404,11 @@ public class RuleChainController extends BaseController { | ||
404 | output = objectMapper.writeValueAsString(states); | 404 | output = objectMapper.writeValueAsString(states); |
405 | break; | 405 | break; |
406 | case "json": | 406 | case "json": |
407 | - JsonNode json = engine.executeJson(inMsg); | 407 | + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
408 | output = objectMapper.writeValueAsString(json); | 408 | output = objectMapper.writeValueAsString(json); |
409 | break; | 409 | break; |
410 | case "string": | 410 | case "string": |
411 | - output = engine.executeToString(inMsg); | 411 | + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); |
412 | break; | 412 | break; |
413 | default: | 413 | default: |
414 | throw new IllegalArgumentException("Unsupported script type: " + scriptType); | 414 | throw new IllegalArgumentException("Unsupported script type: " + scriptType); |
@@ -107,95 +107,76 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | @@ -107,95 +107,76 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | ||
107 | } | 107 | } |
108 | 108 | ||
109 | @Override | 109 | @Override |
110 | - public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException { | ||
111 | - JsonNode result = executeScript(msg); | ||
112 | - if (result.isObject()) { | ||
113 | - return Collections.singletonList(unbindMsg(result, msg)); | ||
114 | - } else if (result.isArray()){ | ||
115 | - List<TbMsg> res = new ArrayList<>(result.size()); | ||
116 | - result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
117 | - return res; | ||
118 | - } else { | ||
119 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
120 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | ||
121 | - } | ||
122 | - } | ||
123 | - | ||
124 | - @Override | ||
125 | public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { | 110 | public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { |
126 | ListenableFuture<JsonNode> result = executeScriptAsync(msg); | 111 | ListenableFuture<JsonNode> result = executeScriptAsync(msg); |
127 | - return Futures.transformAsync(result, json -> { | ||
128 | - if (json.isObject()) { | ||
129 | - return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | ||
130 | - } else if (json.isArray()){ | ||
131 | - List<TbMsg> res = new ArrayList<>(json.size()); | ||
132 | - json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
133 | - return Futures.immediateFuture(res); | ||
134 | - } | ||
135 | - else{ | ||
136 | - log.warn("Wrong result type: {}", json.getNodeType()); | ||
137 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
138 | - } | ||
139 | - }, MoreExecutors.directExecutor()); | 112 | + return Futures.transformAsync(result, |
113 | + json -> executeUpdateTransform(msg, json), | ||
114 | + MoreExecutors.directExecutor()); | ||
115 | + } | ||
116 | + | ||
117 | + ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) { | ||
118 | + if (json.isObject()) { | ||
119 | + return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); | ||
120 | + } else if (json.isArray()) { | ||
121 | + List<TbMsg> res = new ArrayList<>(json.size()); | ||
122 | + json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
123 | + return Futures.immediateFuture(res); | ||
124 | + } | ||
125 | + log.warn("Wrong result type: {}", json.getNodeType()); | ||
126 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
140 | } | 127 | } |
141 | 128 | ||
142 | @Override | 129 | @Override |
143 | public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { | 130 | public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { |
144 | - log.trace("execute generate async, prevMsg {}", prevMsg); | ||
145 | - return Futures.transformAsync(executeScriptAsync(prevMsg), result -> { | ||
146 | - if (!result.isObject()) { | ||
147 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
148 | - Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | ||
149 | - } | ||
150 | - return Futures.immediateFuture(unbindMsg(result, prevMsg)); | ||
151 | - }, MoreExecutors.directExecutor()); | ||
152 | - | 131 | + return Futures.transformAsync(executeScriptAsync(prevMsg), |
132 | + result -> executeGenerateTransform(prevMsg, result), | ||
133 | + MoreExecutors.directExecutor()); | ||
153 | } | 134 | } |
154 | 135 | ||
155 | - @Override | ||
156 | - public JsonNode executeJson(TbMsg msg) throws ScriptException { | ||
157 | - return executeScript(msg); | 136 | + ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) { |
137 | + if (!result.isObject()) { | ||
138 | + log.warn("Wrong result type: {}", result.getNodeType()); | ||
139 | + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | ||
140 | + } | ||
141 | + return Futures.immediateFuture(unbindMsg(result, prevMsg)); | ||
158 | } | 142 | } |
159 | 143 | ||
160 | @Override | 144 | @Override |
161 | - public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException { | 145 | + public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) { |
162 | return executeScriptAsync(msg); | 146 | return executeScriptAsync(msg); |
163 | } | 147 | } |
164 | 148 | ||
165 | @Override | 149 | @Override |
166 | - public String executeToString(TbMsg msg) throws ScriptException { | ||
167 | - JsonNode result = executeScript(msg); | ||
168 | - if (!result.isTextual()) { | ||
169 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
170 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | ||
171 | - } | ||
172 | - return result.asText(); | 150 | + public ListenableFuture<String> executeToStringAsync(TbMsg msg) { |
151 | + return Futures.transformAsync(executeScriptAsync(msg), | ||
152 | + this::executeToStringTransform, | ||
153 | + MoreExecutors.directExecutor()); | ||
173 | } | 154 | } |
174 | 155 | ||
175 | - @Override | ||
176 | - public boolean executeFilter(TbMsg msg) throws ScriptException { | ||
177 | - JsonNode result = executeScript(msg); | ||
178 | - if (!result.isBoolean()) { | ||
179 | - log.warn("Wrong result type: {}", result.getNodeType()); | ||
180 | - throw new ScriptException("Wrong result type: " + result.getNodeType()); | 156 | + ListenableFuture<String> executeToStringTransform(JsonNode result) { |
157 | + if (result.isTextual()) { | ||
158 | + return Futures.immediateFuture(result.asText()); | ||
181 | } | 159 | } |
182 | - return result.asBoolean(); | 160 | + log.warn("Wrong result type: {}", result.getNodeType()); |
161 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); | ||
183 | } | 162 | } |
184 | 163 | ||
185 | @Override | 164 | @Override |
186 | public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { | 165 | public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { |
187 | - ListenableFuture<JsonNode> result = executeScriptAsync(msg); | ||
188 | - return Futures.transformAsync(result, json -> { | ||
189 | - if (!json.isBoolean()) { | ||
190 | - log.warn("Wrong result type: {}", json.getNodeType()); | ||
191 | - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
192 | - } else { | ||
193 | - return Futures.immediateFuture(json.asBoolean()); | ||
194 | - } | ||
195 | - }, MoreExecutors.directExecutor()); | 166 | + return Futures.transformAsync(executeScriptAsync(msg), |
167 | + this::executeFilterTransform, | ||
168 | + MoreExecutors.directExecutor()); | ||
169 | + } | ||
170 | + | ||
171 | + ListenableFuture<Boolean> executeFilterTransform(JsonNode json) { | ||
172 | + if (json.isBoolean()) { | ||
173 | + return Futures.immediateFuture(json.asBoolean()); | ||
174 | + } | ||
175 | + log.warn("Wrong result type: {}", json.getNodeType()); | ||
176 | + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); | ||
196 | } | 177 | } |
197 | 178 | ||
198 | - ListenableFuture<Set<String>> executeSwitchPostProcessAsyncFunction(JsonNode result) { | 179 | + ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) { |
199 | if (result.isTextual()) { | 180 | if (result.isTextual()) { |
200 | return Futures.immediateFuture(Collections.singleton(result.asText())); | 181 | return Futures.immediateFuture(Collections.singleton(result.asText())); |
201 | } | 182 | } |
@@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | @@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | ||
217 | 198 | ||
218 | @Override | 199 | @Override |
219 | public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { | 200 | public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { |
220 | - log.trace("execute switch async, msg {}", msg); | ||
221 | return Futures.transformAsync(executeScriptAsync(msg), | 201 | return Futures.transformAsync(executeScriptAsync(msg), |
222 | - this::executeSwitchPostProcessAsyncFunction, | 202 | + this::executeSwitchTransform, |
223 | MoreExecutors.directExecutor()); //usually runs in a callbackExecutor | 203 | MoreExecutors.directExecutor()); //usually runs in a callbackExecutor |
224 | } | 204 | } |
225 | 205 | ||
226 | - private JsonNode executeScript(TbMsg msg) throws ScriptException { | ||
227 | - try { | ||
228 | - String[] inArgs = prepareArgs(msg); | ||
229 | - String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); | ||
230 | - return mapper.readTree(eval); | ||
231 | - } catch (ExecutionException e) { | ||
232 | - if (e.getCause() instanceof ScriptException) { | ||
233 | - throw (ScriptException) e.getCause(); | ||
234 | - } else if (e.getCause() instanceof RuntimeException) { | ||
235 | - throw new ScriptException(e.getCause().getMessage()); | ||
236 | - } else { | ||
237 | - throw new ScriptException(e); | ||
238 | - } | ||
239 | - } catch (Exception e) { | ||
240 | - throw new ScriptException(e); | ||
241 | - } | ||
242 | - } | ||
243 | - | ||
244 | - private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { | 206 | + ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { |
245 | log.trace("execute script async, msg {}", msg); | 207 | log.trace("execute script async, msg {}", msg); |
246 | String[] inArgs = prepareArgs(msg); | 208 | String[] inArgs = prepareArgs(msg); |
247 | - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), | 209 | + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); |
210 | + } | ||
211 | + | ||
212 | + ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) { | ||
213 | + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args), | ||
248 | o -> { | 214 | o -> { |
249 | try { | 215 | try { |
250 | return Futures.immediateFuture(mapper.readTree(o.toString())); | 216 | return Futures.immediateFuture(mapper.readTree(o.toString())); |
@@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode; | @@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode; | ||
19 | import com.google.common.util.concurrent.ListenableFuture; | 19 | import com.google.common.util.concurrent.ListenableFuture; |
20 | import org.thingsboard.server.common.msg.TbMsg; | 20 | import org.thingsboard.server.common.msg.TbMsg; |
21 | 21 | ||
22 | -import javax.script.ScriptException; | ||
23 | import java.util.List; | 22 | import java.util.List; |
24 | import java.util.Set; | 23 | import java.util.Set; |
25 | 24 | ||
26 | public interface ScriptEngine { | 25 | public interface ScriptEngine { |
27 | 26 | ||
28 | - List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException; | ||
29 | - | ||
30 | ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg); | 27 | ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg); |
31 | 28 | ||
32 | ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg); | 29 | ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg); |
@@ -37,11 +34,9 @@ public interface ScriptEngine { | @@ -37,11 +34,9 @@ public interface ScriptEngine { | ||
37 | 34 | ||
38 | ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg); | 35 | ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg); |
39 | 36 | ||
40 | - JsonNode executeJson(TbMsg msg) throws ScriptException; | ||
41 | - | ||
42 | - ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException; | 37 | + ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg); |
43 | 38 | ||
44 | - String executeToString(TbMsg msg) throws ScriptException; | 39 | + ListenableFuture<String> executeToStringAsync(TbMsg msg); |
45 | 40 | ||
46 | void destroy(); | 41 | void destroy(); |
47 | 42 |
@@ -214,6 +214,10 @@ public interface TbContext { | @@ -214,6 +214,10 @@ public interface TbContext { | ||
214 | 214 | ||
215 | EdgeEventService getEdgeEventService(); | 215 | EdgeEventService getEdgeEventService(); |
216 | 216 | ||
217 | + /** | ||
218 | + * Js script executors call are completely asynchronous | ||
219 | + * */ | ||
220 | + @Deprecated | ||
217 | ListeningExecutor getJsExecutor(); | 221 | ListeningExecutor getJsExecutor(); |
218 | 222 | ||
219 | ListeningExecutor getMailExecutor(); | 223 | ListeningExecutor getMailExecutor(); |
@@ -15,8 +15,11 @@ | @@ -15,8 +15,11 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.rule.engine.action; | 16 | package org.thingsboard.rule.engine.action; |
17 | 17 | ||
18 | +import com.google.common.util.concurrent.FutureCallback; | ||
19 | +import com.google.common.util.concurrent.Futures; | ||
20 | +import com.google.common.util.concurrent.MoreExecutors; | ||
18 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
19 | -import org.thingsboard.common.util.ListeningExecutor; | 22 | +import org.checkerframework.checker.nullness.qual.Nullable; |
20 | import org.thingsboard.rule.engine.api.RuleNode; | 23 | import org.thingsboard.rule.engine.api.RuleNode; |
21 | import org.thingsboard.rule.engine.api.ScriptEngine; | 24 | import org.thingsboard.rule.engine.api.ScriptEngine; |
22 | import org.thingsboard.rule.engine.api.TbContext; | 25 | import org.thingsboard.rule.engine.api.TbContext; |
@@ -55,18 +58,21 @@ public class TbLogNode implements TbNode { | @@ -55,18 +58,21 @@ public class TbLogNode implements TbNode { | ||
55 | 58 | ||
56 | @Override | 59 | @Override |
57 | public void onMsg(TbContext ctx, TbMsg msg) { | 60 | public void onMsg(TbContext ctx, TbMsg msg) { |
58 | - ListeningExecutor jsExecutor = ctx.getJsExecutor(); | ||
59 | ctx.logJsEvalRequest(); | 61 | ctx.logJsEvalRequest(); |
60 | - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), | ||
61 | - toString -> { | ||
62 | - ctx.logJsEvalResponse(); | ||
63 | - log.info(toString); | ||
64 | - ctx.tellSuccess(msg); | ||
65 | - }, | ||
66 | - t -> { | ||
67 | - ctx.logJsEvalResponse(); | ||
68 | - ctx.tellFailure(msg, t); | ||
69 | - }); | 62 | + Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() { |
63 | + @Override | ||
64 | + public void onSuccess(@Nullable String result) { | ||
65 | + ctx.logJsEvalResponse(); | ||
66 | + log.info(result); | ||
67 | + ctx.tellSuccess(msg); | ||
68 | + } | ||
69 | + | ||
70 | + @Override | ||
71 | + public void onFailure(Throwable t) { | ||
72 | + ctx.logJsEvalResponse(); | ||
73 | + ctx.tellFailure(msg, t); | ||
74 | + } | ||
75 | + }, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor | ||
70 | } | 76 | } |
71 | 77 | ||
72 | @Override | 78 | @Override |
@@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; | @@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; | ||
33 | 33 | ||
34 | import java.util.Set; | 34 | import java.util.Set; |
35 | 35 | ||
36 | -import static org.thingsboard.common.util.DonAsynchron.withCallback; | ||
37 | - | ||
38 | @Slf4j | 36 | @Slf4j |
39 | @RuleNode( | 37 | @RuleNode( |
40 | type = ComponentType.FILTER, | 38 | type = ComponentType.FILTER, |
@@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest { | @@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest { | ||
64 | private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); | 64 | private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); |
65 | 65 | ||
66 | @Test | 66 | @Test |
67 | - public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException { | 67 | + public void multipleRoutesAreAllowed() throws TbNodeException { |
68 | initWithScript(); | 68 | initWithScript(); |
69 | TbMsgMetaData metaData = new TbMsgMetaData(); | 69 | TbMsgMetaData metaData = new TbMsgMetaData(); |
70 | metaData.putValue("temp", "10"); | 70 | metaData.putValue("temp", "10"); |
@@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest { | @@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest { | ||
72 | String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; | 72 | String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; |
73 | 73 | ||
74 | TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); | 74 | TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
75 | - mockJsExecutor(); | ||
76 | when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); | 75 | when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); |
77 | 76 | ||
78 | node.onMsg(ctx, msg); | 77 | node.onMsg(ctx, msg); |
79 | - verify(ctx).getJsExecutor(); | ||
80 | verify(ctx).tellNext(msg, Sets.newHashSet("one", "three")); | 78 | verify(ctx).tellNext(msg, Sets.newHashSet("one", "three")); |
81 | } | 79 | } |
82 | 80 | ||
@@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest { | @@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest { | ||
92 | node.init(ctx, nodeConfiguration); | 90 | node.init(ctx, nodeConfiguration); |
93 | } | 91 | } |
94 | 92 | ||
95 | - @SuppressWarnings("unchecked") | ||
96 | - private void mockJsExecutor() { | ||
97 | - when(ctx.getJsExecutor()).thenReturn(executor); | ||
98 | - doAnswer((Answer<ListenableFuture<Set<String>>>) invocationOnMock -> { | ||
99 | - try { | ||
100 | - Callable task = (Callable) (invocationOnMock.getArguments())[0]; | ||
101 | - return Futures.immediateFuture((Set<String>) task.call()); | ||
102 | - } catch (Throwable th) { | ||
103 | - return Futures.immediateFailedFuture(th); | ||
104 | - } | ||
105 | - }).when(executor).executeAsync(ArgumentMatchers.any(Callable.class)); | ||
106 | - } | ||
107 | - | ||
108 | private void verifyError(TbMsg msg, String message, Class expectedClass) { | 93 | private void verifyError(TbMsg msg, String message, Class expectedClass) { |
109 | ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); | 94 | ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); |
110 | verify(ctx).tellFailure(same(msg), captor.capture()); | 95 | verify(ctx).tellFailure(same(msg), captor.capture()); |