Skip to content

Commit

Permalink
[Core] Extract CoreStepDefinitions from Java and Java8 implementation
Browse files Browse the repository at this point in the history
Removes the `CucumberExpression` creation from the backend modules and
will allow a type registry to be created for each pickle.
  • Loading branch information
mpkorstanje committed Jul 20, 2019
1 parent 8520993 commit f2eeac1
Show file tree
Hide file tree
Showing 40 changed files with 840 additions and 718 deletions.
5 changes: 1 addition & 4 deletions core/src/main/java/io/cucumber/core/backend/Glue.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package io.cucumber.core.backend;

import io.cucumber.core.stepexpression.TypeRegistry;
import org.apiguardian.api.API;

import java.util.function.Function;

@API(status = API.Status.STABLE)
public interface Glue {

void addStepDefinition(Function<TypeRegistry, StepDefinition> stepDefinition) throws DuplicateStepDefinitionException;
void addStepDefinition(StepDefinition stepDefinition) throws DuplicateStepDefinitionException;

void addBeforeHook(HookDefinition hookDefinition);

Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/io/cucumber/core/backend/ParameterInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.cucumber.core.backend;

import java.lang.reflect.Type;

public interface ParameterInfo {

Type getType();

boolean isTransposed();

TypeResolver getTypeResolver();
}
21 changes: 5 additions & 16 deletions core/src/main/java/io/cucumber/core/backend/StepDefinition.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
package io.cucumber.core.backend;

import io.cucumber.core.stepexpression.Argument;
import gherkin.pickles.PickleStep;
import org.apiguardian.api.API;

import java.util.List;

@API(status = API.Status.STABLE)
public interface StepDefinition extends io.cucumber.core.event.StepDefinition {
/**
* Returns a list of arguments. Return null if the step definition
* doesn't match at all. Return an empty List if it matches with 0 arguments
* and bigger sizes if it matches several.
*
* @param step The step to match arguments for
* @return The arguments in a list when the step matches, null otherwise.
*/
List<Argument> matchedArguments(PickleStep step);

/**
* Invokes the step definition. The method should raise a Throwable
* if the invocation fails, which will cause the step to fail.
*
*
* @param args The arguments for the step
* @throws Throwable in case of step failure.
*/
Expand All @@ -30,12 +18,13 @@ public interface StepDefinition extends io.cucumber.core.event.StepDefinition {
/**
* @param stackTraceElement The location of the step.
* @return Return true if this matches the location. This is used to filter
* stack traces.
* stack traces.
*/
boolean isDefinedAt(StackTraceElement stackTraceElement);

/**
* @return How many declared parameters this step definition has. Returns null if unknown.
* @return parameter information or null when the language does not provide parameter information
*/
Integer getParameterCount();
List<ParameterInfo> parameterInfos();

}
22 changes: 22 additions & 0 deletions core/src/main/java/io/cucumber/core/backend/TypeResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.cucumber.core.backend;

import org.apiguardian.api.API;

import java.lang.reflect.Type;

/**
* Allows lazy resolution of the type of a data table or doc string.
*/
@API(status = API.Status.STABLE)
public interface TypeResolver {

/**
* A type to data convert the table or doc string to. May not return null.
* <p>
* When the {@link Object} type is returned no transform will be applied.
*
* @return a type
*/
Type resolve();

}
35 changes: 18 additions & 17 deletions core/src/main/java/io/cucumber/core/runner/CachingGlue.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.cucumber.core.runner;

import io.cucumber.core.event.StepDefinedEvent;
import gherkin.pickles.PickleStep;
import io.cucumber.core.backend.DuplicateStepDefinitionException;
import io.cucumber.core.backend.Glue;
import io.cucumber.core.backend.HookDefinition;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.event.StepDefinedEvent;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.stepexpression.Argument;
import gherkin.pickles.PickleStep;
import io.cucumber.core.stepexpression.TypeRegistry;

import java.util.ArrayList;
Expand All @@ -16,13 +16,12 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;

final class CachingGlue implements Glue {
private static final HookComparator ASCENDING = new HookComparator(true);
private static final HookComparator DESCENDING = new HookComparator(false);
final Map<String, StepDefinition> stepDefinitionsByPattern = new TreeMap<>();
final Map<String, StepDefinition> stepDefinitionsByStepText = new HashMap<>();
final Map<String, CoreStepDefinition> stepDefinitionsByPattern = new TreeMap<>();
final Map<String, CoreStepDefinition> stepDefinitionsByStepText = new HashMap<>();
final List<HookDefinition> beforeHooks = new ArrayList<>();
final List<HookDefinition> beforeStepHooks = new ArrayList<>();
final List<HookDefinition> afterHooks = new ArrayList<>();
Expand All @@ -37,13 +36,13 @@ final class CachingGlue implements Glue {
}

@Override
public void addStepDefinition(Function<TypeRegistry, StepDefinition> stepDefinitionFunction) {
StepDefinition stepDefinition = stepDefinitionFunction.apply(typeRegistry);
StepDefinition previous = stepDefinitionsByPattern.get(stepDefinition.getPattern());
public void addStepDefinition(StepDefinition stepDefinition) {
CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry);
CoreStepDefinition previous = stepDefinitionsByPattern.get(coreStepDefinition.getPattern());
if (previous != null) {
throw new DuplicateStepDefinitionException(previous, stepDefinition);
throw new DuplicateStepDefinitionException(previous.getStepDefinition(), stepDefinition);
}
stepDefinitionsByPattern.put(stepDefinition.getPattern(), stepDefinition);
stepDefinitionsByPattern.put(stepDefinition.getPattern(), coreStepDefinition);
bus.send(new StepDefinedEvent(bus.getInstant(), stepDefinition));
}

Expand All @@ -58,6 +57,7 @@ public void addBeforeStepHook(HookDefinition hookDefinition) {
beforeStepHooks.add(hookDefinition);
beforeStepHooks.sort(ASCENDING);
}

@Override
public void addAfterHook(HookDefinition hookDefinition) {
afterHooks.add(hookDefinition);
Expand Down Expand Up @@ -89,7 +89,7 @@ List<HookDefinition> getAfterStepHooks() {
PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep step) {
String stepText = step.getText();

StepDefinition stepDefinition = stepDefinitionsByStepText.get(stepText);
CoreStepDefinition stepDefinition = stepDefinitionsByStepText.get(stepText);
if (stepDefinition != null) {
// Step definition arguments consists of parameters included in the step text and
// gherkin step arguments (doc string and data table) which are not included in
Expand All @@ -109,14 +109,14 @@ PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep ste

PickleStepDefinitionMatch match = matches.get(0);

stepDefinitionsByStepText.put(stepText, match.getStepDefinition());
stepDefinitionsByStepText.put(stepText, (CoreStepDefinition) match.getStepDefinition());

return match;
}

private List<PickleStepDefinitionMatch> stepDefinitionMatches(String featurePath, PickleStep step) {
List<PickleStepDefinitionMatch> result = new ArrayList<PickleStepDefinitionMatch>();
for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) {
for (CoreStepDefinition stepDefinition : stepDefinitionsByPattern.values()) {
List<Argument> arguments = stepDefinition.matchedArguments(step);
if (arguments != null) {
result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step));
Expand Down Expand Up @@ -146,10 +146,11 @@ private void removeScenarioScopedHooks(List<HookDefinition> beforeHooks) {
}
}

private void removeScenariosScopedStepDefinitions(Map<String, StepDefinition> stepDefinitions) {
Iterator<Map.Entry<String, StepDefinition>> stepDefinitionIterator = stepDefinitions.entrySet().iterator();
while(stepDefinitionIterator.hasNext()){
StepDefinition stepDefinition = stepDefinitionIterator.next().getValue();
private void removeScenariosScopedStepDefinitions(Map<String, CoreStepDefinition> stepDefinitions) {
Iterator<Map.Entry<String, CoreStepDefinition>> stepDefinitionIterator = stepDefinitions.entrySet().iterator();
while (stepDefinitionIterator.hasNext()) {
CoreStepDefinition coreStepDefinition = stepDefinitionIterator.next().getValue();
StepDefinition stepDefinition = coreStepDefinition.getStepDefinition();
if (stepDefinition instanceof ScenarioScoped) {
ScenarioScoped scenarioScopedStepDefinition = (ScenarioScoped) stepDefinition;
scenarioScopedStepDefinition.disposeScenarioScope();
Expand Down
88 changes: 88 additions & 0 deletions core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.cucumber.core.runner;

import gherkin.pickles.PickleStep;
import io.cucumber.core.backend.ParameterInfo;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.stepexpression.Argument;
import io.cucumber.core.stepexpression.ArgumentMatcher;
import io.cucumber.core.stepexpression.StepExpression;
import io.cucumber.core.stepexpression.StepExpressionFactory;
import io.cucumber.core.stepexpression.TypeRegistry;
import io.cucumber.core.stepexpression.TypeResolver;

import java.lang.reflect.Type;
import java.util.List;

import static java.util.Objects.requireNonNull;

class CoreStepDefinition implements StepDefinition {

private final StepExpression expression;
private final ArgumentMatcher argumentMatcher;
private final StepDefinition stepDefinition;
private final Type[] types;

CoreStepDefinition(StepDefinition stepDefinition, TypeRegistry typeRegistry) {
this.stepDefinition = requireNonNull(stepDefinition);
List<ParameterInfo> parameterInfos = stepDefinition.parameterInfos();
this.expression = createExpression(parameterInfos, stepDefinition.getPattern(), typeRegistry);
this.argumentMatcher = new ArgumentMatcher(this.expression);
this.types = getTypes(parameterInfos);
}

private StepExpression createExpression(List<ParameterInfo> parameterInfos, String expression, TypeRegistry typeRegistry) {
if (parameterInfos == null || parameterInfos.isEmpty()) {
return new StepExpressionFactory(typeRegistry).createExpression(expression);
} else {
ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1);
TypeResolver typeResolver = parameterInfo.getTypeResolver()::resolve;
boolean transposed = parameterInfo.isTransposed();
return new StepExpressionFactory(typeRegistry).createExpression(expression, typeResolver, transposed);
}
}

@Override
public void execute(Object[] args) throws Throwable {
stepDefinition.execute(args);
}

@Override
public boolean isDefinedAt(StackTraceElement stackTraceElement) {
return stepDefinition.isDefinedAt(stackTraceElement);
}

@Override
public List<ParameterInfo> parameterInfos() {
return stepDefinition.parameterInfos();
}

@Override
public String getLocation(boolean detail) {
return stepDefinition.getLocation(detail);
}

public String getPattern() {
return expression.getSource();
}

public StepDefinition getStepDefinition() {
return stepDefinition;
}

List<Argument> matchedArguments(PickleStep step) {
return argumentMatcher.argumentsFrom(step, types);
}

private static Type[] getTypes(List<ParameterInfo> parameterInfos) {
if (parameterInfos == null) {
return new Type[0];
}

Type[] types = new Type[parameterInfos.size()];
for (int i = 0; i < types.length; i++) {
types[i] = parameterInfos.get(i).getType();
}
return types;
}

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package io.cucumber.core.runner;

import io.cucumber.core.api.Scenario;
import gherkin.pickles.PickleStep;
import io.cucumber.core.stepexpression.Argument;
import io.cucumber.core.api.Scenario;

import java.util.Collections;

final class FailedPickleStepInstantiationMatch extends PickleStepDefinitionMatch {
private final Throwable throwable;

FailedPickleStepInstantiationMatch(String uri, PickleStep step, Throwable throwable) {
super(Collections.<Argument>emptyList(), new NoStepDefinition(), uri, step);
super(Collections.emptyList(), new NoStepDefinition(), uri, step);
this.throwable = removeFrameworkFramesAndAppendStepLocation(throwable, getStepLocation());
}

Expand Down
18 changes: 6 additions & 12 deletions core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
package io.cucumber.core.runner;

import io.cucumber.core.backend.ParameterInfo;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.stepexpression.Argument;
import gherkin.pickles.PickleStep;

import java.util.List;

final class NoStepDefinition implements StepDefinition {

@Override
public List<Argument> matchedArguments(PickleStep step) {
return null;
}

@Override
public String getLocation(boolean detail) {
return null;
}

@Override
public Integer getParameterCount() {
return 0;
}

@Override
public void execute(Object[] args) {
}
Expand All @@ -32,6 +21,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) {
return false;
}

@Override
public List<ParameterInfo> parameterInfos() {
return null;
}

@Override
public String getPattern() {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.cucumber.core.runner;

import io.cucumber.core.api.Scenario;
import io.cucumber.core.backend.ParameterInfo;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.exception.CucumberException;
import gherkin.pickles.PickleStep;
Expand Down Expand Up @@ -30,9 +31,9 @@ class PickleStepDefinitionMatch extends Match implements StepDefinitionMatch {
public void runStep(Scenario scenario) throws Throwable {
int argumentCount = getArguments().size();

Integer parameterCount = stepDefinition.getParameterCount();
if (parameterCount != null && argumentCount != parameterCount) {
throw arityMismatch(parameterCount);
List<ParameterInfo> parameterInfos = stepDefinition.parameterInfos();
if (parameterInfos != null && argumentCount != parameterInfos.size()) {
throw arityMismatch(parameterInfos.size());
}
List<Object> result = new ArrayList<>();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public PickleStep getPickleStep() {

@Override
public String getStepLocation() {
return uri + ":" + Integer.toString(getStepLine());
return uri + ":" + getStepLine();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.cucumber.core.stepexpression;

import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface Argument {

Object getValue();
Expand Down
Loading

0 comments on commit f2eeac1

Please sign in to comment.