diff --git a/core/src/main/java/io/cucumber/core/backend/Glue.java b/core/src/main/java/io/cucumber/core/backend/Glue.java index 14f5889bbb..b47cafd635 100644 --- a/core/src/main/java/io/cucumber/core/backend/Glue.java +++ b/core/src/main/java/io/cucumber/core/backend/Glue.java @@ -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 stepDefinition) throws DuplicateStepDefinitionException; + void addStepDefinition(StepDefinition stepDefinition) throws DuplicateStepDefinitionException; void addBeforeHook(HookDefinition hookDefinition); diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java new file mode 100644 index 0000000000..99c734edf6 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java @@ -0,0 +1,12 @@ +package io.cucumber.core.backend; + +import java.lang.reflect.Type; + +public interface ParameterInfo { + + Type getType(); + + boolean isTransposed(); + + TypeResolver getTypeResolver(); +} diff --git a/core/src/main/java/io/cucumber/core/backend/StepDefinition.java b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java index 0ee182fdab..bbeac8cba0 100644 --- a/core/src/main/java/io/cucumber/core/backend/StepDefinition.java +++ b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java @@ -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 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. */ @@ -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 parameterInfos(); + } diff --git a/core/src/main/java/io/cucumber/core/backend/TypeResolver.java b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java new file mode 100644 index 0000000000..5e3199a6eb --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java @@ -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. + *

