Skip to content

Commit 88026a2

Browse files
Sharma PrashantSharma Prashant
Sharma Prashant
authored and
Sharma Prashant
committed
Add afterFeature and beforeFeature hooks, RunnerBuilder
1 parent 3b5672c commit 88026a2

File tree

7 files changed

+237
-17
lines changed

7 files changed

+237
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.intuit.karate;
2+
3+
import com.intuit.karate.core.*;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.File;
7+
import java.util.*;
8+
import java.util.concurrent.CountDownLatch;
9+
import java.util.concurrent.ExecutorService;
10+
import java.util.concurrent.Executors;
11+
12+
public class RunnerBuilder {
13+
14+
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(RunnerBuilder.class);
15+
16+
private int threadCount = 1;
17+
private Class<?> testClass;
18+
private List<String> paths;
19+
private String reportDir = FileUtils.getBuildDir() + File.separator + ScriptBindings.SUREFIRE_REPORTS;
20+
private List<String> tags;
21+
private Collection<ExecutionHook> hooks = Collections.emptyList();;
22+
private String scenarioName;
23+
24+
private RunnerBuilder(){}
25+
26+
public RunnerBuilder(Class<?> testClass){
27+
this.testClass = testClass;
28+
}
29+
30+
public RunnerBuilder(String... paths){
31+
this.paths = Arrays.asList(paths);
32+
}
33+
34+
public RunnerBuilder(List<String> tags, String... paths){
35+
this.paths = Arrays.asList(paths);
36+
this.tags = tags;
37+
}
38+
public RunnerBuilder threadCount(int threadCount) {
39+
this.threadCount = threadCount;
40+
return this;
41+
}
42+
43+
public RunnerBuilder reportDir(String reportDir) {
44+
this.reportDir = reportDir;
45+
return this;
46+
}
47+
48+
public RunnerBuilder hooks(Collection<ExecutionHook> hooks) {
49+
this.hooks.addAll(hooks);
50+
return this;
51+
}
52+
53+
public RunnerBuilder hook(ExecutionHook hook){
54+
this.hooks.add(hook);
55+
return this;
56+
}
57+
58+
public RunnerBuilder scenarioName(String scenarioName) {
59+
this.scenarioName = scenarioName;
60+
return this;
61+
}
62+
63+
public Results runParallel() {
64+
String tagSelector;
65+
List<Resource> resources;
66+
// check if ambiguous configuration provided
67+
if (testClass != null) {
68+
RunnerOptions options = RunnerOptions.fromAnnotationAndSystemProperties(testClass);
69+
tagSelector = options.getTags() == null ? null : Tags.fromKarateOptionsTags(options.getTags());
70+
resources = FileUtils.scanForFeatureFiles(options.getFeatures(), Thread.currentThread().getContextClassLoader());
71+
}else {
72+
tagSelector = Tags.fromKarateOptionsTags(tags);
73+
resources = FileUtils.scanForFeatureFiles(paths, Thread.currentThread().getContextClassLoader());
74+
}
75+
76+
new File(reportDir).mkdirs();
77+
78+
final String finalReportDir = reportDir;
79+
Results results = Results.startTimer(threadCount);
80+
ExecutorService featureExecutor = Executors.newFixedThreadPool(threadCount);
81+
ExecutorService scenarioExecutor = Executors.newWorkStealingPool(threadCount);
82+
int executedFeatureCount = 0;
83+
try {
84+
int count = resources.size();
85+
CountDownLatch latch = new CountDownLatch(count);
86+
List<FeatureResult> featureResults = new ArrayList(count);
87+
for (int i = 0; i < count; i++) {
88+
Resource resource = resources.get(i);
89+
int index = i + 1;
90+
Feature feature = FeatureParser.parse(resource);
91+
feature.setCallName(scenarioName);
92+
feature.setCallLine(resource.getLine());
93+
FeatureContext featureContext = new FeatureContext(null, feature, tagSelector);
94+
CallContext callContext = CallContext.forAsync(feature, hooks, null, false);
95+
ExecutionContext execContext = new ExecutionContext(results.getStartTime(), featureContext, callContext, reportDir,
96+
r -> featureExecutor.submit(r), scenarioExecutor);
97+
featureResults.add(execContext.result);
98+
FeatureExecutionUnit unit = new FeatureExecutionUnit(execContext);
99+
unit.setNext(() -> {
100+
FeatureResult result = execContext.result;
101+
if (result.getScenarioCount() > 0) { // possible that zero scenarios matched tags
102+
File file = Engine.saveResultJson(finalReportDir, result, null);
103+
if (result.getScenarioCount() < 500) {
104+
// TODO this routine simply cannot handle that size
105+
Engine.saveResultXml(finalReportDir, result, null);
106+
}
107+
String status = result.isFailed() ? "fail" : "pass";
108+
logger.info("<<{}>> feature {} of {}: {}", status, index, count, feature.getRelativePath());
109+
result.printStats(file.getPath());
110+
} else {
111+
results.addToSkipCount(1);
112+
if (logger.isTraceEnabled()) {
113+
logger.trace("<<skip>> feature {} of {}: {}", index, count, feature.getRelativePath());
114+
}
115+
}
116+
latch.countDown();
117+
});
118+
featureExecutor.submit(unit);
119+
}
120+
latch.await();
121+
results.stopTimer();
122+
for (FeatureResult result : featureResults) {
123+
int scenarioCount = result.getScenarioCount();
124+
results.addToScenarioCount(scenarioCount);
125+
if (scenarioCount != 0) {
126+
executedFeatureCount++;
127+
}
128+
results.addToFailCount(result.getFailedCount());
129+
results.addToTimeTaken(result.getDurationMillis());
130+
if (result.isFailed()) {
131+
results.addToFailedList(result.getPackageQualifiedName(), result.getErrorMessages());
132+
}
133+
results.addScenarioResults(result.getScenarioResults());
134+
}
135+
} catch (Exception e) {
136+
logger.error("karate parallel runner failed: ", e.getMessage());
137+
results.setFailureReason(e);
138+
} finally {
139+
featureExecutor.shutdownNow();
140+
scenarioExecutor.shutdownNow();
141+
}
142+
results.setFeatureCount(executedFeatureCount);
143+
results.printStats(threadCount);
144+
Engine.saveStatsJson(reportDir, results, null);
145+
Engine.saveTimelineHtml(reportDir, results, null);
146+
results.setReportDir(reportDir);
147+
return results;
148+
}
149+
}

karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@
3030
* @author pthomas3
3131
*/
3232
public interface ExecutionHook {
33-
33+
3434
/**
35-
*
35+
*
3636
* @param scenario
37-
* @param context
37+
* @param context
3838
* @return false if the scenario should be excluded from the test-run
3939
* @throws RuntimeException (any) to abort the scenario
4040
*/
4141
boolean beforeScenario(Scenario scenario, ScenarioContext context);
42-
42+
4343
void afterScenario(ScenarioResult result, ScenarioContext context);
44-
44+
4545
String getPerfEventName(HttpRequestBuilder req, ScenarioContext context);
46-
46+
4747
void reportPerfEvent(PerfEvent event);
48-
48+
49+
void beforeFeature(Feature feature, FeatureContext context);
50+
51+
void afterFeature(FeatureResult result, FeatureContext context);
4952
}

karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java

+14
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ public void init(Logger logger) { // logger applies only if called from ui
5656
int count = units.size();
5757
results = new ArrayList(count);
5858
latch = new CountDownLatch(count);
59+
if (exec.callContext.executionHooks != null) {
60+
try {
61+
exec.callContext.executionHooks.forEach(executionHook -> executionHook.beforeFeature(exec.featureContext.feature, exec.featureContext));
62+
} catch (Exception e) {
63+
// Need a not null logger
64+
}
65+
}
5966
}
6067

6168
public void setNext(Runnable next) {
@@ -98,6 +105,13 @@ public void stop() {
98105
exec.result.setResultVars(lastContextExecuted.vars);
99106
lastContextExecuted.invokeAfterHookIfConfigured(true);
100107
}
108+
if (exec.callContext.executionHooks != null) {
109+
try {
110+
exec.callContext.executionHooks.forEach(executionHook -> executionHook.afterFeature(exec.result, exec.featureContext));
111+
} catch (Exception e) {
112+
// Need a logger
113+
}
114+
}
101115
}
102116

