Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 1502 #1508

Merged
merged 10 commits into from
Mar 10, 2021
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3814,6 +3814,8 @@ Scenario Outline: examples partitioned by tag
| GB |
```

Note that if you tag an Example Tables, whichever tag selector is used whil running your Features will be applied towards selecting the appropriate Examples Table. There is no concept of defaulting (i.e. if doesn't match, default to table without the tag) so if you want that concept feel free to use the negative form of a tag selector `~@region=GB`.

## Dynamic Port Numbers
In situations where you start an (embedded) application server as part of the test set-up phase, a typical challenge is that the HTTP port may be determined at run-time. So how can you get this value injected into the Karate configuration ?

Expand Down
13 changes: 12 additions & 1 deletion karate-core/src/main/java/com/intuit/karate/RuntimeHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,18 @@ default boolean beforeStep(Step step, ScenarioRuntime sr) {
default void afterStep(StepResult result, ScenarioRuntime sr) {

}


// applicable only for Dynamic Scenario Outlines which have the need
// to run background sections before executing the individual scenarios
// to calculate the Examples table
default void beforeBackground(ScenarioRuntime sr) {

}

default void afterBackground(ScenarioRuntime sr) {

}

default void beforeHttpCall(HttpRequest request, ScenarioRuntime sr) {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ public boolean configure(String key, Variable value) { // TODO use enum
if (value.isMap()) {
Map<String, Object> map = value.getValue();
if (map.containsKey("docker")) {
// todo add the working dir here
driverTarget = new DockerTarget(map);
} else {
throw new RuntimeException("bad driverTarget config, expected key 'docker': " + map);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package com.intuit.karate.core;

import java.util.ArrayList;
import java.util.List;

/**
Expand All @@ -38,6 +39,7 @@ public class ExamplesTable {
public ExamplesTable(ScenarioOutline outline, Table table) {
this.outline = outline;
this.table = table;
this.tags = new ArrayList();
}

public ScenarioOutline getOutline() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,19 @@ public void run() {
if (processor != null) {
processor.execute();
} else {
scenarios.forEachRemaining(this::processScenario);
if (!beforeHook()) {
logger.info("before-feature hook returned [false], aborting: {}", this);
} else {
scenarios.forEachRemaining(this::processScenario);
}
afterFeature();
}
}

private ScenarioRuntime lastExecutedScenario;

private void processScenario(ScenarioRuntime sr) {
if (!beforeHook()) {
logger.info("before-feature hook returned [false], aborting: {}", this);
} else {
if (beforeHook()) {
lastExecutedScenario = sr;
if (suite.jobManager != null) {
CompletableFuture future = suite.jobManager.addChunk(sr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public Scenario copy(int exampleIndex) {
s.description = description;
s.tags = tags;
s.line = line;
s.dynamicExpression = dynamicExpression;
s.steps = new ArrayList(steps.size());
for (Step step : steps) {
Step temp = new Step(s, step.getIndex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public boolean tryAdvance(Consumer<? super ScenarioRuntime> action) {
if (sections.hasNext()) {
FeatureSection section = sections.next();
if (section.isOutline()) {
scenarios = section.getScenarioOutline().getScenarios().iterator();
scenarios = section.getScenarioOutline().getScenarios(featureRuntime).iterator();
} else {
scenarios = Collections.singletonList(section.getScenario()).iterator();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.List;

/**
*
* @author pthomas3
*/
public class ScenarioOutline {
Expand Down Expand Up @@ -79,22 +78,42 @@ public Scenario toScenario(String dynamicExpression, int exampleIndex, int updat
}

public List<Scenario> getScenarios() {
return this.getScenarios(null);
}

public List<Scenario> getScenarios(FeatureRuntime fr) {
List<Scenario> list = new ArrayList();
boolean examplesHaveTags = examplesTables.stream().anyMatch(t -> !t.getTags().isEmpty());
for (ExamplesTable examples : examplesTables) {
Table table = examples.getTable();
if (table.isDynamic()) {
// technically row index 0 to denote an example (not -1)
Scenario scenario = toScenario(table.getDynamicExpression(), 0, table.getLineNumberForRow(0), examples.getTags());
list.add(scenario);
boolean selectedForExecution = false;
if (fr != null && examplesHaveTags) {
// getting examples in the context of an execution
// if the examples do not have any tagged example, do not worry about selecting
Tags tableTags = Tags.merge(examples.getTags());
boolean executeForTable = tableTags.evaluate(fr.suite.tagSelector);
if (executeForTable) {
selectedForExecution = true;
}
} else {
int rowCount = table.getRows().size();
for (int i = 1; i < rowCount; i++) { // don't include header row
int exampleIndex = i - 1; // next line will set exampleIndex on scenario
Scenario scenario = toScenario(null, exampleIndex, table.getLineNumberForRow(i), examples.getTags());
scenario.setExampleData(table.getExampleData(exampleIndex)); // and we set exampleData here
selectedForExecution = true;
}

if (selectedForExecution) {
Table table = examples.getTable();
if (table.isDynamic()) {
// technically row index 0 to denote an example (not -1)
Scenario scenario = toScenario(table.getDynamicExpression(), 0, table.getLineNumberForRow(0), examples.getTags());
list.add(scenario);
for (String key : table.getKeys()) {
scenario.replace("<" + key + ">", table.getValueAsString(key, i));
} else {
int rowCount = table.getRows().size();
for (int i = 1; i < rowCount; i++) { // don't include header row
int exampleIndex = i - 1; // next line will set exampleIndex on scenario
Scenario scenario = toScenario(null, exampleIndex, table.getLineNumberForRow(i), examples.getTags());
scenario.setExampleData(table.getExampleData(exampleIndex)); // and we set exampleData here
list.add(scenario);
for (String key : table.getKeys()) {
scenario.replace("<" + key + ">", table.getValueAsString(key, i));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ public boolean isStopped() {
return stopped;
}

public boolean isDynamicBackground() {
return this.scenario.isDynamic() && this.background == null;
}

public String getEmbedFileName(ResourceType resourceType) {
String extension = resourceType == null ? null : resourceType.getExtension();
return scenario.getUniqueId() + "_" + System.currentTimeMillis() + (extension == null ? "" : "." + extension);
Expand Down Expand Up @@ -267,7 +271,7 @@ private Map<String, Object> initMagicVariables() {
engine.setVariables(caller.arg.getValue());
}
}
if (scenario.isOutlineExample() && !scenario.isDynamic()) { // init examples row magic variables
if (scenario.isOutlineExample() && !this.isDynamicBackground()) { // init examples row magic variables
Map<String, Object> exampleData = scenario.getExampleData();
exampleData.forEach((k, v) -> map.put(k, v));
map.put("__row", exampleData);
Expand Down Expand Up @@ -342,7 +346,7 @@ private static boolean isSelectedForExecution(FeatureRuntime fr, Scenario scenar
//==========================================================================
//
public void beforeRun() {
if (scenario.isDynamic()) {
if (this.isDynamicBackground()) {
steps = scenario.getBackgroundSteps();
} else {
steps = background == null ? scenario.getStepsIncludingBackground() : scenario.getSteps();
Expand All @@ -358,29 +362,34 @@ public void beforeRun() {
evalConfigJs(featureRuntime.suite.karateConfig, "karate-config.js");
evalConfigJs(featureRuntime.suite.karateConfigEnv, "karate-config-" + featureRuntime.suite.env + ".js");
}
if (background == null || scenario.isOutlineExample()) {
if (this.isDynamicBackground()) {
featureRuntime.suite.hooks.forEach(h -> h.beforeBackground(this));
if (featureRuntime.suite.debugMode) {
featureRuntime.suite.hooks.stream()
.filter(DebugThread.class::isInstance)
.forEach(h -> h.beforeScenario(this));
}
} else {
featureRuntime.suite.hooks.forEach(h -> h.beforeScenario(this));
} else if (featureRuntime.suite.debugMode) {
featureRuntime.suite.hooks.stream()
.filter(DebugThread.class::isInstance)
.forEach(h -> h.beforeScenario(this));
}
}
if (!scenario.isDynamic()) {
if (!this.isDynamicBackground()) {
// don't evaluate names when running the background section
evaluateScenarioName();
}
}

@Override
public void run() {
boolean reRun = false;
try { // make sure we call afterRun() even on crashes
// and operate countdown latches, else we may hang the parallel runner
if (steps == null) {
beforeRun();
}
int count = steps.size();
int index = 0;
reRun = stepIndex >= count;
while ((index = nextStepIndex()) < count) {
currentStep = steps.get(index);
execute(currentStep);
Expand All @@ -392,15 +401,21 @@ public void run() {
logError("scenario [run] failed\n" + e.getMessage());
currentStepResult = result.addFakeStepResult("scenario [run] failed", e);
} finally {
if (!scenario.isDynamic()) { // don't add "fake" scenario to feature results
afterRun();
} else if (featureRuntime.suite.debugMode) {
if (this.isDynamicBackground() && !reRun) {
featureRuntime.suite.hooks.forEach(h -> h.afterBackground(this));
// if it's a dynamic scenario running under the debugger
// we still want to execute the afterScenario() hook of the debugger server
featureRuntime.suite.hooks.stream()
.filter(DebugThread.class::isInstance)
.forEach(h -> h.afterScenario(this));
// in the background section
if (featureRuntime.suite.debugMode) {
// allow debugging background section
featureRuntime.suite.hooks.stream()
.filter(DebugThread.class::isInstance)
.forEach(h -> h.afterScenario(this));
}
} else if (!this.isDynamicBackground()) { // don't add "fake" scenario to feature results
afterRun();
}

if (caller.isNone()) {
logAppender.close(); // reclaim memory
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.intuit.karate.Runner;
import com.intuit.karate.Suite;
import com.intuit.karate.core.ScenarioOutline;
import com.intuit.karate.report.ReportUtils;
import com.intuit.karate.core.Feature;
import com.intuit.karate.core.FeatureResult;
Expand Down Expand Up @@ -226,4 +227,61 @@ void testScenarioDescription() {
assertEquals("another line", scenario.getDescription());
}

@Test
void testScenariOutlineReadWithoutTags() {
Feature feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-dynamic.feature");
Runner.Builder builder = Runner.builder();
builder.tags("@a-tag");
FeatureRuntime fr = FeatureRuntime.of(new Suite(builder), feature);
ScenarioOutline outline = feature.getSection(0).getScenarioOutline();

assertEquals(1, outline.getScenarios(fr).size());

feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-name.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(2, outline.getScenarios(fr).size());

// using a tag that does not exist in the Examples Tables
// should not select anything
builder = Runner.builder();
builder.tags("@tag-not-present");
feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-examples-tags.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(0, outline.getScenarios(fr).size());

builder = Runner.builder();
builder.tags("@three-examples");
feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-examples-tags.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(3, outline.getScenarios(fr).size());

builder = Runner.builder();
builder.tags("@two-examples");
feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-examples-tags.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(3, outline.getScenarios(fr).size());

// no tag selector
// bring all example tables
builder = Runner.builder();
feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-examples-tags.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(10, outline.getScenarios(fr).size());

// not the @two-examples Examples Table so will use all the other example tables
builder = Runner.builder();
builder.tags("~@two-examples");
feature = Feature.read("classpath:com/intuit/karate/core/parser/test-outline-examples-tags.feature");
fr = FeatureRuntime.of(new Suite(builder), feature);
outline = feature.getSection(0).getScenarioOutline();
assertEquals(7, outline.getScenarios(fr).size());


System.out.println();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Feature:

Background:
* def foo = 'bar'

Scenario Outline: name is <name>
* def name = '<name>'

Examples:
| name |
| Bob |
| Nyan |
| Dylan |
| Tom |

@three-examples
Examples:
| name |
| Bob |
| Nyan |
| Dylan |

@two-examples
Examples:
| name |
| Bob |
| Nyan |
| Dylan |
Loading