+ * When the {@link Object} type is returned no transform will be applied. + * + * @return a type + */ + Type resolve(); + +} diff --git a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java index b6b26dd7c0..0ccdc284a0 100644 --- a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java +++ b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java @@ -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; @@ -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 stepDefinitionsByPattern = new TreeMap<>(); - final Map stepDefinitionsByStepText = new HashMap<>(); + final Map stepDefinitionsByPattern = new TreeMap<>(); + final Map stepDefinitionsByStepText = new HashMap<>(); final List beforeHooks = new ArrayList<>(); final List beforeStepHooks = new ArrayList<>(); final List afterHooks = new ArrayList<>(); @@ -37,13 +36,13 @@ final class CachingGlue implements Glue { } @Override - public void addStepDefinition(Function 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)); } @@ -58,6 +57,7 @@ public void addBeforeStepHook(HookDefinition hookDefinition) { beforeStepHooks.add(hookDefinition); beforeStepHooks.sort(ASCENDING); } + @Override public void addAfterHook(HookDefinition hookDefinition) { afterHooks.add(hookDefinition); @@ -89,7 +89,7 @@ List 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 @@ -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 stepDefinitionMatches(String featurePath, PickleStep step) { List result = new ArrayList(); - for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) { + for (CoreStepDefinition stepDefinition : stepDefinitionsByPattern.values()) { List arguments = stepDefinition.matchedArguments(step); if (arguments != null) { result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step)); @@ -146,10 +146,11 @@ private void removeScenarioScopedHooks(List beforeHooks) { } } - private void removeScenariosScopedStepDefinitions(Map stepDefinitions) { - Iterator> stepDefinitionIterator = stepDefinitions.entrySet().iterator(); - while(stepDefinitionIterator.hasNext()){ - StepDefinition stepDefinition = stepDefinitionIterator.next().getValue(); + private void removeScenariosScopedStepDefinitions(Map stepDefinitions) { + Iterator> 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(); diff --git a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java new file mode 100644 index 0000000000..ca5fc5bb93 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java @@ -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 parameterInfos = stepDefinition.parameterInfos(); + this.expression = createExpression(parameterInfos, stepDefinition.getPattern(), typeRegistry); + this.argumentMatcher = new ArgumentMatcher(this.expression); + this.types = getTypes(parameterInfos); + } + + private StepExpression createExpression(List 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 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 matchedArguments(PickleStep step) { + return argumentMatcher.argumentsFrom(step, types); + } + + private static Type[] getTypes(List 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; + } + +} diff --git a/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java b/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java index 4b0263367b..06d39572da 100644 --- a/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/FailedPickleStepInstantiationMatch.java @@ -1,8 +1,7 @@ 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; @@ -10,7 +9,7 @@ final class FailedPickleStepInstantiationMatch extends PickleStepDefinitionMatch private final Throwable throwable; FailedPickleStepInstantiationMatch(String uri, PickleStep step, Throwable throwable) { - super(Collections.emptyList(), new NoStepDefinition(), uri, step); + super(Collections.emptyList(), new NoStepDefinition(), uri, step); this.throwable = removeFrameworkFramesAndAppendStepLocation(throwable, getStepLocation()); } diff --git a/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java index 04ab50ca11..f085a076dc 100644 --- a/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java +++ b/core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java @@ -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 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) { } @@ -32,6 +21,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public List parameterInfos() { + return null; + } + @Override public String getPattern() { return null; diff --git a/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java index 991f0f3127..8a7a7a1da3 100644 --- a/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java @@ -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; @@ -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 parameterInfos = stepDefinition.parameterInfos(); + if (parameterInfos != null && argumentCount != parameterInfos.size()) { + throw arityMismatch(parameterInfos.size()); } List result = new ArrayList<>(); try { diff --git a/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java index 84b95e8106..dc5e9bb357 100644 --- a/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java @@ -65,7 +65,7 @@ public PickleStep getPickleStep() { @Override public String getStepLocation() { - return uri + ":" + Integer.toString(getStepLine()); + return uri + ":" + getStepLine(); } @Override diff --git a/core/src/main/java/io/cucumber/core/stepexpression/Argument.java b/core/src/main/java/io/cucumber/core/stepexpression/Argument.java index cde30ddcc2..745df56f01 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/Argument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/Argument.java @@ -1,8 +1,5 @@ package io.cucumber.core.stepexpression; -import org.apiguardian.api.API; - -@API(status = API.Status.STABLE) public interface Argument { Object getValue(); diff --git a/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java index acd905ccc7..bd67411c52 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java @@ -3,12 +3,10 @@ import gherkin.pickles.PickleStep; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; -import org.apiguardian.api.API; import java.lang.reflect.Type; import java.util.List; -@API(status = API.Status.STABLE) public final class ArgumentMatcher { private final StepExpression expression; diff --git a/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java index 676e6f90bd..f49a45c37e 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java @@ -1,13 +1,11 @@ package io.cucumber.core.stepexpression; import io.cucumber.cucumberexpressions.Expression; -import org.apiguardian.api.API; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -@API(status = API.Status.STABLE) public final class StepExpression { private final Expression expression; diff --git a/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java b/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java index ce51905696..4055fdc888 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/TypeRegistry.java @@ -7,11 +7,9 @@ import io.cucumber.datatable.DataTableTypeRegistry; import io.cucumber.datatable.TableCellByTypeTransformer; import io.cucumber.datatable.TableEntryByTypeTransformer; -import org.apiguardian.api.API; import java.util.Locale; -@API(status = API.Status.STABLE) public final class TypeRegistry implements io.cucumber.core.api.TypeRegistry { private final ParameterTypeRegistry parameterTypeRegistry; diff --git a/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java b/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java index 2240262a27..0ae6777cb7 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/TypeResolver.java @@ -1,13 +1,10 @@ package io.cucumber.core.stepexpression; -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 { /** diff --git a/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java b/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java index c94777fafa..dcd9fbed84 100644 --- a/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java +++ b/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java @@ -11,11 +11,9 @@ import io.cucumber.core.api.Scenario; import io.cucumber.core.backend.DuplicateStepDefinitionException; import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; import io.cucumber.core.runtime.TimeServiceEventBus; -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.datatable.DataTable; import org.junit.Test; @@ -31,11 +29,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class CachingGlueTest { @@ -48,13 +43,13 @@ public void throws_duplicate_error_on_dupe_stepdefs() { StepDefinition a = mock(StepDefinition.class); when(a.getPattern()).thenReturn("hello"); when(a.getLocation(true)).thenReturn("foo.bf:10"); - glue.addStepDefinition(typeRegistry -> a); + glue.addStepDefinition(a); StepDefinition b = mock(StepDefinition.class); when(b.getPattern()).thenReturn("hello"); when(b.getLocation(true)).thenReturn("bar.bf:90"); try { - glue.addStepDefinition(typeRegistry -> b); + glue.addStepDefinition(b); fail("should have failed"); } catch (DuplicateStepDefinitionException expected) { assertEquals("Duplicate step definitions in foo.bf:10 and bar.bf:90", expected.getMessage()); @@ -69,7 +64,7 @@ public void removes_glue_that_is_scenario_scoped() { StepDefinition sd = spy(new MockedScenarioScopedStepDefinition()); when(sd.getPattern()).thenReturn("pattern"); - glue.addStepDefinition(typeRegistry -> sd); + glue.addStepDefinition(sd); HookDefinition bh = spy(new MockedScenarioScopedHookDefinition()); glue.addBeforeHook(bh); @@ -91,12 +86,14 @@ public void removes_glue_that_is_scenario_scoped() { @Test public void removes_scenario_scoped_cache_entries() { StepDefinition sd = new MockedScenarioScopedStepDefinition("pattern"); - glue.addStepDefinition(typeRegistry -> sd); + glue.addStepDefinition(sd); String featurePath = "someFeature.feature"; String stepText = "pattern"; PickleStep pickleStep1 = getPickleStep(stepText); - assertEquals(sd, glue.stepDefinitionMatch(featurePath, pickleStep1).getStepDefinition()); + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + CoreStepDefinition coreStepDefinition = (CoreStepDefinition) pickleStepDefinitionMatch.getStepDefinition(); + assertEquals(sd, coreStepDefinition.getStepDefinition()); assertEquals(1, glue.stepDefinitionsByStepText.size()); @@ -108,101 +105,88 @@ public void removes_scenario_scoped_cache_entries() { @Test public void returns_null_if_no_matching_steps_found() { StepDefinition stepDefinition = spy(new MockedStepDefinition("pattern1")); - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); String featurePath = "someFeature.feature"; PickleStep pickleStep = getPickleStep("pattern"); assertNull(glue.stepDefinitionMatch(featurePath, pickleStep)); - verify(stepDefinition).matchedArguments(pickleStep); } @Test public void returns_match_from_cache_if_single_found() { StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); - glue.addStepDefinition(typeRegistry -> stepDefinition1); - glue.addStepDefinition(typeRegistry -> stepDefinition2); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); String featurePath = "someFeature.feature"; String stepText = "pattern1"; PickleStep pickleStep1 = getPickleStep(stepText); - assertEquals(stepDefinition1, glue.stepDefinitionMatch(featurePath, pickleStep1).getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); + + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + CoreStepDefinition coreStepDefinition = (CoreStepDefinition) pickleStepDefinitionMatch.getStepDefinition(); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); + //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); + CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); + assertEquals(stepDefinition1, entry.getStepDefinition()); PickleStep pickleStep2 = getPickleStep(stepText); - assertEquals(stepDefinition1, glue.stepDefinitionMatch(featurePath, pickleStep2).getStepDefinition()); - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep2); + CoreStepDefinition coreStepDefinition2 = (CoreStepDefinition) pickleStepDefinitionMatch2.getStepDefinition(); + assertEquals(stepDefinition1, coreStepDefinition2.getStepDefinition()); } @Test public void returns_match_from_cache_for_step_with_table() { StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); - glue.addStepDefinition(typeRegistry -> stepDefinition1); - glue.addStepDefinition(typeRegistry -> stepDefinition2); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); String featurePath = "someFeature.feature"; String stepText = "pattern1"; PickleStep pickleStep1 = getPickleStepWithSingleCellTable(stepText, "cell 1"); - PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); + CoreStepDefinition coreStepDefinition = (CoreStepDefinition) match1.getStepDefinition(); - assertEquals(stepDefinition1, match1.getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); + CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); + assertEquals(stepDefinition1, entry.getStepDefinition()); //check arguments - assertEquals("cell 1", ((DataTable) match1.getArguments().get(0).getValue()).cell(0,0)); + assertEquals("cell 1", ((DataTable) match1.getArguments().get(0).getValue()).cell(0, 0)); //check second match PickleStep pickleStep2 = getPickleStepWithSingleCellTable(stepText, "cell 2"); PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - //check arguments - assertEquals("cell 2",((DataTable) match2.getArguments().get(0).getValue()).cell(0,0)); - - + assertEquals("cell 2", ((DataTable) match2.getArguments().get(0).getValue()).cell(0, 0)); } @Test public void returns_match_from_cache_for_ste_with_doc_string() { StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); - glue.addStepDefinition(typeRegistry -> stepDefinition1); - glue.addStepDefinition(typeRegistry -> stepDefinition2); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); String featurePath = "someFeature.feature"; String stepText = "pattern1"; PickleStep pickleStep1 = getPickleStepWithDocString(stepText, "doc string 1"); PickleStepDefinitionMatch match1 = glue.stepDefinitionMatch(featurePath, pickleStep1); + CoreStepDefinition coreStepDefinition = (CoreStepDefinition) match1.getStepDefinition(); - assertEquals(stepDefinition1, match1.getStepDefinition()); - //verify if all defs are checked - verify(stepDefinition1).matchedArguments(pickleStep1); - verify(stepDefinition2).matchedArguments(pickleStep1); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); //check cache - StepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1,entry); + CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); + assertEquals(stepDefinition1, entry.getStepDefinition()); //check arguments assertEquals("doc string 1", match1.getArguments().get(0).getValue()); @@ -210,13 +194,8 @@ public void returns_match_from_cache_for_ste_with_doc_string() { //check second match PickleStep pickleStep2 = getPickleStepWithDocString(stepText, "doc string 2"); PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); - - //verify that only cached step definition has called matchedArguments again - verify(stepDefinition1,times(2)).matchedArguments(any(PickleStep.class)); - verify(stepDefinition2).matchedArguments(any(PickleStep.class)); - //check arguments - assertEquals("doc string 2",match2.getArguments().get(0).getValue()); + assertEquals("doc string 2", match2.getArguments().get(0).getValue()); } @@ -227,7 +206,7 @@ private static PickleStep getPickleStepWithSingleCellTable(String stepText, Stri } private static PickleStep getPickleStepWithDocString(String stepText, String doc) { - return new PickleStep(stepText, Collections.singletonList(new PickleString(mock(PickleLocation.class),doc)), Collections.emptyList()); + return new PickleStep(stepText, Collections.singletonList(new PickleString(mock(PickleLocation.class), doc)), Collections.emptyList()); } @Test @@ -235,9 +214,9 @@ public void throws_ambiguous_steps_def_exception_when_many_patterns_match() { StepDefinition stepDefinition1 = new MockedStepDefinition("pattern1"); StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); StepDefinition stepDefinition3 = new MockedStepDefinition("^pattern[1,3]"); - glue.addStepDefinition(typeRegistry -> stepDefinition1); - glue.addStepDefinition(typeRegistry -> stepDefinition2); - glue.addStepDefinition(typeRegistry -> stepDefinition3); + glue.addStepDefinition(stepDefinition1); + glue.addStepDefinition(stepDefinition2); + glue.addStepDefinition(stepDefinition3); String featurePath = "someFeature.feature"; checkAmbiguousCalled(featurePath); @@ -251,7 +230,7 @@ private void checkAmbiguousCalled(String featurePath) { glue.stepDefinitionMatch(featurePath, getPickleStep("pattern1")); } catch (AmbiguousStepDefinitionsException e) { - assertEquals(2,e.getMatches().size()); + assertEquals(2, e.getMatches().size()); ambiguousCalled = true; } assertTrue(ambiguousCalled); @@ -273,13 +252,6 @@ private static class MockedScenarioScopedStepDefinition implements StepDefinitio this("mocked scenario scoped step definition"); } - @Override - public List matchedArguments(PickleStep step) { - StepExpression expression = new StepExpressionFactory(new TypeRegistry(ENGLISH)).createExpression(pattern); - final ArgumentMatcher argumentMatcher = new ArgumentMatcher(expression); - return argumentMatcher.argumentsFrom(step); - } - boolean disposed; @Override @@ -292,11 +264,6 @@ public String getLocation(boolean detail) { return "mocked scenario scoped step definition"; } - @Override - public Integer getParameterCount() { - return 0; - } - @Override public void execute(Object[] args) { @@ -307,6 +274,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public List parameterInfos() { + return null; + } + @Override public String getPattern() { return pattern; @@ -352,23 +324,11 @@ private static class MockedStepDefinition implements StepDefinition { this.pattern = pattern; } - @Override - public List matchedArguments(PickleStep step) { - StepExpression expression = new StepExpressionFactory(new TypeRegistry(ENGLISH)).createExpression(pattern); - final ArgumentMatcher argumentMatcher = new ArgumentMatcher(expression); - return argumentMatcher.argumentsFrom(step); - } - @Override public String getLocation(boolean detail) { return "mocked step location"; } - @Override - public Integer getParameterCount() { - return 0; - } - @Override public void execute(Object[] args) { @@ -379,6 +339,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public List parameterInfos() { + return null; + } + @Override public String getPattern() { return pattern; diff --git a/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java new file mode 100644 index 0000000000..1d846bebe3 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java @@ -0,0 +1,158 @@ +package io.cucumber.core.runner; + +import gherkin.pickles.PickleCell; +import gherkin.pickles.PickleLocation; +import gherkin.pickles.PickleRow; +import gherkin.pickles.PickleStep; +import gherkin.pickles.PickleString; +import gherkin.pickles.PickleTable; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.datatable.DataTable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +public class CoreStepDefinitionTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + + @Test + public void should_apply_identity_transform_to_doc_string_when_target_type_is_object() { + StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); + CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, typeRegistry); + + PickleString pickleString = new PickleString(null, "content", "text"); + List arguments = stepDefinition.matchedArguments(new PickleStep("I have some step", singletonList(pickleString), emptyList())); + assertEquals("content", arguments.get(0).getValue()); + } + + @Test + public void should_apply_identity_transform_to_data_table_when_target_type_is_object() { + StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); + CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, typeRegistry); + + PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(null, "content"))))); + List arguments = stepDefinition.matchedArguments(new PickleStep("I have some step", singletonList(table), emptyList())); + assertEquals(DataTable.create(singletonList(singletonList("content"))), arguments.get(0).getValue()); + } + + + public static class StepDefs { + public void listOfListOfDoubles(List> listOfListOfDoubles) { + } + + public void plainDataTable(DataTable dataTable) { + } + + public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { + } + + public void transposedMapOfDoubleToListOfDouble(Map> mapOfDoubleToListOfDouble) { + } + + } + + @Test + public void transforms_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); + Map stepDefs = runStepDef(m, false, new PickleTable(listOfDoublesWithoutHeader())); + + assertThat(stepDefs, hasEntry(1000.0, 999.0)); + assertThat(stepDefs, hasEntry(0.5, -0.5)); + assertThat(stepDefs, hasEntry(100.5, 99.5)); + } + + @Test + public void transforms_transposed_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("transposedMapOfDoubleToListOfDouble", Map.class); + Map> stepDefs = runStepDef(m, true, new PickleTable(listOfDoublesWithoutHeader())); + assertThat(stepDefs, hasEntry(100.5, asList(0.5, 1000.0))); + } + + @Test + public void transforms_to_list_of_single_values() throws Throwable { + Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); + List> stepDefs = runStepDef(m, false, new PickleTable(listOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.toString()); + } + + @Test + public void transforms_to_list_of_single_values_transposed() throws Throwable { + Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); + List> stepDefs = runStepDef(m, true, new PickleTable(transposedListOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.toString()); + } + + @Test + public void passes_plain_data_table() throws Throwable { + Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); + DataTable stepDefs = runStepDef(m, false, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.cell(1, 0)); + } + + @Test + public void passes_transposed_data_table() throws Throwable { + Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); + DataTable stepDefs = runStepDef(m, true, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.cell(0, 1)); + } + + private T runStepDef(Method method, boolean transposed, PickleTable table) throws Throwable { + StubStepDefinition stub = new StubStepDefinition("some text", transposed, method.getGenericParameterTypes()); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stub, typeRegistry); + + PickleStep stepWithTable = new PickleStep("some text", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); + List arguments = coreStepDefinition.matchedArguments(stepWithTable); + + List result = new ArrayList<>(); + for (Argument argument : arguments) { + result.add(argument.getValue()); + } + coreStepDefinition.execute(result.toArray(new Object[0])); + + return (T) stub.getArgs().get(0); + } + + private List listOfDatesWithHeader() { + List rows = new ArrayList<>(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10")))); + return rows; + } + + private List listOfDoublesWithoutHeader() { + List rows = new ArrayList<>(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "99.5")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "-0.5")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1000"), new PickleCell(mock(PickleLocation.class), "999")))); + return rows; + } + + private List transposedListOfDoublesWithoutHeader() { + List rows = new ArrayList<>(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "1000")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "99.5"), new PickleCell(mock(PickleLocation.class), "-0.5"), new PickleCell(mock(PickleLocation.class), "999")))); + return rows; + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java index 08053a352b..3b75640470 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java @@ -28,7 +28,6 @@ import static java.util.Collections.singletonList; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class HookOrderTest { @@ -38,7 +37,7 @@ public class HookOrderTest { private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - private final StubStepDefinition stepDefinition = new StubStepDefinition("pattern1", new TypeRegistry(Locale.ENGLISH)); + private final StubStepDefinition stepDefinition = new StubStepDefinition("pattern1"); private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2,2))); private final PickleEvent pickleEvent = new PickleEvent("uri", new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1,1)))); @@ -50,7 +49,7 @@ public void before_hooks_execute_in_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> new StubStepDefinition("pattern1", typeRegistry)); + glue.addStepDefinition(new StubStepDefinition("pattern1")); for (HookDefinition hook : hooks) { glue.addBeforeHook(hook); } @@ -77,7 +76,7 @@ public void before_step_hooks_execute_in_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addBeforeStepHook(hook); } @@ -104,7 +103,7 @@ public void after_hooks_execute_in_reverse_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addAfterHook(hook); } @@ -131,7 +130,7 @@ public void after_step_hooks_execute_in_reverse_order() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); for (HookDefinition hook : hooks) { glue.addAfterStepHook(hook); } @@ -160,7 +159,7 @@ public void hooks_order_across_many_backends() throws Throwable { TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); for (HookDefinition hook : backend1Hooks) { glue.addBeforeHook(hook); diff --git a/core/src/test/java/io/cucumber/core/runner/RunnerTest.java b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java index f8e675e8bf..bdb98a1bc4 100644 --- a/core/src/test/java/io/cucumber/core/runner/RunnerTest.java +++ b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java @@ -10,13 +10,11 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.backend.StepDefinition; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.options.RuntimeOptions; import io.cucumber.core.options.RuntimeOptionsBuilder; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.snippets.TestSnippet; -import io.cucumber.core.stepexpression.Argument; import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Test; import org.mockito.ArgumentMatchers; @@ -26,19 +24,22 @@ import java.net.URI; import java.time.Clock; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -86,8 +87,8 @@ public Object answer(InvocationOnMock invocation) { @Test public void steps_are_skipped_after_failure() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); final HookDefinition failingBeforeHook = addBeforeHook(); doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); @@ -95,7 +96,7 @@ public void steps_are_skipped_after_failure() throws Throwable { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addBeforeHook(failingBeforeHook); - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); } }; @@ -108,9 +109,15 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void aftersteps_are_executed_after_failed_step() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - doThrow(RuntimeException.class).when(stepDefinition).execute(ArgumentMatchers.any()); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step") { + + @Override + public void execute(Object[] args) throws Throwable { + throw new RuntimeException(); + } + }); + + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); final HookDefinition afteStepHook = addAfterStepHook(); @@ -118,7 +125,7 @@ public void aftersteps_are_executed_after_failed_step() throws Throwable { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addAfterHook(afteStepHook); - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); } }; @@ -131,18 +138,18 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void aftersteps_executed_for_passed_step() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(stepDefinition); - final HookDefinition afteStepHook1 = addAfterStepHook(); - final HookDefinition afteStepHook2 = addAfterStepHook(); + HookDefinition afteStepHook1 = addAfterStepHook(); + HookDefinition afteStepHook2 = addAfterStepHook(); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addAfterHook(afteStepHook1); glue.addAfterHook(afteStepHook2); - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); } }; @@ -182,32 +189,32 @@ public void loadGlue(Glue glue, List gluePaths) { @Test public void steps_are_executed() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + StubStepDefinition stepDefinition = new StubStepDefinition("some step"); + PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); } }; runnerSupplier.get().runPickle(pickleEventMatchingStepDefinitions); - verify(stepDefinition).execute(any(Object[].class)); + assertEquals(emptyList(), stepDefinition.getArgs()); } @Test - public void steps_are_not_executed_on_dry_run() throws Throwable { - final StepDefinition stepDefinition = mock(StepDefinition.class); - final PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(asList(stepDefinition)); + public void steps_are_not_executed_on_dry_run() { + StubStepDefinition stepDefinition = new StubStepDefinition("some step"); + PickleEvent pickleEvent = createPickleEventMatchingStepDefinitions(stepDefinition); RuntimeOptions runtimeOptions = new RuntimeOptionsBuilder().setDryRun().build(); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(typeRegistry -> stepDefinition); + glue.addStepDefinition(stepDefinition); } }; runnerSupplier.get().runPickle(pickleEvent); - verify(stepDefinition, never()).execute(any(Object[].class)); + assertNull(stepDefinition.getArgs()); } @Test @@ -294,16 +301,11 @@ private PickleEvent createEmptyPickleEvent() { return new PickleEvent("uri", new Pickle(NAME, ENGLISH, NO_STEPS, NO_TAGS, MOCK_LOCATIONS)); } - private PickleEvent createPickleEventMatchingStepDefinitions(List stepDefinitions) { - List steps = new ArrayList<>(stepDefinitions.size()); - int i = 0; - for (StepDefinition stepDefinition : stepDefinitions) { - PickleStep step = mock(PickleStep.class); - steps.add(step); - when(stepDefinition.matchedArguments(step)).thenReturn(Collections.emptyList()); - when(stepDefinition.getPattern()).thenReturn("pattern" + Integer.toString(++i)); - } - return new PickleEvent("uri", new Pickle(NAME, ENGLISH, steps, NO_TAGS, MOCK_LOCATIONS)); + private PickleEvent createPickleEventMatchingStepDefinitions(StubStepDefinition stepDefinition) { + PickleStep step = mock(PickleStep.class); + String pattern = stepDefinition.getPattern(); + when(step.getText()).thenReturn(pattern); + return new PickleEvent("uri", new Pickle(NAME, ENGLISH, singletonList(step), NO_TAGS, MOCK_LOCATIONS)); } private PickleEvent createPickleEventWithSteps(List steps) { diff --git a/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java index a930bf2819..07c89e8e4d 100644 --- a/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java +++ b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java @@ -1,18 +1,18 @@ package io.cucumber.core.runner; -import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.runtime.StubStepDefinition; -import io.cucumber.core.stepexpression.TypeRegistry; import gherkin.pickles.PickleCell; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleRow; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTable; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.runtime.StubStepDefinition; +import io.cucumber.core.stepexpression.Argument; +import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.cucumberexpressions.ParameterType; import io.cucumber.cucumberexpressions.Transformer; import io.cucumber.datatable.DataTableType; import io.cucumber.datatable.TableCellTransformer; -import io.cucumber.core.stepexpression.Argument; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -35,8 +35,9 @@ public class StepDefinitionMatchTest { public void executes_a_step() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); stepDefinitionMatch.runStep(null); } @@ -45,8 +46,9 @@ public void executes_a_step() throws Throwable { public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_arguments() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -70,8 +72,9 @@ public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage( @@ -90,8 +93,9 @@ public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than @Test public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_arguments() throws Throwable { PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) mock(PickleTable.class)), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.TYPE, Short.TYPE, List.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + "Step [I have {int} cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + @@ -107,8 +111,9 @@ public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_ @Test public void throws_arity_mismatch_exception_when_there_are_more_parameters_and_no_arguments() throws Throwable { PickleStep step = new PickleStep("I have cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", Integer.TYPE, Short.TYPE, List.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + "Step [I have cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + @@ -123,8 +128,9 @@ public void throws_register_type_in_configuration_exception_when_there_is_no_dat PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), "A"))))); PickleStep step = new PickleStep("I have a datatable", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have a datatable", typeRegistry, UndefinedDataTableType.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have a datatable", UndefinedDataTableType.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + @@ -148,8 +154,9 @@ public ItemQuantity transform(String s) throws Throwable { })); PickleStep step = new PickleStep("I have some cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", typeRegistry, ItemQuantity.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", ItemQuantity.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + @@ -181,8 +188,9 @@ public ItemQuantity transform(String s) { )); PickleStep step = new PickleStep("I have some cukes in my belly", singletonList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", typeRegistry, ItemQuantity.class); - List arguments = stepDefinition.matchedArguments(step); + StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); expectedException.expectMessage("" + diff --git a/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java b/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java new file mode 100644 index 0000000000..5b9e204a6f --- /dev/null +++ b/core/src/test/java/io/cucumber/core/runner/StubStepDefinition.java @@ -0,0 +1,86 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; + +class StubStepDefinition implements StepDefinition { + private final List parameterInfos; + private final String expression; + private final boolean transposed; + + private List args; + + StubStepDefinition(String pattern, Type... types) { + this(pattern, false, types); + } + + StubStepDefinition(String pattern, boolean transposed, Type... types) { + this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); + this.expression = pattern; + this.transposed = transposed; + } + + @Override + public String getLocation(boolean detail) { + return "{stubbed location" + (detail ? " with details" : "") + "}"; + } + + @Override + public void execute(Object[] args) throws Throwable { + assertEquals(parameterInfos.size(), args.length); + this.args = Arrays.asList(args); + } + + public List getArgs() { + return args; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public List parameterInfos() { + return parameterInfos; + } + + @Override + public String getPattern() { + return expression; + } + + private final class StubParameterInfo implements ParameterInfo { + + private final Type type; + + private StubParameterInfo(Type type) { + this.type = type; + } + + @Override + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return transposed; + } + + @Override + public TypeResolver getTypeResolver() { + return () -> type; + } + } + +} diff --git a/core/src/test/java/io/cucumber/core/runner/TestHelper.java b/core/src/test/java/io/cucumber/core/runner/TestHelper.java index 15e8656f6b..3ecc2baf18 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestHelper.java +++ b/core/src/test/java/io/cucumber/core/runner/TestHelper.java @@ -161,7 +161,7 @@ private static void mockSteps(Glue glue, List features, } Type[] types = mapArgumentToTypes(step); - StepDefinition stepDefinition = new StubStepDefinition(step.getText(), typeRegistry, types) { + StepDefinition stepDefinition = new StubStepDefinition(step.getText(), types) { @Override public void execute(Object[] args) throws Throwable { @@ -183,7 +183,7 @@ public String getLocation(boolean detail) { } }; - glue.addStepDefinition(t -> stepDefinition); + glue.addStepDefinition(stepDefinition); } } diff --git a/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java index f25ff87f9e..9369ffff38 100644 --- a/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java @@ -2,33 +2,33 @@ import gherkin.ast.ScenarioDefinition; import gherkin.ast.Step; -import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; import io.cucumber.core.api.Scenario; -import io.cucumber.core.event.Result; -import io.cucumber.core.event.Status; -import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.backend.Glue; +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.event.EventHandler; -import io.cucumber.core.plugin.EventListener; import io.cucumber.core.event.EventPublisher; import io.cucumber.core.event.HookType; +import io.cucumber.core.event.Result; +import io.cucumber.core.event.Status; import io.cucumber.core.event.StepDefinedEvent; +import io.cucumber.core.event.StepDefinition; import io.cucumber.core.event.TestCase; import io.cucumber.core.event.TestCaseFinished; import io.cucumber.core.event.TestStepFinished; -import io.cucumber.core.plugin.Plugin; -import io.cucumber.core.backend.Glue; -import io.cucumber.core.backend.HookDefinition; -import io.cucumber.core.event.StepDefinition; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.exception.CompositeCucumberException; +import io.cucumber.core.feature.CucumberFeature; import io.cucumber.core.io.Resource; import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.io.TestClasspathResourceLoader; -import io.cucumber.core.feature.CucumberFeature; import io.cucumber.core.options.CommandlineOptionsParser; +import io.cucumber.core.plugin.ConcurrentEventListener; +import io.cucumber.core.plugin.EventListener; import io.cucumber.core.plugin.FormatterBuilder; import io.cucumber.core.plugin.FormatterSpy; +import io.cucumber.core.plugin.Plugin; import io.cucumber.core.runner.ScenarioScoped; import io.cucumber.core.runner.StepDurationTimeService; import io.cucumber.core.runner.TestBackendSupplier; @@ -56,6 +56,7 @@ import static io.cucumber.core.runner.TestHelper.feature; import static io.cucumber.core.runner.TestHelper.result; import static java.time.Duration.ZERO; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -583,14 +584,14 @@ public void loadGlue(Glue glue, List gluePaths) { this.glue = glue; final io.cucumber.core.backend.StepDefinition mockedStepDefinition = new MockedStepDefinition(); definedStepDefinitions.add(mockedStepDefinition); - glue.addStepDefinition(typeRegistry -> mockedStepDefinition); + glue.addStepDefinition(mockedStepDefinition); } @Override public void buildWorld() { final io.cucumber.core.backend.StepDefinition mockedScenarioScopedStepDefinition = new MockedScenarioScopedStepDefinition(); definedStepDefinitions.add(mockedScenarioScopedStepDefinition); - glue.addStepDefinition(typeRegistry -> mockedScenarioScopedStepDefinition); + glue.addStepDefinition(mockedScenarioScopedStepDefinition); } }; @@ -717,8 +718,8 @@ public List get() { } private void mockMatch(Glue glue, String text) { - io.cucumber.core.backend.StepDefinition stepDefinition = new StubStepDefinition(text, TYPE_REGISTRY); - glue.addStepDefinition(typeRegistry -> stepDefinition); + io.cucumber.core.backend.StepDefinition stepDefinition = new StubStepDefinition(text); + glue.addStepDefinition(stepDefinition); } private void mockHook(Glue glue, HookDefinition hook, HookType hookType) { @@ -746,19 +747,14 @@ private TestCaseFinished testCaseFinishedWithStatus(Status resultStatus) { private static final class MockedStepDefinition implements io.cucumber.core.backend.StepDefinition { - @Override - public List matchedArguments(PickleStep step) { - return step.getText().equals(getPattern()) ? new ArrayList<>() : null; - } - @Override public String getLocation(boolean detail) { return "mocked step definition"; } @Override - public Integer getParameterCount() { - return 0; + public List parameterInfos() { + return emptyList(); } @Override @@ -787,19 +783,14 @@ public void disposeScenarioScope() { this.disposed = true; } - @Override - public List matchedArguments(PickleStep step) { - return step.getText().equals(getPattern()) ? new ArrayList<>() : null; - } - @Override public String getLocation(boolean detail) { return "mocked scenario scoped step definition"; } @Override - public Integer getParameterCount() { - return 0; + public List parameterInfos() { + return emptyList(); } @Override diff --git a/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java b/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java index d5c5d02a14..0704144230 100644 --- a/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java +++ b/core/src/test/java/io/cucumber/core/runtime/StubStepDefinition.java @@ -1,38 +1,23 @@ package io.cucumber.core.runtime; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.stepexpression.TypeRegistry; -import io.cucumber.core.stepexpression.Argument; -import gherkin.pickles.PickleStep; -import io.cucumber.core.stepexpression.ArgumentMatcher; -import io.cucumber.core.stepexpression.StepExpression; -import io.cucumber.core.stepexpression.StepExpressionFactory; +import io.cucumber.core.backend.TypeResolver; import java.lang.reflect.Type; -import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.Assert.assertEquals; public class StubStepDefinition implements StepDefinition { - private final List parameters; - private final StepExpression expression; - private final ArgumentMatcher argumentMatcher; - - public StubStepDefinition(String pattern, TypeRegistry typeRegistry, Type... types) { - this.parameters = Arrays.asList(types); - if (parameters.isEmpty()) { - this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern); - } else { - Type lastParameter = parameters.get(parameters.size() - 1); - this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern, lastParameter); - } - this.argumentMatcher = new ArgumentMatcher(expression); - } + private final List parameterInfos; + private final String expression; - @Override - public List matchedArguments(PickleStep step) { - return argumentMatcher.argumentsFrom(step); + public StubStepDefinition(String pattern, Type... types) { + this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); + this.expression = pattern; } @Override @@ -41,15 +26,10 @@ public String getLocation(boolean detail) { } @Override - public Integer getParameterCount() { - return parameters.size(); - } - - @Override - public void execute(Object[] args) throws Throwable { - assertEquals(parameters.size(), args.length); + public void execute(Object[] args) { + assertEquals(parameterInfos.size(), args.length); for (int i = 0; i < args.length; i++) { - assertEquals(parameters.get(i), args[i].getClass()); + assertEquals(parameterInfos.get(i).getType(), args[i].getClass()); } } @@ -58,9 +38,38 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public List parameterInfos() { + return parameterInfos; + } + @Override public String getPattern() { - return expression.getSource(); + return expression; + } + + private final class StubParameterInfo implements ParameterInfo { + + private final Type type; + + private StubParameterInfo(Type type) { + this.type = type; + } + + @Override + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return false; + } + + @Override + public TypeResolver getTypeResolver() { + return () -> type; + } } } diff --git a/java/src/main/java/io/cucumber/java/JavaBackend.java b/java/src/main/java/io/cucumber/java/JavaBackend.java index 299e7f08c2..b32204d823 100644 --- a/java/src/main/java/io/cucumber/java/JavaBackend.java +++ b/java/src/main/java/io/cucumber/java/JavaBackend.java @@ -62,8 +62,7 @@ void addStepDefinition(Annotation annotation, Method method) { String expression = expression(annotation); long timeoutMillis = timeoutMillis(annotation); container.addClass(method.getDeclaringClass()); - glue.addStepDefinition(typeRegistry -> - new JavaStepDefinition(method, expression, timeoutMillis, lookup, typeRegistry)); + glue.addStepDefinition(new JavaStepDefinition(method, expression, timeoutMillis, lookup)); } void addHook(Annotation annotation, Method method) { diff --git a/java/src/main/java/io/cucumber/java/JavaParameterInfo.java b/java/src/main/java/io/cucumber/java/JavaParameterInfo.java new file mode 100644 index 0000000000..51064a9ccf --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaParameterInfo.java @@ -0,0 +1,58 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * This class composes all interesting parameter information into one object. + */ +class JavaParameterInfo implements ParameterInfo { + private final Type type; + private final boolean transposed; + + static List fromMethod(Method method) { + List result = new ArrayList<>(); + Type[] genericParameterTypes = method.getGenericParameterTypes(); + Annotation[][] annotations = method.getParameterAnnotations(); + for (int i = 0; i < genericParameterTypes.length; i++) { + boolean transposed = false; + for (Annotation annotation : annotations[i]) { + if (annotation instanceof Transpose) { + transposed = ((Transpose) annotation).value(); + } + } + result.add(new JavaParameterInfo(genericParameterTypes[i], transposed)); + } + return result; + } + + private JavaParameterInfo(Type type, boolean transposed) { + this.type = type; + this.transposed = transposed; + } + + public Type getType() { + return type; + } + + public boolean isTransposed() { + return transposed; + } + + @Override + public TypeResolver getTypeResolver() { + return () -> type; + } + + @Override + public String toString() { + return type.toString(); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaStepDefinition.java b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java index 7d8f6b3fc4..4a364fa55f 100644 --- a/java/src/main/java/io/cucumber/java/JavaStepDefinition.java +++ b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java @@ -1,86 +1,47 @@ package io.cucumber.java; import io.cucumber.core.backend.Lookup; -import io.cucumber.core.runtime.Invoker; -import io.cucumber.core.stepexpression.TypeRegistry; -import io.cucumber.core.stepexpression.Argument; -import io.cucumber.core.stepexpression.ArgumentMatcher; -import io.cucumber.core.reflection.MethodFormat; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.stepexpression.StepExpression; -import io.cucumber.core.stepexpression.StepExpressionFactory; -import gherkin.pickles.PickleStep; +import io.cucumber.core.reflection.MethodFormat; +import io.cucumber.core.runtime.Invoker; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.List; final class JavaStepDefinition implements StepDefinition { private final Method method; - private final StepExpression expression; + private final String expression; private final long timeoutMillis; private final Lookup lookup; - private final ArgumentMatcher argumentMatcher; - private final Type[] parameterTypes; private final String shortFormat; private final String fullFormat; + private final List parameterInfos; JavaStepDefinition(Method method, String expression, long timeoutMillis, - Lookup lookup, - TypeRegistry typeRegistry) { + Lookup lookup) { this.method = method; this.timeoutMillis = timeoutMillis; this.lookup = lookup; - List parameterInfos = ParameterInfo.fromMethod(method); - this.parameterTypes = getTypes(parameterInfos); - this.expression = createExpression(parameterInfos, expression, typeRegistry); - this.argumentMatcher = new ArgumentMatcher(this.expression); + this.parameterInfos = JavaParameterInfo.fromMethod(method); + this.expression = expression; this.shortFormat = MethodFormat.SHORT.format(method); this.fullFormat = MethodFormat.FULL.format(method); } - private StepExpression createExpression(List parameterInfos, String expression, TypeRegistry typeRegistry) { - if (parameterInfos.isEmpty()) { - return new StepExpressionFactory(typeRegistry).createExpression(expression); - } else { - ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); - return new StepExpressionFactory(typeRegistry).createExpression(expression, parameterInfo.getType(), parameterInfo.isTransposed()); - } - } - @Override public void execute(Object[] args) throws Throwable { Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); } - @Override - public List matchedArguments(PickleStep step) { - return argumentMatcher.argumentsFrom(step, parameterTypes); - } - - private static Type[] getTypes(List parameterInfos) { - Type[] types = new Type[parameterInfos.size()]; - for (int i = 0; i < types.length; i++) { - types[i] = parameterInfos.get(i).getType(); - } - return types; - } - @Override public String getLocation(boolean detail) { return detail ? fullFormat : shortFormat; } - @Override - public Integer getParameterCount() { - return parameterTypes.length; - } - @Override public boolean isDefinedAt(StackTraceElement e) { return e.getClassName().equals(method.getDeclaringClass().getName()) && e.getMethodName().equals(method.getName()); @@ -88,49 +49,12 @@ public boolean isDefinedAt(StackTraceElement e) { @Override public String getPattern() { - return expression.getSource(); + return expression; } - /** - * This class composes all interesting parameter information into one object. - */ - static class ParameterInfo { - private final Type type; - private final boolean transposed; - - static List fromMethod(Method method) { - List result = new ArrayList(); - Type[] genericParameterTypes = method.getGenericParameterTypes(); - Annotation[][] annotations = method.getParameterAnnotations(); - for (int i = 0; i < genericParameterTypes.length; i++) { - boolean transposed = false; - for (Annotation annotation : annotations[i]) { - if (annotation instanceof Transpose) { - transposed = ((Transpose) annotation).value(); - } - } - result.add(new ParameterInfo(genericParameterTypes[i], transposed)); - } - return result; - } - - private ParameterInfo(Type type, boolean transposed) { - this.type = type; - this.transposed = transposed; - } - - Type getType() { - return type; - } - - boolean isTransposed() { - return transposed; - } - - @Override - public String toString() { - return type.toString(); - } - + @Override + public List parameterInfos() { + return parameterInfos; } + } diff --git a/java/src/test/java/io/cucumber/java/JavaBackendTest.java b/java/src/test/java/io/cucumber/java/JavaBackendTest.java index 091606fcbe..ec11dac401 100644 --- a/java/src/test/java/io/cucumber/java/JavaBackendTest.java +++ b/java/src/test/java/io/cucumber/java/JavaBackendTest.java @@ -1,6 +1,5 @@ package io.cucumber.java; -import io.cucumber.core.backend.Container; import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.backend.StepDefinition; @@ -40,7 +39,7 @@ public class JavaBackendTest { public MockitoRule mockitoRule = MockitoJUnit.rule(); @Captor - public ArgumentCaptor> stepDefinition; + public ArgumentCaptor stepDefinition; @Mock private Glue glue; @@ -76,11 +75,8 @@ public void detects_repeated_annotations() { backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/repeatable"))); verify(glue, times(2)).addStepDefinition(stepDefinition.capture()); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); - List patterns = stepDefinition.getAllValues() .stream() - .map(stepDefinitionFunction -> stepDefinitionFunction.apply(typeRegistry)) .map(StepDefinition::getPattern) .collect(toList()); assertThat(patterns, equalTo(asList("test", "test again"))); diff --git a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java index d570e1bc71..1dc368e67d 100755 --- a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java +++ b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTransposeTest.java @@ -2,150 +2,44 @@ import io.cucumber.core.backend.Lookup; import io.cucumber.core.backend.StepDefinition; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTable; -import io.cucumber.datatable.DataTable; -import io.cucumber.core.stepexpression.Argument; -import io.cucumber.core.stepexpression.TypeRegistry; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Map; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class JavaStepDefinitionTransposeTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); public static class StepDefs { - List> listOfListOfDoubles; - public Map mapOfDoubleToDouble; - - public DataTable dataTable; - private Map> mapOfDoubleToListOfDouble; - - - public void listOfListOfDoubles(List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } - - public void listOfListOfDoublesTransposed(@Transpose List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } - - public void plainDataTable(DataTable dataTable) { - this.dataTable = dataTable; - } - - public void transposedDataTable(@Transpose DataTable dataTable) { - this.dataTable = dataTable; - } public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { - this.mapOfDoubleToDouble = mapOfDoubleToDouble; + } public void transposedMapOfDoubleToListOfDouble(@Transpose Map> mapOfDoubleToListOfDouble) { - this.mapOfDoubleToListOfDouble = mapOfDoubleToListOfDouble; } } @Test public void transforms_to_map_of_double_to_double() throws Throwable { Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals(Double.valueOf(999.0), stepDefs.mapOfDoubleToDouble.get(1000.0)); - assertEquals(Double.valueOf(-0.5), stepDefs.mapOfDoubleToDouble.get(0.5)); - assertEquals(Double.valueOf(99.5), stepDefs.mapOfDoubleToDouble.get(100.5)); + assertFalse(isTransposed(m)); } @Test public void transforms_transposed_to_map_of_double_to_double() throws Throwable { Method m = StepDefs.class.getMethod("transposedMapOfDoubleToListOfDouble", Map.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals(asList(0.5, 1000.0), stepDefs.mapOfDoubleToListOfDouble.get(100.5)); - } - - @Test - public void transforms_to_list_of_single_values() throws Throwable { - Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); - } - - @Test - public void transforms_to_list_of_single_values_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("listOfListOfDoublesTransposed", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDoublesWithoutHeader())); - assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); + assertTrue(isTransposed(m)); } - @Test - public void passes_plain_data_table() throws Throwable { - Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals("Birth Date", stepDefs.dataTable.cell(0, 0)); - assertEquals("1957-05-10", stepDefs.dataTable.cell(1, 0)); - } - - @Test - public void passes_transposed_data_table() throws Throwable { - Method m = StepDefs.class.getMethod("transposedDataTable", DataTable.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals("Birth Date", stepDefs.dataTable.cell(0, 0)); - assertEquals("1957-05-10", stepDefs.dataTable.cell(0, 1)); - } - - private StepDefs runStepDef(Method method, PickleTable table) throws Throwable { + private boolean isTransposed(Method method) { StepDefs stepDefs = new StepDefs(); Lookup lookup = new SingletonFactory(stepDefs); + StepDefinition stepDefinition = new JavaStepDefinition(method, "some text", 0, lookup); - StepDefinition stepDefinition = new JavaStepDefinition(method, "some text", 0, lookup, typeRegistry); - PickleStep stepWithTable = new PickleStep("some text", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); - List arguments = stepDefinition.matchedArguments(stepWithTable); - - List result = new ArrayList<>(); - for (Argument argument : arguments) { - result.add(argument.getValue()); - } - stepDefinition.execute(result.toArray(new Object[0])); - - return stepDefs; - } - - private List listOfDatesWithHeader() { - List rows = new ArrayList<>(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10")))); - return rows; - } - - private List listOfDoublesWithoutHeader() { - List rows = new ArrayList<>(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "99.5")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "-0.5")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1000"), new PickleCell(mock(PickleLocation.class), "999")))); - return rows; - } - - private List transposedListOfDoublesWithoutHeader() { - List rows = new ArrayList<>(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "1000")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "99.5"), new PickleCell(mock(PickleLocation.class), "-0.5"), new PickleCell(mock(PickleLocation.class), "999")))); - return rows; + return stepDefinition.parameterInfos().get(0).isTransposed(); } - } diff --git a/java8/src/main/groovy/lambda.java.gsp b/java8/src/main/groovy/lambda.java.gsp index 74c2997703..f0e5313b90 100644 --- a/java8/src/main/groovy/lambda.java.gsp +++ b/java8/src/main/groovy/lambda.java.gsp @@ -46,9 +46,7 @@ public interface ${className} extends LambdaGlue { * @param body a lambda expression with no parameters */ default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, A0.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, A0.class, body)); } /** @@ -68,9 +66,7 @@ public interface ${className} extends LambdaGlue { */ @Deprecated default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, timeoutMillis, A0.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, timeoutMillis, A0.class, body)); } <% (1..9).each { arity -> @@ -85,9 +81,7 @@ public interface ${className} extends LambdaGlue { * @param type of argument ${i} <% } %> */ default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, A${arity}.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, A${arity}.class, body)); } /** @@ -109,9 +103,7 @@ public interface ${className} extends LambdaGlue { */ @Deprecated default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> - Java8StepDefinition.create(expression, timeoutMillis, A${arity}.class, body, typeRegistry) - ); + LambdaGlueRegistry.INSTANCE.get().addStepDefinition(Java8StepDefinition.create(expression, timeoutMillis, A${arity}.class, body)); } <% } %> diff --git a/java8/src/main/java/io/cucumber/java8/Java8Backend.java b/java8/src/main/java/io/cucumber/java8/Java8Backend.java index ff0d6ee6cc..2e43c35baf 100644 --- a/java8/src/main/java/io/cucumber/java8/Java8Backend.java +++ b/java8/src/main/java/io/cucumber/java8/Java8Backend.java @@ -72,8 +72,8 @@ public Snippet getSnippet() { } @Override - public void addStepDefinition(Function stepDefinitionFunction) { - glue.addStepDefinition(stepDefinitionFunction); + public void addStepDefinition(StepDefinition stepDefinition) { + glue.addStepDefinition(stepDefinition); } @Override diff --git a/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java b/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java new file mode 100644 index 0000000000..2a9607b31b --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/Java8ParameterInfo.java @@ -0,0 +1,35 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.ParameterInfo; +import io.cucumber.core.backend.TypeResolver; + +import java.lang.reflect.Type; + +final class Java8ParameterInfo implements ParameterInfo { + private final Type type; + private final TypeResolver typeResolver; + + Java8ParameterInfo(Type type, TypeResolver typeResolver) { + this.type = type; + this.typeResolver = typeResolver; + } + + public Type getType() { + return type; + } + + @Override + public boolean isTransposed() { + return false; + } + + @Override + public TypeResolver getTypeResolver() { + return typeResolver; + } + + @Override + public String toString() { + return type.toString(); + } +} diff --git a/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java b/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java index c877ec1cbe..5011a5dfa5 100644 --- a/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java +++ b/java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java @@ -1,38 +1,28 @@ package io.cucumber.java8; -import gherkin.pickles.PickleStep; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.exception.CucumberException; -import io.cucumber.core.runtime.Invoker; import io.cucumber.core.runner.ScenarioScoped; -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 net.jodah.typetools.TypeResolver.Unknown; +import io.cucumber.core.runtime.Invoker; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import static io.cucumber.java8.Java8StepDefinition.ParameterInfo.fromTypes; import static java.lang.String.format; import static net.jodah.typetools.TypeResolver.resolveRawArguments; final class Java8StepDefinition implements StepDefinition, ScenarioScoped { public static Java8StepDefinition create( - String expression, Class bodyClass, T body, TypeRegistry typeRegistry) { - return new Java8StepDefinition(expression, 0, bodyClass, body, typeRegistry); + String expression, Class bodyClass, T body) { + return new Java8StepDefinition(expression, 0, bodyClass, body); } public static StepDefinition create( - String expression, long timeoutMillis, Class bodyClass, T body, TypeRegistry typeRegistry) { - return new Java8StepDefinition(expression, timeoutMillis, bodyClass, body, typeRegistry); + String expression, long timeoutMillis, Class bodyClass, T body) { + return new Java8StepDefinition(expression, timeoutMillis, bodyClass, body); } private final long timeoutMillis; @@ -40,31 +30,18 @@ public static StepDefinition create( private final StackTraceElement location; private final Method method; private final List parameterInfos; - private final StepExpression expression; - private final ArgumentMatcher argumentMatcher; + private final String expression; private Java8StepDefinition(String expression, long timeoutMillis, Class bodyClass, - T body, - TypeRegistry typeRegistry) { + T body) { this.timeoutMillis = timeoutMillis; this.body = body; - - this.location = new Exception().getStackTrace()[5]; + this.location = new Exception().getStackTrace()[3]; this.method = getAcceptMethod(body.getClass()); - this.parameterInfos = fromTypes(resolveRawArguments(bodyClass, body.getClass())); - this.expression = createExpression(expression, typeRegistry); - this.argumentMatcher = new ArgumentMatcher(this.expression); - } - - private StepExpression createExpression(String expression, TypeRegistry typeRegistry) { - if (parameterInfos.isEmpty()) { - return new StepExpressionFactory(typeRegistry).createExpression(expression); - } else { - ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); - return new StepExpressionFactory(typeRegistry).createExpression(expression, new LambdaTypeResolver(parameterInfo)); - } + this.expression = expression; + this.parameterInfos = fromTypes(expression, location, resolveRawArguments(bodyClass, body.getClass())); } private Method getAcceptMethod(Class bodyClass) { @@ -81,30 +58,12 @@ private Method getAcceptMethod(Class bodyClass) { return acceptMethods.get(0); } - private CucumberException withLocation(CucumberException exception) { - exception.setStackTrace(new StackTraceElement[]{this.location}); - return exception; - } - - @Override - public List matchedArguments(PickleStep step) { - Type[] types = new Type[parameterInfos.size()]; - for (int i = 0; i < types.length; i++) { - types[i] = parameterInfos.get(i).getType(); - } - return argumentMatcher.argumentsFrom(step, types); - } @Override public String getLocation(boolean detail) { return location.getFileName() + ":" + location.getLineNumber(); } - @Override - public Integer getParameterCount() { - return parameterInfos.size(); - } - @Override public void execute(final Object[] args) throws Throwable { Invoker.invoke(body, method, timeoutMillis, args); @@ -116,73 +75,28 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { } @Override - public String getPattern() { - return expression.getSource(); + public List parameterInfos() { + return parameterInfos; } - private final class LambdaTypeResolver implements TypeResolver { - - - private final ParameterInfo parameterInfo; - - LambdaTypeResolver(ParameterInfo parameterInfo) { - this.parameterInfo = parameterInfo; - } - - @Override - public Type resolve() { - Type type = parameterInfo.getType(); - if (Unknown.class.equals(type)) { - return Object.class; - } - - return requireNonMapOrListType(type); - } - - private Type requireNonMapOrListType(Type argumentType) { - if (argumentType instanceof Class) { - Class argumentClass = (Class) argumentType; - if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { - throw withLocation( - new CucumberException( - format("Can't use %s in lambda step definition \"%s\". " + - "Declare a DataTable argument instead and convert " + - "manually with asList/asLists/asMap/asMaps", - argumentClass.getName(), expression.getSource()))); - } - } - return argumentType; - } + @Override + public String getPattern() { + return expression; } + @Override public void disposeScenarioScope() { this.body = null; } - static class ParameterInfo { - private final Type type; - - static List fromTypes(Type[] genericParameterTypes) { - List result = new ArrayList<>(); - for (Type genericParameterType : genericParameterTypes) { - result.add(new ParameterInfo(genericParameterType)); - } - return result; - } - - private ParameterInfo(Type type) { - this.type = type; - } - - Type getType() { - return type; + private static List fromTypes(String expression, StackTraceElement location, Type[] genericParameterTypes) { + List result = new ArrayList<>(); + for (Type type : genericParameterTypes) { + LambdaTypeResolver typeResolver = new LambdaTypeResolver(type, expression, location); + result.add(new Java8ParameterInfo(type, typeResolver)); } - - @Override - public String toString() { - return type.toString(); - } - + return result; } + } diff --git a/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java b/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java index 960997e02f..121a7e5e89 100644 --- a/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java +++ b/java8/src/main/java/io/cucumber/java8/LambdaGlueRegistry.java @@ -1,15 +1,12 @@ package io.cucumber.java8; -import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.StepDefinition; -import java.util.function.Function; - interface LambdaGlueRegistry { ThreadLocal INSTANCE = new ThreadLocal<>(); - void addStepDefinition(Function stepDefinition); + void addStepDefinition(StepDefinition stepDefinition); void addBeforeStepHookDefinition(HookDefinition beforeStepHook); diff --git a/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java b/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java new file mode 100644 index 0000000000..4af00cab52 --- /dev/null +++ b/java8/src/main/java/io/cucumber/java8/LambdaTypeResolver.java @@ -0,0 +1,51 @@ +package io.cucumber.java8; + +import io.cucumber.core.backend.TypeResolver; +import io.cucumber.core.exception.CucumberException; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; + +final class LambdaTypeResolver implements TypeResolver { + private final Type type; + private final String expression; + private final StackTraceElement location; + + LambdaTypeResolver(Type type, String expression, StackTraceElement location) { + this.type = type; + this.expression = expression; + this.location = location; + } + + @Override + public Type resolve() { + if (net.jodah.typetools.TypeResolver.Unknown.class.equals(type)) { + return Object.class; + } + return requireNonMapOrListType(type); + } + + private Type requireNonMapOrListType(Type argumentType) { + if (argumentType instanceof Class) { + Class argumentClass = (Class) argumentType; + if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { + throw withLocation( + new CucumberException( + format("Can't use %s in lambda step definition \"%s\". " + + "Declare a DataTable argument instead and convert " + + "manually with asList/asLists/asMap/asMaps", + argumentClass.getName(), expression))); + } + } + return argumentType; + } + + private CucumberException withLocation(CucumberException exception) { + exception.setStackTrace(new StackTraceElement[]{location}); + return exception; + } + +} diff --git a/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java index ca31a61f23..f4ec246e76 100644 --- a/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java +++ b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java @@ -1,28 +1,19 @@ package io.cucumber.java8; -import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.stepexpression.TypeRegistry; - -import java.util.function.Function; - import static org.junit.Assert.assertEquals; public class AnonInnerClassStepdefs implements LambdaGlue { public AnonInnerClassStepdefs() { - Java8Backend.INSTANCE.get().addStepDefinition(new Function() { - @Override - public StepDefinition apply(TypeRegistry typeRegistry) { - return Java8StepDefinition.create( - "I have {int} java7 beans in my {word}", StepdefBody.A2.class, - new StepdefBody.A2() { - @Override - public void accept(Integer cukes, String what) throws Throwable { - assertEquals(42, cukes.intValue()); - assertEquals("belly", what); - } - }, typeRegistry); - } - }); + Java8Backend.INSTANCE.get().addStepDefinition( + Java8StepDefinition.create( + "I have {int} java7 beans in my {word}", StepdefBody.A2.class, + new StepdefBody.A2() { + @Override + public void accept(Integer cukes, String what) throws Throwable { + assertEquals(42, cukes.intValue()); + assertEquals("belly", what); + } + })); } } diff --git a/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java b/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java index 6979183dcc..26ccb14129 100644 --- a/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8AnonInnerClassStepDefinitionTest.java @@ -1,27 +1,23 @@ package io.cucumber.java8; -import static org.junit.Assert.assertEquals; - -import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Test; import java.util.List; -import java.util.Locale; -public class Java8AnonInnerClassStepDefinitionTest { +import static org.junit.Assert.assertEquals; - private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); +public class Java8AnonInnerClassStepDefinitionTest { @Test public void should_calculate_parameters_count_from_body_with_one_param() { - Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, oneParamStep(), typeRegistry); - assertEquals(Integer.valueOf(1), java8StepDefinition.getParameterCount()); + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, oneParamStep()); + assertEquals(1, java8StepDefinition.parameterInfos().size()); } @Test public void should_calculate_parameters_count_from_body_with_two_params() { - Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, twoParamStep(), typeRegistry); - assertEquals(Integer.valueOf(2), java8StepDefinition.getParameterCount()); + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, twoParamStep()); + assertEquals(2, java8StepDefinition.parameterInfos().size()); } private StepdefBody.A1 oneParamStep() { diff --git a/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java index c45a871f9c..39263936c5 100644 --- a/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java @@ -1,18 +1,12 @@ package io.cucumber.java8; -import io.cucumber.core.stepexpression.TypeRegistry; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.java8.En; import org.hamcrest.CustomTypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import java.util.function.Function; - -import static java.util.Locale.ENGLISH; - public class Java8LambdaStepDefinitionMarksCorrectStackElementTest { @Rule @@ -48,8 +42,8 @@ private class MyLambdaGlueRegistry implements LambdaGlueRegistry { private StepDefinition stepDefinition; @Override - public void addStepDefinition(Function stepDefinitionFunction) { - stepDefinition = stepDefinitionFunction.apply(new TypeRegistry(ENGLISH)); + public void addStepDefinition(StepDefinition stepDefinition) { + this.stepDefinition = stepDefinition; } @Override diff --git a/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java index ad1bd70c9f..ea62a54e0a 100644 --- a/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionTest.java @@ -1,83 +1,60 @@ package io.cucumber.java8; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleString; -import gherkin.pickles.PickleTable; -import io.cucumber.core.exception.CucumberException; -import io.cucumber.core.stepexpression.Argument; -import io.cucumber.core.stepexpression.TypeRegistry; -import io.cucumber.datatable.DataTable; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.util.List; -import java.util.Locale; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; public class Java8LambdaStepDefinitionTest { - private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + @Rule + public ExpectedException expectedException = ExpectedException.none(); @Test public void should_calculate_parameters_count_from_body_with_one_param() { StepdefBody.A1 body = p1 -> { }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - assertEquals(Integer.valueOf(1), def.getParameterCount()); + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + assertEquals(1, stepDefinition.parameterInfos().size()); } @Test public void should_calculate_parameters_count_from_body_with_two_params() { StepdefBody.A2 body = (p1, p2) -> { }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, body, typeRegistry); - assertEquals(Integer.valueOf(2), def.getParameterCount()); + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A2.class, body); + assertEquals(2, stepDefinition.parameterInfos().size()); } @Test - public void should_apply_identity_transform_to_doc_string_when_target_type_is_object() { + public void should_resolve_type_to_object() { StepdefBody.A1 body = (p1) -> { }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - PickleString pickleString = new PickleString(null, "content", "text"); - List arguments = def.matchedArguments(new PickleStep("I have some step", singletonList(pickleString), emptyList())); - assertEquals("content", arguments.get(0).getValue()); - } + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); - @Test - public void should_apply_identity_transform_to_data_table_when_target_type_is_object() { - StepdefBody.A1 body = (p1) -> { - }; - Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(null, "content"))))); - List arguments = def.matchedArguments(new PickleStep("I have some step", singletonList(table), emptyList())); - assertEquals(DataTable.create(singletonList(singletonList("content"))), arguments.get(0).getValue()); + assertEquals(Object.class, stepDefinition.parameterInfos().get(0).getType()); } - @Test public void should_fail_for_param_with_non_generic_list() { - try { - StepdefBody.A1 body = p1 -> { - }; - Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } + expectedException.expectMessage("Can't use java.util.List in lambda step definition \"some step\". Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps"); + + StepdefBody.A1 body = p1 -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + stepDefinition.parameterInfos().get(0).getTypeResolver().resolve(); } @Test public void should_fail_for_param_with_generic_list() { - try { - StepdefBody.A1> body = p1 -> { - }; - Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } + expectedException.expectMessage("Can't use java.util.List in lambda step definition \"some step\". Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps"); + + StepdefBody.A1> body = p1 -> { + }; + Java8StepDefinition stepDefinition = Java8StepDefinition.create("some step", StepdefBody.A1.class, body); + stepDefinition.parameterInfos().get(0).getTypeResolver().resolve(); } } diff --git a/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java b/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java index 2070dc0313..a8e3996463 100644 --- a/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java +++ b/testng/src/test/java/io/cucumber/testng/StubBackendProviderService.java @@ -1,16 +1,15 @@ package io.cucumber.testng; -import gherkin.pickles.PickleStep; import io.cucumber.core.backend.Backend; import io.cucumber.core.backend.BackendProviderService; import io.cucumber.core.backend.Container; import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.snippets.Snippet; -import io.cucumber.core.stepexpression.Argument; import java.lang.reflect.Type; import java.net.URI; @@ -36,34 +35,25 @@ private static class StubBackend implements Backend { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addStepDefinition(t -> createStepDefinition("background step")); - glue.addStepDefinition(t -> createStepDefinition("scenario name")); - glue.addStepDefinition(t -> createStepDefinition("scenario C")); - glue.addStepDefinition(t -> createStepDefinition("scenario D")); - glue.addStepDefinition(t -> createStepDefinition("scenario E")); - glue.addStepDefinition(t -> createStepDefinition("first step")); - glue.addStepDefinition(t -> createStepDefinition("second step")); - glue.addStepDefinition(t -> createStepDefinition("third step")); + glue.addStepDefinition(createStepDefinition("background step")); + glue.addStepDefinition(createStepDefinition("scenario name")); + glue.addStepDefinition(createStepDefinition("scenario C")); + glue.addStepDefinition(createStepDefinition("scenario D")); + glue.addStepDefinition(createStepDefinition("scenario E")); + glue.addStepDefinition(createStepDefinition("first step")); + glue.addStepDefinition(createStepDefinition("second step")); + glue.addStepDefinition(createStepDefinition("third step")); } private StepDefinition createStepDefinition(final String pattern) { return new StepDefinition() { - @Override - public List matchedArguments(PickleStep step) { - return pattern.equals(step.getText()) ? Collections.emptyList() : null; - } @Override public String getLocation(boolean detail) { return null; } - @Override - public Integer getParameterCount() { - return 0; - } - @Override public void execute(Object[] args) { @@ -74,6 +64,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public List parameterInfos() { + return Collections.emptyList(); + } + @Override public String getPattern() { return pattern;