103117
public boolean isSelected(ScenarioExecutionUnit unit) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.intuit.karate;
2+
3+
import org.junit.Test;
4+
5+
import java.io.File;
6+
import java.util.Collections;
7+
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertTrue;
10+
11+
@KarateOptions(tags = {"~@ignore"})
12+
public class RunnerBuilderTest {
13+
private boolean contains(String reportPath, String textToFind) {
14+
String contents = FileUtils.toString(new File(reportPath));
15+
return contents.contains(textToFind);
16+
}
17+
@Test
18+
public void testBuilderWithTestClass() {
19+
RunnerBuilder builder = new RunnerBuilder(getClass());
20+
Results results = builder.runParallel();
21+
assertEquals(2, results.getFailCount());
22+
}
23+
24+
@Test
25+
public void testBuilderWithTestPath() {
26+
RunnerBuilder builder = new RunnerBuilder(Collections.singletonList("~@ignore"),"classpath:com/intuit/karate");
27+
Results results = builder.runParallel();
28+
assertEquals(2, results.getFailCount());
29+
String pathBase = "target/surefire-reports/com.intuit.karate.";
30+
assertTrue(contains(pathBase + "core.scenario.xml", "Then match b == { foo: 'bar'}"));
31+
assertTrue(contains(pathBase + "core.outline.xml", "Then assert a == 55"));
32+
assertTrue(contains(pathBase + "multi-scenario.xml", "Then assert a != 2"));
33+
// a scenario failure should not stop other features from running
34+
assertTrue(contains(pathBase + "multi-scenario-fail.xml", "Then assert a != 2 ........................................................ passed"));
35+
assertEquals(2, results.getFailedMap().size());
36+
assertTrue(results.getFailedMap().keySet().contains("com.intuit.karate.no-scenario-name"));
37+
assertTrue(results.getFailedMap().keySet().contains("com.intuit.karate.multi-scenario-fail"));
38+
}
39+
}

karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java

+10
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,14 @@ public void reportPerfEvent(PerfEvent event) {
6565

6666
}
6767

68+
@Override
69+
public void beforeFeature(Feature feature, FeatureContext context) {
70+
71+
}
72+
73+
@Override
74+
public void afterFeature(FeatureResult result, FeatureContext context) {
75+
76+
}
77+
6878
}

karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class KarateAction(val name: String, val protocol: KarateProtocol, val system: A
5757

5858
override def afterScenario(scenarioResult: ScenarioResult, scenarioContext: ScenarioContext) = {}
5959

60+
override def beforeFeature(Feature: Feature, ctx: FeatureContext) = {}
61+
62+
override def afterFeature(FeatureResult: FeatureResult, ctx: FeatureContext) = {}
63+
6064
override def getPerfEventName(req: HttpRequestBuilder, ctx: ScenarioContext): String = {
6165
val customName = protocol.nameResolver.apply(req, ctx)
6266
val finalName = if (customName != null) customName else protocol.defaultNameResolver.apply(req, ctx)

karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java

+11-10
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,7 @@
2424
package com.intuit.karate.junit4;
2525

2626
import com.intuit.karate.CallContext;
27-
import com.intuit.karate.core.FeatureContext;
28-
import com.intuit.karate.core.ExecutionContext;
29-
import com.intuit.karate.core.ExecutionHook;
30-
import com.intuit.karate.core.Feature;
31-
import com.intuit.karate.core.FeatureExecutionUnit;
32-
import com.intuit.karate.core.PerfEvent;
33-
import com.intuit.karate.core.Scenario;
34-
import com.intuit.karate.core.ScenarioContext;
35-
import com.intuit.karate.core.ScenarioExecutionUnit;
36-
import com.intuit.karate.core.ScenarioResult;
27+
import com.intuit.karate.core.*;
3728
import com.intuit.karate.http.HttpRequestBuilder;
3829
import org.junit.runner.Description;
3930
import org.junit.runner.notification.Failure;
@@ -114,4 +105,14 @@ public void reportPerfEvent(PerfEvent event) {
114105

115106
}
116107

108+
@Override
109+
public void beforeFeature(Feature feature, FeatureContext context) {
110+
111+
}
112+
113+
@Override
114+
public void afterFeature(FeatureResult result, FeatureContext context) {
115+
116+
}
117+
117118
}

0 commit comments

Comments
 (0)