Skip to content

Commit

Permalink
Added signal and listen back to karate object
Browse files Browse the repository at this point in the history
  • Loading branch information
leozilla committed Apr 5, 2019
1 parent c084979 commit d0985e8
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3066,6 +3066,7 @@ Operation | Description
<a name="karate-get"><code>karate.get(name)</code></a> | get the value of a variable by name (or JsonPath expression), if not found - this returns `null` which is easier to handle in JavaScript (than `undefined`)
<a name="karate-info"><code>karate.info</code></a> | within a test (or within the [`afterScenario`](#configure) function if configured) you can access metadata such as the `Scenario` name, refer to this example: [`hooks.feature`](karate-demo/src/test/java/demo/hooks/hooks.feature)
<a name="karate-jsonpath"><code>karate.jsonPath(json, expression)</code></a> | brings the power of [JsonPath](https://github.com/json-path/JsonPath) into JavaScript, and you can find an example [here](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/js-arrays.feature).
<a name="karate-listen"><code>karate.listen(timeout)</code></a> | wait until [`karate.signal(result)`](#karate-signal) has been called or time-out after `timeout` milliseconds. see examples: [websocket](karate-demo/src/test/java/demo/websocket/websocket.feature) / [message-queue](karate-demo/src/test/java/mock/contract/payment-service.feature)
<a name="karate-log"><code>karate.log(... args)</code></a> | log to the same logger (and log file) being used by the parent process, logging can be suppressed with [`configure printEnabled`](#configure) set to `false`
<a name="karate-lowercase"><code>karate.lowerCase(object)</code></a> | useful to brute-force all keys and values in a JSON or XML payload to lower-case, useful in some cases, see [example](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/lower-case.feature)
<a name="karate-map"><code>karate.map(list, function)</code></a> | functional-style 'map' operation useful to transform list-like objects (e.g. JSON arrays), see [example](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/js-arrays.feature), the second argument has to be a JS function (item, [index])
Expand All @@ -3080,6 +3081,7 @@ Operation | Description
<a name="karate-set"><code>karate.set(name, value)</code></a> | sets the value of a variable (immediately), which may be needed in case any other routines (such as the [configured headers](#configure-headers)) depend on that variable
<a name="karate-setpath"><code>karate.set(name, path, value)</code></a> | only needed when you need to conditionally build payload elements, especially XML. This is best explained via [an example](karate-junit4/src/test/java/com/intuit/karate/junit4/xml/xml.feature#L211), and it behaves the same way as the [`set`](#set) keyword. Also see [`eval`](#eval).
<a name="karate-setxml"><code>karate.setXml(name, xmlString)</code></a> | rarely used, refer to the example above
<a name="karate-signal"><code>karate.signal(result)</code></a> | trigger an event that [`karate.listen(timeout)`](#karate-listen) is waiting for, and pass the data, see examples: [websocket](karate-demo/src/test/java/demo/websocket/websocket.feature) / [message-queue](karate-demo/src/test/java/mock/contract/payment-service.feature)
<a name="karate-tags"><code>karate.tags</code></a> | for advanced users - scripts can introspect the tags that apply to the current scope, refer to this example: [`tags.feature`](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/tags.feature)
<a name="karate-tagvalues"><code>karate.tagValues</code></a> | for even more advanced users - Karate natively supports tags in a `@name=val1,val2` format, and there is an inheritance mechanism where `Scenario` level tags can over-ride `Feature` level tags, refer to this example: [`tags.feature`](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/tags.feature)
<a name="karate-tobean"><code>karate.toBean(json, className)</code></a> | converts a JSON string or map-like object into a Java object, given the Java class name as the second argument, refer to this [file](karate-junit4/src/test/java/com/intuit/karate/junit4/demos/type-conv.feature) for an example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@
import com.intuit.karate.netty.WebSocketClient;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

Expand Down Expand Up @@ -99,7 +104,9 @@ public class ScenarioContext {
private Function<CallContext, FeatureResult> callable;

// websocket
private final Object LOCK = new Object();
private List<WebSocketClient> webSocketClients = new ArrayList<>();
private Object signalResult;

public void logLastPerfEvent(String failureMessage) {
if (prevPerfEvent != null && executionHooks != null) {
Expand Down Expand Up @@ -313,6 +320,7 @@ private ScenarioContext(ScenarioContext sc, ScenarioInfo info, Logger logger) {
prevPerfEvent = sc.prevPerfEvent;
callResults = sc.callResults;
webSocketClients = sc.webSocketClients;
signalResult = sc.signalResult;
}

public void configure(Config config) {
Expand Down Expand Up @@ -795,7 +803,40 @@ public WebSocketClient webSocket(String url, String subProtocol, Optional<Consum
return webSocketClient;
}

// driver ==================================================================
public void signal(Object result) {
logger.trace("signal called: {}", result);
synchronized (LOCK) {
signalResult = result;
LOCK.notify();
}
}

public Object listen(long timeout, Runnable runnable) {
if (runnable != null) {
logger.trace("submitting listen function");
new Thread(runnable).start();
}
synchronized (LOCK) {
if (signalResult != null) {
logger.debug("signal arrived early ! result: {}", signalResult);
Object temp = signalResult;
signalResult = null;
return temp;
}
try {
logger.trace("entered listen wait state");
LOCK.wait(timeout);
logger.trace("exit listen wait state, result: {}", signalResult);
} catch (InterruptedException e) {
logger.error("listen timed out: {}", e.getMessage());
}
Object temp = signalResult;
signalResult = null;
return temp;
}
}

// driver ==================================================================
//
private void setDriver(Driver driver) {
this.driver = driver;
Expand Down
15 changes: 15 additions & 0 deletions karate-core/src/main/java/com/intuit/karate/core/ScriptBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,21 @@ public WebSocketClient webSocket(String url, String subProtocol, Consumer<String
return context.webSocket(url, subProtocol, Optional.of(textHandler), Optional.of(binaryHandler));
}

public void signal(Object result) {
context.signal(result);
}

public Object listen(long timeout, ScriptObjectMirror som) {
if (!som.isFunction()) {
throw new RuntimeException("not a JS function: " + som);
}
return context.listen(timeout, () -> Script.evalFunctionCall(som, null, context));
}

public Object listen(long timeout) {
return context.listen(timeout, null);
}

private ScriptValue getValue(String name) {
ScriptValue sv = context.vars.get(name);
return sv == null ? ScriptValue.NULL : sv;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public WebSocketClient(String url, String subProtocol, Optional<Consumer<String>
}

public WebSocketClient(String url, String subProtocol, Optional<Consumer<String>> maybeTextHandler, Optional<Consumer<byte[]>> maybeBinaryHandler) {
this.textHandler = maybeTextHandler.orElse(this::signal);
this.binaryHandler = maybeBinaryHandler.orElse(this::signal);
this.textHandler = maybeTextHandler.orElse(msg -> {});
this.binaryHandler = maybeBinaryHandler.orElse(msg -> {});
uri = URI.create(url);
ssl = "wss".equalsIgnoreCase(uri.getScheme());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.function.Consumer;

/**
*
* @author pthomas3
Expand Down
8 changes: 0 additions & 8 deletions karate-demo/src/test/java/demo/websocket/websocket.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,3 @@ Scenario: using the websocket instance to send as well as receive messages
* eval ws.send('Billie')
* def result = ws.listen(5000)
* match result == 'hello Billie !'

Scenario: perform custom action when message is received
* def ws = karate.webSocket(demoBaseUrl + '/websocket')
# will call the function myJavaScriptFunction (which is defined elsewhere) for each text message that is received
* eval ws.setTextHandler(function(msg){ myJavaScriptFunction() })
* eval ws.send('Billie')
* def result = ws.listen(5000)
* match result == 'hello Billie !'

0 comments on commit d0985e8

Please sign in to comment.