diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java index 401ac3ff7..c832d2b7f 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java @@ -66,13 +66,13 @@ * @author pthomas3 */ public class ScenarioEngine { - + private static final String KARATE = "karate"; private static final String READ = "read"; private static final String DRIVER = "driver"; private static final String ROBOT = "robot"; private static final String KEY = "Key"; - + public static final String RESPONSE = "response"; public static final String RESPONSE_HEADERS = "responseHeaders"; public static final String RESPONSE_STATUS = "responseStatus"; @@ -80,9 +80,9 @@ public class ScenarioEngine { private static final String RESPONSE_COOKIES = "responseCookies"; private static final String RESPONSE_TIME = "responseTime"; private static final String RESPONSE_TYPE = "responseType"; - + private static final String LISTEN_RESULT = "listenResult"; - + public static final String REQUEST = "request"; public static final String REQUEST_URL_BASE = "requestUrlBase"; public static final String REQUEST_URI = "requestUri"; @@ -90,26 +90,26 @@ public class ScenarioEngine { public static final String REQUEST_METHOD = "requestMethod"; public static final String REQUEST_HEADERS = "requestHeaders"; private static final String REQUEST_TIME_STAMP = "requestTimeStamp"; - + public final ScenarioRuntime runtime; public final ScenarioFileReader fileReader; public final Map vars; public final Logger logger; - + private final Function readFunction; private final ScenarioBridge bridge; private final Collection hooks; - + private boolean aborted; private Throwable failedReason; - + protected JsEngine JS; // only used by mock server public ScenarioEngine(ScenarioRuntime runtime, Map vars) { this(runtime.engine.config, runtime, vars, runtime.logger); } - + public ScenarioEngine(Config config, ScenarioRuntime runtime, Map vars, Logger logger) { this.config = config; this.runtime = runtime; @@ -120,17 +120,17 @@ public ScenarioEngine(Config config, ScenarioRuntime runtime, Map children; private ScenarioEngine parent; - + public ScenarioEngine child() { ScenarioEngine child = new ScenarioEngine(config, runtime, detachVariables(true), logger); child.parent = this; @@ -140,17 +140,17 @@ public ScenarioEngine child() { children.add(child); return child; } - + private static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); - + public static ScenarioEngine get() { return THREAD_LOCAL.get(); } - + public static void set(ScenarioEngine se) { THREAD_LOCAL.set(se); } - + protected static void remove() { THREAD_LOCAL.remove(); } @@ -160,42 +160,42 @@ protected static void remove() { public boolean isAborted() { return aborted; } - + public void setAborted(boolean aborted) { this.aborted = aborted; } - + public boolean isFailed() { return failedReason != null; } - + public boolean isIgnoringStepErrors() { return !config.getContinueOnStepFailureMethods().isEmpty(); } - + public void setFailedReason(Throwable failedReason) { this.failedReason = failedReason; } - + public Throwable getFailedReason() { return failedReason; } - + public void matchResult(Match.Type matchType, String expression, String path, String expected) { Match.Result mr = match(matchType, expression, path, expected); if (!mr.pass) { setFailedReason(new KarateException(mr.message)); } } - + public void set(String name, String path, String exp) { set(name, path, exp, false, false); } - + public void remove(String name, String path) { set(name, path, null, true, false); } - + public void table(String name, List> rows) { List> result = new ArrayList<>(rows.size()); for (Map map : rows) { @@ -221,7 +221,7 @@ public void table(String name, List> rows) { } setVariable(name.trim(), result); } - + public void replace(String name, String token, String value) { name = name.trim(); Variable v = vars.get(name); @@ -232,21 +232,21 @@ public void replace(String name, String token, String value) { String replaced = replacePlaceholderText(text, token, value); setVariable(name, replaced); } - + public void assertTrue(String expression) { if (!evalJs(expression).isTrue()) { String message = "did not evaluate to 'true': " + expression; setFailedReason(new KarateException(message)); } } - + public void print(String exp) { if (!config.isPrintEnabled()) { return; } evalJs("karate.log('[print]'," + exp + ")"); } - + public void invokeAfterHookIfConfigured(boolean afterFeature) { if (runtime.caller.depth > 0) { return; @@ -268,7 +268,7 @@ public void invokeAfterHookIfConfigured(boolean afterFeature) { // gatling ================================================================= // private PerfEvent prevPerfEvent; - + public void logLastPerfEvent(String failureMessage) { if (prevPerfEvent != null && runtime.perfMode) { if (failureMessage != null) { @@ -279,7 +279,7 @@ public void logLastPerfEvent(String failureMessage) { } prevPerfEvent = null; } - + public void capturePerfEvent(PerfEvent event) { logLastPerfEvent(null); prevPerfEvent = event; @@ -291,7 +291,7 @@ public void capturePerfEvent(PerfEvent event) { private HttpRequest request; private Response response; private Config config; - + public Config getConfig() { return config; } @@ -305,24 +305,24 @@ public void setConfig(Config config) { requestBuilder.client.setConfig(config); } } - + public HttpRequest getRequest() { return request; } - + public Response getResponse() { return response; } - + public HttpRequestBuilder getRequestBuilder() { return requestBuilder; } - + public void configure(String key, String exp) { Variable v = evalKarateExpression(exp); configure(key, v); } - + public void configure(String key, Variable v) { key = StringUtils.trimToEmpty(key); // if next line returns true, config is http-client related @@ -332,7 +332,7 @@ public void configure(String key, Variable v) { } } } - + private void evalAsMap(String exp, BiConsumer> fun) { Variable var = evalKarateExpression(exp); if (!var.isMap()) { @@ -355,12 +355,12 @@ private void evalAsMap(String exp, BiConsumer> fun) { } }); } - + public void url(String exp) { Variable var = evalKarateExpression(exp); requestBuilder.url(var.getAsString()); } - + public void path(String exp) { List list = evalJs("[" + exp + "]").getValue(); for (Object o : list) { @@ -380,11 +380,11 @@ public void param(String name, String exp) { requestBuilder.param(name, var.getAsString()); } } - + public void params(String expr) { evalAsMap(expr, (k, v) -> requestBuilder.param(k, v)); } - + public void header(String name, String exp) { Variable var = evalKarateExpression(exp); if (var.isList()) { @@ -393,11 +393,11 @@ public void header(String name, String exp) { requestBuilder.header(name, var.getAsString()); } } - + public void headers(String expr) { evalAsMap(expr, (k, v) -> requestBuilder.header(k, v)); } - + public void cookie(String name, String exp) { Variable var = evalKarateExpression(exp); if (var.isString()) { @@ -415,7 +415,7 @@ public void cookies(String exp) { Map cookies = Cookies.normalize(var.getValue()); requestBuilder.cookies(cookies.values()); } - + private void updateConfigCookies(Map cookies) { if (cookies == null) { return; @@ -428,7 +428,7 @@ private void updateConfigCookies(Map cookies) { config.setCookies(new Variable(map)); } } - + public void formField(String name, String exp) { Variable var = evalKarateExpression(exp); if (var.isList()) { @@ -437,7 +437,7 @@ public void formField(String name, String exp) { requestBuilder.formField(name, var.getAsString()); } } - + public void formFields(String exp) { Variable var = evalKarateExpression(exp); if (var.isMap()) { @@ -449,15 +449,15 @@ public void formFields(String exp) { logger.warn("did not evaluate to map {}: {}", exp, var); } } - + public void multipartField(String name, String value) { multipartFile(name, value); } - + public void multipartFields(String exp) { multipartFiles(exp); } - + private void multiPartInternal(String name, Object value) { Map map = new HashMap(); if (name != null) { @@ -483,12 +483,12 @@ private void multiPartInternal(String name, Object value) { logger.trace("did not evaluate to string, map or list {}: {}", name, value); } } - + public void multipartFile(String name, String exp) { Variable var = evalKarateExpression(exp); multiPartInternal(name, var.getValue()); } - + public void multipartFiles(String exp) { Variable var = evalKarateExpression(exp); if (var.isMap()) { @@ -503,12 +503,12 @@ public void multipartFiles(String exp) { logger.warn("did not evaluate to map or list {}: {}", exp, var); } } - + public void request(String body) { Variable v = evalKarateExpression(body); requestBuilder.body(v.getValue()); } - + public void soapAction(String exp) { String action = evalKarateExpression(exp).getAsString(); if (action == null) { @@ -518,11 +518,11 @@ public void soapAction(String exp) { requestBuilder.contentType("text/xml"); method("POST"); } - + public void retry(String condition) { requestBuilder.setRetryUntil(condition); } - + public void method(String method) { if (!HttpConstants.HTTP_METHODS.contains(method.toUpperCase())) { // support expressions also method = evalKarateExpression(method).getAsString(); @@ -541,7 +541,7 @@ public Response httpInvoke() { requestBuilder.reset(); return response; } - + private void httpInvokeOnce() { Map cookies = getOrEvalAsMap(config.getCookies()); if (cookies != null) { @@ -623,10 +623,10 @@ private void httpInvokeOnce() { if (perfEventName != null) { PerfEvent pe = new PerfEvent(startTime, endTime, perfEventName, response.getStatus()); capturePerfEvent(pe); - + } } - + private void httpInvokeWithRetries() { int maxRetries = config.getRetryCount(); int sleep = config.getRetryInterval(); @@ -662,7 +662,7 @@ private void httpInvokeWithRetries() { retryCount++; } } - + public void status(int status) { if (status != response.getStatus()) { // make sure log masking is applied @@ -670,7 +670,7 @@ public void status(int status) { setFailedReason(new KarateException(message)); } } - + public KeyStore getKeyStore(String trustStoreFile, String password, String type) { if (trustStoreFile == null) { return null; @@ -712,11 +712,11 @@ public void mockProceed(String requestUrlBase) { } httpInvoke(); } - + public Map mockConfigureHeaders() { return getOrEvalAsMap(config.getResponseHeaders()); } - + public void mockAfterScenario() { if (config.getAfterScenario().isJsOrJavaFunction()) { executeFunction(config.getAfterScenario()); @@ -727,7 +727,7 @@ public void mockAfterScenario() { // private List webSocketClients; CompletableFuture SIGNAL = new CompletableFuture(); - + public WebSocketClient webSocket(WebSocketOptions options) { WebSocketClient webSocketClient = new WebSocketClient(options, logger); if (webSocketClients == null) { @@ -736,7 +736,7 @@ public WebSocketClient webSocket(WebSocketOptions options) { webSocketClients.add(webSocketClient); return webSocketClient; } - + public void signal(Object result) { logger.debug("signal called: {}", result); if (parent != null) { @@ -747,7 +747,7 @@ public void signal(Object result) { } } } - + public Object listen(String exp) { Variable v = evalKarateExpression(exp); int timeout = v.getAsInt(); @@ -765,15 +765,15 @@ public Object listen(String exp) { return listenResult; } } - + public Command fork(boolean useLineFeed, List args) { return fork(useLineFeed, Collections.singletonMap("args", args)); } - + public Command fork(boolean useLineFeed, String line) { return fork(useLineFeed, Collections.singletonMap("line", line)); } - + public Command fork(boolean useLineFeed, Map options) { Boolean useShell = (Boolean) options.get("useShell"); if (useShell == null) { @@ -828,7 +828,7 @@ public Command fork(boolean useLineFeed, Map options) { // protected Driver driver; protected Plugin robot; - + private void autoDef(Plugin plugin, String instanceName) { for (String methodName : plugin.methodNames()) { String invoke = instanceName + "." + methodName; @@ -840,7 +840,7 @@ private void autoDef(Plugin plugin, String instanceName) { setHiddenVariable(methodName, evalJs(sb.toString())); } } - + public void driver(String exp) { Variable v = evalKarateExpression(exp); // re-create driver within a test if needed @@ -860,7 +860,7 @@ public void driver(String exp) { driver.setUrl(v.getAsString()); } } - + public void robot(String exp) { Variable v = evalKarateExpression(exp); if (robot == null) { @@ -887,7 +887,7 @@ public void robot(String exp) { setRobot(robot); } } - + public void setDriver(Driver driver) { this.driver = driver; setHiddenVariable(DRIVER, driver); @@ -898,7 +898,7 @@ public void setDriver(Driver driver) { autoDef(driver, DRIVER); setHiddenVariable(KEY, Key.INSTANCE); } - + public void setRobot(Plugin robot) { // TODO unify this.robot = robot; // robot.setContext(this); @@ -910,7 +910,7 @@ public void setRobot(Plugin robot) { // TODO unify autoDef(robot, ROBOT); setHiddenVariable(KEY, Key.INSTANCE); } - + public void stop(StepResult lastStepResult) { if (runtime.caller.isSharedScope()) { // TODO life-cycle this hand off @@ -949,7 +949,7 @@ public void stop(StepResult lastStepResult) { } } } - + private void embedVideo(String path) { if (path != null) { File videoFile = new File(path); @@ -963,7 +963,7 @@ private void embedVideo(String path) { // doc ===================================================================== // private KarateTemplateEngine templateEngine; - + public void doc(String exp) { if (runtime.reportDisabled) { return; @@ -1022,7 +1022,7 @@ public void init() { // not in constructor because it has to be on Runnable.run( } } } - + private void attachVariables() { vars.forEach((k, v) -> { switch (v.type) { @@ -1049,7 +1049,7 @@ private void attachVariables() { JS.put(k, v.getValue()); }); } - + public Map detachVariables(boolean deep) { // TODO make deep the sole default Map detached = new HashMap(vars.size()); vars.forEach((k, v) -> { @@ -1074,7 +1074,7 @@ public Map detachVariables(boolean deep) { // TODO make deep t }); return detached; } - + protected Object recurseAndAttach(Object o) { if (o instanceof Value) { Value value = (Value) o; @@ -1106,7 +1106,7 @@ protected Object recurseAndAttach(Object o) { return null; } } - + protected Object recurseAndDetach(Object o) { if (o instanceof Value) { Value value = (Value) o; @@ -1135,7 +1135,7 @@ protected Object recurseAndDetach(Object o) { return null; } } - + protected Object recurseAndAttachAndDeepClone(Object o) { if (o instanceof Value) { // should never happen if the detach was done properly @@ -1148,7 +1148,7 @@ protected Object recurseAndAttachAndDeepClone(Object o) { } catch (Exception e) { logger.warn("failed to re-attach graal value: {}", e.getMessage()); return null; // we try to move on after stripping this (js function) from the tree - } + } } if (o instanceof JsFunction) { JsFunction jf = (JsFunction) o; @@ -1167,7 +1167,7 @@ protected Object recurseAndAttachAndDeepClone(Object o) { return o; } } - + protected Object recurseAndDetachAndDeepClone(Object o) { if (o instanceof Value) { Value value = (Value) o; @@ -1189,16 +1189,16 @@ protected Object recurseAndDetachAndDeepClone(Object o) { } else { return o; } - } - + } + public Value attachSource(CharSequence source) { return JS.attachSource(source); } - + public Value attach(Value before) { return JS.attach(before); } - + protected Map getOrEvalAsMap(Variable var, Object... args) { if (var.isJsOrJavaFunction()) { Variable res = executeFunction(var, args); @@ -1207,7 +1207,7 @@ protected Map getOrEvalAsMap(Variable var, Object... args) { return var.isMap() ? var.getValue() : null; } } - + public Variable executeFunction(Variable var, Object... args) { switch (var.type) { case JS_FUNCTION: @@ -1223,7 +1223,7 @@ public Variable executeFunction(Variable var, Object... args) { throw new RuntimeException("expected function, but was: " + var); } } - + private JsValue executeJsValue(Value function, Object... args) { try { return JS.execute(function, args); @@ -1234,7 +1234,7 @@ private JsValue executeJsValue(Value function, Object... args) { throw ke; } } - + public Variable evalJs(String js) { try { return new Variable(JS.eval(js)); @@ -1244,7 +1244,7 @@ public Variable evalJs(String js) { throw ke; } } - + protected static KarateException fromJsEvalException(String js, Exception e) { // do our best to make js error traces informative, else thrown exception seems to // get swallowed by the java reflection based method invoke flow @@ -1267,14 +1267,14 @@ protected static KarateException fromJsEvalException(String js, Exception e) { } return new KarateException(sb.toString()); } - + public void setHiddenVariable(String key, Object value) { if (value instanceof Variable) { value = ((Variable) value).getValue(); } JS.put(key, value); } - + public void setVariable(String key, Object value) { Variable v; if (value instanceof Variable) { @@ -1290,30 +1290,30 @@ public void setVariable(String key, Object value) { children.forEach(c -> c.setVariable(key, value)); } } - + public void setVariables(Map map) { if (map == null) { return; } map.forEach((k, v) -> setVariable(k, v)); } - + private static Map copy(Map source, boolean deep) { Map map = new HashMap(source.size()); source.forEach((k, v) -> map.put(k, v.copy(deep))); return map; } - + public Map copyVariables(boolean deep) { return copy(vars, deep); } - + public Map getAllVariablesAsMap() { Map map = new HashMap(vars.size()); vars.forEach((k, v) -> map.put(k, v == null ? null : v.getValue())); return map; } - + private static void validateVariableName(String name) { if (!isValidVariableName(name)) { throw new RuntimeException("invalid variable name: " + name); @@ -1325,7 +1325,7 @@ private static void validateVariableName(String name) { throw new RuntimeException("'" + name + "' is a reserved name, also use the form '* " + name + " ' instead"); } } - + private Variable evalAndCastTo(AssignType assignType, String exp) { Variable v = evalKarateExpression(exp); switch (assignType) { @@ -1350,7 +1350,7 @@ private Variable evalAndCastTo(AssignType assignType, String exp) { return v; // as is } } - + public void assign(AssignType assignType, String name, String exp) { name = StringUtils.trimToEmpty(name); validateVariableName(name); // always validate when gherkin @@ -1363,31 +1363,31 @@ public void assign(AssignType assignType, String name, String exp) { setVariable(name, evalAndCastTo(assignType, exp)); } } - + private static boolean isEmbeddedExpression(String text) { return text != null && (text.startsWith("#(") || text.startsWith("##(")) && text.endsWith(")"); } - + private static class EmbedAction { - + final boolean remove; final Object value; - + private EmbedAction(boolean remove, Object value) { this.remove = remove; this.value = value; } - + static EmbedAction remove() { return new EmbedAction(true, null); } - + static EmbedAction update(Object value) { return new EmbedAction(false, value); } - + } - + public Variable evalEmbeddedExpressions(Variable value) { switch (value.type) { case STRING: @@ -1405,7 +1405,7 @@ public Variable evalEmbeddedExpressions(Variable value) { return value; } } - + private EmbedAction recurseEmbeddedExpressions(Variable node) { switch (node.type) { case LIST: @@ -1480,7 +1480,7 @@ private EmbedAction recurseEmbeddedExpressions(Variable node) { return null; } } - + private void recurseXmlEmbeddedExpressions(Node node) { if (node.getNodeType() == Node.DOCUMENT_NODE) { node = node.getFirstChild(); @@ -1564,7 +1564,7 @@ private void recurseXmlEmbeddedExpressions(Node node) { grandParent.removeChild(parent); } } - + public String replacePlaceholderText(String text, String token, String replaceWith) { if (text == null) { return null; @@ -1592,9 +1592,9 @@ public String replacePlaceholderText(String text, String token, String replaceWi } return text.replace(token, replaceWith); } - + private static final String TOKEN = "token"; - + public void replaceTable(String text, List> list) { if (text == null) { return; @@ -1617,17 +1617,17 @@ public void replaceTable(String text, List> list) { replace(text, token, value); } } - + } - + public void set(String name, String path, Variable value) { set(name, path, false, value, false, false); } - + private void set(String name, String path, String exp, boolean delete, boolean viaTable) { set(name, path, isWithinParentheses(exp), evalKarateExpression(exp), delete, viaTable); } - + private void set(String name, String path, boolean isWithinParentheses, Variable value, boolean delete, boolean viaTable) { name = StringUtils.trimToEmpty(name); path = StringUtils.trimToNull(path); @@ -1691,11 +1691,11 @@ private void set(String name, String path, boolean isWithinParentheses, Variable json.set(path, value.getValue()); } } - + } - + private static final String PATH = "path"; - + public void setViaTable(String name, String path, List> list) { name = StringUtils.trimToEmpty(name); path = StringUtils.trimToNull(path); @@ -1745,7 +1745,7 @@ public void setViaTable(String name, String path, List> list } } } - + public static StringUtils.Pair parseVariableAndPath(String text) { Matcher matcher = VAR_AND_PATH_PATTERN.matcher(text); matcher.find(); @@ -1763,7 +1763,7 @@ public static StringUtils.Pair parseVariableAndPath(String text) { } return StringUtils.pair(name, path); } - + public Match.Result match(Match.Type matchType, String expression, String path, String rhs) { String name = StringUtils.trimToEmpty(expression); if (isDollarPrefixedJsonPath(name) || isXmlPath(name)) { // @@ -1823,81 +1823,81 @@ private Match.Result matchHeader(Match.Type matchType, String name, String exp) String actual = response.getHeader(name); return match(matchType, actual, expected.getValue()); } - + public Match.Result match(Match.Type matchType, Object actual, Object expected) { return Match.execute(JS, matchType, actual, expected); } - + private static final Pattern VAR_AND_PATH_PATTERN = Pattern.compile("\\w+"); private static final String VARIABLE_PATTERN_STRING = "[a-zA-Z][\\w]*"; private static final Pattern VARIABLE_PATTERN = Pattern.compile(VARIABLE_PATTERN_STRING); private static final Pattern FUNCTION_PATTERN = Pattern.compile("^function[^(]*\\("); private static final Pattern JS_PLACEHODER = Pattern.compile("\\$\\{.*?\\}"); - + public static boolean isJavaScriptFunction(String text) { return FUNCTION_PATTERN.matcher(text).find(); } - + public static boolean isValidVariableName(String name) { return VARIABLE_PATTERN.matcher(name).matches(); } - + public static boolean hasJavaScriptPlacehoder(String exp) { return JS_PLACEHODER.matcher(exp).find(); } - + public static final boolean isVariableAndSpaceAndPath(String text) { return text.matches("^" + VARIABLE_PATTERN_STRING + "\\s+.+"); } - + public static final boolean isVariable(String text) { return VARIABLE_PATTERN.matcher(text).matches(); } - + public static final boolean isWithinParentheses(String text) { return text != null && text.startsWith("(") && text.endsWith(")"); } - + public static final boolean isCallSyntax(String text) { return text.startsWith("call "); } - + public static final boolean isCallOnceSyntax(String text) { return text.startsWith("callonce "); } - + public static final boolean isGetSyntax(String text) { return text.startsWith("get ") || text.startsWith("get["); } - + public static final boolean isJson(String text) { return text.startsWith("{") || text.startsWith("["); } - + public static final boolean isXml(String text) { return text.startsWith("<"); } - + public static boolean isXmlPath(String text) { return text.startsWith("/"); } - + public static boolean isXmlPathFunction(String text) { return text.matches("^[a-z-]+\\(.+"); } - + public static final boolean isJsonPath(String text) { return text.indexOf('*') != -1 || text.contains("..") || text.contains("[?"); } - + public static final boolean isDollarPrefixed(String text) { return text.startsWith("$"); } - + public static final boolean isDollarPrefixedJsonPath(String text) { return text.startsWith("$.") || text.startsWith("$[") || text.equals("$"); } - + public static StringUtils.Pair parseCallArgs(String line) { int pos = line.indexOf("read("); if (pos != -1) { @@ -1913,7 +1913,7 @@ public static StringUtils.Pair parseCallArgs(String line) { } return new StringUtils.Pair(line.substring(0, pos), StringUtils.trimToNull(line.substring(pos))); } - + public Variable call(Variable called, Variable arg, boolean sharedScope) { switch (called.type) { case JS_FUNCTION: @@ -1927,7 +1927,7 @@ public Variable call(Variable called, Variable arg, boolean sharedScope) { throw new RuntimeException("not a callable feature or js function: " + called); } } - + public Variable call(boolean callOnce, String exp, boolean sharedScope) { StringUtils.Pair pair = parseCallArgs(exp); Variable called = evalKarateExpression(pair.left); @@ -1943,7 +1943,7 @@ public Variable call(boolean callOnce, String exp, boolean sharedScope) { } return result; } - + private Variable callOnceResult(ScenarioCall.Result result, boolean sharedScope) { if (sharedScope) { // if shared scope vars.clear(); // clean slate @@ -1959,7 +1959,7 @@ private Variable callOnceResult(ScenarioCall.Result result, boolean sharedScope) return new Variable(resultValue); } } - + private Variable callOnce(String cacheKey, Variable called, Variable arg, boolean sharedScope) { // IMPORTANT: the call result is always cloned before returning // so that call result (especially if a java Map) is not mutated by other scenarios @@ -1994,7 +1994,7 @@ private Variable callOnce(String cacheKey, Variable called, Variable arg, boolea return resultValue; // another routine will apply globally if needed } } - + public Variable callFeature(Feature feature, Variable arg, int index, boolean sharedScope) { if (arg == null || arg.isMap()) { ScenarioCall call = new ScenarioCall(runtime, feature, arg); @@ -2054,7 +2054,7 @@ public Variable callFeature(Feature feature, Variable arg, int index, boolean sh throw new RuntimeException("feature call argument is not a json object or array: " + arg); } } - + public Variable evalJsonPath(Variable v, String path) { Json json = Json.of(v.getValueAndForceParsingAsJson()); try { @@ -2063,7 +2063,7 @@ public Variable evalJsonPath(Variable v, String path) { return Variable.NOT_PRESENT; } } - + public static Variable evalXmlPath(Variable xml, String path) { NodeList nodeList; Node doc = xml.getAsXml(); @@ -2093,7 +2093,7 @@ public static Variable evalXmlPath(Variable xml, String path) { } return new Variable(list); } - + private static Variable nodeToValue(Node node) { int childElementCount = XmlUtils.getChildElementCount(node); if (childElementCount == 0) { @@ -2106,15 +2106,15 @@ private static Variable nodeToValue(Node node) { return new Variable(XmlUtils.toNewDocument(node)); } } - + public Variable evalJsonPathOnVariableByName(String name, String path) { return evalJsonPath(vars.get(name), path); } - + public Variable evalXmlPathOnVariableByName(String name, String path) { return evalXmlPath(vars.get(name), path); } - + public Variable evalKarateExpression(String text) { text = StringUtils.trimToNull(text); if (text == null) { @@ -2196,5 +2196,5 @@ public Variable evalKarateExpression(String text) { return evalJs(text); } } - + }