diff --git a/examples/jobserver/pom.xml b/examples/jobserver/pom.xml
index c6688de1b..bc2c88053 100644
--- a/examples/jobserver/pom.xml
+++ b/examples/jobserver/pom.xml
@@ -11,7 +11,7 @@
UTF-8
1.8
3.6.0
- 0.9.6
+ 2.0.0
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 e5565ae7c..fd6a74d8f 100644
--- a/karate-core/src/main/java/com/intuit/karate/Runner.java
+++ b/karate-core/src/main/java/com/intuit/karate/Runner.java
@@ -35,7 +35,9 @@
import com.intuit.karate.core.HtmlFeatureReport;
import com.intuit.karate.core.HtmlReport;
import com.intuit.karate.core.HtmlSummaryReport;
+import com.intuit.karate.core.ParallelProcessor;
import com.intuit.karate.core.ScenarioExecutionUnit;
+import com.intuit.karate.core.Subscriber;
import com.intuit.karate.core.Tags;
import com.intuit.karate.job.JobConfig;
import com.intuit.karate.job.JobServer;
@@ -45,9 +47,10 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -293,53 +296,79 @@ public static Results parallel(Builder options) {
if (options.hooks != null) {
options.hooks.forEach(h -> h.beforeAll(results));
}
- ExecutorService featureExecutor = Executors.newFixedThreadPool(threadCount, Executors.privilegedThreadFactory());
- ExecutorService scenarioExecutor = Executors.newWorkStealingPool(threadCount);
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ ExecutorService featureExecutor = Executors.newWorkStealingPool(threadCount);
+ List futures = new ArrayList();
+ CompletableFuture latch = new CompletableFuture();
+ Subscriber subscriber = new Subscriber() {
+ @Override
+ public void onNext(CompletableFuture result) {
+ futures.add(result);
+ }
+
+ @Override
+ public void onComplete() {
+ latch.complete(Boolean.TRUE);
+ }
+ };
List resources = options.resolveResources();
- 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(options.scenarioName);
- feature.setCallLine(resource.getLine());
- FeatureContext featureContext = new FeatureContext(null, feature, options.tagSelector());
- CallContext callContext = CallContext.forAsync(feature, options.hooks, options.hookFactory, null, false);
- ExecutionContext execContext = new ExecutionContext(results, results.getStartTime(), featureContext, callContext, reportDir,
- r -> featureExecutor.submit(r), scenarioExecutor, Thread.currentThread().getContextClassLoader());
- featureResults.add(execContext.result);
- if (jobServer != null) {
- List units = feature.getScenarioExecutionUnits(execContext);
- jobServer.addFeature(execContext, units, () -> {
- onFeatureDone(results, execContext, reportDir, index, count);
- latch.countDown();
- });
- } else {
- FeatureExecutionUnit unit = new FeatureExecutionUnit(execContext);
- unit.setNext(() -> {
- onFeatureDone(results, execContext, reportDir, index, count);
- latch.countDown();
- });
- featureExecutor.submit(unit);
+ int count = resources.size();
+ List featureResults = new ArrayList(count);
+ ParallelProcessor processor = new ParallelProcessor(featureExecutor, resources.iterator()) {
+ int index = 0;
+
+ @Override
+ public Iterator process(Resource resource) {
+ CompletableFuture future = new CompletableFuture();
+ try {
+ Feature feature = FeatureParser.parse(resource);
+ feature.setCallName(options.scenarioName);
+ feature.setCallLine(resource.getLine());
+ FeatureContext featureContext = new FeatureContext(null, feature, options.tagSelector());
+ CallContext callContext = CallContext.forAsync(feature, options.hooks, options.hookFactory, null, false);
+ ExecutionContext execContext = new ExecutionContext(results, results.getStartTime(), featureContext, callContext, reportDir,
+ r -> featureExecutor.submit(r), featureExecutor, classLoader);
+ featureResults.add(execContext.result);
+ if (jobServer != null) {
+ List units = feature.getScenarioExecutionUnits(execContext);
+ jobServer.addFeature(execContext, units, () -> {
+ onFeatureDone(results, execContext, reportDir, ++index, count);
+ future.complete(Boolean.TRUE);
+ });
+ } else {
+ FeatureExecutionUnit unit = new FeatureExecutionUnit(execContext);
+ unit.setNext(() -> {
+ onFeatureDone(results, execContext, reportDir, ++index, count);
+ future.complete(Boolean.TRUE);
+ });
+ unit.run();
+ }
+ } catch (Exception e) {
+ future.complete(Boolean.FALSE);
+ LOGGER.error("runner failed: {}", e.getMessage());
+ results.setFailureReason(e);
}
+ return Collections.singletonList(future).iterator();
}
+
+ };
+ try {
if (jobServer != null) {
jobServer.startExecutors();
}
- LOGGER.info("waiting for parallel features to complete ...");
+ processor.subscribe(subscriber);
+ latch.join();
+ CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[futures.size()]);
+ CompletableFuture allFutures = CompletableFuture.allOf(futuresArray);
+ LOGGER.info("waiting for {} parallel features to complete ...", futuresArray.length);
if (options.timeoutMinutes > 0) {
- latch.await(options.timeoutMinutes, TimeUnit.MINUTES);
- if (latch.getCount() > 0) {
- LOGGER.warn("parallel execution timed out after {} minutes, features remaining: {}",
- options.timeoutMinutes, latch.getCount());
- }
+ allFutures.get(options.timeoutMinutes, TimeUnit.MINUTES);
} else {
- latch.await();
+ allFutures.join();
}
+ LOGGER.info("all features complete");
results.stopTimer();
+ featureExecutor.shutdownNow();
HtmlSummaryReport summary = new HtmlSummaryReport();
for (FeatureResult result : featureResults) {
int scenarioCount = result.getScenarioCount();
@@ -367,11 +396,8 @@ public static Results parallel(Builder options) {
options.hooks.forEach(h -> h.afterAll(results));
}
} catch (Exception e) {
- LOGGER.error("karate parallel runner failed: ", e.getMessage());
+ LOGGER.error("runner failed: {}", e);
results.setFailureReason(e);
- } finally {
- featureExecutor.shutdownNow();
- scenarioExecutor.shutdownNow();
}
return results;
}
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 e05be0ee1..a6bc122d7 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
@@ -24,10 +24,8 @@
package com.intuit.karate.core;
import com.intuit.karate.Logger;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.Iterator;
import org.slf4j.LoggerFactory;
/**
@@ -41,9 +39,7 @@ public class FeatureExecutionUnit implements Runnable {
public final ExecutionContext exec;
private final boolean parallelScenarios;
- private CountDownLatch latch;
- private List units;
- private List results;
+ private Iterator units;
private Runnable next;
public FeatureExecutionUnit(ExecutionContext exec) {
@@ -51,7 +47,7 @@ public FeatureExecutionUnit(ExecutionContext exec) {
parallelScenarios = exec.scenarioExecutor != null;
}
- public List getScenarioExecutionUnits() {
+ public Iterator getScenarioExecutionUnits() {
return units;
}
@@ -67,16 +63,13 @@ public void init() {
hookResult = false;
}
if (hookResult == false) {
- units = Collections.EMPTY_LIST;
+ units = Collections.emptyIterator();
}
}
}
if (units == null) { // no hook failed
- units = exec.featureContext.feature.getScenarioExecutionUnits(exec);
+ units = exec.featureContext.feature.getScenarioExecutionUnits(exec).iterator();
}
- int count = units.size();
- results = new ArrayList(count);
- latch = new CountDownLatch(count);
}
public void setNext(Runnable next) {
@@ -90,30 +83,49 @@ public void run() {
if (units == null) {
init();
}
- for (ScenarioExecutionUnit unit : units) {
- if (isSelected(unit) && run(unit)) {
- // unit.next should count down latch when done
- } else { // un-selected / failed scenario
- latch.countDown();
+ Subscriber subscriber = new Subscriber() {
+ @Override
+ public void onNext(ScenarioResult result) {
+ exec.result.addResult(result);
}
- }
- try {
- latch.await();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- stop();
- if (next != null) {
- next.run();
- }
+
+ @Override
+ public void onComplete() {
+ stop();
+ if (next != null) {
+ next.run();
+ }
+ }
+ };
+ ParallelProcessor processor = new ParallelProcessor(exec.scenarioExecutor, units) {
+ @Override
+ public Iterator process(ScenarioExecutionUnit unit) {
+ if (isSelected(unit) && !unit.result.isFailed()) { // can happen for dynamic scenario outlines with a failed background !
+ unit.run();
+ // we also hold a reference to the last scenario-context that executed
+ // for cases where the caller needs a result
+ lastContextExecuted = unit.getContext();
+ return Collections.singletonList(unit.result).iterator();
+ } else {
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public boolean runSync(ScenarioExecutionUnit unit) {
+ if (!parallelScenarios) {
+ return true;
+ }
+ Tags tags = unit.scenario.getTagsEffective();
+ return tags.valuesFor("parallel").isAnyOf("false");
+ }
+
+ };
+ processor.subscribe(subscriber);
}
+ // extracted for junit 5
public void stop() {
- // this is where the feature gets "populated" with stats
- // but best of all, the original order is retained
- for (ScenarioResult sr : results) {
- exec.result.addResult(sr);
- }
if (lastContextExecuted != null) {
// set result map that caller will see
exec.result.setResultVars(lastContextExecuted.vars);
@@ -175,28 +187,4 @@ public static boolean isSelected(FeatureContext fc, Scenario scenario, Logger lo
return false;
}
- public boolean run(ScenarioExecutionUnit unit) {
- // this is an elegant solution to retaining the order of scenarios
- // in the final report - even if they run in parallel !
- results.add(unit.result);
- if (unit.result.isFailed()) { // can happen for dynamic scenario outlines with a failed background !
- return false;
- }
- Tags tags = unit.scenario.getTagsEffective();
- unit.setNext(() -> {
- latch.countDown();
- // we also hold a reference to the last scenario-context that executed
- // for cases where the caller needs a result
- lastContextExecuted = unit.getContext(); // IMPORTANT: will handle if actions is null
- });
- boolean sequential = !parallelScenarios || tags.valuesFor("parallel").isAnyOf("false");
- // main
- if (sequential) {
- unit.run();
- } else {
- exec.scenarioExecutor.submit(unit);
- }
- return true;
- }
-
}
diff --git a/karate-core/src/main/java/com/intuit/karate/core/ParallelProcessor.java b/karate-core/src/main/java/com/intuit/karate/core/ParallelProcessor.java
new file mode 100644
index 000000000..c9ba1c58c
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/core/ParallelProcessor.java
@@ -0,0 +1,98 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.core;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author pthomas3
+ */
+public abstract class ParallelProcessor implements Processor {
+
+ private static final Logger logger = LoggerFactory.getLogger(ParallelProcessor.class);
+
+ private final ExecutorService executor;
+ private final Iterator publisher;
+ private final List> futures = new ArrayList();
+
+ private Subscriber subscriber;
+
+ public ParallelProcessor(ExecutorService executor, Iterator publisher) {
+ this.executor = executor;
+ this.publisher = publisher;
+ }
+
+ private void execute(I in) {
+ Iterator out = process(in);
+ out.forEachRemaining(o -> {
+ synchronized (this) { // synchronized is important if multiple threads
+ subscriber.onNext(o);
+ }
+ });
+ }
+
+ @Override
+ public void onNext(I in) {
+ if (runSync(in)) {
+ execute(in);
+ } else {
+ CompletableFuture cf = new CompletableFuture();
+ futures.add(cf);
+ executor.submit(() -> {
+ execute(in);
+ cf.complete(Boolean.TRUE);
+ });
+ }
+ }
+
+ @Override
+ public void subscribe(Subscriber subscriber) {
+ this.subscriber = subscriber;
+ publisher.forEachRemaining(this::onNext);
+ CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[futures.size()]);
+ if (futuresArray.length > 0) {
+ executor.submit(() -> { // will not block caller even when waiting for completion
+ CompletableFuture.allOf(futuresArray).join();
+ subscriber.onComplete();
+ });
+ } else {
+ subscriber.onComplete();
+ }
+ }
+
+ @Override
+ public abstract Iterator process(I in);
+
+ public boolean runSync(I in) {
+ return false;
+ }
+
+}
diff --git a/karate-core/src/main/java/com/intuit/karate/core/Processor.java b/karate-core/src/main/java/com/intuit/karate/core/Processor.java
new file mode 100644
index 000000000..e69c03276
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/core/Processor.java
@@ -0,0 +1,40 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.core;
+
+import java.util.Iterator;
+
+/**
+ *
+ * @author pthomas3
+ */
+public interface Processor {
+
+ void onNext(I i);
+
+ void subscribe(Subscriber s);
+
+ Iterator process(I i);
+
+}
diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioExecutionUnit.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioExecutionUnit.java
index b77d8aebb..e006dbcc6 100644
--- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioExecutionUnit.java
+++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioExecutionUnit.java
@@ -56,7 +56,6 @@ public class ScenarioExecutionUnit implements Runnable {
private boolean stopped = false;
private boolean aborted = false;
private StepResult lastStepResult;
- private Runnable next;
private boolean last;
private Step currentStep;
@@ -121,10 +120,6 @@ public boolean isStopped() {
return stopped;
}
- public void setNext(Runnable next) {
- this.next = next;
- }
-
public void setLast(boolean last) {
this.last = last;
}
@@ -329,10 +324,6 @@ public void run() {
} catch (Exception e) {
result.addError("scenario execution failed", e);
LOGGER.error("scenario execution failed: {}", e.getMessage());
- } finally {
- if (next != null) {
- next.run();
- }
}
}
diff --git a/karate-core/src/main/java/com/intuit/karate/core/Subscriber.java b/karate-core/src/main/java/com/intuit/karate/core/Subscriber.java
new file mode 100644
index 000000000..69428edab
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/core/Subscriber.java
@@ -0,0 +1,36 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.core;
+
+/**
+ *
+ * @author pthomas3
+ */
+public interface Subscriber {
+
+ void onNext(T t);
+
+ void onComplete();
+
+}
diff --git a/karate-demo/src/test/java/demo/upload/upload-retry.feature b/karate-demo/src/test/java/demo/upload/upload-retry.feature
index c5dacb762..02c94868a 100644
--- a/karate-demo/src/test/java/demo/upload/upload-retry.feature
+++ b/karate-demo/src/test/java/demo/upload/upload-retry.feature
@@ -1,11 +1,13 @@
+@mock-servlet-todo
Feature: file upload retry
Background:
* url demoBaseUrl
Scenario: upload file
- * def count = 0
- * def done = function(){ var temp = karate.get('count'); temp = temp + 1; karate.set('count', temp); return temp > 1 }
+ * def count = { value: 0 }
+ * configure retry = { interval: 100 }
+ * def done = function(){ return count.value++ == 1 }
Given path 'files'
And multipart file myFile = { read: 'test.pdf', filename: 'upload-name.pdf', contentType: 'application/pdf' }
And multipart field message = 'hello world'
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 2369bf9ce..68fd0ead2 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
@@ -34,7 +34,6 @@
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.Step;
import com.intuit.karate.core.StepResult;
@@ -50,7 +49,6 @@
public class FeatureInfo implements ExecutionHook {
public final Feature feature;
- public final ExecutionContext exec;
public final Description description;
public final FeatureExecutionUnit unit;
@@ -69,13 +67,9 @@ public FeatureInfo(Feature feature, String tagSelector) {
description = Description.createSuiteDescription(feature.getNameForReport(), feature.getResource().getPackageQualifiedName());
FeatureContext featureContext = new FeatureContext(null, feature, tagSelector);
CallContext callContext = new CallContext(null, true, this);
- exec = new ExecutionContext(null, System.currentTimeMillis(), featureContext, callContext, null, null, null);
+ ExecutionContext exec = new ExecutionContext(null, 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);
- }
}
@Override
@@ -131,8 +125,8 @@ public boolean beforeStep(Step step, ScenarioContext context) {
@Override
public void afterStep(StepResult result, ScenarioContext context) {
- }
-
+ }
+
@Override
public String getPerfEventName(HttpRequestBuilder req, ScenarioContext context) {
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 6e8f03895..b0cee8fae 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
@@ -131,7 +131,7 @@ protected void runChild(Feature feature, RunNotifier notifier) {
FeatureInfo info = featureMap.get(feature.getRelativePath());
info.setNotifier(notifier);
info.unit.run();
- FeatureResult result = info.exec.result;
+ FeatureResult result = info.unit.exec.result;
if (!result.isEmpty()) {
result.printStats(null);
HtmlFeatureReport.saveFeatureResult(targetDir, result);
diff --git a/karate-junit5/src/main/java/com/intuit/karate/junit5/FeatureNode.java b/karate-junit5/src/main/java/com/intuit/karate/junit5/FeatureNode.java
index 2e6d53767..13518dea0 100644
--- a/karate-junit5/src/main/java/com/intuit/karate/junit5/FeatureNode.java
+++ b/karate-junit5/src/main/java/com/intuit/karate/junit5/FeatureNode.java
@@ -32,9 +32,7 @@
import com.intuit.karate.core.HtmlFeatureReport;
import com.intuit.karate.core.HtmlSummaryReport;
import com.intuit.karate.core.ScenarioExecutionUnit;
-import java.util.ArrayList;
import java.util.Iterator;
-import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicTest;
@@ -49,8 +47,7 @@ public class FeatureNode implements Iterator, Iterable
public final FeatureExecutionUnit featureUnit;
public final HtmlSummaryReport summary;
public final String reportDir;
-
- Iterator iterator;
+ public final Iterator iterator;
public FeatureNode(String reportDir, HtmlSummaryReport summary, Feature feature, String tagSelector) {
this.reportDir = reportDir;
@@ -61,16 +58,7 @@ public FeatureNode(String reportDir, HtmlSummaryReport summary, Feature feature,
exec = new ExecutionContext(null, System.currentTimeMillis(), featureContext, callContext, null, null, null);
featureUnit = new FeatureExecutionUnit(exec);
featureUnit.init();
- List selected = new ArrayList();
- for (ScenarioExecutionUnit unit : featureUnit.getScenarioExecutionUnits()) {
- if (featureUnit.isSelected(unit)) { // tag filtering
- selected.add(unit);
- }
- }
- if (!selected.isEmpty()) { // make sure we trigger junit html report on last unit (after tag filtering)
- selected.get(selected.size() - 1).setLast(true);
- }
- iterator = selected.iterator();
+ iterator = featureUnit.getScenarioExecutionUnits();
}
@Override
@@ -82,9 +70,11 @@ public boolean hasNext() {
public DynamicTest next() {
ScenarioExecutionUnit unit = iterator.next();
return DynamicTest.dynamicTest(unit.scenario.getNameForReport(), () -> {
- featureUnit.run(unit);
+ if (featureUnit.isSelected(unit)) {
+ unit.run();
+ }
boolean failed = unit.result.isFailed();
- if (unit.isLast()) {
+ if (!iterator.hasNext()) {
featureUnit.stop();
FeatureResult result = exec.result;
if (!result.isEmpty()) {