Skip to content

Commit

Permalink
Merge pull request #2097 from karatelabs/issue1905
Browse files Browse the repository at this point in the history
Issue1905
  • Loading branch information
ptrthomas authored Aug 22, 2022
2 parents bb5d2d5 + 5288c38 commit e1c6880
Show file tree
Hide file tree
Showing 38 changed files with 354 additions and 419 deletions.
23 changes: 23 additions & 0 deletions karate-core/src/main/java/com/intuit/karate/core/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@ public Step findStepByLine(int line) {
}
return null;
}

public Scenario getSetup(String name) {
for (FeatureSection section : sections) {
if (section.isOutline()) {
continue;
}
Scenario scenario = section.getScenario();
List<Tag> foundTags = scenario.getTags();
if (foundTags != null) {
for (Tag tag : foundTags) {
if (Tag.SETUP.equals(tag.getName())) {
if (name == null) {
return scenario;
}
if (tag.getValues().contains(name)) {
return scenario;
}
}
}
}
}
return null;
}

public void addSection(FeatureSection section) {
section.setIndex(sections.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void setMockEngine(ScenarioEngine mockEngine) {

public ScenarioEngine getMockEngine() {
return mockEngine;
}
}

public static FeatureRuntime forTempUse(HttpClientFactory hcf) {
Suite sr = Suite.forTempUse(hcf);
Expand Down
104 changes: 54 additions & 50 deletions karate-core/src/main/java/com/intuit/karate/core/MockHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,39 +91,22 @@ public MockHandler(List<Feature> features) {

public MockHandler(String prefix, List<Feature> features, Map<String, Object> args) {
this.prefix = "/".equals(prefix) ? null : prefix;
for (Feature feature : features) {
FeatureRuntime featureRuntime = FeatureRuntime.of(Suite.forTempUse(HttpClientFactory.DEFAULT), feature, args);
FeatureSection section = new FeatureSection();
section.setIndex(-1); // TODO util for creating dummy scenario
Scenario dummy = new Scenario(feature, section, -1);
section.setScenario(dummy);
ScenarioRuntime runtime = new ScenarioRuntime(featureRuntime, dummy);
initRuntime(runtime);
if (feature.isBackgroundPresent()) {
// if we are within a scenario already e.g. karate.start(), preserve context
ScenarioEngine prevEngine = ScenarioEngine.get();
try {
ScenarioEngine.set(runtime.engine);
for (Step step : feature.getBackground().getSteps()) {
Result result = StepRuntime.execute(step, runtime.actions);
if (result.isFailed()) {
String message = "mock-server background failed - " + feature + ":" + step.getLine();
runtime.logger.error(message);
throw new KarateException(message, result.getError());
}
}
} finally {
ScenarioEngine.set(prevEngine);
}
}
features.forEach(feature -> {
ScenarioRuntime runtime = initRuntime(feature, args);
corsEnabled = corsEnabled || runtime.engine.getConfig().isCorsEnabled();
globals.putAll(runtime.engine.shallowCloneVariables());
runtime.logger.info("mock server initialized: {}", feature);
featureRuntimes.put(feature, runtime);
}
featureRuntimes.put(feature, runtime);
});
}

private void initRuntime(ScenarioRuntime runtime) {
private ScenarioRuntime initRuntime(Feature feature, Map<String, Object> args) {
FeatureRuntime featureRuntime = FeatureRuntime.of(Suite.forTempUse(HttpClientFactory.DEFAULT), feature, args);
FeatureSection section = new FeatureSection();
section.setIndex(-1); // TODO util for creating dummy scenario
Scenario dummy = new Scenario(feature, section, -1);
section.setScenario(dummy);
ScenarioRuntime runtime = new ScenarioRuntime(featureRuntime, dummy);
runtime.engine.setVariable(PATH_MATCHES, (Function<String, Boolean>) this::pathMatches);
runtime.engine.setVariable(PARAM_EXISTS, (Function<String, Boolean>) this::paramExists);
runtime.engine.setVariable(PARAM_VALUE, (Function<String, String>) this::paramValue);
Expand All @@ -133,6 +116,24 @@ private void initRuntime(ScenarioRuntime runtime) {
runtime.engine.setVariable(HEADER_CONTAINS, (BiFunction<String, String, Boolean>) this::headerContains);
runtime.engine.setVariable(BODY_PATH, (Function<String, Object>) this::bodyPath);
runtime.engine.init();
if (feature.isBackgroundPresent()) {
// if we are within a scenario already e.g. karate.start(), preserve context
ScenarioEngine prevEngine = ScenarioEngine.get();
try {
ScenarioEngine.set(runtime.engine);
for (Step step : feature.getBackground().getSteps()) {
Result result = StepRuntime.execute(step, runtime.actions);
if (result.isFailed()) {
String message = "mock-server background failed - " + feature + ":" + step.getLine();
runtime.logger.error(message);
throw new KarateException(message, result.getError());
}
}
} finally {
ScenarioEngine.set(prevEngine);
}
}
return runtime;
}

private static final Result PASSED = Result.passed(0);
Expand Down Expand Up @@ -161,27 +162,10 @@ public synchronized Response handle(Request req) { // note the [synchronized]
Feature feature = entry.getKey();
ScenarioRuntime runtime = entry.getValue();
// important for graal to work properly
Thread.currentThread().setContextClassLoader(runtime.featureRuntime.suite.classLoader);
// begin init engine for this request
Thread.currentThread().setContextClassLoader(runtime.featureRuntime.suite.classLoader);
LOCAL_REQUEST.set(req);
req.processBody();
ScenarioEngine engine = new ScenarioEngine(runtime, new HashMap<>(globals));
engine.init();
engine.setVariable(ScenarioEngine.REQUEST_URL_BASE, req.getUrlBase());
engine.setVariable(ScenarioEngine.REQUEST_URI, req.getPath());
engine.setVariable(ScenarioEngine.REQUEST_METHOD, req.getMethod());
engine.setVariable(ScenarioEngine.REQUEST_HEADERS, req.getHeaders());
engine.setVariable(ScenarioEngine.REQUEST, req.getBodyConverted());
engine.setVariable(REQUEST_PARAMS, req.getParams());
engine.setVariable(REQUEST_BYTES, req.getBody());
engine.setRequest(req);
runtime.featureRuntime.setMockEngine(engine);
ScenarioEngine.set(engine);
Map<String, List<Map<String, Object>>> parts = req.getMultiParts();
if (parts != null) {
engine.setHiddenVariable(REQUEST_PARTS, parts);
}
// end init engine for this request
ScenarioEngine engine = initEngine(runtime, globals, req);
for (FeatureSection fs : feature.getSections()) {
if (fs.isOutline()) {
runtime.logger.warn("skipping scenario outline - {}:{}", feature, fs.getScenarioOutline().getLine());
Expand All @@ -192,8 +176,7 @@ public synchronized Response handle(Request req) { // note the [synchronized]
Map<String, Object> configureHeaders;
Variable response, responseStatus, responseHeaders, responseDelay;
ScenarioActions actions = new ScenarioActions(engine);
Result result = PASSED;
result = executeScenarioSteps(feature, runtime, scenario, actions, result);
Result result = executeScenarioSteps(feature, runtime, scenario, actions);
engine.mockAfterScenario();
configureHeaders = engine.mockConfigureHeaders();
response = engine.vars.remove(ScenarioEngine.RESPONSE);
Expand Down Expand Up @@ -242,8 +225,29 @@ public synchronized Response handle(Request req) { // note the [synchronized]
}
return new Response(404);
}

private static ScenarioEngine initEngine(ScenarioRuntime runtime, Map<String, Variable> globals, Request req) {
ScenarioEngine engine = new ScenarioEngine(runtime.engine.getConfig(), runtime, new HashMap(globals), runtime.logger);
engine.init();
engine.setVariable(ScenarioEngine.REQUEST_URL_BASE, req.getUrlBase());
engine.setVariable(ScenarioEngine.REQUEST_URI, req.getPath());
engine.setVariable(ScenarioEngine.REQUEST_METHOD, req.getMethod());
engine.setVariable(ScenarioEngine.REQUEST_HEADERS, req.getHeaders());
engine.setVariable(ScenarioEngine.REQUEST, req.getBodyConverted());
engine.setVariable(REQUEST_PARAMS, req.getParams());
engine.setVariable(REQUEST_BYTES, req.getBody());
engine.setRequest(req);
runtime.featureRuntime.setMockEngine(engine);
ScenarioEngine.set(engine);
Map<String, List<Map<String, Object>>> parts = req.getMultiParts();
if (parts != null) {
engine.setHiddenVariable(REQUEST_PARTS, parts);
}
return engine;
}

private Result executeScenarioSteps(Feature feature, ScenarioRuntime runtime, Scenario scenario, ScenarioActions actions, Result result) {
private Result executeScenarioSteps(Feature feature, ScenarioRuntime runtime, Scenario scenario, ScenarioActions actions) {
Result result = PASSED;
for (Step step : scenario.getSteps()) {
result = StepRuntime.execute(step, actions);
if (result.isAborted()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,12 @@ public Object call(boolean sharedScope, String fileName, Value arg) {
ScenarioEngine engine = getEngine();
Variable called = new Variable(engine.fileReader.readFile(fileName));
Variable result = engine.call(called, arg == null ? null : new Variable(arg), sharedScope);
Variable resultVariables = engine.getCallFeatureVariables(result);
if (sharedScope) {
if (resultVariables.isMap()) {
engine.setVariables(resultVariables.getValue());
}
if (result.getValue() instanceof FeatureResult) {
engine.setConfig(((FeatureResult) result.getValue()).getConfig());
if (result.isMap()) {
engine.setVariables(result.getValue());
}
}
return JsValue.fromJava(resultVariables.getValue());
return JsValue.fromJava(result.getValue());
}

private static Object callSingleResult(ScenarioEngine engine, Object o) throws Exception {
Expand Down Expand Up @@ -236,8 +232,7 @@ public Object callSingle(String fileName, Value arg) throws Exception {
}
Variable resultVar;
try {
Variable featureResult = engine.call(called, argVar, false);
resultVar = engine.getCallFeatureVariables(featureResult);
resultVar = engine.call(called, argVar, false);
} catch (Exception e) {
// don't retain any vestiges of graal-js
RuntimeException re = new RuntimeException(e.getMessage());
Expand Down Expand Up @@ -538,11 +533,11 @@ public Object getPrevRequest() {
public Object getProperties() {
return new JsMap(getEngine().runtime.featureRuntime.suite.systemProperties);
}

public Object getResponse() {
return getEngine().getResponse();
}

public Object getRequest() {
return getEngine().getRequest();
}
Expand Down Expand Up @@ -757,6 +752,33 @@ public void set(String name, String path, Object value) {
getEngine().set(name, path, new Variable(value));
}

public Object setup() {
return setup(null);
}

public Object setup(String name) {
ScenarioEngine engine = getEngine();
Feature feature = engine.runtime.featureRuntime.feature;
Scenario scenario = feature.getSetup(name);
if (scenario == null) {
String message = "no scenario found with @setup tag";
if (name != null) {
message = message + " and name '" + name + "'";
}
engine.logger.error(message);
throw new RuntimeException(message);
}
ScenarioRuntime sr = new ScenarioRuntime(engine.runtime.featureRuntime, scenario);
sr.setSkipBackground(true);
sr.run();
ScenarioEngine.set(engine);
FeatureResult result = engine.runtime.featureRuntime.result;
synchronized (result) {
result.addResult(sr.result);
}
return JsValue.fromJava(sr.engine.getAllVariablesAsMap());
}

public void setXml(String name, String xml) {
getEngine().setVariable(name, XmlUtils.toXmlDoc(xml));
}
Expand All @@ -766,7 +788,7 @@ public void setXml(String name, String path, String xml) {
getEngine().set(name, path, new Variable(XmlUtils.toXmlDoc(xml)));
}

public void signal(Value v) {
public void signal(Value v) {
getEngine().signal(JsValue.toJava(v));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
*/
package com.intuit.karate.core;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -67,11 +66,8 @@ public Config getParentConfig(boolean copy) {
if (parentRuntime == null) {
return new Config();
}
if (copy) {
return new Config(parentRuntime.engine.getConfig());
} else {
return parentRuntime.engine.getConfig();
}
Config temp = parentRuntime.engine.getConfig();
return copy ? new Config(temp) : temp;
}

public boolean isNone() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,6 @@ public class ScenarioEngine {

protected JsEngine JS;

// only used by mock server
public ScenarioEngine(ScenarioRuntime runtime, Map<String, Variable> vars) {
this(runtime.engine.config, runtime, vars, runtime.logger);
}

public ScenarioEngine(Config config, ScenarioRuntime runtime, Map<String, Variable> vars, Logger logger) {
this.config = config;
this.runtime = runtime;
Expand Down Expand Up @@ -1766,31 +1761,12 @@ public Variable call(Variable called, Variable arg, boolean sharedScope) {
case FEATURE:
// will be always a map or a list of maps (loop call result)
Object callResult = callFeature(called.getValue(), arg, -1, sharedScope);
// this.rehydrateCallFeatureResult(callResult);
return new Variable(callResult);
default:
throw new RuntimeException("not a callable feature or js function: " + called);
}
}

public Variable getCallFeatureVariables(Variable featureResult) {
if (featureResult.getValue() instanceof FeatureResult) {
return new Variable(((FeatureResult) featureResult.getValue()).getVariables());
} else if (featureResult.isList()) {
List resultVariables = new ArrayList();
((List) featureResult.getValue()).forEach(result -> {
if (result instanceof FeatureResult) {
resultVariables.add(this.getCallFeatureVariables(new Variable(result)).getValue());
} else {
resultVariables.add(result);
}
});
return new Variable(resultVariables);
} else {
return featureResult;
}
}

public Variable call(boolean callOnce, String exp, boolean sharedScope) {
StringUtils.Pair pair = parseCallArgs(exp);
Variable called = evalKarateExpression(pair.left);
Expand All @@ -1801,20 +1777,16 @@ public Variable call(boolean callOnce, String exp, boolean sharedScope) {
} else {
result = call(called, arg, sharedScope);
}
Variable resultVariables = this.getCallFeatureVariables(result);
if (sharedScope) {
if (resultVariables.isMap()) {
if (result.isMap()) {
// even the act of introspecting graal values as part of the JsValue constructor
// triggers the dreaded graal js single-thread check, so we lock here
synchronized (JsValue.LOCK) {
setVariables(resultVariables.getValue());
setVariables(result.getValue());
}
}
if (result.getValue() instanceof FeatureResult) {
setConfig(((FeatureResult) result.getValue()).getConfig());
}
}
return new Variable(resultVariables.getValue());
return result;
}

private Variable callOnceResult(ScenarioCall.Result result, boolean sharedScope) {
Expand Down Expand Up @@ -1867,11 +1839,10 @@ private Variable callOnce(String cacheKey, Variable called, Variable arg, boolea
}
// this thread is the 'winner'
logger.info(">> lock acquired, begin callonce: {}", cacheKey);
Variable resultValue = call(called, arg, sharedScope);
Variable resultVariables = this.getCallFeatureVariables(resultValue);
Variable callResult = call(called, arg, sharedScope);
// we clone result (and config) here, to snapshot state at the point the callonce was invoked
Map<String, Variable> clonedVars = called.isFeature() && sharedScope ? shallowCloneVariables() : null;
result = new ScenarioCall.Result(resultVariables.copy(false), new Config(config), clonedVars);
result = new ScenarioCall.Result(callResult.copy(false), new Config(config), clonedVars);
CACHE.put(cacheKey, result);
logger.info("<< lock released, cached callonce: {}", cacheKey);
// another routine will apply globally if needed
Expand All @@ -1894,7 +1865,7 @@ public Object callFeature(Feature feature, Variable arg, int index, boolean shar
KarateException ke = result.getErrorMessagesCombined();
throw ke;
} else {
return result;
return result.getVariables();
}
} else if (arg.isList() || arg.isJsOrJavaFunction()) {
List result = new ArrayList();
Expand Down
Loading

0 comments on commit e1c6880

Please sign in to comment.