From 88026a2c3cbaf1ed4a1ecd701af6a9338e4bc79e Mon Sep 17 00:00:00 2001 From: Sharma Prashant Date: Wed, 14 Aug 2019 10:38:49 +0200 Subject: [PATCH 1/3] Add afterFeature and beforeFeature hooks, RunnerBuilder --- .../java/com/intuit/karate/RunnerBuilder.java | 149 ++++++++++++++++++ .../com/intuit/karate/core/ExecutionHook.java | 17 +- .../karate/core/FeatureExecutionUnit.java | 14 ++ .../com/intuit/karate/RunnerBuilderTest.java | 39 +++++ .../intuit/karate/core/MandatoryTagHook.java | 10 ++ .../intuit/karate/gatling/KarateAction.scala | 4 + .../com/intuit/karate/junit4/FeatureInfo.java | 21 +-- 7 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java create mode 100644 karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java diff --git a/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java b/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java new file mode 100644 index 000000000..ff59e4976 --- /dev/null +++ b/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java @@ -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 paths; + private String reportDir = FileUtils.getBuildDir() + File.separator + ScriptBindings.SUREFIRE_REPORTS; + private List tags; + private Collection 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 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 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 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 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("<> 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; + } +} diff --git a/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java b/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java index b11be80e7..0ef1cf026 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java @@ -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); } diff --git a/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java b/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java index 0e1f59b5b..feac6ab5f 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java +++ b/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java @@ -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) { @@ -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) { diff --git a/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java b/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java new file mode 100644 index 000000000..cb476e4f6 --- /dev/null +++ b/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java @@ -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")); + } +} diff --git a/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java b/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java index 1b0bd9976..d5d4145c8 100644 --- a/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java +++ b/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java @@ -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) { + + } + } diff --git a/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala b/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala index f10e98c8e..d5b711b08 100644 --- a/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala +++ b/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala @@ -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) diff --git a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java b/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java index 924d8d6ed..4d870c40d 100644 --- a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java +++ b/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java @@ -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; @@ -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) { + + } + } From 49014f90a37f267f4c8c330b3174bef6babe7846 Mon Sep 17 00:00:00 2001 From: Sharma Prashant Date: Mon, 19 Aug 2019 09:22:44 +0200 Subject: [PATCH 2/3] Revert "Add afterFeature and beforeFeature hooks, RunnerBuilder" This reverts commit 88026a2 --- .../java/com/intuit/karate/RunnerBuilder.java | 149 ------------------ .../com/intuit/karate/core/ExecutionHook.java | 17 +- .../karate/core/FeatureExecutionUnit.java | 14 -- .../com/intuit/karate/RunnerBuilderTest.java | 39 ----- .../intuit/karate/core/MandatoryTagHook.java | 10 -- .../intuit/karate/gatling/KarateAction.scala | 4 - .../com/intuit/karate/junit4/FeatureInfo.java | 21 ++- 7 files changed, 17 insertions(+), 237 deletions(-) delete mode 100644 karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java delete mode 100644 karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java diff --git a/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java b/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java deleted file mode 100644 index ff59e4976..000000000 --- a/karate-core/src/main/java/com/intuit/karate/RunnerBuilder.java +++ /dev/null @@ -1,149 +0,0 @@ -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 paths; - private String reportDir = FileUtils.getBuildDir() + File.separator + ScriptBindings.SUREFIRE_REPORTS; - private List tags; - private Collection 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 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 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 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 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("<> 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; - } -} diff --git a/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java b/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java index 0ef1cf026..b11be80e7 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ExecutionHook.java @@ -30,23 +30,20 @@ * @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); + } diff --git a/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java b/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java index feac6ab5f..0e1f59b5b 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java +++ b/karate-core/src/main/java/com/intuit/karate/core/FeatureExecutionUnit.java @@ -56,13 +56,6 @@ 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) { @@ -105,13 +98,6 @@ 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) { diff --git a/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java b/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java deleted file mode 100644 index cb476e4f6..000000000 --- a/karate-core/src/test/java/com/intuit/karate/RunnerBuilderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -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")); - } -} diff --git a/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java b/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java index d5d4145c8..1b0bd9976 100644 --- a/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java +++ b/karate-core/src/test/java/com/intuit/karate/core/MandatoryTagHook.java @@ -65,14 +65,4 @@ public void reportPerfEvent(PerfEvent event) { } - @Override - public void beforeFeature(Feature feature, FeatureContext context) { - - } - - @Override - public void afterFeature(FeatureResult result, FeatureContext context) { - - } - } diff --git a/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala b/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala index d5b711b08..f10e98c8e 100644 --- a/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala +++ b/karate-gatling/src/main/scala/com/intuit/karate/gatling/KarateAction.scala @@ -57,10 +57,6 @@ 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) diff --git a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java b/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java index 4d870c40d..924d8d6ed 100644 --- a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java +++ b/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java @@ -24,7 +24,16 @@ package com.intuit.karate.junit4; import com.intuit.karate.CallContext; -import com.intuit.karate.core.*; +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.http.HttpRequestBuilder; import org.junit.runner.Description; import org.junit.runner.notification.Failure; @@ -105,14 +114,4 @@ public void reportPerfEvent(PerfEvent event) { } - @Override - public void beforeFeature(Feature feature, FeatureContext context) { - - } - - @Override - public void afterFeature(FeatureResult result, FeatureContext context) { - - } - } From 7bdc7c8bd5427431d6c898b83b97d4c61c343567 Mon Sep 17 00:00:00 2001 From: Sharma Prashant Date: Mon, 19 Aug 2019 14:39:23 +0200 Subject: [PATCH 3/3] Junit4 can run tests in parallel --- .../java/com/intuit/karate/KarateOptions.java | 3 +- .../main/java/com/intuit/karate/Runner.java | 10 ++- .../java/com/intuit/karate/RunnerOptions.java | 17 ++++- .../{FeatureInfo.java => JunitHook.java} | 53 +++++--------- .../java/com/intuit/karate/junit4/Karate.java | 69 +++++++------------ .../junit4/KarateJunitParallelTest.java | 15 ++++ .../java/com/intuit/karate/junit5/Karate.java | 2 +- 7 files changed, 85 insertions(+), 84 deletions(-) rename karate-junit4/src/main/java/com/intuit/karate/junit4/{FeatureInfo.java => JunitHook.java} (63%) create mode 100644 karate-junit4/src/test/java/com/intuit/karate/junit4/KarateJunitParallelTest.java diff --git a/karate-core/src/main/java/com/intuit/karate/KarateOptions.java b/karate-core/src/main/java/com/intuit/karate/KarateOptions.java index 1126e7703..f18c699ea 100644 --- a/karate-core/src/main/java/com/intuit/karate/KarateOptions.java +++ b/karate-core/src/main/java/com/intuit/karate/KarateOptions.java @@ -39,5 +39,6 @@ String[] features() default {}; String[] tags() default {}; - + + int threads() default 1; } diff --git a/karate-core/src/main/java/com/intuit/karate/Runner.java b/karate-core/src/main/java/com/intuit/karate/Runner.java index 013b40343..179ec6684 100644 --- a/karate-core/src/main/java/com/intuit/karate/Runner.java +++ b/karate-core/src/main/java/com/intuit/karate/Runner.java @@ -69,10 +69,18 @@ public Builder path(String... paths) { return this; } + public Builder path(List paths) { + this.paths.addAll(paths); + return this; + } public Builder tags(String... tags) { this.tags.addAll(Arrays.asList(tags)); return this; } + public Builder tags(List tags) { + this.tags.addAll(tags); + return this; + } public Builder forClass(Class clazz) { this.optionsClass = clazz; @@ -108,7 +116,7 @@ List resources() { return resources; } - Results parallel(int threadCount) { + public Results parallel(int threadCount) { this.threadCount = threadCount; return Runner.parallel(this); } diff --git a/karate-core/src/main/java/com/intuit/karate/RunnerOptions.java b/karate-core/src/main/java/com/intuit/karate/RunnerOptions.java index d8d89eb21..d3b76e71b 100644 --- a/karate-core/src/main/java/com/intuit/karate/RunnerOptions.java +++ b/karate-core/src/main/java/com/intuit/karate/RunnerOptions.java @@ -62,6 +62,9 @@ public class RunnerOptions { @CommandLine.Option(names = {"-n", "--name"}, description = "name of scenario to run") String name; + @CommandLine.Option(names = {"-T", "--threads"}, description = "number of threads when running tests") + int threads = 1; + @CommandLine.Parameters(description = "one or more tests (features) or search-paths to run") List features; @@ -81,6 +84,10 @@ public List getFeatures() { return features; } + public int getThreads(){ + return threads; + } + public static RunnerOptions parseStringArgs(String[] args) { RunnerOptions options = CommandLine.populateCommand(new RunnerOptions(), args); List featuresTemp = new ArrayList(); @@ -114,6 +121,7 @@ public static RunnerOptions parseCommandLine(String line) { public static RunnerOptions fromAnnotationAndSystemProperties(Class clazz) { List tags = null; List features = null; + int threads = 1; KarateOptions ko = clazz.getAnnotation(KarateOptions.class); if (ko == null) { CucumberOptions co = clazz.getAnnotation(CucumberOptions.class); @@ -122,13 +130,14 @@ public static RunnerOptions fromAnnotationAndSystemProperties(Class clazz) { features = Arrays.asList(co.features()); } } else { + threads = ko.threads(); tags = Arrays.asList(ko.tags()); features = Arrays.asList(ko.features()); } - return fromAnnotationAndSystemProperties(features, tags, clazz); + return fromAnnotationAndSystemProperties(features, tags, threads, clazz); } - public static RunnerOptions fromAnnotationAndSystemProperties(List features, List tags, Class clazz) { + public static RunnerOptions fromAnnotationAndSystemProperties(List features, List tags, int threads, Class clazz) { if (clazz != null && (features == null || features.isEmpty())) { String relative = FileUtils.toRelativeClassPath(clazz); features = Collections.singletonList(relative); @@ -144,6 +153,7 @@ public static RunnerOptions fromAnnotationAndSystemProperties(List featu options = new RunnerOptions(); options.tags = tags; options.features = features; + options.threads = threads; } else { logger.info("found system property 'karate.options': {}", line); options = parseCommandLine(line); @@ -153,6 +163,9 @@ public static RunnerOptions fromAnnotationAndSystemProperties(List featu if (options.features == null) { options.features = features; } + if (options.threads == 0){ + options.threads = threads; + } } return options; } diff --git a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java b/karate-junit4/src/main/java/com/intuit/karate/junit4/JunitHook.java similarity index 63% rename from karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java rename to karate-junit4/src/main/java/com/intuit/karate/junit4/JunitHook.java index c381ddaf3..767c90410 100644 --- a/karate-junit4/src/main/java/com/intuit/karate/junit4/FeatureInfo.java +++ b/karate-junit4/src/main/java/com/intuit/karate/junit4/JunitHook.java @@ -23,61 +23,42 @@ */ 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.FeatureResult; -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; import org.junit.runner.notification.RunNotifier; +import static org.junit.runner.Description.createTestDescription; + /** - * * @author pthomas3 */ -public class FeatureInfo implements ExecutionHook { +public class JunitHook implements ExecutionHook { - public final Feature feature; - public final ExecutionContext exec; public final Description description; - public final FeatureExecutionUnit unit; private RunNotifier notifier; + public JunitHook(Class clazz) { + description = Description.createSuiteDescription(clazz); + } + public void setNotifier(RunNotifier notifier) { this.notifier = notifier; } private static String getFeatureName(Feature feature) { - return "[" + feature.getResource().getFileNameWithoutExtension() + "]"; + return feature.getResource().getFileNameWithoutExtension(); } public static Description getScenarioDescription(Scenario scenario) { String featureName = getFeatureName(scenario.getFeature()); - return Description.createTestDescription(featureName, scenario.getDisplayMeta() + ' ' + scenario.getName()); + return createTestDescription("Feature: " + featureName, "Scenario: " + scenario.getDisplayMeta() + ' ' + scenario.getName()); } - public FeatureInfo(Feature feature, String tagSelector) { - this.feature = feature; - description = Description.createSuiteDescription(getFeatureName(feature), feature.getResource().getPackageQualifiedName()); - FeatureContext featureContext = new FeatureContext(null, feature, tagSelector); - CallContext callContext = new CallContext(null, true, this); - exec = new ExecutionContext(System.currentTimeMillis(), featureContext, callContext, null, null, null); - unit = new FeatureExecutionUnit(exec); - unit.init(); - for (ScenarioExecutionUnit u : unit.getScenarioExecutionUnits()) { - Description scenarioDescription = getScenarioDescription(u.scenario); - description.addChild(scenarioDescription); - } + public Description getDescription() { + return description; } @Override @@ -93,7 +74,7 @@ public boolean beforeScenario(Scenario scenario, ScenarioContext context) { @Override public void afterScenario(ScenarioResult result, ScenarioContext context) { // if dynamic scenario outline background or a call - if (notifier == null || context.callDepth > 0) { + if (notifier == null || context.callDepth > 0) { return; } Description scenarioDescription = getScenarioDescription(result.getScenario()); @@ -103,7 +84,7 @@ public void afterScenario(ScenarioResult result, ScenarioContext context) { // apparently this method should be always called // even if fireTestFailure was called notifier.fireTestFinished(scenarioDescription); - } + } @Override public boolean beforeFeature(Feature feature) { @@ -112,9 +93,9 @@ public boolean beforeFeature(Feature feature) { @Override public void afterFeature(FeatureResult result) { - - } - + + } + @Override public String getPerfEventName(HttpRequestBuilder req, ScenarioContext context) { return null; diff --git a/karate-junit4/src/main/java/com/intuit/karate/junit4/Karate.java b/karate-junit4/src/main/java/com/intuit/karate/junit4/Karate.java index d6bc5824d..d03dac272 100644 --- a/karate-junit4/src/main/java/com/intuit/karate/junit4/Karate.java +++ b/karate-junit4/src/main/java/com/intuit/karate/junit4/Karate.java @@ -23,24 +23,10 @@ */ package com.intuit.karate.junit4; -import com.intuit.karate.Resource; -import com.intuit.karate.FileUtils; +import com.intuit.karate.Runner.Builder; import com.intuit.karate.RunnerOptions; -import com.intuit.karate.core.Engine; -import com.intuit.karate.core.Feature; -import com.intuit.karate.core.FeatureParser; -import com.intuit.karate.core.FeatureResult; -import com.intuit.karate.core.ScenarioResult; -import com.intuit.karate.core.Tags; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.junit.Test; import org.junit.runner.Description; -import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.ParentRunner; import org.junit.runners.model.FrameworkMethod; @@ -49,39 +35,42 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + /** - * * @author pthomas3 */ -public class Karate extends ParentRunner { +public class Karate extends ParentRunner { private static final Logger logger = LoggerFactory.getLogger(Karate.class); - private final List children; - private final Map featureMap; - private final String tagSelector; + private final List children; + + private Builder builder; - public Karate(Class clazz) throws InitializationError, IOException { + private int threads; + + public Karate(Class clazz) throws InitializationError { super(clazz); List testMethods = getTestClass().getAnnotatedMethods(Test.class); if (!testMethods.isEmpty()) { logger.warn("WARNING: there are methods annotated with '@Test', they will NOT be run when using '@RunWith(Karate.class)'"); } + + JunitHook junitHook = new JunitHook(clazz); RunnerOptions options = RunnerOptions.fromAnnotationAndSystemProperties(clazz); - List resources = FileUtils.scanForFeatureFiles(options.getFeatures(), clazz.getClassLoader()); - children = new ArrayList(resources.size()); - featureMap = new HashMap(resources.size()); - for (Resource resource : resources) { - Feature feature = FeatureParser.parse(resource); - feature.setCallName(options.getName()); - feature.setCallLine(resource.getLine()); - children.add(feature); - } - tagSelector = Tags.fromKarateOptionsTags(options.getTags()); + builder = new Builder().hook(junitHook).path(options.getFeatures()); + if (options.getTags() != null) + builder = builder.tags(options.getTags()); + threads = options.getThreads(); + children = new ArrayList<>(); + children.add(junitHook); } @Override - public List getChildren() { + public List getChildren() { return children; } @@ -103,7 +92,7 @@ protected Statement withBeforeClasses(Statement statement) { } @Override - protected Description describeChild(Feature feature) { + protected Description describeChild(JunitHook junitHook) { if (!beforeClassDone) { try { Statement statement = withBeforeClasses(NO_OP); @@ -113,19 +102,13 @@ protected Description describeChild(Feature feature) { throw new RuntimeException(e); } } - FeatureInfo info = new FeatureInfo(feature, tagSelector); - featureMap.put(feature.getRelativePath(), info); - return info.description; + return junitHook.getDescription(); } @Override - protected void runChild(Feature feature, RunNotifier notifier) { - FeatureInfo info = featureMap.get(feature.getRelativePath()); - info.setNotifier(notifier); - info.unit.run(); - FeatureResult result = info.exec.result; - result.printStats(null); - Engine.saveResultHtml(FileUtils.getBuildDir() + File.separator + "surefire-reports", result, null); + protected void runChild(JunitHook feature, RunNotifier notifier) { + feature.setNotifier(notifier); + builder.parallel(threads); } @Override diff --git a/karate-junit4/src/test/java/com/intuit/karate/junit4/KarateJunitParallelTest.java b/karate-junit4/src/test/java/com/intuit/karate/junit4/KarateJunitParallelTest.java new file mode 100644 index 000000000..75fbb41b8 --- /dev/null +++ b/karate-junit4/src/test/java/com/intuit/karate/junit4/KarateJunitParallelTest.java @@ -0,0 +1,15 @@ +package com.intuit.karate.junit4; + + +import com.intuit.karate.KarateOptions; +import org.junit.runner.RunWith; + +/** + * + * @author pthomas3 + */ +@KarateOptions(tags = "~@ignore", threads = 5) +@RunWith(Karate.class) +public class KarateJunitParallelTest { + +} diff --git a/karate-junit5/src/main/java/com/intuit/karate/junit5/Karate.java b/karate-junit5/src/main/java/com/intuit/karate/junit5/Karate.java index 0134d292b..bc65eeb2f 100644 --- a/karate-junit5/src/main/java/com/intuit/karate/junit5/Karate.java +++ b/karate-junit5/src/main/java/com/intuit/karate/junit5/Karate.java @@ -71,7 +71,7 @@ public Karate tags(String... tags) { @Override public Iterator iterator() { - RunnerOptions options = RunnerOptions.fromAnnotationAndSystemProperties(paths, tags, clazz); + RunnerOptions options = RunnerOptions.fromAnnotationAndSystemProperties(paths, tags, 1, clazz); List resources = FileUtils.scanForFeatureFiles(options.getFeatures(), clazz); List features = new ArrayList(resources.size()); for (Resource resource : resources) {