Skip to content

Commit

Permalink
Add afterFeature and beforeFeature hooks, RunnerBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
Sharma Prashant authored and Sharma Prashant committed Aug 14, 2019
1 parent 3b5672c commit 88026a2
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 17 deletions.
149 changes: 149 additions & 0 deletions karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.intuit.karate;

import com.intuit.karate.core.*;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RunnerBuilder {

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(RunnerBuilder.class);

private int threadCount = 1;
private Class<?> testClass;
private List<String> paths;
private String reportDir = FileUtils.getBuildDir() + File.separator + ScriptBindings.SUREFIRE_REPORTS;
private List<String> tags;
private Collection<ExecutionHook> hooks = Collections.emptyList();;
private String scenarioName;

private RunnerBuilder(){}

public RunnerBuilder(Class<?> testClass){
this.testClass = testClass;
}

public RunnerBuilder(String... paths){
this.paths = Arrays.asList(paths);
}

public RunnerBuilder(List<String> tags, String... paths){
this.paths = Arrays.asList(paths);
this.tags = tags;
}
public RunnerBuilder threadCount(int threadCount) {
this.threadCount = threadCount;
return this;
}

public RunnerBuilder reportDir(String reportDir) {
this.reportDir = reportDir;
return this;
}

public RunnerBuilder hooks(Collection<ExecutionHook> hooks) {
this.hooks.addAll(hooks);
return this;
}

public RunnerBuilder hook(ExecutionHook hook){
this.hooks.add(hook);
return this;
}

public RunnerBuilder scenarioName(String scenarioName) {
this.scenarioName = scenarioName;
return this;
}

public Results runParallel() {
String tagSelector;
List<Resource> resources;
// check if ambiguous configuration provided
if (testClass != null) {
RunnerOptions options = RunnerOptions.fromAnnotationAndSystemProperties(testClass);
tagSelector = options.getTags() == null ? null : Tags.fromKarateOptionsTags(options.getTags());
resources = FileUtils.scanForFeatureFiles(options.getFeatures(), Thread.currentThread().getContextClassLoader());
}else {
tagSelector = Tags.fromKarateOptionsTags(tags);
resources = FileUtils.scanForFeatureFiles(paths, Thread.currentThread().getContextClassLoader());
}

new File(reportDir).mkdirs();

final String finalReportDir = reportDir;
Results results = Results.startTimer(threadCount);
ExecutorService featureExecutor = Executors.newFixedThreadPool(threadCount);
ExecutorService scenarioExecutor = Executors.newWorkStealingPool(threadCount);
int executedFeatureCount = 0;
try {
int count = resources.size();
CountDownLatch latch = new CountDownLatch(count);
List<FeatureResult> featureResults = new ArrayList(count);
for (int i = 0; i < count; i++) {
Resource resource = resources.get(i);
int index = i + 1;
Feature feature = FeatureParser.parse(resource);
feature.setCallName(scenarioName);
feature.setCallLine(resource.getLine());
FeatureContext featureContext = new FeatureContext(null, feature, tagSelector);
CallContext callContext = CallContext.forAsync(feature, hooks, null, false);
ExecutionContext execContext = new ExecutionContext(results.getStartTime(), featureContext, callContext, reportDir,
r -> featureExecutor.submit(r), scenarioExecutor);
featureResults.add(execContext.result);
FeatureExecutionUnit unit = new FeatureExecutionUnit(execContext);
unit.setNext(() -> {
FeatureResult result = execContext.result;
if (result.getScenarioCount() > 0) { // possible that zero scenarios matched tags
File file = Engine.saveResultJson(finalReportDir, result, null);
if (result.getScenarioCount() < 500) {
// TODO this routine simply cannot handle that size
Engine.saveResultXml(finalReportDir, result, null);
}
String status = result.isFailed() ? "fail" : "pass";
logger.info("<<{}>> feature {} of {}: {}", status, index, count, feature.getRelativePath());
result.printStats(file.getPath());
} else {
results.addToSkipCount(1);
if (logger.isTraceEnabled()) {
logger.trace("<<skip>> feature {} of {}: {}", index, count, feature.getRelativePath());
}
}
latch.countDown();
});
featureExecutor.submit(unit);
}
latch.await();
results.stopTimer();
for (FeatureResult result : featureResults) {
int scenarioCount = result.getScenarioCount();
results.addToScenarioCount(scenarioCount);
if (scenarioCount != 0) {
executedFeatureCount++;
}
results.addToFailCount(result.getFailedCount());
results.addToTimeTaken(result.getDurationMillis());
if (result.isFailed()) {
results.addToFailedList(result.getPackageQualifiedName(), result.getErrorMessages());
}
results.addScenarioResults(result.getScenarioResults());
}
} catch (Exception e) {
logger.error("karate parallel runner failed: ", e.getMessage());
results.setFailureReason(e);
} finally {
featureExecutor.shutdownNow();
scenarioExecutor.shutdownNow();
}
results.setFeatureCount(executedFeatureCount);
results.printStats(threadCount);
Engine.saveStatsJson(reportDir, results, null);
Engine.saveTimelineHtml(reportDir, results, null);
results.setReportDir(reportDir);
return results;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,23 @@
* @author pthomas3
*/
public interface ExecutionHook {

/**
*
*
* @param scenario
* @param context
* @param context
* @return false if the scenario should be excluded from the test-run
* @throws RuntimeException (any) to abort the scenario
*/
boolean beforeScenario(Scenario scenario, ScenarioContext context);

void afterScenario(ScenarioResult result, ScenarioContext context);

String getPerfEventName(HttpRequestBuilder req, ScenarioContext context);

void reportPerfEvent(PerfEvent event);


void beforeFeature(Feature feature, FeatureContext context);

void afterFeature(FeatureResult result, FeatureContext context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ public void init(Logger logger) { // logger applies only if called from ui
int count = units.size();
results = new ArrayList(count);
latch = new CountDownLatch(count);
if (exec.callContext.executionHooks != null) {
try {
exec.callContext.executionHooks.forEach(executionHook -> executionHook.beforeFeature(exec.featureContext.feature, exec.featureContext));
} catch (Exception e) {
// Need a not null logger
}
}
}

public void setNext(Runnable next) {
Expand Down Expand Up @@ -98,6 +105,13 @@ public void stop() {
exec.result.setResultVars(lastContextExecuted.vars);
lastContextExecuted.invokeAfterHookIfConfigured(true);
}
if (exec.callContext.executionHooks != null) {
try {
exec.callContext.executionHooks.forEach(executionHook -> executionHook.afterFeature(exec.result, exec.featureContext));
} catch (Exception e) {
// Need a logger
}
}
}

public boolean isSelected(ScenarioExecutionUnit unit) {
Expand Down
39 changes: 39 additions & 0 deletions karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.intuit.karate;

import org.junit.Test;

import java.io.File;
import java.util.Collections;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@KarateOptions(tags = {"~@ignore"})
public class RunnerBuilderTest {
private boolean contains(String reportPath, String textToFind) {
String contents = FileUtils.toString(new File(reportPath));
return contents.contains(textToFind);
}
@Test
public void testBuilderWithTestClass() {
RunnerBuilder builder = new RunnerBuilder(getClass());
Results results = builder.runParallel();
assertEquals(2, results.getFailCount());
}

@Test
public void testBuilderWithTestPath() {
RunnerBuilder builder = new RunnerBuilder(Collections.singletonList("~@ignore"),"classpath:com/intuit/karate");
Results results = builder.runParallel();
assertEquals(2, results.getFailCount());
String pathBase = "target/surefire-reports/com.intuit.karate.";
assertTrue(contains(pathBase + "core.scenario.xml", "Then match b == { foo: 'bar'}"));
assertTrue(contains(pathBase + "core.outline.xml", "Then assert a == 55"));
assertTrue(contains(pathBase + "multi-scenario.xml", "Then assert a != 2"));
// a scenario failure should not stop other features from running
assertTrue(contains(pathBase + "multi-scenario-fail.xml", "Then assert a != 2 ........................................................ passed"));
assertEquals(2, results.getFailedMap().size());
assertTrue(results.getFailedMap().keySet().contains("com.intuit.karate.no-scenario-name"));
assertTrue(results.getFailedMap().keySet().contains("com.intuit.karate.multi-scenario-fail"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,14 @@ public void reportPerfEvent(PerfEvent event) {

}

@Override
public void beforeFeature(Feature feature, FeatureContext context) {

}

@Override
public void afterFeature(FeatureResult result, FeatureContext context) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class KarateAction(val name: String, val protocol: KarateProtocol, val system: A

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

override def beforeFeature(Feature: Feature, ctx: FeatureContext) = {}

override def afterFeature(FeatureResult: FeatureResult, ctx: FeatureContext) = {}

override def getPerfEventName(req: HttpRequestBuilder, ctx: ScenarioContext): String = {
val customName = protocol.nameResolver.apply(req, ctx)
val finalName = if (customName != null) customName else protocol.defaultNameResolver.apply(req, ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,7 @@
package com.intuit.karate.junit4;

import com.intuit.karate.CallContext;
import com.intuit.karate.core.FeatureContext;
import com.intuit.karate.core.ExecutionContext;
import com.intuit.karate.core.ExecutionHook;
import com.intuit.karate.core.Feature;
import com.intuit.karate.core.FeatureExecutionUnit;
import com.intuit.karate.core.PerfEvent;
import com.intuit.karate.core.Scenario;
import com.intuit.karate.core.ScenarioContext;
import com.intuit.karate.core.ScenarioExecutionUnit;
import com.intuit.karate.core.ScenarioResult;
import com.intuit.karate.core.*;
import com.intuit.karate.http.HttpRequestBuilder;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
Expand Down Expand Up @@ -114,4 +105,14 @@ public void reportPerfEvent(PerfEvent event) {

}

@Override
public void beforeFeature(Feature feature, FeatureContext context) {

}

@Override
public void afterFeature(FeatureResult result, FeatureContext context) {

}

}

0 comments on commit 88026a2

Please sign in to comment.