diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a8ac6ac0..470450e031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,10 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO - Use `io.cucumber.core.cli.Main` instead * [Core] Deprecate `cucumber.api.Scenario` - Use `io.cucumber.core.api.Scenario` instead + * [Java] Deprecate `cucumber.api.java.*` + - Use `io.cucumber.java.*` instead + * [Java] Deprecate `cucumber.api.java8.*` + - Use `io.cucumber.java8.*` instead * [JUnit] Deprecate `cucumber.api.junit.Cucumber` - Use `io.cucumber.junit.Cucumber` instead. * [TestNG] Deprecate `cucumber.api.testng.TestNGCucumberRunner` diff --git a/README.md b/README.md index c68299727b..dcbba32358 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ [![Coverage Status](https://coveralls.io/repos/github/cucumber/cucumber-jvm/badge.svg?branch=master)](https://coveralls.io/github/cucumber/cucumber-jvm?branch=master) Cucumber-JVM is a pure Java implementation of Cucumber. -You can [run](https://docs.cucumber.io/cucumber/api/#running-cucumber) it with +You can [run](https://cucumber.io/docs/cucumber/api/#running-cucumber) it with the tool of your choice. Cucumber-JVM also integrates with all the popular -[Dependency Injection containers](https://docs.cucumber.io/installation/java/#dependency-injection). +[Dependency Injection containers](https://cucumber.io/docs/installation/java/#dependency-injection). ## Getting started - * [Installation](https://docs.cucumber.io/installation/java/) + * [Installation](https://cucumber.io/docs/installation/java/) * [Documentation](https://cucumber.io/docs) * [Hello World project](https://github.com/cucumber/cucumber-java-skeleton) diff --git a/cdi2/pom.xml b/cdi2/pom.xml index e94d0edc8b..9bbd55ace2 100644 --- a/cdi2/pom.xml +++ b/cdi2/pom.xml @@ -1,6 +1,7 @@ 4.0.0 + io.cucumber.cdi2 2.0.10 diff --git a/core/pom.xml b/core/pom.xml index 9b33b7f007..eed65005d2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,6 +12,7 @@ Cucumber-JVM: Core + io.cucumber.core 1.12.1 1.6 diff --git a/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java b/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java index 8d965b4df1..86a968e4e6 100644 --- a/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java +++ b/core/src/main/java/io/cucumber/core/api/TypeRegistryConfigurer.java @@ -10,12 +10,15 @@ @API(status = API.Status.STABLE) public interface TypeRegistryConfigurer { /** - * @return The locale to use. + * @return The locale to use, or null when language from feature file should be used. */ - Locale locale(); + default Locale locale() { + return null; + } /** * Configures the type registry. + * * @param typeRegistry The new type registry. */ void configureTypeRegistry(TypeRegistry typeRegistry); diff --git a/core/src/main/java/io/cucumber/core/backend/Backend.java b/core/src/main/java/io/cucumber/core/backend/Backend.java index 9a793f1c8d..d00ced1933 100644 --- a/core/src/main/java/io/cucumber/core/backend/Backend.java +++ b/core/src/main/java/io/cucumber/core/backend/Backend.java @@ -9,16 +9,18 @@ @API(status = API.Status.STABLE) public interface Backend { /** - * Invoked once before all features. This is where stepdefs and hooks should be loaded. - * - * @param glue Glue that provides the stepdefs to be executed. + * Invoked once before all features. This is where steps and hooks should be loaded. + * + * @param glue Glue that provides the steps to be executed. * @param gluePaths The locations for the glue to be loaded. */ void loadGlue(Glue glue, List gluePaths); /** * Invoked before a new scenario starts. Implementations should do any necessary - * setup of new, isolated state here. + * setup of new, isolated state here. Additional scenario scoped step definitions + * can be loaded here. These step definitions should implement + * {@link io.cucumber.core.runner.ScenarioScoped} */ void buildWorld(); diff --git a/core/src/main/java/io/cucumber/core/backend/Container.java b/core/src/main/java/io/cucumber/core/backend/Container.java index 684993387b..b8c68a8088 100644 --- a/core/src/main/java/io/cucumber/core/backend/Container.java +++ b/core/src/main/java/io/cucumber/core/backend/Container.java @@ -8,7 +8,7 @@ public interface Container { * Collects glue classes in the classpath. Called once on init. * * @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...) - * @return true if stepdefs and hooks in this class should be used, false if they should be ignored. + * @return true if steps and hook definitions in this class should be used, false if they should be ignored. */ boolean addClass(Class glueClass); } diff --git a/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java b/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java new file mode 100644 index 0000000000..40ff965f08 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DataTableTypeDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.DataTableType; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DataTableTypeDefinition { + + DataTableType dataTableType(); + + /** + * The source line where the data table type is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java new file mode 100644 index 0000000000..8c8ad6db91 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableCellTransformerDefinition.java @@ -0,0 +1,20 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.TableCellByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultDataTableCellTransformerDefinition { + + TableCellByTypeTransformer tableCellByTypeTransformer(); + + /** + * The source line where the default data table cell is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); + +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java new file mode 100644 index 0000000000..3db632f6f5 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultDataTableEntryTransformerDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.datatable.TableEntryByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultDataTableEntryTransformerDefinition { + + TableEntryByTypeTransformer tableEntryByTypeTransformer(); + + /** + * The source line where the default table entry transformer is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java b/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java new file mode 100644 index 0000000000..14c0103d29 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/DefaultParameterTransformerDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE) +public interface DefaultParameterTransformerDefinition { + + ParameterByTypeTransformer parameterByTypeTransformer(); + + /** + * The source line where the default parameter transformer is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/backend/DuplicateStepDefinitionException.java b/core/src/main/java/io/cucumber/core/backend/DuplicateStepDefinitionException.java deleted file mode 100644 index 4e9a5be781..0000000000 --- a/core/src/main/java/io/cucumber/core/backend/DuplicateStepDefinitionException.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.cucumber.core.backend; - -import io.cucumber.core.exception.CucumberException; -import org.apiguardian.api.API; - -@API(status = API.Status.STABLE) -public final class DuplicateStepDefinitionException extends CucumberException { - public DuplicateStepDefinitionException(StepDefinition a, StepDefinition b) { - super(createMessage(a, b)); - } - - private static String createMessage(StepDefinition a, StepDefinition b) { - return String.format("Duplicate step definitions in %s and %s", a.getLocation(true), b.getLocation(true)); - } -} 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 b47cafd635..5da600fd72 100644 --- a/core/src/main/java/io/cucumber/core/backend/Glue.java +++ b/core/src/main/java/io/cucumber/core/backend/Glue.java @@ -5,14 +5,24 @@ @API(status = API.Status.STABLE) public interface Glue { - void addStepDefinition(StepDefinition stepDefinition) throws DuplicateStepDefinitionException; + void addStepDefinition(StepDefinition stepDefinition); - void addBeforeHook(HookDefinition hookDefinition); + void addBeforeHook(HookDefinition beforeHook); - void addAfterHook(HookDefinition hookDefinition); + void addAfterHook(HookDefinition afterHook); void addBeforeStepHook(HookDefinition beforeStepHook); - void addAfterStepHook(HookDefinition hookDefinition); + void addAfterStepHook(HookDefinition afterStepHook); + + void addParameterType(ParameterTypeDefinition parameterTypeDefinition); + + void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition); + + void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer); + + void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer); + + void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer); } diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java new file mode 100644 index 0000000000..e0956cbcb4 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java @@ -0,0 +1,19 @@ +package io.cucumber.core.backend; + +import io.cucumber.cucumberexpressions.ParameterType; +import org.apiguardian.api.API; + +@API(status = API.Status.EXPERIMENTAL) +public interface ParameterTypeDefinition { + + ParameterType parameterType(); + + /** + * The source line where the parameter type is defined. + * Example: com/example/app/Cucumber.test():42 + * + * @param detail true if extra detailed location information should be included. + * @return The source line of the step definition. + */ + String getLocation(boolean detail); +} diff --git a/core/src/main/java/io/cucumber/core/event/StepDefinition.java b/core/src/main/java/io/cucumber/core/event/StepDefinition.java index b43e083565..3f6353a619 100644 --- a/core/src/main/java/io/cucumber/core/event/StepDefinition.java +++ b/core/src/main/java/io/cucumber/core/event/StepDefinition.java @@ -8,7 +8,7 @@ public interface StepDefinition { /** * The source line where the step definition is defined. - * Example: foo/bar/Zap.brainfuck:42 + * Example: com/example/app/Cucumber.test():42 * * @param detail true if extra detailed location information should be included. * @return The source line of the step definition. diff --git a/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java b/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java index dd31929d51..c1eea722ae 100644 --- a/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java +++ b/core/src/main/java/io/cucumber/core/reflection/MethodFormat.java @@ -13,7 +13,7 @@ */ public final class MethodFormat { private static final Pattern METHOD_PATTERN = Pattern.compile("((?:static\\s|public\\s)+)([^\\s]*)\\s\\.?(.*)\\.([^\\(]*)\\(([^\\)]*)\\)(?: throws )?(.*)"); - private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,]*\\."); + private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,<>]*\\."); private final MessageFormat format; public static final MethodFormat SHORT = new MethodFormat("%c.%m(%a)"); 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 0ccdc284a0..3db3379fa7 100644 --- a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java +++ b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java @@ -1,49 +1,59 @@ package io.cucumber.core.runner; import gherkin.pickles.PickleStep; -import io.cucumber.core.backend.DuplicateStepDefinitionException; +import io.cucumber.core.backend.DataTableTypeDefinition; +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.ParameterTypeDefinition; 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 io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; 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 List beforeHooks = new ArrayList<>(); - final List beforeStepHooks = new ArrayList<>(); - final List afterHooks = new ArrayList<>(); - final List afterStepHooks = new ArrayList<>(); + private static final Comparator ASCENDING = Comparator.comparing(HookDefinition::getOrder); + private static final Comparator DESCENDING = ASCENDING.reversed(); + + private final List parameterTypeDefinitions = new ArrayList<>(); + private final List dataTableTypeDefinitions = new ArrayList<>(); + private final List defaultParameterTransformers = new ArrayList<>(); + private final List defaultDataTableEntryTransformers = new ArrayList<>(); + private final List defaultDataTableCellTransformers = new ArrayList<>(); + + private final List beforeHooks = new ArrayList<>(); + private final List beforeStepHooks = new ArrayList<>(); + private final List stepDefinitions = new ArrayList<>(); + private final List afterStepHooks = new ArrayList<>(); + private final List afterHooks = new ArrayList<>(); + + /* + * Storing the pattern that matches the step text allows us to cache the rather slow + * regex comparisons in `stepDefinitionMatches`. + * This cache does not need to be cleaned. The matching pattern be will used to look + * up a pickle specific step definition from `stepDefinitionsByPattern`. + */ + private final Map stepPatternByStepText = new HashMap<>(); + private final Map stepDefinitionsByPattern = new TreeMap<>(); private final EventBus bus; - private final TypeRegistry typeRegistry; - CachingGlue(EventBus bus, TypeRegistry typeRegistry) { + + CachingGlue(EventBus bus) { this.bus = bus; - this.typeRegistry = typeRegistry; } @Override public void addStepDefinition(StepDefinition stepDefinition) { - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); - CoreStepDefinition previous = stepDefinitionsByPattern.get(coreStepDefinition.getPattern()); - if (previous != null) { - throw new DuplicateStepDefinitionException(previous.getStepDefinition(), stepDefinition); - } - stepDefinitionsByPattern.put(stepDefinition.getPattern(), coreStepDefinition); - bus.send(new StepDefinedEvent(bus.getInstant(), stepDefinition)); + stepDefinitions.add(stepDefinition); } @Override @@ -70,35 +80,148 @@ public void addAfterStepHook(HookDefinition hookDefinition) { afterStepHooks.sort(DESCENDING); } - List getBeforeHooks() { - return new ArrayList<>(beforeHooks); + @Override + public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) { + parameterTypeDefinitions.add(parameterTypeDefinition); + } + + @Override + public void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition) { + dataTableTypeDefinitions.add(dataTableTypeDefinition); + } + + @Override + public void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer) { + defaultParameterTransformers.add(defaultParameterTransformer); + } + + @Override + public void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer) { + defaultDataTableEntryTransformers.add(defaultDataTableEntryTransformer); + } + + @Override + public void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer) { + defaultDataTableCellTransformers.add(defaultDataTableCellTransformer); + } + + Collection getBeforeHooks() { + return beforeHooks; + } + + Collection getBeforeStepHooks() { + return beforeStepHooks; + } + + Collection getAfterHooks() { + return afterHooks; + } + + Collection getAfterStepHooks() { + return afterStepHooks; + } + + Collection getParameterTypeDefinitions() { + return parameterTypeDefinitions; + } + + Collection getDataTableTypeDefinitions() { + return dataTableTypeDefinitions; + } + + Collection getStepDefinitions() { + return stepDefinitions; + } + + Map getStepPatternByStepText() { + return stepPatternByStepText; + } + + Map getStepDefinitionsByPattern() { + return stepDefinitionsByPattern; + } + + Collection getDefaultParameterTransformers() { + return defaultParameterTransformers; } - List getBeforeStepHooks() { - return new ArrayList<>(beforeStepHooks); + Collection getDefaultDataTableEntryTransformers() { + return defaultDataTableEntryTransformers; } - List getAfterHooks() { - return new ArrayList<>(afterHooks); + Collection getDefaultDataTableCellTransformers() { + return defaultDataTableCellTransformers; } - List getAfterStepHooks() { - return new ArrayList<>(afterStepHooks); + void prepareGlue(TypeRegistry typeRegistry) throws DuplicateStepDefinitionException { + parameterTypeDefinitions.forEach(ptd -> typeRegistry.defineParameterType(ptd.parameterType())); + dataTableTypeDefinitions.forEach(dtd -> typeRegistry.defineDataTableType(dtd.dataTableType())); + + if (defaultParameterTransformers.size() == 1) { + DefaultParameterTransformerDefinition definition = defaultParameterTransformers.get(0); + ParameterByTypeTransformer transformer = definition.parameterByTypeTransformer(); + typeRegistry.setDefaultParameterTransformer(transformer); + } else if (defaultParameterTransformers.size() > 1) { + throw new DuplicateDefaultParameterTransformers(defaultParameterTransformers); + } + + if (defaultDataTableEntryTransformers.size() == 1) { + DefaultDataTableEntryTransformerDefinition definition = defaultDataTableEntryTransformers.get(0); + TableEntryByTypeTransformer transformer = definition.tableEntryByTypeTransformer(); + typeRegistry.setDefaultDataTableEntryTransformer(transformer); + } else if (defaultDataTableEntryTransformers.size() > 1) { + throw new DuplicateDefaultDataTableEntryTransformers(defaultDataTableEntryTransformers); + } + + + if (defaultDataTableCellTransformers.size() == 1) { + DefaultDataTableCellTransformerDefinition definition = defaultDataTableCellTransformers.get(0); + TableCellByTypeTransformer transformer = definition.tableCellByTypeTransformer(); + typeRegistry.setDefaultDataTableCellTransformer(transformer); + } else if (defaultDataTableCellTransformers.size() > 1) { + throw new DuplicateDefaultDataTableCellTransformers(defaultDataTableCellTransformers); + } + + stepDefinitions.forEach(stepDefinition -> { + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, typeRegistry); + CoreStepDefinition previous = stepDefinitionsByPattern.get(stepDefinition.getPattern()); + if (previous != null) { + throw new DuplicateStepDefinitionException(previous.getStepDefinition(), stepDefinition); + } + stepDefinitionsByPattern.put(coreStepDefinition.getPattern(), coreStepDefinition); + bus.send(new StepDefinedEvent(bus.getInstant(), stepDefinition)); + }); } PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep step) { - String stepText = step.getText(); - - 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 - // the step text. As such the step definition arguments can not be cached and - // must be recreated each time. - List arguments = stepDefinition.matchedArguments(step); - return new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step); + PickleStepDefinitionMatch cachedMatch = cachedStepDefinitionMatch(featurePath, step); + if (cachedMatch != null) { + return cachedMatch; + } + return findStepDefinitionMatch(featurePath, step); + } + + + private PickleStepDefinitionMatch cachedStepDefinitionMatch(String featurePath, PickleStep step) { + String stepDefinitionPattern = stepPatternByStepText.get(step.getText()); + if (stepDefinitionPattern == null) { + return null; + } + + CoreStepDefinition coreStepDefinition = stepDefinitionsByPattern.get(stepDefinitionPattern); + if (coreStepDefinition == null) { + return 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 + // the step text. As such the step definition arguments can not be cached and + // must be recreated each time. + List arguments = coreStepDefinition.matchedArguments(step); + return new PickleStepDefinitionMatch(arguments, coreStepDefinition.getStepDefinition(), featurePath, step); + } + + private PickleStepDefinitionMatch findStepDefinitionMatch(String featurePath, PickleStep step) { List matches = stepDefinitionMatches(featurePath, step); if (matches.isEmpty()) { return null; @@ -109,53 +232,46 @@ PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep ste PickleStepDefinitionMatch match = matches.get(0); - stepDefinitionsByStepText.put(stepText, (CoreStepDefinition) match.getStepDefinition()); + stepPatternByStepText.put(step.getText(), match.getPattern()); return match; } private List stepDefinitionMatches(String featurePath, PickleStep step) { - List result = new ArrayList(); - for (CoreStepDefinition stepDefinition : stepDefinitionsByPattern.values()) { - List arguments = stepDefinition.matchedArguments(step); + List result = new ArrayList<>(); + for (CoreStepDefinition coreStepDefinition : stepDefinitionsByPattern.values()) { + List arguments = coreStepDefinition.matchedArguments(step); if (arguments != null) { - result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step)); + result.add(new PickleStepDefinitionMatch(arguments, coreStepDefinition.getStepDefinition(), featurePath, step)); } } return result; } void removeScenarioScopedGlue() { - removeScenarioScopedHooks(beforeHooks); - removeScenarioScopedHooks(beforeStepHooks); - removeScenarioScopedHooks(afterHooks); - removeScenarioScopedHooks(afterStepHooks); - removeScenariosScopedStepDefinitions(stepDefinitionsByPattern); - removeScenariosScopedStepDefinitions(stepDefinitionsByStepText); - } - - private void removeScenarioScopedHooks(List beforeHooks) { - Iterator hookIterator = beforeHooks.iterator(); - while (hookIterator.hasNext()) { - HookDefinition hook = hookIterator.next(); - if (hook instanceof ScenarioScoped) { - ScenarioScoped scenarioScopedHookDefinition = (ScenarioScoped) hook; - scenarioScopedHookDefinition.disposeScenarioScope(); - hookIterator.remove(); - } - } + stepDefinitionsByPattern.clear(); + removeScenarioScopedGlue(beforeHooks); + removeScenarioScopedGlue(beforeStepHooks); + removeScenarioScopedGlue(afterHooks); + removeScenarioScopedGlue(afterStepHooks); + removeScenarioScopedGlue(stepDefinitions); + removeScenarioScopedGlue(dataTableTypeDefinitions); + removeScenarioScopedGlue(parameterTypeDefinitions); + removeScenarioScopedGlue(defaultParameterTransformers); + removeScenarioScopedGlue(defaultDataTableEntryTransformers); + removeScenarioScopedGlue(defaultDataTableCellTransformers); } - 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(); - stepDefinitionIterator.remove(); + private void removeScenarioScopedGlue(Iterable glues) { + Iterator glueIterator = glues.iterator(); + while (glueIterator.hasNext()) { + Object glue = glueIterator.next(); + if (glue instanceof ScenarioScoped) { + ScenarioScoped scenarioScopedHookDefinition = (ScenarioScoped) glue; + scenarioScopedHookDefinition.disposeScenarioScope(); + glueIterator.remove(); } } } + } diff --git a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java index ca5fc5bb93..bbf85dac29 100644 --- a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java +++ b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java @@ -15,7 +15,7 @@ import static java.util.Objects.requireNonNull; -class CoreStepDefinition implements StepDefinition { +class CoreStepDefinition { private final StepExpression expression; private final ArgumentMatcher argumentMatcher; @@ -41,26 +41,6 @@ private StepExpression createExpression(List parameterInfos, Stri } } - @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(); } diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java new file mode 100644 index 0000000000..26446a51e5 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableCellTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultDataTableCellTransformers extends CucumberException { + DuplicateDefaultDataTableCellTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default table cell transformers. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java new file mode 100644 index 0000000000..16ed120bee --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultDataTableEntryTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultDataTableEntryTransformers extends CucumberException { + DuplicateDefaultDataTableEntryTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default data table entry. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java new file mode 100644 index 0000000000..b52f1313f7 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateDefaultParameterTransformers.java @@ -0,0 +1,20 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; +import io.cucumber.core.exception.CucumberException; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +class DuplicateDefaultParameterTransformers extends CucumberException { + DuplicateDefaultParameterTransformers(List definitions) { + super(createMessage(definitions)); + } + + private static String createMessage(List definitions) { + return "There may not be more then one default parameter transformer. Found:" + definitions.stream() + .map(d -> d.getLocation(true)) + .collect(joining("\n - ", "\n - ", "\n")); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java b/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java new file mode 100644 index 0000000000..bde5758872 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/DuplicateStepDefinitionException.java @@ -0,0 +1,14 @@ +package io.cucumber.core.runner; + +import io.cucumber.core.backend.StepDefinition; +import io.cucumber.core.exception.CucumberException; + +final class DuplicateStepDefinitionException extends CucumberException { + DuplicateStepDefinitionException(StepDefinition a, StepDefinition b) { + super(createMessage(a, b)); + } + + private static String createMessage(StepDefinition a, StepDefinition b) { + return "Duplicate step definitions in " + a.getLocation(true) + " and " + b.getLocation(true); + } +} diff --git a/core/src/main/java/io/cucumber/core/runner/HookComparator.java b/core/src/main/java/io/cucumber/core/runner/HookComparator.java deleted file mode 100644 index 735b716918..0000000000 --- a/core/src/main/java/io/cucumber/core/runner/HookComparator.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.cucumber.core.runner; - -import io.cucumber.core.backend.HookDefinition; - -import java.util.Comparator; - -final class HookComparator implements Comparator { - private final boolean ascending; - - HookComparator(boolean ascending) { - this.ascending = ascending; - } - - @Override - public int compare(HookDefinition hook1, HookDefinition hook2) { - int x = hook1.getOrder(); - int y = hook2.getOrder(); - return ascending ? Integer.compare(x, y) : Integer.compare(y, x); - } -} diff --git a/core/src/main/java/io/cucumber/core/runner/Runner.java b/core/src/main/java/io/cucumber/core/runner/Runner.java index 79562fef48..e95d01f647 100644 --- a/core/src/main/java/io/cucumber/core/runner/Runner.java +++ b/core/src/main/java/io/cucumber/core/runner/Runner.java @@ -1,13 +1,14 @@ package io.cucumber.core.runner; -import io.cucumber.core.event.HookType; -import io.cucumber.core.event.SnippetsSuggestedEvent; import gherkin.events.PickleEvent; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; +import io.cucumber.core.api.TypeRegistryConfigurer; import io.cucumber.core.backend.Backend; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.event.HookType; +import io.cucumber.core.event.SnippetsSuggestedEvent; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; @@ -18,9 +19,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; -import static java.util.stream.Collectors.joining; +import static java.util.Collections.emptyList; public final class Runner { @@ -31,20 +33,18 @@ public final class Runner { private final Collection backends; private final Options runnerOptions; private final ObjectFactory objectFactory; - private final List snippetGenerators; + private final TypeRegistryConfigurer typeRegistryConfigurer; + private List snippetGenerators; - public Runner(EventBus bus, Collection backends, ObjectFactory objectFactory, TypeRegistry typeRegistry, Options runnerOptions) { + public Runner(EventBus bus, Collection backends, ObjectFactory objectFactory, TypeRegistryConfigurer typeRegistryConfigurer, Options runnerOptions) { this.bus = bus; - this.glue = new CachingGlue(bus, typeRegistry); this.runnerOptions = runnerOptions; this.backends = backends; - this.snippetGenerators = backends.stream() - .map(Backend::getSnippet) - .map(s -> new SnippetGenerator(s, typeRegistry.parameterTypeRegistry())) - .collect(Collectors.toList()); + this.glue = new CachingGlue(bus); this.objectFactory = objectFactory; + this.typeRegistryConfigurer = typeRegistryConfigurer; List gluePaths = runnerOptions.getGlue(); - log.debug("Loading glue from " + gluePaths.stream().map(URI::toString).collect(joining(", "))); + log.debug("Loading glue from " + gluePaths); for (Backend backend : backends) { log.debug("Loading glue for backend " + backend.getClass().getName()); backend.loadGlue(this.glue, gluePaths); @@ -56,25 +56,53 @@ public EventBus getBus() { } public void runPickle(PickleEvent pickle) { - buildBackendWorlds(); // Java8 step definitions will be added to the glue here - TestCase testCase = createTestCaseForPickle(pickle); - testCase.run(bus); - disposeBackendWorlds(); + try { + TypeRegistry typeRegistry = createTypeRegistryForPickle(pickle); + snippetGenerators = createSnippetGeneratorsForPickle(typeRegistry); + + buildBackendWorlds(); // Java8 step definitions will be added to the glue here + + glue.prepareGlue(typeRegistry); + + TestCase testCase = createTestCaseForPickle(pickle); + testCase.run(bus); + } finally { + glue.removeScenarioScopedGlue(); + disposeBackendWorlds(); + } + } + + private List createSnippetGeneratorsForPickle(TypeRegistry typeRegistry) { + return backends.stream() + .map(Backend::getSnippet) + .map(s -> new SnippetGenerator(s, typeRegistry.parameterTypeRegistry())) + .collect(Collectors.toList()); + } + + private TypeRegistry createTypeRegistryForPickle(PickleEvent pickle) { + Locale locale = typeRegistryConfigurer.locale(); + if(locale == null){ + locale = new Locale(pickle.pickle.getLanguage()); + } + TypeRegistry typeRegistry = new TypeRegistry(locale); + typeRegistryConfigurer.configureTypeRegistry(typeRegistry); + return typeRegistry; } private TestCase createTestCaseForPickle(PickleEvent pickleEvent) { - List testSteps = new ArrayList<>(); - List beforeHooks = new ArrayList<>(); - List afterHooks = new ArrayList<>(); - if (!pickleEvent.pickle.getSteps().isEmpty()) { - addTestStepsForBeforeHooks(beforeHooks, pickleEvent.pickle.getTags()); - addTestStepsForPickleSteps(testSteps, pickleEvent); - addTestStepsForAfterHooks(afterHooks, pickleEvent.pickle.getTags()); + if (pickleEvent.pickle.getSteps().isEmpty()) { + return new TestCase(emptyList(), emptyList(), emptyList(), pickleEvent, runnerOptions.isDryRun()); } + + List testSteps = createTestStepsForPickleSteps(pickleEvent); + List beforeHooks = createTestStepsForBeforeHooks(pickleEvent.pickle.getTags()); + List afterHooks = createTestStepsForAfterHooks(pickleEvent.pickle.getTags()); return new TestCase(testSteps, beforeHooks, afterHooks, pickleEvent, runnerOptions.isDryRun()); } - private void addTestStepsForPickleSteps(List testSteps, PickleEvent pickleEvent) { + private List createTestStepsForPickleSteps(PickleEvent pickleEvent) { + List testSteps = new ArrayList<>(); + for (PickleStep step : pickleEvent.pickle.getSteps()) { PickleStepDefinitionMatch match; try { @@ -97,10 +125,12 @@ private void addTestStepsForPickleSteps(List testSteps, Pick } - List afterStepHookSteps = getAfterStepHooks(pickleEvent.pickle.getTags()); - List beforeStepHookSteps = getBeforeStepHooks(pickleEvent.pickle.getTags()); + List afterStepHookSteps = createAfterStepHooks(pickleEvent.pickle.getTags()); + List beforeStepHookSteps = createBeforeStepHooks(pickleEvent.pickle.getTags()); testSteps.add(new PickleStepTestStep(pickleEvent.uri, step, beforeStepHookSteps, afterStepHookSteps, match)); } + + return testSteps; } private List locations(PickleStep step) { @@ -109,33 +139,27 @@ private List locations(PickleStep step) { .collect(Collectors.toList()); } - private void addTestStepsForBeforeHooks(List testSteps, List tags) { - addTestStepsForHooks(testSteps, tags, glue.getBeforeHooks(), HookType.BEFORE); + private List createTestStepsForBeforeHooks(List tags) { + return createTestStepsForHooks(tags, glue.getBeforeHooks(), HookType.BEFORE); } - private void addTestStepsForAfterHooks(List testSteps, List tags) { - addTestStepsForHooks(testSteps, tags, glue.getAfterHooks(), HookType.AFTER); + private List createTestStepsForAfterHooks(List tags) { + return createTestStepsForHooks(tags, glue.getAfterHooks(), HookType.AFTER); } - private void addTestStepsForHooks(List testSteps, List tags, List hooks, HookType hookType) { - for (HookDefinition hook : hooks) { - if (hook.matches(tags)) { - HookTestStep testStep = new HookTestStep(hookType, new HookDefinitionMatch(hook)); - testSteps.add(testStep); - } - } + private List createTestStepsForHooks(List tags, Collection hooks, HookType hookType) { + return hooks.stream() + .filter(hook -> hook.matches(tags)) + .map(hook -> new HookTestStep(hookType, new HookDefinitionMatch(hook))) + .collect(Collectors.toList()); } - private List getAfterStepHooks(List tags) { - List hookSteps = new ArrayList<>(); - addTestStepsForHooks(hookSteps, tags, glue.getAfterStepHooks(), HookType.AFTER_STEP); - return hookSteps; + private List createAfterStepHooks(List tags) { + return createTestStepsForHooks(tags, glue.getAfterStepHooks(), HookType.AFTER_STEP); } - private List getBeforeStepHooks(List tags) { - List hookSteps = new ArrayList<>(); - addTestStepsForHooks(hookSteps, tags, glue.getBeforeStepHooks(), HookType.BEFORE_STEP); - return hookSteps; + private List createBeforeStepHooks(List tags) { + return createTestStepsForHooks(tags, glue.getBeforeStepHooks(), HookType.BEFORE_STEP); } private void buildBackendWorlds() { @@ -150,6 +174,5 @@ private void disposeBackendWorlds() { backend.disposeWorld(); } objectFactory.stop(); - glue.removeScenarioScopedGlue(); } } diff --git a/core/src/main/java/io/cucumber/core/runner/TestCase.java b/core/src/main/java/io/cucumber/core/runner/TestCase.java index 467efbfab1..85c6926a07 100644 --- a/core/src/main/java/io/cucumber/core/runner/TestCase.java +++ b/core/src/main/java/io/cucumber/core/runner/TestCase.java @@ -22,7 +22,7 @@ final class TestCase implements io.cucumber.core.event.TestCase { private final List beforeHooks; private final List afterHooks; - public TestCase(List testSteps, + TestCase(List testSteps, List beforeHooks, List afterHooks, PickleEvent pickleEvent, diff --git a/core/src/main/java/io/cucumber/core/runtime/Runtime.java b/core/src/main/java/io/cucumber/core/runtime/Runtime.java index 984c4ee791..ea3ab04d9b 100644 --- a/core/src/main/java/io/cucumber/core/runtime/Runtime.java +++ b/core/src/main/java/io/cucumber/core/runtime/Runtime.java @@ -217,11 +217,11 @@ public Runtime build() { plugins.setEventBusOnEventListenerPlugins(eventBus); } - final TypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); + final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); final RunnerSupplier runnerSupplier = runtimeOptions.isMultiThreaded() - ? new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistrySupplier) - : new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistrySupplier); + ? new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier) + : new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); final ExecutorService executor = runtimeOptions.isMultiThreaded() ? Executors.newFixedThreadPool(runtimeOptions.getThreads(), new CucumberThreadFactory()) diff --git a/core/src/main/java/io/cucumber/core/runtime/ConfiguringTypeRegistrySupplier.java b/core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java similarity index 55% rename from core/src/main/java/io/cucumber/core/runtime/ConfiguringTypeRegistrySupplier.java rename to core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java index ac96061be8..dc51894d4c 100644 --- a/core/src/main/java/io/cucumber/core/runtime/ConfiguringTypeRegistrySupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/ScanningTypeRegistryConfigurerSupplier.java @@ -4,27 +4,23 @@ import io.cucumber.core.io.ClassFinder; import io.cucumber.core.reflection.Reflections; import io.cucumber.core.runner.Options; -import io.cucumber.core.stepexpression.TypeRegistry; import java.util.Locale; -public final class ConfiguringTypeRegistrySupplier implements TypeRegistrySupplier { +public final class ScanningTypeRegistryConfigurerSupplier implements TypeRegistryConfigurerSupplier { private final ClassFinder classFinder; private final Options options; - public ConfiguringTypeRegistrySupplier(ClassFinder classFinder, Options options) { + public ScanningTypeRegistryConfigurerSupplier(ClassFinder classFinder, Options options) { this.classFinder = classFinder; this.options = options; } @Override - public TypeRegistry get() { + public TypeRegistryConfigurer get() { Reflections reflections = new Reflections(classFinder); - TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, options.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); - TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale()); - typeRegistryConfigurer.configureTypeRegistry(typeRegistry); - return typeRegistry; + return reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, options.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); } private static final class DefaultTypeRegistryConfiguration implements TypeRegistryConfigurer { diff --git a/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java index fba17eaa02..93e57da1ec 100644 --- a/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java @@ -15,19 +15,19 @@ public final class SingletonRunnerSupplier implements RunnerSupplier { private final Options runnerOptions; private final EventBus eventBus; private final ObjectFactorySupplier objectFactorySupplier; - private final TypeRegistrySupplier typeRegistrySupplier; + private final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier; public SingletonRunnerSupplier( Options runnerOptions, EventBus eventBus, BackendSupplier backendSupplier, - ObjectFactorySupplier objectFactorySupplier, TypeRegistrySupplier typeRegistrySupplier) { + ObjectFactorySupplier objectFactorySupplier, TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier) { this.backendSupplier = backendSupplier; this.runnerOptions = runnerOptions; this.eventBus = eventBus; this.objectFactorySupplier = objectFactorySupplier; - this.typeRegistrySupplier = typeRegistrySupplier; + this.typeRegistryConfigurerSupplier = typeRegistryConfigurerSupplier; } private Runner runner; @@ -45,7 +45,7 @@ private Runner createRunner() { eventBus, backendSupplier.get(), objectFactorySupplier.get(), - typeRegistrySupplier.get(), + typeRegistryConfigurerSupplier.get(), runnerOptions ); } diff --git a/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java index 35c23ff4e3..cc0e6db910 100644 --- a/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java @@ -1,7 +1,5 @@ package io.cucumber.core.runtime; -import java.time.Instant; - import io.cucumber.core.event.Event; import io.cucumber.core.event.EventHandler; import io.cucumber.core.eventbus.AbstractEventBus; @@ -9,6 +7,8 @@ import io.cucumber.core.runner.Options; import io.cucumber.core.runner.Runner; +import java.time.Instant; + /** * Creates a distinct runner for each calling thread. Each runner has its own bus, backend- and glue-suppliers. *

@@ -20,7 +20,7 @@ public final class ThreadLocalRunnerSupplier implements RunnerSupplier { private final io.cucumber.core.runner.Options runnerOptions; private final SynchronizedEventBus sharedEventBus; private final ObjectFactorySupplier objectFactorySupplier; - private final TypeRegistrySupplier typeRegistrySupplier; + private final TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier; private final ThreadLocal runners = ThreadLocal.withInitial(this::createRunner); @@ -28,12 +28,14 @@ public ThreadLocalRunnerSupplier( Options runnerOptions, EventBus sharedEventBus, BackendSupplier backendSupplier, - ObjectFactorySupplier objectFactorySupplier, TypeRegistrySupplier typeRegistrySupplier) { + ObjectFactorySupplier objectFactorySupplier, + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier + ) { this.runnerOptions = runnerOptions; this.sharedEventBus = SynchronizedEventBus.synchronize(sharedEventBus); this.backendSupplier = backendSupplier; this.objectFactorySupplier = objectFactorySupplier; - this.typeRegistrySupplier = typeRegistrySupplier; + this.typeRegistryConfigurerSupplier = typeRegistryConfigurerSupplier; } @Override @@ -46,7 +48,7 @@ private Runner createRunner() { new LocalEventBus(sharedEventBus), backendSupplier.get(), objectFactorySupplier.get(), - typeRegistrySupplier.get(), + typeRegistryConfigurerSupplier.get(), runnerOptions ); } diff --git a/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java new file mode 100644 index 0000000000..18d77a2d4c --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runtime/TypeRegistryConfigurerSupplier.java @@ -0,0 +1,7 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.api.TypeRegistryConfigurer; + +public interface TypeRegistryConfigurerSupplier { + TypeRegistryConfigurer get(); +} diff --git a/core/src/main/java/io/cucumber/core/runtime/TypeRegistrySupplier.java b/core/src/main/java/io/cucumber/core/runtime/TypeRegistrySupplier.java deleted file mode 100644 index 5a4d2e32b7..0000000000 --- a/core/src/main/java/io/cucumber/core/runtime/TypeRegistrySupplier.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.cucumber.core.runtime; - -import io.cucumber.core.stepexpression.TypeRegistry; - -public interface TypeRegistrySupplier { - TypeRegistry get(); -} diff --git a/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java b/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java index fa83533ebb..4490018b64 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/StepExpressionFactory.java @@ -1,19 +1,16 @@ package io.cucumber.core.stepexpression; import io.cucumber.core.exception.CucumberException; +import io.cucumber.cucumberexpressions.Expression; import io.cucumber.cucumberexpressions.UndefinedParameterTypeException; -import io.cucumber.datatable.DataTableTypeRegistryTableConverter; import io.cucumber.datatable.DataTable; - -import io.cucumber.cucumberexpressions.Expression; -import org.apiguardian.api.API; +import io.cucumber.datatable.DataTableTypeRegistryTableConverter; import java.lang.reflect.Type; import java.util.List; import static java.util.Collections.singletonList; -@API(status = API.Status.STABLE) public final class StepExpressionFactory { private final io.cucumber.cucumberexpressions.ExpressionFactory expressionFactory; 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 dcd9fbed84..02130f799f 100644 --- a/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java +++ b/core/src/test/java/io/cucumber/core/runner/CachingGlueTest.java @@ -1,42 +1,37 @@ package io.cucumber.core.runner; -import gherkin.pickles.Argument; -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 gherkin.pickles.PickleTag; +import gherkin.pickles.*; 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.backend.*; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.stepexpression.TypeRegistry; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import io.cucumber.cucumberexpressions.ParameterType; import io.cucumber.datatable.DataTable; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; import org.junit.Test; import java.time.Clock; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import static java.util.Collections.singletonList; import static java.util.Locale.ENGLISH; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; public class CachingGlueTest { private final TypeRegistry typeRegistry = new TypeRegistry(ENGLISH); - private CachingGlue glue = new CachingGlue(new TimeServiceEventBus(Clock.systemUTC()), typeRegistry); + private CachingGlue glue = new CachingGlue(new TimeServiceEventBus(Clock.systemUTC())); @Test public void throws_duplicate_error_on_dupe_stepdefs() { @@ -48,64 +43,113 @@ public void throws_duplicate_error_on_dupe_stepdefs() { StepDefinition b = mock(StepDefinition.class); when(b.getPattern()).thenReturn("hello"); when(b.getLocation(true)).thenReturn("bar.bf:90"); - try { - glue.addStepDefinition(b); - fail("should have failed"); - } catch (DuplicateStepDefinitionException expected) { - assertEquals("Duplicate step definitions in foo.bf:10 and bar.bf:90", expected.getMessage()); - } + glue.addStepDefinition(b); + + DuplicateStepDefinitionException exception = assertThrows( + DuplicateStepDefinitionException.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("Duplicate step definitions in foo.bf:10 and bar.bf:90")); } @Test - public void removes_glue_that_is_scenario_scoped() { - // This test is a bit fragile - it is testing state, not behaviour. - // But it was too much hassle creating a better test without refactoring RuntimeGlue - // and probably some of its immediate collaborators... Aslak. - - StepDefinition sd = spy(new MockedScenarioScopedStepDefinition()); - when(sd.getPattern()).thenReturn("pattern"); - glue.addStepDefinition(sd); - - HookDefinition bh = spy(new MockedScenarioScopedHookDefinition()); - glue.addBeforeHook(bh); - - HookDefinition ah = spy(new MockedScenarioScopedHookDefinition()); - glue.addAfterHook(ah); - - assertEquals(1, glue.stepDefinitionsByPattern.size()); - assertEquals(1, glue.beforeHooks.size()); - assertEquals(1, glue.afterHooks.size()); - - glue.removeScenarioScopedGlue(); + public void throws_on_duplicate_default_parameter_transformer() { + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + + DuplicateDefaultParameterTransformers exception = assertThrows( + DuplicateDefaultParameterTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default parameter transformer. Found:\n" + + " - mocked default parameter transformer\n" + + " - mocked default parameter transformer\n" + )); + } - assertEquals(0, glue.stepDefinitionsByPattern.size()); - assertEquals(0, glue.beforeHooks.size()); - assertEquals(0, glue.afterHooks.size()); + @Test + public void throws_on_duplicate_default_table_entry_transformer() { + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + + DuplicateDefaultDataTableEntryTransformers exception = assertThrows( + DuplicateDefaultDataTableEntryTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default data table entry. Found:\n" + + " - mocked default data table entry transformer\n" + + " - mocked default data table entry transformer\n" + )); } @Test - public void removes_scenario_scoped_cache_entries() { - StepDefinition sd = new MockedScenarioScopedStepDefinition("pattern"); - glue.addStepDefinition(sd); - String featurePath = "someFeature.feature"; + public void throws_on_duplicate_default_table_cell_transformer() { + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + + DuplicateDefaultDataTableCellTransformers exception = assertThrows( + DuplicateDefaultDataTableCellTransformers.class, + () -> glue.prepareGlue(typeRegistry) + ); + assertThat(exception.getMessage(), equalTo("" + + "There may not be more then one default table cell transformers. Found:\n" + + " - mocked default data table cell transformer\n" + + " - mocked default data table cell transformer\n" + )); + } - String stepText = "pattern"; - PickleStep pickleStep1 = getPickleStep(stepText); - PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); - CoreStepDefinition coreStepDefinition = (CoreStepDefinition) pickleStepDefinitionMatch.getStepDefinition(); - assertEquals(sd, coreStepDefinition.getStepDefinition()); - assertEquals(1, glue.stepDefinitionsByStepText.size()); + @Test + public void removes_glue_that_is_scenario_scoped() { + // This test is a bit fragile - it is testing state, not behaviour. + // But it was too much hassle creating a better test without refactoring RuntimeGlue + // and probably some of its immediate collaborators... Aslak. + + glue.addStepDefinition(new MockedScenarioScopedStepDefinition("pattern")); + glue.addBeforeHook(new MockedScenarioScopedHookDefinition()); + glue.addAfterHook(new MockedScenarioScopedHookDefinition()); + glue.addBeforeStepHook(new MockedScenarioScopedHookDefinition()); + glue.addAfterStepHook(new MockedScenarioScopedHookDefinition()); + glue.addDataTableType(new MockedDataTableTypeDefinition()); + glue.addParameterType(new MockedParameterTypeDefinition()); + glue.addDefaultParameterTransformer(new MockedDefaultParameterTransformer()); + glue.addDefaultDataTableCellTransformer(new MockedDefaultDataTableCellTransformer()); + glue.addDefaultDataTableEntryTransformer(new MockedDefaultDataTableEntryTransformer()); + + glue.prepareGlue(typeRegistry); + + assertEquals(1, glue.getStepDefinitions().size()); + assertEquals(1, glue.getBeforeHooks().size()); + assertEquals(1, glue.getAfterHooks().size()); + assertEquals(1, glue.getBeforeStepHooks().size()); + assertEquals(1, glue.getAfterStepHooks().size()); + assertEquals(1, glue.getDataTableTypeDefinitions().size()); + assertEquals(1, glue.getParameterTypeDefinitions().size()); + assertEquals(1, glue.getDefaultParameterTransformers().size()); + assertEquals(1, glue.getDefaultDataTableCellTransformers().size()); + assertEquals(1, glue.getDefaultDataTableEntryTransformers().size()); glue.removeScenarioScopedGlue(); - assertEquals(0, glue.stepDefinitionsByStepText.size()); + assertEquals(0, glue.getStepDefinitions().size()); + assertEquals(0, glue.getBeforeHooks().size()); + assertEquals(0, glue.getAfterHooks().size()); + assertEquals(0, glue.getBeforeStepHooks().size()); + assertEquals(0, glue.getAfterStepHooks().size()); + assertEquals(0, glue.getDataTableTypeDefinitions().size()); + assertEquals(0, glue.getParameterTypeDefinitions().size()); + assertEquals(0, glue.getDefaultParameterTransformers().size()); + assertEquals(0, glue.getDefaultDataTableCellTransformers().size()); + assertEquals(0, glue.getDefaultDataTableEntryTransformers().size()); } @Test public void returns_null_if_no_matching_steps_found() { - StepDefinition stepDefinition = spy(new MockedStepDefinition("pattern1")); + StepDefinition stepDefinition = new MockedStepDefinition("pattern1"); glue.addStepDefinition(stepDefinition); + String featurePath = "someFeature.feature"; PickleStep pickleStep = getPickleStep("pattern"); @@ -114,48 +158,50 @@ public void returns_null_if_no_matching_steps_found() { @Test public void returns_match_from_cache_if_single_found() { - StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); - StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); glue.addStepDefinition(stepDefinition1); glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + String featurePath = "someFeature.feature"; String stepText = "pattern1"; PickleStep pickleStep1 = getPickleStep(stepText); PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); - CoreStepDefinition coreStepDefinition = (CoreStepDefinition) pickleStepDefinitionMatch.getStepDefinition(); - assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); //check cache - CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1, entry.getStepDefinition()); + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); PickleStep pickleStep2 = getPickleStep(stepText); PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep2); - CoreStepDefinition coreStepDefinition2 = (CoreStepDefinition) pickleStepDefinitionMatch2.getStepDefinition(); - assertEquals(stepDefinition1, coreStepDefinition2.getStepDefinition()); + assertEquals(stepDefinition1, pickleStepDefinitionMatch2.getStepDefinition()); } @Test public void returns_match_from_cache_for_step_with_table() { - StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); - StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); glue.addStepDefinition(stepDefinition1); glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + 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, coreStepDefinition.getStepDefinition()); + assertEquals(stepDefinition1, match1.getStepDefinition()); //check cache - CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1, entry.getStepDefinition()); + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); //check arguments assertEquals("cell 1", ((DataTable) match1.getArguments().get(0).getValue()).cell(0, 0)); @@ -170,23 +216,24 @@ public void returns_match_from_cache_for_step_with_table() { @Test public void returns_match_from_cache_for_ste_with_doc_string() { - StepDefinition stepDefinition1 = spy(new MockedStepDefinition("^pattern1")); - StepDefinition stepDefinition2 = spy(new MockedStepDefinition("^pattern2")); + StepDefinition stepDefinition1 = new MockedStepDefinition("^pattern1"); + StepDefinition stepDefinition2 = new MockedStepDefinition("^pattern2"); glue.addStepDefinition(stepDefinition1); glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + 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, coreStepDefinition.getStepDefinition()); + assertEquals(stepDefinition1, match1.getStepDefinition()); //check cache - CoreStepDefinition entry = glue.stepDefinitionsByStepText.get(stepText); - assertEquals(stepDefinition1, entry.getStepDefinition()); + assertEquals(stepDefinition1.getPattern(), glue.getStepPatternByStepText().get(stepText)); + CoreStepDefinition coreStepDefinition = glue.getStepDefinitionsByPattern().get(stepDefinition1.getPattern()); + assertEquals(stepDefinition1, coreStepDefinition.getStepDefinition()); //check arguments assertEquals("doc string 1", match1.getArguments().get(0).getValue()); @@ -196,17 +243,63 @@ public void returns_match_from_cache_for_ste_with_doc_string() { PickleStepDefinitionMatch match2 = glue.stepDefinitionMatch(featurePath, pickleStep2); //check arguments assertEquals("doc string 2", match2.getArguments().get(0).getValue()); + } + + @Test + public void returns_fresh_match_from_cache_after_evicting_scenario_scoped() { + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + PickleStep pickleStep1 = getPickleStep(stepText); + + + StepDefinition stepDefinition1 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition1); + glue.prepareGlue(typeRegistry); + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); + + glue.removeScenarioScopedGlue(); + + StepDefinition stepDefinition2 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition2); + glue.prepareGlue(typeRegistry); + + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition2, pickleStepDefinitionMatch2.getStepDefinition()); } + @Test + public void returns_no_match_after_evicting_scenario_scoped() { + String featurePath = "someFeature.feature"; + String stepText = "pattern1"; + PickleStep pickleStep1 = getPickleStep(stepText); + + + StepDefinition stepDefinition1 = new MockedScenarioScopedStepDefinition("^pattern1"); + glue.addStepDefinition(stepDefinition1); + glue.prepareGlue(typeRegistry); + + + PickleStepDefinitionMatch pickleStepDefinitionMatch = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertEquals(stepDefinition1, pickleStepDefinitionMatch.getStepDefinition()); + + glue.removeScenarioScopedGlue(); + + glue.prepareGlue(typeRegistry); + + PickleStepDefinitionMatch pickleStepDefinitionMatch2 = glue.stepDefinitionMatch(featurePath, pickleStep1); + assertThat(pickleStepDefinitionMatch2, nullValue()); + } + private static PickleStep getPickleStepWithSingleCellTable(String stepText, String cell) { - return new PickleStep(stepText, Collections.singletonList(new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), cell)))))), Collections.emptyList()); + return new PickleStep(stepText, Collections.singletonList(new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), cell)))))), Collections.emptyList()); } 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 @@ -217,6 +310,8 @@ public void throws_ambiguous_steps_def_exception_when_many_patterns_match() { glue.addStepDefinition(stepDefinition1); glue.addStepDefinition(stepDefinition2); glue.addStepDefinition(stepDefinition3); + glue.prepareGlue(typeRegistry); + String featurePath = "someFeature.feature"; checkAmbiguousCalled(featurePath); @@ -237,7 +332,7 @@ private void checkAmbiguousCalled(String featurePath) { } private static PickleStep getPickleStep(String text) { - return new PickleStep(text, Collections.emptyList(), Collections.emptyList()); + return new PickleStep(text, Collections.emptyList(), Collections.emptyList()); } private static class MockedScenarioScopedStepDefinition implements StepDefinition, ScenarioScoped { @@ -286,6 +381,48 @@ public String getPattern() { } + private static class MockedDataTableTypeDefinition implements DataTableTypeDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public DataTableType dataTableType() { + return new DataTableType(Object.class, (DataTable table) -> new Object()); + } + + @Override + public String getLocation(boolean detail) { + return "mocked data table type definition"; + } + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + } + + private static class MockedParameterTypeDefinition implements ParameterTypeDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public ParameterType parameterType() { + return new ParameterType<>("mock", "[ab]", Object.class, (String arg) -> new Object()); + } + + @Override + public String getLocation(boolean detail) { + return "mocked parameter type location"; + } + } + + private static class MockedScenarioScopedHookDefinition implements HookDefinition, ScenarioScoped { boolean disposed; @@ -349,4 +486,74 @@ public String getPattern() { return pattern; } } + + private static class MockedDefaultParameterTransformer implements DefaultParameterTransformerDefinition, ScenarioScoped { + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public ParameterByTypeTransformer parameterByTypeTransformer() { + return (fromValue, toValueType) -> new Object(); + } + + @Override + public String getLocation(boolean detail) { + return "mocked default parameter transformer"; + } + } + + private static class MockedDefaultDataTableCellTransformer implements DefaultDataTableCellTransformerDefinition, ScenarioScoped { + + + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public TableCellByTypeTransformer tableCellByTypeTransformer() { + return new TableCellByTypeTransformer() { + @Override + public T transform(String value, Class cellType) { + return (T) new Object(); + } + }; + } + + @Override + public String getLocation(boolean detail) { + return "mocked default data table cell transformer"; + } + } + + private static class MockedDefaultDataTableEntryTransformer implements DefaultDataTableEntryTransformerDefinition, ScenarioScoped { + boolean disposed; + + @Override + public void disposeScenarioScope() { + this.disposed = true; + } + + @Override + public TableEntryByTypeTransformer tableEntryByTypeTransformer() { + return new TableEntryByTypeTransformer() { + @Override + public T transform(Map entry, Class type, TableCellByTypeTransformer cellTransformer) { + return (T) new Object(); + } + }; + } + + @Override + public String getLocation(boolean detail) { + return "mocked default data table entry transformer"; + } + } } diff --git a/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java index 1d846bebe3..ae9244824b 100644 --- a/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java +++ b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java @@ -128,7 +128,7 @@ private T runStepDef(Method method, boolean transposed, PickleTable table) t for (Argument argument : arguments) { result.add(argument.getValue()); } - coreStepDefinition.execute(result.toArray(new Object[0])); + coreStepDefinition.getStepDefinition().execute(result.toArray(new Object[0])); return (T) stub.getArgs().get(0); } 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 3b75640470..68ac48a788 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java @@ -13,7 +13,6 @@ import io.cucumber.core.options.RuntimeOptions; import io.cucumber.core.runtime.StubStepDefinition; import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; @@ -23,7 +22,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import static java.util.Collections.singletonList; import static org.mockito.Mockito.inOrder; @@ -35,18 +33,17 @@ public class HookOrderTest { private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); - private final TypeRegistry typeRegistry = 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 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)))); + new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1,1)))); @Test public void before_hooks_execute_in_order() throws Throwable { final List hooks = mockHooks(3, Integer.MAX_VALUE, 1, -1, 0, 10000, Integer.MIN_VALUE); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(new StubStepDefinition("pattern1")); @@ -60,20 +57,20 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); } @Test public void before_step_hooks_execute_in_order() throws Throwable { final List hooks = mockHooks(3, Integer.MAX_VALUE, 1, -1, 0, 10000, Integer.MIN_VALUE); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -87,20 +84,20 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); } @Test public void after_hooks_execute_in_reverse_order() throws Throwable { final List hooks = mockHooks(Integer.MIN_VALUE, 2, Integer.MAX_VALUE, 4, -1, 0, 10000); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -114,20 +111,20 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); } @Test public void after_step_hooks_execute_in_reverse_order() throws Throwable { final List hooks = mockHooks(Integer.MIN_VALUE, 2, Integer.MAX_VALUE, 4, -1, 0, 10000); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -141,13 +138,13 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleEvent); InOrder inOrder = inOrder(hooks.toArray()); - inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); - inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(2)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(6)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(3)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(1)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(5)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(4)).execute(ArgumentMatchers.any()); + inOrder.verify(hooks.get(0)).execute(ArgumentMatchers.any()); } @Test @@ -156,7 +153,7 @@ public void hooks_order_across_many_backends() throws Throwable { final List backend2Hooks = mockHooks(2, Integer.MAX_VALUE, 4); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -191,7 +188,7 @@ private List mockHooks(int... ordering) { for (int order : ordering) { HookDefinition hook = mock(HookDefinition.class, "Mock number " + order); when(hook.getOrder()).thenReturn(order); - when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); + when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); hooks.add(hook); } return hooks; diff --git a/core/src/test/java/io/cucumber/core/runner/HookTest.java b/core/src/test/java/io/cucumber/core/runner/HookTest.java index 35e38b310d..8fef4acf87 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookTest.java @@ -1,30 +1,25 @@ package io.cucumber.core.runner; -import io.cucumber.core.api.Scenario; +import gherkin.events.PickleEvent; +import gherkin.pickles.Pickle; +import gherkin.pickles.PickleLocation; +import gherkin.pickles.PickleStep; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.core.backend.Backend; import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; -import io.cucumber.core.backend.Backend; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.options.RuntimeOptions; -import gherkin.events.PickleEvent; -import gherkin.pickles.Argument; -import gherkin.pickles.Pickle; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.net.URI; import java.time.Clock; import java.util.Collections; -import java.util.Locale; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; @@ -37,38 +32,34 @@ public class HookTest { private final static String ENGLISH = "en"; private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - private final PickleStep pickleStep = new PickleStep("pattern1", Collections.emptyList(), singletonList(new PickleLocation(2, 2))); + 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)))); + new Pickle("scenario1", ENGLISH, singletonList(pickleStep), Collections.emptyList(), singletonList(new PickleLocation(1, 1)))); /** * Test for #23. */ @Test public void after_hooks_execute_before_objects_are_disposed() throws Throwable { - Backend backend = mock(Backend.class); ObjectFactory objectFactory = mock(ObjectFactory.class); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); final HookDefinition hook = mock(HookDefinition.class); - when(hook.matches(ArgumentMatchers.anyCollection())).thenReturn(true); + TypeRegistryConfigurer typeRegistryConfigurer = mock(TypeRegistryConfigurer.class); + when(hook.matches(ArgumentMatchers.anyCollection())).thenReturn(true); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - Glue glue = invocation.getArgument(0); - glue.addBeforeHook(hook); - return null; - } - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); + doAnswer(invocation -> { + Glue glue = invocation.getArgument(0); + glue.addBeforeHook(hook); + return null; + }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); - Runner runner = new Runner(bus, Collections.singleton(backend), objectFactory, typeRegistry, runtimeOptions); + Runner runner = new Runner(bus, Collections.singleton(backend), objectFactory, typeRegistryConfigurer, runtimeOptions); runner.runPickle(pickleEvent); InOrder inOrder = inOrder(hook, backend); inOrder.verify(backend).buildWorld(); - inOrder.verify(hook).execute(ArgumentMatchers.any()); + inOrder.verify(hook).execute(ArgumentMatchers.any()); inOrder.verify(backend).disposeWorld(); } } 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 bdb98a1bc4..465492bd04 100644 --- a/core/src/test/java/io/cucumber/core/runner/RunnerTest.java +++ b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java @@ -6,6 +6,7 @@ import gherkin.pickles.PickleStep; import gherkin.pickles.PickleTag; import io.cucumber.core.api.Scenario; +import io.cucumber.core.api.TypeRegistryConfigurer; import io.cucumber.core.backend.Backend; import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; @@ -15,7 +16,6 @@ import io.cucumber.core.options.RuntimeOptionsBuilder; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.snippets.TestSnippet; -import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; @@ -26,7 +26,6 @@ import java.time.Clock; import java.util.Collections; import java.util.List; -import java.util.Locale; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -52,7 +51,7 @@ public class RunnerTest { private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); - private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + private final TypeRegistryConfigurer typeRegistryConfigurer = typeRegistry -> {}; @Test public void hooks_execute_when_world_exist() throws Throwable { @@ -70,13 +69,13 @@ public Object answer(InvocationOnMock invocation) { glue.addBeforeHook(beforeHook); return null; } - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); + }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); PickleStep step = mock(PickleStep.class); when(step.getText()).thenReturn("some step"); - new Runner(bus, singletonList(backend), objectFactory, typeRegistry, runtimeOptions).runPickle(createPickleEventWithSteps(asList(step))); + new Runner(bus, singletonList(backend), objectFactory, typeRegistryConfigurer, runtimeOptions).runPickle(createPickleEventWithSteps(asList(step))); InOrder inOrder = inOrder(beforeHook, afterHook, backend); inOrder.verify(backend).buildWorld(); @@ -91,8 +90,8 @@ public void steps_are_skipped_after_failure() throws Throwable { PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); final HookDefinition failingBeforeHook = addBeforeHook(); - doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addBeforeHook(failingBeforeHook); @@ -112,7 +111,7 @@ public void aftersteps_are_executed_after_failed_step() throws Throwable { StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step") { @Override - public void execute(Object[] args) throws Throwable { + public void execute(Object[] args) { throw new RuntimeException(); } }); @@ -121,7 +120,7 @@ public void execute(Object[] args) throws Throwable { final HookDefinition afteStepHook = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addAfterHook(afteStepHook); @@ -144,7 +143,7 @@ public void aftersteps_executed_for_passed_step() throws Throwable { HookDefinition afteStepHook1 = addAfterStepHook(); HookDefinition afteStepHook2 = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addAfterHook(afteStepHook1); @@ -168,7 +167,7 @@ public void hooks_execute_also_after_failure() throws Throwable { final HookDefinition beforeHook = addBeforeHook(); final HookDefinition afterHook = addAfterHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addBeforeHook(failingBeforeHook); @@ -188,10 +187,10 @@ public void loadGlue(Glue glue, List gluePaths) { } @Test - public void steps_are_executed() throws Throwable { + public void steps_are_executed() { StubStepDefinition stepDefinition = new StubStepDefinition("some step"); PickleEvent pickleEventMatchingStepDefinitions = createPickleEventMatchingStepDefinitions(stepDefinition); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -206,7 +205,7 @@ 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) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { glue.addStepDefinition(stepDefinition); @@ -225,7 +224,7 @@ public void hooks_not_executed_in_dry_run_mode() throws Throwable { final HookDefinition afterHook = addAfterHook(); final HookDefinition afterStepHook = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -250,7 +249,7 @@ public void hooks_not_executed_for_empty_pickles() throws Throwable { final HookDefinition afterHook = addAfterHook(); final HookDefinition afterStepHook = addAfterStepHook(); - TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, typeRegistry, runtimeOptions) { + TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -274,7 +273,7 @@ public void backends_are_asked_for_snippets_for_undefined_steps() { Backend backend = mock(Backend.class); when(backend.getSnippet()).thenReturn(new TestSnippet()); ObjectFactory objectFactory = mock(ObjectFactory.class); - Runner runner = new Runner(bus, singletonList(backend), objectFactory, typeRegistry, runtimeOptions); + Runner runner = new Runner(bus, singletonList(backend), objectFactory, typeRegistryConfigurer, runtimeOptions); runner.runPickle(createPickleEventWithSteps(asList(step))); verify(backend).getSnippet(); } @@ -293,7 +292,7 @@ private HookDefinition addAfterStepHook() { private HookDefinition addHook() { HookDefinition hook = mock(HookDefinition.class); - when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); + when(hook.matches(ArgumentMatchers.anyList())).thenReturn(true); return hook; } diff --git a/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java b/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java index 0d5cea2e98..f68766e68d 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java +++ b/core/src/test/java/io/cucumber/core/runner/TestRunnerSupplier.java @@ -8,21 +8,19 @@ import io.cucumber.core.runtime.RunnerSupplier; import io.cucumber.core.snippets.Snippet; import io.cucumber.core.snippets.TestSnippet; -import io.cucumber.core.stepexpression.TypeRegistry; import java.net.URI; -import java.util.Collections; import java.util.List; +import static java.util.Collections.singleton; + public class TestRunnerSupplier implements Backend, RunnerSupplier, ObjectFactory { private final EventBus bus; private final RuntimeOptions runtimeOptions; - private final TypeRegistry typeRegistry; - protected TestRunnerSupplier(EventBus bus, TypeRegistry typeRegistry, RuntimeOptions runtimeOptions) { + protected TestRunnerSupplier(EventBus bus, RuntimeOptions runtimeOptions) { this.bus = bus; - this.typeRegistry = typeRegistry; this.runtimeOptions = runtimeOptions; } @@ -48,7 +46,7 @@ public Snippet getSnippet() { @Override public Runner get() { - return new Runner(bus, Collections.singleton(this), this, typeRegistry, runtimeOptions); + return new Runner(bus, singleton(this), this, typeRegistry -> {}, runtimeOptions); } @Override 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 9369ffff38..3c2a56e9ea 100644 --- a/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java @@ -60,7 +60,7 @@ import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -573,7 +573,8 @@ public void receive(StepDefinedEvent event) { }; - final List definedStepDefinitions = new ArrayList<>(); + final MockedStepDefinition mockedStepDefinition = new MockedStepDefinition(); + final MockedScenarioScopedStepDefinition mockedScenarioScopedStepDefinition = new MockedScenarioScopedStepDefinition(); BackendSupplier backendSupplier = new TestBackendSupplier() { @@ -582,15 +583,11 @@ public void receive(StepDefinedEvent event) { @Override public void loadGlue(Glue glue, List gluePaths) { this.glue = glue; - final io.cucumber.core.backend.StepDefinition mockedStepDefinition = new MockedStepDefinition(); - definedStepDefinitions.add(mockedStepDefinition); glue.addStepDefinition(mockedStepDefinition); } @Override public void buildWorld() { - final io.cucumber.core.backend.StepDefinition mockedScenarioScopedStepDefinition = new MockedScenarioScopedStepDefinition(); - definedStepDefinitions.add(mockedScenarioScopedStepDefinition); glue.addStepDefinition(mockedScenarioScopedStepDefinition); } }; @@ -612,7 +609,14 @@ public List get() { .build() .run(); - assertThat(stepDefinedEvents, equalTo(definedStepDefinitions)); + + assertThat(stepDefinedEvents, contains( + mockedStepDefinition, + mockedScenarioScopedStepDefinition, + // Twice, once for each scenario + mockedStepDefinition, + mockedScenarioScopedStepDefinition + )); for (StepDefinition stepDefinedEvent : stepDefinedEvents) { if (stepDefinedEvent instanceof MockedScenarioScopedStepDefinition) { diff --git a/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java index 990ec3a6e0..62f75ddf1e 100644 --- a/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/SingletonRunnerSupplierTest.java @@ -33,8 +33,8 @@ public void before() { ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); EventBus eventBus = new TimeServiceEventBus(Clock.systemUTC()); - TypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); - runnerSupplier = new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistrySupplier); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + runnerSupplier = new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistryConfigurerSupplier); } @Test diff --git a/core/src/test/java/io/cucumber/core/runtime/TestTypeRegistrySupplier.java b/core/src/test/java/io/cucumber/core/runtime/TestTypeRegistrySupplier.java deleted file mode 100644 index 85a86fc54e..0000000000 --- a/core/src/test/java/io/cucumber/core/runtime/TestTypeRegistrySupplier.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.cucumber.core.runtime; - -import io.cucumber.core.stepexpression.TypeRegistry; - -import java.util.Locale; - -public class TestTypeRegistrySupplier implements TypeRegistrySupplier { - - @Override - public TypeRegistry get() { - return new TypeRegistry(Locale.ENGLISH); - } -} diff --git a/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java b/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java index 6ddc73ea15..063f3b42a9 100644 --- a/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplierTest.java @@ -42,8 +42,8 @@ public void before() { ObjectFactorySupplier objectFactory = new SingletonObjectFactorySupplier(objectFactoryServiceLoader); BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactory); eventBus = new TimeServiceEventBus(Clock.systemUTC()); - TypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); - runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistrySupplier); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactory, typeRegistryConfigurerSupplier); } diff --git a/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java b/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java index c5719987ec..ecd2f98e6b 100644 --- a/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java +++ b/examples/java-calculator/src/main/java/io/cucumber/examples/java/DateCalculator.java @@ -1,15 +1,15 @@ package io.cucumber.examples.java; -import java.util.Date; +import java.time.LocalDate; public class DateCalculator { - private Date now; + private LocalDate now; - public DateCalculator(Date now) { + public DateCalculator(LocalDate now) { this.now = now; } - public String isDateInThePast(Date date) { - return (date.before(now)) ? "yes" : "no"; + public String isDateInThePast(LocalDate date) { + return (date.isBefore(now)) ? "yes" : "no"; } } diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java index 469a8bb059..8c6a2473ad 100644 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java +++ b/examples/java-calculator/src/test/java/io/cucumber/examples/java/DateStepdefs.java @@ -1,10 +1,11 @@ package io.cucumber.examples.java; +import io.cucumber.java.ParameterType; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.util.Date; +import java.time.LocalDate; import static org.junit.Assert.assertEquals; @@ -12,13 +13,18 @@ public class DateStepdefs { private String result; private DateCalculator calculator; - @Given("^today is ([0-9]{4}-[0-9]{2}-[0-9]{2})$") - public void today_is(Date date) { + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate iso8601Date(String year, String month, String day){ + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {iso8601Date}") + public void today_is(LocalDate date) { calculator = new DateCalculator(date); } - @When("^I ask if ([0-9]{4}-[0-9]{2}-[0-9]{2}) is in the past$") - public void I_ask_if_date_is_in_the_past(Date date) { + @When("I ask if {iso8601Date} is in the past") + public void I_ask_if_date_is_in_the_past(LocalDate date) { result = calculator.isDateInThePast(date); } diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000000..c2f86a8d75 --- /dev/null +++ b/java/README.md @@ -0,0 +1,163 @@ +Cucumber Java +============= + +Provides annotation based step definitions. To use add the `cucumber-java` dependency to your pom.xml: + +```xml + + [...] + + io.cucumber + cucumber-java + ${cucumber.version} + test + + [...] + +``` + +## Step Definitions + +Declare a step definition by annotating a method. It is possible use the same method for multiple steps by repeating +the annotation. For localized annotations import the annotations from `io.cucumber.java..*` + +```java +package com.example.app; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static org.junit.Assert.assertEquals; + +public class CalculatorSteps{ + private RpnCalculator calc; + + @Given("a calculator I just turned on") + public void a_calculator_I_just_turned_on() { + calc = new RpnCalculator(); + } + + @When("I add {int} and {int}") + public void adding(int arg1, int arg2) { + calc.push(arg1); + calc.push(arg2); + calc.push("+"); + } + + @Then("the result is {int}") + public void the_result_is(double expected) { + assertEquals(expected, calc.value()); + } +} +``` + +## Hooks + +Declare hooks that will be executed before/after each scenario/step by annotating a method. The method may declare an +argument of type `io.cucumber.core.api.Scenario`. + + * `@Before` + * `@After` + * `@BeforeStep` + * `@AfterStep` + +## Transformers + +### Parameter Type + +Step definition parameter types can be declared by using `@ParameterType`. The name of the annotated method will be used +as the parameter name. + +```java +package com.example.app; + +import io.cucumber.java.ParameterType; +import io.cucumber.java.en.Given; + +import java.time.LocalDate; + +public class Steps { + + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate iso8601Date(String year, String month, String day) { + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {iso8601Date}") + public void today_is(LocalDate date) { + + } +} +``` + +### Data Table Type + +Data table types can be declared by annotating a method with `@DataTableType`. Depending on the parameter type this +will be either a: + * `String` -> `io.cucumber.datatable.TableCellTranformer` + * `Map` -> `io.cucumber.datatable.TableEntry` + * `List `io.cucumber.datatable.TableRow` + * `DataTable` -> `io.cucumber.datatable.TableTransformer` + +```java +package com.example.app; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.DataTableType; + +import java.util.List; +import java.util.Map; + +public class Steps { + + @DataTableType + public Author authorEntryTransformer(Map entry) { + return new Author( + entry.get("firstName"), + entry.get("lastName"), + entry.get("birthDate")); + } + + @DataTableType + public Author authorEntryTransformer(List row) { + return new Author( + row.get(0), + row.get(0), + row.get(0)); + } +} + +``` + +### Default Transformers + +Default transformers allow you to specific a transformer that will be used when there is no transform defined. This can +be combined with an object mapper like Jackson to quickly transform well known string representations to Java objects. + + * `@DefaultParameterTransformer` + * `@DefaultDataTableEntryTransformer` + * `@DefaultDataTableCellTransformer` + + ```java +package com.example.app; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.DefaultDataTableCellTransformer; +import io.cucumber.java.DefaultDataTableEntryTransformer; +import io.cucumber.java.DefaultParameterTransformer; + +import java.lang.reflect.Type; + +public class DataTableSteps { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @DefaultParameterTransformer + @DefaultDataTableEntryTransformer + @DefaultDataTableCellTransformer + public Object defaultTransformer(Object fromValue, Type toValueType) { + return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType)); + } +} +``` \ No newline at end of file diff --git a/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java b/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java new file mode 100644 index 0000000000..e0d8c0a985 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java @@ -0,0 +1,37 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.reflection.MethodFormat; + +import java.lang.reflect.Method; + +class AbstractGlueDefinition { + + protected final Method method; + protected final Lookup lookup; + private String shortFormat; + private String fullFormat; + + AbstractGlueDefinition(Method method, Lookup lookup) { + this.method = method; + this.lookup = lookup; + } + + public final String getLocation(boolean detail) { + return detail ? getFullLocationLocation() : getShortFormatLocation(); + } + + private String getShortFormatLocation() { + if (shortFormat == null) { + shortFormat = MethodFormat.SHORT.format(method); + } + return shortFormat; + } + + private String getFullLocationLocation() { + if (fullFormat == null) { + fullFormat = MethodFormat.FULL.format(method); + } + return fullFormat; + } +} diff --git a/java/src/main/java/io/cucumber/java/After.java b/java/src/main/java/io/cucumber/java/After.java index dbef92d640..7e01be9477 100644 --- a/java/src/main/java/io/cucumber/java/After.java +++ b/java/src/main/java/io/cucumber/java/After.java @@ -7,6 +7,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Execute method after each scenario. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @API(status = API.Status.STABLE) @@ -22,7 +25,7 @@ /** * Duration in milliseconds this hook is allowed to run. Cucumber * will mark the hook as failed when exceeded. - * + *

* When the maximum duration is exceeded the thread will * receive an interrupt. Note: if the interrupt is ignored * Cucumber will wait for the this hook to finish. diff --git a/java/src/main/java/io/cucumber/java/AfterStep.java b/java/src/main/java/io/cucumber/java/AfterStep.java index 20b9e565e9..e06ee66df5 100644 --- a/java/src/main/java/io/cucumber/java/AfterStep.java +++ b/java/src/main/java/io/cucumber/java/AfterStep.java @@ -7,6 +7,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Execute method after each step. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @API(status = API.Status.STABLE) diff --git a/java/src/main/java/io/cucumber/java/Before.java b/java/src/main/java/io/cucumber/java/Before.java index af13c88873..bd2312a366 100644 --- a/java/src/main/java/io/cucumber/java/Before.java +++ b/java/src/main/java/io/cucumber/java/Before.java @@ -7,6 +7,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Execute method before each scenario. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @API(status = API.Status.STABLE) diff --git a/java/src/main/java/io/cucumber/java/BeforeStep.java b/java/src/main/java/io/cucumber/java/BeforeStep.java index eb626b54d3..5a4c56ebb2 100644 --- a/java/src/main/java/io/cucumber/java/BeforeStep.java +++ b/java/src/main/java/io/cucumber/java/BeforeStep.java @@ -7,6 +7,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Execute method before each step. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @API(status = API.Status.STABLE) @@ -22,7 +25,7 @@ /** * Duration in milliseconds this hook is allowed to run. Cucumber * will mark the hook as failed when exceeded. - * + *

* When the maximum duration is exceeded the thread will * receive an interrupt. Note: if the interrupt is ignored * Cucumber will wait for the this hook to finish. diff --git a/java/src/main/java/io/cucumber/java/DataTableType.java b/java/src/main/java/io/cucumber/java/DataTableType.java new file mode 100644 index 0000000000..32bfb44b2f --- /dev/null +++ b/java/src/main/java/io/cucumber/java/DataTableType.java @@ -0,0 +1,30 @@ +package io.cucumber.java; + +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Register a data table type. + *

+ * The signature of the method is used to determine which data table type is registered: + * + *

    + *
  • {@code String -> Author} will register a {@link io.cucumber.datatable.TableCellTransformer}
  • + *
  • {@code Map -> Author} will register a {@link io.cucumber.datatable.TableEntryTransformer}
  • + *
  • {@code List -> Author} will register a {@link io.cucumber.datatable.TableRowTransformer}
  • + *
  • {@code DataTable -> Author} will register a {@link io.cucumber.datatable.TableTransformer}
  • + *
+ * NOTE: {@code Author} is an example of the class you want to convert the table to. + * + * @see io.cucumber.datatable.DataTableType + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface DataTableType { + +} diff --git a/java/src/main/java/io/cucumber/java/DefaultDataTableCellTransformer.java b/java/src/main/java/io/cucumber/java/DefaultDataTableCellTransformer.java new file mode 100644 index 0000000000..f8fc865081 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/DefaultDataTableCellTransformer.java @@ -0,0 +1,27 @@ +package io.cucumber.java; + +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Register default data table cell transformer. + *

+ * Valid method signatures are: + *

    + *
  • {@code String, Type -> Object}
  • + *
  • {@code Object, Type -> Object}
  • + *
+ * + * @see io.cucumber.datatable.TableCellByTypeTransformer + * @see io.cucumber.datatable.DataTableType + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface DefaultDataTableCellTransformer { + +} diff --git a/java/src/main/java/io/cucumber/java/DefaultDataTableEntryTransformer.java b/java/src/main/java/io/cucumber/java/DefaultDataTableEntryTransformer.java new file mode 100644 index 0000000000..9491c20418 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/DefaultDataTableEntryTransformer.java @@ -0,0 +1,29 @@ +package io.cucumber.java; + +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Register default data table entry transformer. + *

+ * Valid method signatures are: + *

    + *
  • {@code Map, Type -> Object}
  • + *
  • {@code Object, Type -> Object}
  • + *
  • {@code Map, Type, TableCellByTypeTransformer -> Object}
  • + *
  • {@code Object, Type, TableCellByTypeTransformer -> Object}
  • + *
+ * + * @see io.cucumber.datatable.TableEntryByTypeTransformer + * @see io.cucumber.datatable.DataTableType + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface DefaultDataTableEntryTransformer { + +} diff --git a/java/src/main/java/io/cucumber/java/DefaultParameterTransformer.java b/java/src/main/java/io/cucumber/java/DefaultParameterTransformer.java new file mode 100644 index 0000000000..5e8984611d --- /dev/null +++ b/java/src/main/java/io/cucumber/java/DefaultParameterTransformer.java @@ -0,0 +1,29 @@ +package io.cucumber.java; + +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Register default parameter type transformer. + *

+ * Valid method signatures are: + *

    + *
  • {@code String, Type -> Object}
  • + *
  • {@code Object, Type -> Object}
  • + *
+ * + * @see io.cucumber.cucumberexpressions.ParameterByTypeTransformer + * @see io.cucumber.cucumberexpressions.ParameterType + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface DefaultParameterTransformer { + +} diff --git a/java/src/main/java/io/cucumber/java/InvalidMethodException.java b/java/src/main/java/io/cucumber/java/InvalidMethodException.java new file mode 100644 index 0000000000..e1765b524a --- /dev/null +++ b/java/src/main/java/io/cucumber/java/InvalidMethodException.java @@ -0,0 +1,23 @@ +package io.cucumber.java; + +import io.cucumber.core.exception.CucumberException; + +import java.lang.reflect.Method; + +final class InvalidMethodException extends CucumberException { + + private InvalidMethodException(String message) { + super(message); + } + + static InvalidMethodException createInvalidMethodException(Method method, Class glueCodeClass) { + return new InvalidMethodException( + "You're not allowed to extend classes that define Step Definitions or hooks. " + + glueCodeClass + " extends " + method.getDeclaringClass()); + } + + static InvalidMethodException createMethodDeclaringClassNotAssignableFromGlue(Method method, Class glueCodeClass) { + return new InvalidMethodException(method.getDeclaringClass() + " isn't assignable from " + glueCodeClass); + } + +} diff --git a/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java b/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java new file mode 100644 index 0000000000..66d481f268 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/InvalidMethodSignatureException.java @@ -0,0 +1,87 @@ +package io.cucumber.java; + +import io.cucumber.core.exception.CucumberException; +import io.cucumber.core.reflection.MethodFormat; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +final class InvalidMethodSignatureException extends CucumberException { + + private InvalidMethodSignatureException(String message) { + super(message); + } + + static InvalidMethodSignatureExceptionBuilder builder(Method method) { + return new InvalidMethodSignatureExceptionBuilder(method); + } + + static class InvalidMethodSignatureExceptionBuilder { + + private Method method; + private final List> annotations = new ArrayList<>(); + private final List signatures = new ArrayList<>(); + private final List notes = new ArrayList<>(); + + private InvalidMethodSignatureExceptionBuilder(Method method) { + this.method = method; + } + + InvalidMethodSignatureExceptionBuilder addAnnotation(Class annotation) { + annotations.add(annotation); + return this; + } + + + InvalidMethodSignatureExceptionBuilder addSignature(String signature) { + signatures.add(signature); + return this; + } + + InvalidMethodSignatureExceptionBuilder addNote(String note) { + this.notes.add(note); + return this; + } + + public InvalidMethodSignatureException build() { + return new InvalidMethodSignatureException("" + + describeAnnotations() + " must have one of these signatures:\n" + + " * " + describeAvailableSignature() + "\n" + + "at " + describeLocation() + "\n" + + describeNote() + "\n" + ); + } + + private String describeNote() { + return String.join("\n", notes); + } + + private Object describeLocation() { + return MethodFormat.FULL.format(method); + } + + private String describeAvailableSignature() { + return String.join("\n * ", signatures); + } + + private String describeAnnotations() { + if (annotations.size() == 1) { + return "A @" + annotations.get(0).getSimpleName() + " annotated method"; + } + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < annotations.size(); i++) { + builder.append(annotations.get(i).getSimpleName()); + + if (i < annotations.size() - 2) { + builder.append(", "); + } else if (i < annotations.size() - 1) { + builder.append(" or "); + } + } + + return "A method annotated with " + builder.toString(); + } + } +} diff --git a/java/src/main/java/io/cucumber/java/JavaBackend.java b/java/src/main/java/io/cucumber/java/JavaBackend.java index b32204d823..65ca77ed6d 100644 --- a/java/src/main/java/io/cucumber/java/JavaBackend.java +++ b/java/src/main/java/io/cucumber/java/JavaBackend.java @@ -87,6 +87,21 @@ void addHook(Annotation annotation, Method method) { String tagExpression = afterStep.value(); long timeout = afterStep.timeout(); glue.addAfterStepHook(new JavaHookDefinition(method, tagExpression, afterStep.order(), timeout, lookup)); + } else if (annotation.annotationType().equals(ParameterType.class)) { + ParameterType parameterType = (ParameterType) annotation; + String pattern = parameterType.value(); + String name = parameterType.name(); + boolean useForSnippets = parameterType.useForSnippets(); + boolean preferForRegexMatch = parameterType.preferForRegexMatch(); + glue.addParameterType(new JavaParameterTypeDefinition(name, pattern, method, useForSnippets, preferForRegexMatch, lookup)); + } else if (annotation.annotationType().equals(DataTableType.class)) { + glue.addDataTableType(new JavaDataTableTypeDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultParameterTransformer.class)) { + glue.addDefaultParameterTransformer(new JavaDefaultParameterTransformerDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultDataTableEntryTransformer.class)) { + glue.addDefaultDataTableEntryTransformer(new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } else if (annotation.annotationType().equals(DefaultDataTableCellTransformer.class)) { + glue.addDefaultDataTableCellTransformer(new JavaDefaultDataTableCellTransformerDefinition(method, lookup)); } } } diff --git a/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java b/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java new file mode 100644 index 0000000000..f10fa945c5 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java @@ -0,0 +1,120 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DataTableTypeDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.*; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDataTableTypeDefinition extends AbstractGlueDefinition implements DataTableTypeDefinition { + + private final DataTableType dataTableType; + + JavaDataTableTypeDefinition(Method method, Lookup lookup) { + super(method, lookup); + this.dataTableType = createDataTableType(method); + } + + @SuppressWarnings("unchecked") + private DataTableType createDataTableType(Method method) { + Class returnType = requireValidReturnType(method); + Type parameterType = requireValidParameterType(method); + + if (DataTable.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableTransformer) this::execute + ); + } + + if (List.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableRowTransformer) this::execute + ); + } + + if (Map.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableEntryTransformer) this::execute + ); + } + + if (String.class.equals(parameterType)) { + return new DataTableType( + returnType, + (TableCellTransformer) this::execute + ); + } + + throw createInvalidSignatureException(method); + + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DataTableType.class) + .addSignature("public Author author(DataTable table)") + .addSignature("public Author author(List row)") + .addSignature("public Author author(Map entry)") + .addSignature("public Author author(String cell)") + .addNote("Note: Author is an example of the class you want to convert the table to.") + .build(); + } + + + private static Type requireValidParameterType(Method method) { + Type[] parameterTypes = method.getGenericParameterTypes(); + if (parameterTypes.length != 1) { + throw createInvalidSignatureException(method); + } + + Type parameterType = parameterTypes[0]; + if (!(parameterType instanceof ParameterizedType)) { + return parameterType; + } + + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + Type[] typeParameters = parameterizedType.getActualTypeArguments(); + for (Type typeParameter : typeParameters) { + if (!String.class.equals(typeParameter)) { + throw createInvalidSignatureException(method); + } + } + + return parameterizedType.getRawType(); + } + + private static Class requireValidReturnType(Method method) { + Type returnType = method.getGenericReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + if (!(returnType instanceof Class)) { + throw createInvalidSignatureException(method); + } + + return (Class) returnType; + } + + + @Override + public DataTableType dataTableType() { + return dataTableType; + } + + private Object execute(Object arg) throws Throwable { + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, arg); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java new file mode 100644 index 0000000000..af41ee4f47 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinition.java @@ -0,0 +1,63 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultDataTableCellTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.TableCellByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultDataTableCellTransformerDefinition extends AbstractGlueDefinition implements DefaultDataTableCellTransformerDefinition { + + private final TableCellByTypeTransformer transformer; + + JavaDefaultDataTableCellTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 2) { + throw createInvalidSignatureException(method); + } + + if (!(Object.class.equals(parameterTypes[0]) || String.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + + if (!Type.class.equals(parameterTypes[1])) { + throw createInvalidSignatureException(method); + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultDataTableCellTransformer.class) + .addSignature("public Object defaultDataTableCell(String fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableCell(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public TableCellByTypeTransformer tableCellByTypeTransformer() { + return transformer; + } + + + @SuppressWarnings("unchecked") + private T execute(String fromValue, Class toValueType) throws Throwable { + return (T) Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, fromValue, toValueType); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java new file mode 100644 index 0000000000..fe0f9d71ba --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinition.java @@ -0,0 +1,96 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultDataTableEntryTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.datatable.TableCellByTypeTransformer; +import io.cucumber.datatable.TableEntryByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultDataTableEntryTransformerDefinition extends AbstractGlueDefinition implements DefaultDataTableEntryTransformerDefinition { + + private final TableEntryByTypeTransformer transformer; + + JavaDefaultDataTableEntryTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Type[] parameterTypes = method.getParameterTypes(); + Type[] genericParameterTypes = method.getGenericParameterTypes(); + + if (parameterTypes.length < 2 || parameterTypes.length > 3) { + throw createInvalidSignatureException(method); + } + + Type parameterType = genericParameterTypes[0]; + + if (!Object.class.equals(parameterType)) { + if (!(parameterType instanceof ParameterizedType)) { + throw createInvalidSignatureException(method); + } + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + Type rawType = parameterizedType.getRawType(); + if (!Map.class.equals(rawType)) { + throw createInvalidSignatureException(method); + } + Type[] typeParameters = parameterizedType.getActualTypeArguments(); + for (Type typeParameter : typeParameters) { + if (!String.class.equals(typeParameter)) { + throw createInvalidSignatureException(method); + } + } + } + + if (!(Type.class.equals(parameterTypes[1]) || Class.class.equals(parameterTypes[1]))) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 3) { + if (!(Object.class.equals(parameterTypes[2]) || TableCellByTypeTransformer.class.equals(parameterTypes[2]))) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultDataTableEntryTransformer.class) + .addSignature("public T defaultDataTableEntry(Map fromValue, Class toValueType)") + .addSignature("public T defaultDataTableCell(Map fromValue, Class toValueType, TableCellByTypeTransformer cellTransformer)") + .addSignature("public Object defaultDataTableEntry(Map fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableEntry(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public TableEntryByTypeTransformer tableEntryByTypeTransformer() { + return transformer; + } + + @SuppressWarnings("unchecked") + private T execute(Map fromValue, Class toValueType, TableCellByTypeTransformer cellTransformer) throws Throwable { + Object[] args; + if (method.getParameterTypes().length == 3) { + args = new Object[]{fromValue, toValueType, cellTransformer}; + } else { + args = new Object[]{fromValue, toValueType}; + } + return (T) Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, args); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java b/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java new file mode 100644 index 0000000000..ed60aba099 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaDefaultParameterTransformerDefinition.java @@ -0,0 +1,63 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.DefaultParameterTransformerDefinition; +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +class JavaDefaultParameterTransformerDefinition extends AbstractGlueDefinition implements DefaultParameterTransformerDefinition { + + private final Lookup lookup; + private final ParameterByTypeTransformer transformer; + + JavaDefaultParameterTransformerDefinition(Method method, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.lookup = lookup; + this.transformer = this::execute; + } + + private static Method requireValidMethod(Method method) { + Class returnType = method.getReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 2) { + throw createInvalidSignatureException(method); + } + + if (!(Object.class.equals(parameterTypes[0]) || String.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + + if (!Type.class.equals(parameterTypes[1])) { + throw createInvalidSignatureException(method); + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(DefaultParameterTransformer.class) + .addSignature("public Object defaultDataTableEntry(String fromValue, Type toValueType)") + .addSignature("public Object defaultDataTableEntry(Object fromValue, Type toValueType)") + .build(); + } + + @Override + public ParameterByTypeTransformer parameterByTypeTransformer() { + return transformer; + } + + private Object execute(String fromValue, Type toValueType) throws Throwable { + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, fromValue, toValueType); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaHookDefinition.java b/java/src/main/java/io/cucumber/java/JavaHookDefinition.java index a46996d300..7d98921a92 100644 --- a/java/src/main/java/io/cucumber/java/JavaHookDefinition.java +++ b/java/src/main/java/io/cucumber/java/JavaHookDefinition.java @@ -1,27 +1,26 @@ package io.cucumber.java; +import gherkin.pickles.PickleTag; import io.cucumber.core.api.Scenario; -import io.cucumber.core.backend.Lookup; -import io.cucumber.core.exception.CucumberException; import io.cucumber.core.backend.HookDefinition; -import io.cucumber.core.reflection.MethodFormat; +import io.cucumber.core.backend.Lookup; import io.cucumber.core.filter.TagPredicate; -import gherkin.pickles.PickleTag; import io.cucumber.core.runtime.Invoker; import java.lang.reflect.Method; import java.util.Collection; -final class JavaHookDefinition implements HookDefinition { +import static io.cucumber.java.InvalidMethodSignatureException.builder; + +final class JavaHookDefinition extends AbstractGlueDefinition implements HookDefinition { - private final Method method; private final long timeoutMillis; private final TagPredicate tagPredicate; private final int order; private final Lookup lookup; JavaHookDefinition(Method method, String tagExpression, int order, long timeoutMillis, Lookup lookup) { - this.method = method; + super(requireValidMethod(method), lookup); this.timeoutMillis = timeoutMillis; this.tagPredicate = new TagPredicate(tagExpression); this.order = order; @@ -32,27 +31,39 @@ Method getMethod() { return method; } - @Override - public String getLocation(boolean detail) { - MethodFormat format = detail ? MethodFormat.FULL : MethodFormat.SHORT; - return format.format(method); + private static Method requireValidMethod(Method method) { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length > 1) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 1) { + if (!(Object.class.equals(parameterTypes[0]) || Scenario.class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(Before.class) + .addAnnotation(After.class) + .addAnnotation(BeforeStep.class) + .addAnnotation(AfterStep.class) + .addSignature("public void before_or_after(Scenario scenario)") + .addSignature("public void before_or_after()") + .build(); } @Override public void execute(Scenario scenario) throws Throwable { Object[] args; - switch (method.getParameterTypes().length) { - case 0: - args = new Object[0]; - break; - case 1: - if (!Scenario.class.equals(method.getParameterTypes()[0])) { - throw new CucumberException("When a hook declares an argument it must be of type " + Scenario.class.getName() + ". " + method.toString()); - } - args = new Object[]{scenario}; - break; - default: - throw new CucumberException("Hooks must declare 0 or 1 arguments. " + method.toString()); + if (method.getParameterTypes().length == 1) { + args = new Object[]{scenario}; + } else { + args = new Object[0]; } Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); diff --git a/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java b/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java new file mode 100644 index 0000000000..f92f4c8d74 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java @@ -0,0 +1,87 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.core.backend.ParameterTypeDefinition; +import io.cucumber.core.runtime.Invoker; +import io.cucumber.cucumberexpressions.ParameterType; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static io.cucumber.java.InvalidMethodSignatureException.builder; +import static java.util.Collections.singletonList; + +class JavaParameterTypeDefinition extends AbstractGlueDefinition implements ParameterTypeDefinition { + + private final ParameterType parameterType; + + JavaParameterTypeDefinition(String name, String pattern, Method method, boolean useForSnippets, boolean preferForRegexpMatch, Lookup lookup) { + super(requireValidMethod(method), lookup); + this.parameterType = new ParameterType<>( + name.isEmpty() ? method.getName() : name, + singletonList(pattern), + this.method.getReturnType(), + this::execute, + useForSnippets, + preferForRegexpMatch + ); + } + + private static Method requireValidMethod(Method method) { + Type returnType = method.getGenericReturnType(); + if (Void.class.equals(returnType) || void.class.equals(returnType)) { + throw createInvalidSignatureException(method); + } + if (!(returnType instanceof Class)) { + throw createInvalidSignatureException(method); + } + + Type[] parameterTypes = method.getGenericParameterTypes(); + if (parameterTypes.length == 0) { + throw createInvalidSignatureException(method); + } + + if (parameterTypes.length == 1) { + if (!(String.class.equals(parameterTypes[0]) || String[].class.equals(parameterTypes[0]))) { + throw createInvalidSignatureException(method); + } + return method; + } + + for (Type parameterType : parameterTypes) { + if (!String.class.equals(parameterType)) { + throw createInvalidSignatureException(method); + } + } + + return method; + } + + private static InvalidMethodSignatureException createInvalidSignatureException(Method method) { + return builder(method) + .addAnnotation(ParameterType.class) + .addSignature("public Author parameterName(String all)") + .addSignature("public Author parameterName(String captureGroup1, String captureGroup2, ...ect )") + .addSignature("public Author parameterName(String... captureGroups)") + .addNote("Note: Author is an example of the class you want to convert captureGroups to") + .build(); + } + + @Override + public ParameterType parameterType() { + return parameterType; + } + + private Object execute(String[] captureGroups) throws Throwable { + Object[] args; + + if (String[].class.equals(method.getParameterTypes()[0])) { + args = new Object[][]{captureGroups}; + } else { + args = captureGroups; + } + + return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, args); + } + +} diff --git a/java/src/main/java/io/cucumber/java/JavaSnippet.java b/java/src/main/java/io/cucumber/java/JavaSnippet.java index db5a505f20..1c21f30187 100644 --- a/java/src/main/java/io/cucumber/java/JavaSnippet.java +++ b/java/src/main/java/io/cucumber/java/JavaSnippet.java @@ -10,7 +10,7 @@ public MessageFormat template() { "@{0}(\"{1}\")\n" + "public void {2}({3}) '{'\n" + " // {4}\n" + - "{5} throw new io.cucumber.java.PendingException();\n" + + "{5} throw new " + PendingException.class.getName() + "();\n" + "'}'\n"); } } diff --git a/java/src/main/java/io/cucumber/java/JavaStepDefinition.java b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java index 4a364fa55f..65d2942148 100644 --- a/java/src/main/java/io/cucumber/java/JavaStepDefinition.java +++ b/java/src/main/java/io/cucumber/java/JavaStepDefinition.java @@ -3,33 +3,25 @@ import io.cucumber.core.backend.Lookup; import io.cucumber.core.backend.ParameterInfo; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.reflection.MethodFormat; import io.cucumber.core.runtime.Invoker; import java.lang.reflect.Method; import java.util.List; -final class JavaStepDefinition implements StepDefinition { - private final Method method; +final class JavaStepDefinition extends AbstractGlueDefinition implements StepDefinition { private final String expression; private final long timeoutMillis; - private final Lookup lookup; - private final String shortFormat; - private final String fullFormat; private final List parameterInfos; JavaStepDefinition(Method method, String expression, long timeoutMillis, Lookup lookup) { - this.method = method; + super(method, lookup); this.timeoutMillis = timeoutMillis; - this.lookup = lookup; this.parameterInfos = JavaParameterInfo.fromMethod(method); this.expression = expression; - this.shortFormat = MethodFormat.SHORT.format(method); - this.fullFormat = MethodFormat.FULL.format(method); } @Override @@ -37,11 +29,6 @@ public void execute(Object[] args) throws Throwable { Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, timeoutMillis, args); } - @Override - public String getLocation(boolean detail) { - return detail ? fullFormat : shortFormat; - } - @Override public boolean isDefinedAt(StackTraceElement e) { return e.getClassName().equals(method.getDeclaringClass().getName()) && e.getMethodName().equals(method.getName()); diff --git a/java/src/main/java/io/cucumber/java/MethodScanner.java b/java/src/main/java/io/cucumber/java/MethodScanner.java index 6fa06c90f3..a5305f54d7 100644 --- a/java/src/main/java/io/cucumber/java/MethodScanner.java +++ b/java/src/main/java/io/cucumber/java/MethodScanner.java @@ -10,6 +10,9 @@ import java.net.URI; import java.util.List; +import static io.cucumber.java.InvalidMethodException.createInvalidMethodException; +import static io.cucumber.java.InvalidMethodException.createMethodDeclaringClassNotAssignableFromGlue; + final class MethodScanner { private final ClassFinder classFinder; @@ -21,7 +24,7 @@ final class MethodScanner { /** * Registers step definitions and hooks. * - * @param javaBackend the backend where stepdefs and hooks will be registered + * @param javaBackend the backend where steps and hooks will be registered * @param gluePaths where to look */ void scan(JavaBackend javaBackend, List gluePaths) { @@ -46,7 +49,7 @@ void scan(JavaBackend javaBackend, List gluePaths) { /** * Registers step definitions and hooks. * - * @param javaBackend the backend where stepdefs and hooks will be registered. + * @param javaBackend the backend where steps and hooks will be registered. * @param method a candidate for being a stepdef or hook. * @param glueCodeClass the class where the method is declared. */ @@ -70,10 +73,10 @@ private void scan(JavaBackend javaBackend, Method method, Class glueCodeClass private void validateMethod(Method method, Class glueCodeClass) { if (!method.getDeclaringClass().isAssignableFrom(glueCodeClass)) { - throw new CucumberException(String.format("%s isn't assignable from %s", method.getDeclaringClass(), glueCodeClass)); + throw createMethodDeclaringClassNotAssignableFromGlue(method, glueCodeClass); } if (!glueCodeClass.equals(method.getDeclaringClass())) { - throw new CucumberException(String.format("You're not allowed to extend classes that define Step Definitions or hooks. %s extends %s", glueCodeClass, method.getDeclaringClass())); + throw createInvalidMethodException(method, glueCodeClass); } } @@ -83,10 +86,12 @@ private boolean isHookAnnotation(Annotation annotation) { || annotationClass.equals(After.class) || annotationClass.equals(BeforeStep.class) || annotationClass.equals(AfterStep.class) - || annotationClass.equals(io.cucumber.java.Before.class) - || annotationClass.equals(io.cucumber.java.After.class) - || annotationClass.equals(io.cucumber.java.BeforeStep.class) - || annotationClass.equals(io.cucumber.java.AfterStep.class); + || annotationClass.equals(ParameterType.class) + || annotationClass.equals(DataTableType.class) + || annotationClass.equals(DefaultParameterTransformer.class) + || annotationClass.equals(DefaultDataTableEntryTransformer.class) + || annotationClass.equals(DefaultDataTableCellTransformer.class) + ; } private boolean isStepdefAnnotation(Annotation annotation) { @@ -100,10 +105,10 @@ private boolean isRepeatedStepdefAnnotation(Annotation annotation) { return annotationClass.getAnnotation(StepDefAnnotations.class) != null; } - private Annotation[] repeatedAnnotations(Annotation annotation) { + private Annotation[] repeatedAnnotations(Annotation annotation) { try { Method expressionMethod = annotation.getClass().getMethod("value"); - return ( Annotation[]) Invoker.invoke(annotation, expressionMethod, 0); + return (Annotation[]) Invoker.invoke(annotation, expressionMethod, 0); } catch (Throwable e) { throw new CucumberException(e); } diff --git a/java/src/main/java/io/cucumber/java/ParameterType.java b/java/src/main/java/io/cucumber/java/ParameterType.java new file mode 100644 index 0000000000..ee052c5d54 --- /dev/null +++ b/java/src/main/java/io/cucumber/java/ParameterType.java @@ -0,0 +1,79 @@ +package io.cucumber.java; + +import io.cucumber.cucumberexpressions.GeneratedExpression; +import io.cucumber.cucumberexpressions.RegularExpression; +import org.apiguardian.api.API; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Register parameter type. + *

+ * The name of the method is used as the name of the {@link io.cucumber.cucumberexpressions.ParameterType}. + *

+ * The method must have one of these signatures. The number of {@code String} parameters must match the + * number of capture groups in the regular expression. + * + *

    + *
  • {@code String -> Author}
  • + *
  • {@code String, String -> Author}
  • + *
  • {@code String, String, ect -> Author}
  • + *
  • {@code String... -> Author}
  • + *
+ * NOTE: {@code Author} is an example of the type of the parameter type. {@link io.cucumber.cucumberexpressions.ParameterType#getType()} + * + * @see io.cucumber.cucumberexpressions.ParameterType + * @see Cucumber Expressions + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@API(status = API.Status.STABLE) +public @interface ParameterType { + + /** + * Regular expression. + *

+ * Describes which patterns match this parameter type. If the expression includes capture groups their captured + * strings will be provided as individual arguments. + * + * @return a regular expression. + * @see io.cucumber.cucumberexpressions.ParameterType#getRegexps() + */ + String value(); + + /** + * Name of the parameter type. + *

+ * This is used in the type name in typed expressions. When not provided this will default to the name of + * the annotated method. + * + * @return human readable type name + * @see io.cucumber.cucumberexpressions.ParameterType#getName() + */ + String name() default ""; + + /** + * Indicates whether or not this is a preferential parameter type when matching text + * against a {@link RegularExpression}. In case there are multiple parameter types + * with a regexp identical to the capture group's regexp, a preferential parameter type will + * win. If there are more than 1 preferential ones, an error will be thrown. + * + * @return true if this is a preferential type + * @see io.cucumber.cucumberexpressions.ParameterType#preferForRegexpMatch() + */ + boolean preferForRegexMatch() default false; + + /** + * Indicates whether or not this is a parameter type that should be used for generating + * {@link GeneratedExpression}s from text. Typically, parameter types with greedy regexps + * should return false. + * + * @return true is this parameter type is used for expression generation + * @see io.cucumber.cucumberexpressions.ParameterType#useForSnippets() + */ + boolean useForSnippets() default false; +} diff --git a/java/src/main/java/io/cucumber/java/PendingException.java b/java/src/main/java/io/cucumber/java/PendingException.java index 2e1f11d51e..3858c999a2 100644 --- a/java/src/main/java/io/cucumber/java/PendingException.java +++ b/java/src/main/java/io/cucumber/java/PendingException.java @@ -1,9 +1,15 @@ package io.cucumber.java; import io.cucumber.core.backend.Pending; +import org.apiguardian.api.API; -// We're deliberately not extending CucumberException (which is used to signal fatal errors) +/** + * When thrown from a step marks it as not yet implemented. + * + * @see JavaSnippet + */ @Pending +@API(status = API.Status.STABLE) public final class PendingException extends RuntimeException { public PendingException() { this("TODO: implement me"); diff --git a/java/src/main/java/io/cucumber/java/StepDefAnnotation.java b/java/src/main/java/io/cucumber/java/StepDefAnnotation.java index c678bf299b..dbb8db8c8f 100644 --- a/java/src/main/java/io/cucumber/java/StepDefAnnotation.java +++ b/java/src/main/java/io/cucumber/java/StepDefAnnotation.java @@ -1,5 +1,7 @@ package io.cucumber.java; +import org.apiguardian.api.API; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -7,5 +9,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) +@API(status = API.Status.INTERNAL) public @interface StepDefAnnotation { } diff --git a/java/src/main/java/io/cucumber/java/StepDefAnnotations.java b/java/src/main/java/io/cucumber/java/StepDefAnnotations.java index cc7f48fe11..093fc599cd 100644 --- a/java/src/main/java/io/cucumber/java/StepDefAnnotations.java +++ b/java/src/main/java/io/cucumber/java/StepDefAnnotations.java @@ -1,5 +1,7 @@ package io.cucumber.java; +import org.apiguardian.api.API; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -7,5 +9,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) +@API(status = API.Status.INTERNAL) public @interface StepDefAnnotations { } diff --git a/java/src/test/java/io/cucumber/java/JavaBackendTest.java b/java/src/test/java/io/cucumber/java/JavaBackendTest.java index ec11dac401..c3f5835cfe 100644 --- a/java/src/test/java/io/cucumber/java/JavaBackendTest.java +++ b/java/src/test/java/io/cucumber/java/JavaBackendTest.java @@ -3,11 +3,9 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.backend.StepDefinition; -import io.cucumber.core.exception.CucumberException; import io.cucumber.core.io.MultiLoader; import io.cucumber.core.io.ResourceLoader; -import io.cucumber.core.stepexpression.TypeRegistry; -import io.cucumber.java.stepdefs.Stepdefs; +import io.cucumber.java.steps.Steps; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -20,8 +18,6 @@ import java.net.URI; import java.util.List; -import java.util.Locale; -import java.util.function.Function; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; @@ -58,16 +54,16 @@ public void createBackend() { @Test public void finds_step_definitions_by_classpath_url() { - backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/stepdefs"))); + backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/steps"))); backend.buildWorld(); - verify(factory).addClass(Stepdefs.class); + verify(factory).addClass(Steps.class); } @Test public void detects_subclassed_glue_and_throws_exception() { - final Executable testMethod = () -> backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/stepdefs"), URI.create("classpath:io/cucumber/java/incorrectlysubclassedstepdefs"))); - final CucumberException expectedThrown = assertThrows(CucumberException.class, testMethod); - assertThat(expectedThrown.getMessage(), is(equalTo("You're not allowed to extend classes that define Step Definitions or hooks. class io.cucumber.java.incorrectlysubclassedstepdefs.SubclassesStepdefs extends class io.cucumber.java.stepdefs.Stepdefs"))); + final Executable testMethod = () -> backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/steps"), URI.create("classpath:io/cucumber/java/incorrectlysubclassedsteps"))); + final InvalidMethodException expectedThrown = assertThrows(InvalidMethodException.class, testMethod); + assertThat(expectedThrown.getMessage(), is(equalTo("You're not allowed to extend classes that define Step Definitions or hooks. class io.cucumber.java.incorrectlysubclassedsteps.SubclassesSteps extends class io.cucumber.java.steps.Steps"))); } @Test diff --git a/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java new file mode 100644 index 0000000000..07914edf9f --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDataTableTypeDefinitionTest.java @@ -0,0 +1,146 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.datatable.DataTable; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDataTableTypeDefinitionTest { + + private final Lookup lookup = new Lookup() { + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDataTableTypeDefinitionTest.this; + } + }; + + private final DataTable dataTable = DataTable.create(asList( + asList("a", "b"), + asList("c", "d") + )); + + @Test + public void can_define_data_table_converter() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("convert_data_table_to_string", DataTable.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), is("convert_data_table_to_string")); + } + + public String convert_data_table_to_string(DataTable table) { + return "convert_data_table_to_string"; + } + + @Test + public void can_define_table_row_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("convert_table_row_to_string", List.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), + is(asList("convert_table_row_to_string", "convert_table_row_to_string"))); + } + + public String convert_table_row_to_string(List row) { + return "convert_table_row_to_string"; + } + + @Test + public void can_define_table_entry_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_table_entry_to_string", Map.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), + is(singletonList("converts_table_entry_to_string"))); + } + + public String converts_table_entry_to_string(Map entry) { + return "converts_table_entry_to_string"; + } + + @Test + public void can_define_table_cell_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_table_cell_to_string", String.class); + JavaDataTableTypeDefinition definition = new JavaDataTableTypeDefinition(method, lookup); + assertThat(definition.dataTableType().transform(dataTable.asLists()), is(asList( + asList("converts_table_cell_to_string", "converts_table_cell_to_string"), + asList("converts_table_cell_to_string", "converts_table_cell_to_string")) + )); + } + + public String converts_table_cell_to_string(String cell) { + return "converts_table_cell_to_string"; + } + + @Test + public void target_type_must_class_type() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_datatable_to_optional_string", DataTable.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DataTableType annotated method must have one of these signatures:\n" + + " * public Author author(DataTable table)\n" + + " * public Author author(List row)\n" + + " * public Author author(Map entry)\n" + + " * public Author author(String cell)\n" + + "at io.cucumber.java.JavaDataTableTypeDefinitionTest.converts_datatable_to_optional_string(DataTable) in")); + } + + public Optional converts_datatable_to_optional_string(DataTable table) { + return Optional.of("converts_datatable_to_optional_string"); + } + + @Test + public void target_type_must_not_be_void() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_data_table_to_void", DataTable.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public void converts_data_table_to_void(DataTable table) { + } + + @Test + public void must_have_exactly_one_argument() throws NoSuchMethodException { + Method noArgs = JavaDataTableTypeDefinitionTest.class.getMethod("converts_nothing_to_string"); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(noArgs, lookup)); + Method twoArgs = JavaDataTableTypeDefinitionTest.class.getMethod("converts_two_strings_to_string", String.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(twoArgs, lookup)); + } + + public String converts_nothing_to_string() { + return "converts_object_to_string"; + } + + public String converts_two_strings_to_string(String arg1, String arg2) { + return "converts_two_strings_to_string"; + } + + @Test + public void argument_must_match_existing_transformer() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_object_to_string", Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public String converts_object_to_string(Object string) { + return "converts_object_to_string"; + } + + @Test + public void table_entry_transformer_must_have_map_of_strings() throws NoSuchMethodException { + Method method = JavaDataTableTypeDefinitionTest.class.getMethod("converts_map_of_objects_to_string", Map.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDataTableTypeDefinition(method, lookup)); + } + + public String converts_map_of_objects_to_string(Map entry) { + return "converts_map_of_objects_to_string"; + } + + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java new file mode 100644 index 0000000000..5ccadb0b0d --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableCellTransformerDefinitionTest.java @@ -0,0 +1,102 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultDataTableCellTransformerDefinitionTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultDataTableCellTransformerDefinitionTest.this; + } + }; + + @Test + public void can_transform_string_to_type() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transform_string_to_type", String.class, Type.class); + JavaDefaultDataTableCellTransformerDefinition definition = new JavaDefaultDataTableCellTransformerDefinition(method, lookup); + String transformed = definition.tableCellByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_string_to_type")); + } + + public Object transform_string_to_type(String fromValue, Type toValueType) { + return "transform_string_to_type"; + } + + @Test + public void can_transform_object_to_type() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transform_object_to_type", Object.class, Type.class); + JavaDefaultDataTableCellTransformerDefinition definition = new JavaDefaultDataTableCellTransformerDefinition(method, lookup); + String transformed = definition.tableCellByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_object_to_type")); + } + + public Object transform_object_to_type(Object fromValue, Type toValueType) { + return "transform_object_to_type"; + } + + @Test + public void must_have_non_void_return() throws Throwable { + Method method = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("transforms_string_to_void", String.class, Type.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DefaultDataTableCellTransformer annotated method must have one of these signatures:\n" + + " * public Object defaultDataTableCell(String fromValue, Type toValueType)\n" + + " * public Object defaultDataTableCell(Object fromValue, Type toValueType)\n" + + "at io.cucumber.java.JavaDefaultDataTableCellTransformerDefinitionTest.transforms_string_to_void(String,Type) in" + )); + } + + public void transforms_string_to_void(String fromValue, Type toValueType) { + } + + @Test + public void must_have_two_arguments() throws Throwable { + Method oneArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("one_argument", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(oneArg, lookup)); + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("three_arguments", String.class, Type.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + public Object one_argument(String fromValue) { + return "one_arguments"; + } + + public Object three_arguments(String fromValue, Type toValueType, Object extra) { + return "three_arguments"; + } + + @Test + public void must_have_string_or_object_as_from_value() throws Throwable { + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("map_as_from_value", Map.class, Type.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + + public Object map_as_from_value(Map fromValue, Type toValueType) { + return "map_as_from_value"; + } + + @Test + public void must_have_type_as_to_value_type() throws Throwable { + Method threeArg = JavaDefaultDataTableCellTransformerDefinitionTest.class.getMethod("object_as_to_value_type", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableCellTransformerDefinition(threeArg, lookup)); + } + + public Object object_as_to_value_type(String fromValue, Object toValueType) { + return "object_as_to_value_type"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java new file mode 100644 index 0000000000..066c3fe7ff --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultDataTableEntryTransformerDefinitionTest.java @@ -0,0 +1,147 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.datatable.TableCellByTypeTransformer; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Collections.singletonMap; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultDataTableEntryTransformerDefinitionTest { + + private final Map fromValue = singletonMap("key", "value"); + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultDataTableEntryTransformerDefinitionTest.this; + } + }; + + private TableCellByTypeTransformer cellTransformer = new TableCellByTypeTransformer() { + @Override + public T transform(String value, Class cellType) { + throw new IllegalStateException(); + } + }; + + @Test + public void transforms_with_correct_method() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("correct_method", Map.class, Class.class); + JavaDefaultDataTableEntryTransformerDefinition definition = + new JavaDefaultDataTableEntryTransformerDefinition(method, lookup); + + assertThat(definition.tableEntryByTypeTransformer() + .transform(fromValue, String.class, cellTransformer), is("key=value")); + + } + + public T correct_method(Map fromValue, Class toValue) { + return join(fromValue); + } + + + @Test + public void transforms_with_correct_method_with_cell_transformer() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("correct_method_with_cell_transformer", Map.class, Class.class, TableCellByTypeTransformer.class); + JavaDefaultDataTableEntryTransformerDefinition definition = + new JavaDefaultDataTableEntryTransformerDefinition(method, lookup); + + assertThat(definition.tableEntryByTypeTransformer() + .transform(fromValue, String.class, cellTransformer), is("key=value")); + + } + + + public T correct_method_with_cell_transformer(Map fromValue, Class toValue, TableCellByTypeTransformer cellTransformer) { + return join(fromValue); + } + + @Test + public void method_must_have_2_or_3_arguments() throws Throwable { + Method toFew = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("one_argument", Map.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(toFew, lookup)); + Method toMany = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("four_arguments", Map.class, String.class, String.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(toMany, lookup)); + } + + public T one_argument(Map fromValue) { + return null; + } + + + public T four_arguments(Map fromValue, String one, String two, String three) { + return null; + } + + + @Test + public void method_must_have_return_type() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("void_return_type", Map.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + public void void_return_type(Map fromValue, Class toValue) { + } + + + @Test + public void method_must_have_map_as_first_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", String.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + Method method2 = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", List.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method2, lookup)); + Method method3 = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_first_type", Map.class, Class.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method3, lookup)); + } + + + public T invalid_first_type(String fromValue, Class toValue) { + return null; + } + + public T invalid_first_type(List fromValue, Class toValue) { + return null; + } + + public T invalid_first_type(Map fromValue, Class toValue) { + return null; + } + + @Test + public void method_must_have_class_as_second_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_second_type", Map.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + public T invalid_second_type(Map fromValue, String toValue) { + return null; + } + + + @Test + public void method_must_have_cell_transformer_as_optional_third_argument() throws Throwable { + Method method = JavaDefaultDataTableEntryTransformerDefinitionTest.class.getMethod("invalid_optional_third_type", Map.class, Class.class, String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultDataTableEntryTransformerDefinition(method, lookup)); + } + + + public T invalid_optional_third_type(Map fromValue, Class toValue, String cellTransformer) { + return null; + } + + private static T join(Map fromValue) { + //noinspection unchecked + return (T) fromValue.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining()); + } + + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java new file mode 100644 index 0000000000..0e029df54c --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaDefaultParameterTransformerDefinitionTest.java @@ -0,0 +1,102 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaDefaultParameterTransformerDefinitionTest { + + private final Lookup lookup = new Lookup() { + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaDefaultParameterTransformerDefinitionTest.this; + } + }; + + @Test + public void can_transform_string_to_type() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transform_string_to_type", String.class, Type.class); + JavaDefaultParameterTransformerDefinition definition = new JavaDefaultParameterTransformerDefinition(method, lookup); + Object transformed = definition.parameterByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_string_to_type")); + } + + public Object transform_string_to_type(String fromValue, Type toValueType) { + return "transform_string_to_type"; + } + + @Test + public void can_transform_object_to_type() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transform_object_to_type", Object.class, Type.class); + JavaDefaultParameterTransformerDefinition definition = new JavaDefaultParameterTransformerDefinition(method, lookup); + String transformed = (String) definition.parameterByTypeTransformer().transform("something", String.class); + assertThat(transformed, is("transform_object_to_type")); + } + + public Object transform_object_to_type(Object fromValue, Type toValueType) { + return "transform_object_to_type"; + } + + @Test + public void must_have_non_void_return() throws Throwable { + Method method = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("transforms_string_to_void", String.class, Type.class); + InvalidMethodSignatureException exception = assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(method, lookup)); + assertThat(exception.getMessage(), startsWith("" + + "A @DefaultParameterTransformer annotated method must have one of these signatures:\n" + + " * public Object defaultDataTableEntry(String fromValue, Type toValueType)\n" + + " * public Object defaultDataTableEntry(Object fromValue, Type toValueType)\n" + + "at io.cucumber.java.JavaDefaultParameterTransformerDefinitionTest.transforms_string_to_void(String,Type) in" + )); + } + + public void transforms_string_to_void(String fromValue, Type toValueType) { + } + + @Test + public void must_have_two_arguments() throws Throwable { + Method oneArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("one_argument", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(oneArg, lookup)); + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("three_arguments", String.class, Type.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + public Object one_argument(String fromValue) { + return "one_arguments"; + } + + public Object three_arguments(String fromValue, Type toValueType, Object extra) { + return "three_arguments"; + } + + @Test + public void must_have_string_or_object_as_from_value() throws Throwable { + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("map_as_from_value", Map.class, Type.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + + public Object map_as_from_value(Map fromValue, Type toValueType) { + return "map_as_from_value"; + } + + @Test + public void must_have_type_as_to_value_type() throws Throwable { + Method threeArg = JavaDefaultParameterTransformerDefinitionTest.class.getMethod("object_as_to_value_type", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaDefaultParameterTransformerDefinition(threeArg, lookup)); + } + + public Object object_as_to_value_type(String fromValue, Object toValueType) { + return "object_as_to_value_type"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaHookTest.java b/java/src/test/java/io/cucumber/java/JavaHookTest.java index 65c41c40d6..cbe20ae624 100644 --- a/java/src/test/java/io/cucumber/java/JavaHookTest.java +++ b/java/src/test/java/io/cucumber/java/JavaHookTest.java @@ -4,27 +4,29 @@ import gherkin.pickles.PickleTag; import io.cucumber.core.api.Scenario; import io.cucumber.core.backend.Glue; -import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.io.ClassFinder; import io.cucumber.core.io.MultiLoader; import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.io.ResourceLoaderClassFinder; -import io.cucumber.core.stepexpression.TypeRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.lang.reflect.Method; -import java.net.URI; import java.util.Collections; -import java.util.Locale; +import java.util.List; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; @@ -46,6 +48,10 @@ public class JavaHookTest { private static final Method BEFORESTEP; private static final Method AFTERSTEP; private static final Method BAD_AFTER; + private static final Method BAD_GENERIC_AFTER; + private static final Method BAD_MULTIPLE; + private static final Method SINGLE_ARG; + private static final Method ZERO_ARG; static { try { @@ -54,49 +60,44 @@ public class JavaHookTest { BEFORESTEP = HasHooks.class.getMethod("beforeStep"); AFTERSTEP = HasHooks.class.getMethod("afterStep"); BAD_AFTER = BadHook.class.getMethod("after", String.class); - } catch (NoSuchMethodException note) { - throw new InternalError("dang"); + BAD_GENERIC_AFTER = BadGenericHook.class.getMethod("after", List.class); + BAD_MULTIPLE = BadHookMultipleArgs.class.getMethod("after", Scenario.class, String.class); + SINGLE_ARG = SingleArg.class.getMethod("after", Scenario.class); + ZERO_ARG = ZeroArg.class.getMethod("after"); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); } } @Mock private Glue glue; - private JavaBackend backend; - - private SingletonFactory objectFactory = new SingletonFactory(); + private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + private final ResourceLoader resourceLoader = new MultiLoader(classLoader); + private final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); + private final SingletonFactory objectFactory = new SingletonFactory(); + private final JavaBackend backend = new JavaBackend(objectFactory, objectFactory, classFinder); @org.junit.Before public void createBackendAndLoadNoGlue() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - ResourceLoader resourceLoader = new MultiLoader(classLoader); - ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - this.backend = new JavaBackend(objectFactory, objectFactory, classFinder); - backend.loadGlue(glue, Collections.emptyList()); + backend.loadGlue(glue, Collections.emptyList()); } @Test public void before_hooks_get_registered() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); verify(glue).addBeforeHook(argThat(isHookFor(BEFORE))); } private static ArgumentMatcher isHookFor(final Method method) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition javaHookDefinition) { - return method.equals(javaHookDefinition.getMethod()); - } - }; + return javaHookDefinition -> method.equals(javaHookDefinition.getMethod()); } @Test public void before_step_hooks_get_registered() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(BEFORESTEP.getAnnotation(BeforeStep.class), BEFORESTEP); verify(glue).addBeforeStepHook(argThat(isHookFor(BEFORESTEP))); @@ -105,7 +106,6 @@ public void before_step_hooks_get_registered() { @Test public void after_step_hooks_get_registered() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(AFTERSTEP.getAnnotation(AfterStep.class), AFTERSTEP); verify(glue).addAfterStepHook(argThat(isHookFor(AFTERSTEP))); } @@ -122,19 +122,13 @@ public void after_hooks_get_registered() { @Test public void hook_order_gets_registered() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(AFTER.getAnnotation(After.class), AFTER); verify(glue).addAfterHook(argThat(isHookWithOrder(1))); } private static ArgumentMatcher isHookWithOrder(final int order) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition argument) { - return argument.getOrder() == order; - } - }; + return argument -> argument.getOrder() == order; } @Test @@ -148,42 +142,21 @@ public void hook_with_no_order_is_last() { @Test public void matches_matching_tags() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); verify(glue).addBeforeHook(argThat(isHookThatMatches(pickleTagBar, pickleTagZap))); } private static ArgumentMatcher isHookThatMatches(final PickleTag... pickleTag) { - return new ArgumentMatcher() { - @Override - public boolean matches(JavaHookDefinition argument) { - return argument.matches(asList(pickleTag)); - } - }; + return argument -> argument.matches(asList(pickleTag)); } @Test public void does_not_match_non_matching_tags() { objectFactory.setInstance(new HasHooks()); - backend.buildWorld(); backend.addHook(BEFORE.getAnnotation(Before.class), BEFORE); verify(glue).addBeforeHook(not(argThat(isHookThatMatches(pickleTagBar)))); } - @Test - public void fails_if_hook_argument_is_not_scenario_result() throws Throwable { - objectFactory.setInstance(new BadHook()); - backend.buildWorld(); - backend.addHook(BAD_AFTER.getAnnotation(After.class), BAD_AFTER); - - ArgumentCaptor javaHookDefinitionArgumentCaptor = ArgumentCaptor.forClass(JavaHookDefinition.class); - verify(glue).addAfterHook(javaHookDefinitionArgumentCaptor.capture()); - - HookDefinition bad = javaHookDefinitionArgumentCaptor.getValue(); - expectedException.expectMessage("When a hook declares an argument it must be of type io.cucumber.core.api.Scenario. public void io.cucumber.java.JavaHookTest$BadHook.after(java.lang.String)"); - bad.execute(mock(Scenario.class)); - } - public static class HasHooks { @Before("(@foo or @bar) and @zap") @@ -207,10 +180,106 @@ public void after() { } } + @Test + public void fails_if_hook_argument_is_not_scenario_result() { + objectFactory.setInstance(new BadHook()); + InvalidMethodSignatureException cucumberException = assertThrows( + InvalidMethodSignatureException.class, + () -> backend.addHook(BAD_AFTER.getAnnotation(After.class), BAD_AFTER) + ); + assertThat(cucumberException.getMessage(), startsWith("" + + "A method annotated with Before, After, BeforeStep or AfterStep must have one of these signatures:\n" + + " * public void before_or_after(Scenario scenario)\n" + + " * public void before_or_after()\n" + + "at io.cucumber.java.JavaHookTest$BadHook.after(String) in file:")); + } + public static class BadHook { @After public void after(String badType) { } } + + @Test + public void fails_if_generic_hook_argument_is_not_scenario_result() { + objectFactory.setInstance(new BadGenericHook()); + InvalidMethodSignatureException exception = assertThrows( + InvalidMethodSignatureException.class, + () -> backend.addHook(BAD_GENERIC_AFTER.getAnnotation(After.class), BAD_GENERIC_AFTER) + ); + assertThat(exception.getMessage(), startsWith("" + + "A method annotated with Before, After, BeforeStep or AfterStep must have one of these signatures:\n" + + " * public void before_or_after(Scenario scenario)\n" + + " * public void before_or_after()\n" + + "at io.cucumber.java.JavaHookTest$BadGenericHook.after(List) in file:")); + } + + public static class BadGenericHook { + @After + public void after(List badType) { + + } + } + + @Test + public void fails_if_too_many_arguments() { + objectFactory.setInstance(new BadGenericHook()); + InvalidMethodSignatureException exception = assertThrows( + InvalidMethodSignatureException.class, + () -> backend.addHook(BAD_MULTIPLE.getAnnotation(After.class), BAD_MULTIPLE) + ); + assertThat(exception.getMessage(), startsWith("" + + "A method annotated with Before, After, BeforeStep or AfterStep must have one of these signatures:\n" + + " * public void before_or_after(Scenario scenario)\n" + + " * public void before_or_after()\n" + + "at io.cucumber.java.JavaHookTest$BadHookMultipleArgs.after(Scenario,String) in file:")); + } + + public static class BadHookMultipleArgs { + @After + public void after(Scenario arg1, String arg2) { + + } + } + + @Test + public void invokes_hook_with_zero_arguments() throws Throwable { + ZeroArg singleArg = new ZeroArg(); + SingletonFactory objectFactory = new SingletonFactory(singleArg); + JavaHookDefinition hook = new JavaHookDefinition(ZERO_ARG, "", 0, 0, objectFactory); + Scenario scenario = Mockito.mock(Scenario.class); + hook.execute(scenario); + assertTrue(objectFactory.getInstance(ZeroArg.class).invoked); + } + + public static class ZeroArg { + + boolean invoked; + + @After + public void after() { + this.invoked = true; + } + } + + @Test + public void invokes_hook_with_one_arguments() throws Throwable { + SingleArg singleArg = new SingleArg(); + SingletonFactory objectFactory = new SingletonFactory(singleArg); + JavaHookDefinition hook = new JavaHookDefinition(SINGLE_ARG, "", 0, 0, objectFactory); + Scenario scenario = Mockito.mock(Scenario.class); + hook.execute(scenario); + assertThat(objectFactory.getInstance(SingleArg.class).scenario, is(scenario)); + } + + public static class SingleArg { + + Scenario scenario; + + @After + public void after(Scenario scenario) { + this.scenario = scenario; + } + } } diff --git a/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java new file mode 100644 index 0000000000..99d14d85b2 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/JavaParameterTypeDefinitionTest.java @@ -0,0 +1,133 @@ +package io.cucumber.java; + +import io.cucumber.core.backend.Lookup; +import io.cucumber.cucumberexpressions.Argument; +import io.cucumber.cucumberexpressions.CucumberExpression; +import io.cucumber.cucumberexpressions.CucumberExpressionException; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JavaParameterTypeDefinitionTest { + + private final Lookup lookup = new Lookup() { + @Override + @SuppressWarnings("unchecked") + public T getInstance(Class glueClass) { + return (T) JavaParameterTypeDefinitionTest.this; + } + }; + + private final ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH); + + @Test + public void can_define_parameter_type_converters_with_one_capture_group() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_one_capture_group_to_string", String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_one_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test"); + assertThat(test.get(0).getValue(), equalTo("convert_one_capture_group_to_string")); + } + + public String convert_one_capture_group_to_string(String all) { + return "convert_one_capture_group_to_string"; + } + + @Test + public void can_define_parameter_type_converters_with_two_capture_groups() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_two_capture_group_to_string", String.class, String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "([^ ]*) ([^ ]*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_two_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test test"); + assertThat(test.get(0).getValue(), equalTo("convert_two_capture_group_to_string")); + } + + public String convert_two_capture_group_to_string(String captureGroup1, String captureGroup2) { + return "convert_two_capture_group_to_string"; + } + + @Test + public void can_define_parameter_type_converters_with_var_args() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_varargs_capture_group_to_string", String[].class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", "([^ ]*) ([^ ]*)", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_varargs_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test test"); + assertThat(test.get(0).getValue(), equalTo("convert_varargs_capture_group_to_string")); + } + + public String convert_varargs_capture_group_to_string(String... captureGroups) { + return "convert_varargs_capture_group_to_string"; + } + + @Test + public void arguments_must_match_captured_groups() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_two_capture_group_to_string", String.class, String.class); + JavaParameterTypeDefinition definition = new JavaParameterTypeDefinition("", ".*", method, false, false, lookup); + registry.defineParameterType(definition.parameterType()); + CucumberExpression cucumberExpression = new CucumberExpression("{convert_two_capture_group_to_string}", registry); + List> test = cucumberExpression.match("test"); + assertThrows(CucumberExpressionException.class, () -> test.get(0).getValue()); + } + + + @Test + public void converter_must_have_return_type() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_capture_group_to_void", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public void convert_capture_group_to_void(String all) { + } + + @Test + public void converter_must_have_non_generic_return_type() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_capture_group_to_optional_string", String.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public Optional convert_capture_group_to_optional_string(String all) { + return Optional.of("convert_capture_group_to_optional_string"); + } + + @Test + public void converter_must_have_at_least_one_argument() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("convert_nothing_to_string"); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String convert_nothing_to_string() { + return "convert_nothing_to_string"; + } + + @Test + public void converter_must_have_string_arguments() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("converts_object_to_string", Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String converts_object_to_string(Object other) { + return "converts_object_to_string"; + } + + @Test + public void converter_must_have_all_string_arguments() throws NoSuchMethodException { + Method method = JavaParameterTypeDefinitionTest.class.getMethod("converts_objects_to_string", String.class, Object.class); + assertThrows(InvalidMethodSignatureException.class, () -> new JavaParameterTypeDefinition("", "(.*)", method, false, false, lookup)); + } + + public String converts_objects_to_string(String all, Object other) { + return "converts_object_to_string"; + } + +} \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java index 52ae39f414..034fb4d67e 100644 --- a/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java +++ b/java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java @@ -1,48 +1,33 @@ package io.cucumber.java; +import gherkin.events.PickleEvent; +import gherkin.pickles.*; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.event.Result; -import io.cucumber.core.event.EventHandler; import io.cucumber.core.event.TestStepFinished; -import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.java.en.Given; -import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.eventbus.EventBus; -import io.cucumber.core.runner.Runner; -import io.cucumber.core.runner.AmbiguousStepDefinitionsException; -import io.cucumber.core.backend.Backend; -import io.cucumber.core.runtime.BackendSupplier; -import io.cucumber.core.backend.DuplicateStepDefinitionException; -import io.cucumber.core.options.RuntimeOptions; -import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.io.MultiLoader; import io.cucumber.core.io.ResourceLoader; -import gherkin.events.PickleEvent; -import gherkin.pickles.Argument; -import gherkin.pickles.Pickle; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTag; +import io.cucumber.core.options.RuntimeOptions; +import io.cucumber.core.runner.AmbiguousStepDefinitionsException; +import io.cucumber.core.runner.Runner; +import io.cucumber.core.runtime.*; +import io.cucumber.java.en.Given; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.function.Executable; import java.lang.reflect.Method; import java.time.Clock; -import java.util.Collection; import java.util.Collections; -import java.util.Locale; - -import io.cucumber.core.stepexpression.TypeRegistry; -import org.junit.jupiter.api.function.Executable; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringStartsWith.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -57,7 +42,7 @@ public class JavaStepDefinitionTest { THREE_DISABLED_MICE = Defs.class.getMethod("threeDisabledMice", String.class); THREE_BLIND_ANIMALS = Defs.class.getMethod("threeBlindAnimals", String.class); } catch (NoSuchMethodException e) { - throw new InternalError("dang"); + throw new IllegalStateException(e); } } @@ -71,31 +56,30 @@ public void createBackendAndLoadNoGlue() { ClassLoader classLoader = currentThread().getContextClassLoader(); ResourceLoader resourceLoader = new MultiLoader(classLoader); ObjectFactory objectFactory = new SingletonFactory(defs); - TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); this.backend = new JavaBackend(objectFactory, objectFactory, resourceLoader); RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); EventBus bus = new TimeServiceEventBus(Clock.systemUTC()); - BackendSupplier backendSupplier = new BackendSupplier() { - @Override - public Collection get() { - return asList(backend); - } + BackendSupplier backendSupplier = () -> asList(backend); + ObjectFactorySupplier objectFactorySupplier = () -> objectFactory; + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = () -> typeRegistry -> { }; - this.runner = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, () -> objectFactory, () -> typeRegistry).get(); - - bus.registerHandlerFor(TestStepFinished.class, new EventHandler() { - @Override - public void receive(TestStepFinished event) { - latestReceivedResult = event.getResult(); - } - }); + this.runner = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier).get(); + + bus.registerHandlerFor(TestStepFinished.class, event -> latestReceivedResult = event.getResult()); } @Test public void throws_duplicate_when_two_stepdefs_with_same_regexp_found() { backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_DISABLED_MICE); - final Executable testMethod = () -> backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_BLIND_ANIMALS); - final DuplicateStepDefinitionException expectedThrown = assertThrows(DuplicateStepDefinitionException.class, testMethod); + backend.addStepDefinition(THREE_BLIND_ANIMALS.getAnnotation(Given.class), THREE_BLIND_ANIMALS); + + PickleTag tag = new PickleTag(mock(PickleLocation.class), "@foo"); + PickleStep step = new PickleStep("three blind mice", Collections.emptyList(), asList(mock(PickleLocation.class))); + Pickle pickle = new Pickle("pickle name", ENGLISH, asList(step), asList(tag), asList(mock(PickleLocation.class))); + PickleEvent pickleEvent = new PickleEvent("uri", pickle); + + final Executable testMethod = () -> runner.runPickle(pickleEvent); + final CucumberException expectedThrown = assertThrows(CucumberException.class, testMethod); assertThat(expectedThrown.getMessage(), is(startsWith("Duplicate step definitions in io.cucumber.java.JavaStepDefinitionTest$Defs.threeDisabledMice(String) in file:"))); } diff --git a/java/src/test/java/io/cucumber/java/MethodScannerTest.java b/java/src/test/java/io/cucumber/java/MethodScannerTest.java index f2e69b7806..b2a69f8dd0 100644 --- a/java/src/test/java/io/cucumber/java/MethodScannerTest.java +++ b/java/src/test/java/io/cucumber/java/MethodScannerTest.java @@ -2,7 +2,6 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.exception.CucumberException; import io.cucumber.core.io.MultiLoader; import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.io.ResourceLoaderClassFinder; @@ -18,11 +17,10 @@ import java.util.Collections; import static java.lang.Thread.currentThread; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; public class MethodScannerTest { @@ -59,23 +57,20 @@ public void loadGlue_registers_the_methods_declaring_class_in_the_object_factory @Test public void loadGlue_fails_when_class_is_not_method_declaring_class() throws NoSuchMethodException { MethodScanner methodScanner = new MethodScanner(classFinder); - try { - methodScanner.scan(backend, BaseStepDefs.class.getMethod("m"), Stepdefs2.class); - fail(); - } catch (CucumberException e) { - assertEquals("You're not allowed to extend classes that define Step Definitions or hooks. class io.cucumber.java.MethodScannerTest$Stepdefs2 extends class io.cucumber.java.MethodScannerTest$BaseStepDefs", e.getMessage()); - } + InvalidMethodException exception = assertThrows(InvalidMethodException.class, () -> methodScanner.scan(backend, BaseStepDefs.class.getMethod("m"), Stepdefs2.class)); + assertThat(exception.getMessage(), is( + "You're not allowed to extend classes that define Step Definitions or hooks. " + + "class io.cucumber.java.MethodScannerTest$Stepdefs2 extends class io.cucumber.java.MethodScannerTest$BaseStepDefs" + )); } @Test public void loadGlue_fails_when_class_is_not_subclass_of_declaring_class() throws NoSuchMethodException { MethodScanner methodScanner = new MethodScanner(classFinder); - try { - methodScanner.scan(backend, BaseStepDefs.class.getMethod("m"), String.class); - fail(); - } catch (CucumberException e) { - assertEquals("class io.cucumber.java.MethodScannerTest$BaseStepDefs isn't assignable from class java.lang.String", e.getMessage()); - } + InvalidMethodException exception = assertThrows(InvalidMethodException.class, () -> methodScanner.scan(backend, BaseStepDefs.class.getMethod("m"), String.class)); + assertThat(exception.getMessage(), is( + "class io.cucumber.java.MethodScannerTest$BaseStepDefs isn't assignable from class java.lang.String" + )); } public static class Stepdefs2 extends BaseStepDefs { diff --git a/java/src/test/java/io/cucumber/java/annotation/Authors.java b/java/src/test/java/io/cucumber/java/annotation/Authors.java deleted file mode 100644 index 4023b23aea..0000000000 --- a/java/src/test/java/io/cucumber/java/annotation/Authors.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.cucumber.java.annotation; - -import io.cucumber.java.Transpose; -import io.cucumber.java.en.Given; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class Authors { - - private final Author expected = new Author("Annie M. G.", "Schmidt", "1911-03-20"); - - @Given("a list of authors in a table") - public void aListOfAuthorsInATable(List authors) throws Throwable { - assertTrue(authors.contains(expected)); - } - - @Given("a list of authors in a transposed table") - public void aListOfAuthorsInATransposedTable(@Transpose List authors) throws Throwable { - assertTrue(authors.contains(expected)); - } - - @Given("a single author in a table") - public void aSingleAuthorInATable(Author author) throws Throwable { - assertEquals(expected, author); - } - - @Given("a single author in a transposed table") - public void aSingleAuthorInATransposedTable(@Transpose Author author) throws Throwable { - assertEquals(expected, author); - } - - public static class Author { - final String firstName; - final String lastName; - final String birthDate; - - Author(String firstName, String lastName, String birthDate) { - this.firstName = firstName; - this.lastName = lastName; - this.birthDate = birthDate; - } - - @Override - public String toString() { - return "Author{" + - "firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", birthDate='" + birthDate + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Author author = (Author) o; - - if (!firstName.equals(author.firstName)) return false; - if (!lastName.equals(author.lastName)) return false; - return birthDate.equals(author.birthDate); - } - - @Override - public int hashCode() { - int result = firstName.hashCode(); - result = 31 * result + lastName.hashCode(); - result = 31 * result + birthDate.hashCode(); - return result; - } - } -} diff --git a/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java b/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java new file mode 100644 index 0000000000..c8bdaf5498 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/DataTableSteps.java @@ -0,0 +1,132 @@ +package io.cucumber.java.annotation; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.DataTableType; +import io.cucumber.java.Transpose; +import io.cucumber.java.en.Given; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class DataTableSteps { + + private final Author expectedAuthor = new Author("Annie M. G.", "Schmidt", "1911-03-20"); + private final Person expectedPerson = new Person("Astrid", "Lindgren"); + + @DataTableType + public Author authorEntryTransformer(Map entry) { + return new DataTableSteps.Author( + entry.get("firstName"), + entry.get("lastName"), + entry.get("birthDate")); + } + + @DataTableType + public Author singleAuthorTransformer(DataTable table) { + return authorEntryTransformer(table.asMaps().get(0)); + } + + @Given("a list of authors in a table") + public void aListOfAuthorsInATable(List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a list of authors in a transposed table") + public void aListOfAuthorsInATransposedTable(@Transpose List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a single author in a table") + public void aSingleAuthorInATable(Author author) { + assertEquals(expectedAuthor, author); + } + + @Given("a single author in a transposed table") + public void aSingleAuthorInATransposedTable(@Transpose Author author) { + assertEquals(expectedAuthor, author); + } + + public static class Author { + final String firstName; + final String lastName; + final String birthDate; + + Author(String firstName, String lastName, String birthDate) { + this.firstName = firstName; + this.lastName = lastName; + this.birthDate = birthDate; + } + + @Override + public String toString() { + return "Author{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate='" + birthDate + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + return birthDate.equals(author.birthDate); + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + result = 31 * result + birthDate.hashCode(); + return result; + } + } + + @Given("a list of people in a table") + public void this_table_of_authors(List persons) { + assertTrue(persons.contains(expectedPerson)); + } + + @DataTableType + public DataTableSteps.Person transform(Map tableEntry) { + return new Person(tableEntry.get("first"), tableEntry.get("last")); + } + + public static class Person { + private final String first; + private final String last; + + public Person(String first, String last) { + this.first = first; + this.last = last; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Person person = (Person) o; + return first.equals(person.first) && + last.equals(person.last); + } + + @Override + public int hashCode() { + return Objects.hash(first, last); + } + + + } + + +} diff --git a/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java b/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java new file mode 100644 index 0000000000..5393743503 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/FrenchSteps.java @@ -0,0 +1,21 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.fr.Étantdonné; + +import java.math.BigDecimal; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class FrenchSteps { + + @Étantdonné("j'ai {bigdecimal} concombres fractionnaires") + public void jAiConcombresFractionnaires(BigDecimal arg0) { + assertThat(arg0, is(new BigDecimal("5.5"))); + } + + @Étantdonné("j'ai {int} concombres") + public void jAiConcombres(int arg0) { + assertThat(arg0, is(5)); + } +} diff --git a/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java b/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java new file mode 100644 index 0000000000..a650b3a7bf --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/ParameterTypeSteps.java @@ -0,0 +1,28 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.ParameterType; +import io.cucumber.java.en.Given; +import org.junit.jupiter.api.Assertions; + +import java.time.LocalDate; + +public class ParameterTypeSteps { + + private final LocalDate expected = LocalDate.of(1907, 11, 14); + + @ParameterType("([0-9]{4})-([0-9]{2})-([0-9]{2})") + public LocalDate parameterTypeIso8601Date(String year, String month, String day) { + return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); + } + + @Given("today is {parameterTypeIso8601Date}") + public void today_is(LocalDate date) { + + Assertions.assertEquals(expected, date); + } + + @Given("tomorrow is {parameterTypeRegistryIso8601Date}") + public void tomorrow_is(LocalDate date) { + Assertions.assertEquals(expected, date); + } +} diff --git a/java/src/test/java/io/cucumber/java/annotation/ScenarioStepDefs.java b/java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java similarity index 96% rename from java/src/test/java/io/cucumber/java/annotation/ScenarioStepDefs.java rename to java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java index 8f14e02aef..a3d3c3eac0 100644 --- a/java/src/test/java/io/cucumber/java/annotation/ScenarioStepDefs.java +++ b/java/src/test/java/io/cucumber/java/annotation/ScenarioSteps.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertEquals; -public class ScenarioStepDefs { +public class ScenarioSteps { private String scenarioName = ""; diff --git a/java/src/test/java/io/cucumber/java/annotation/Stepdefs.java b/java/src/test/java/io/cucumber/java/annotation/Stepdefs.java deleted file mode 100644 index 6c5d99f38f..0000000000 --- a/java/src/test/java/io/cucumber/java/annotation/Stepdefs.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.cucumber.java.annotation; - -import io.cucumber.java.en.Given; - -import java.util.List; - -public class Stepdefs { - @Given("I have {int} cukes in the belly") - public void I_have_cukes_in_the_belly(int arg1) { - } - - @Given("this data table:") - public void this_data_table(List people) throws Throwable { - } - - public static class Person { - String first; - String last; - } -} diff --git a/java/src/test/java/io/cucumber/java/annotation/Steps.java b/java/src/test/java/io/cucumber/java/annotation/Steps.java new file mode 100644 index 0000000000..5fb8caac2f --- /dev/null +++ b/java/src/test/java/io/cucumber/java/annotation/Steps.java @@ -0,0 +1,11 @@ +package io.cucumber.java.annotation; + +import io.cucumber.java.en.Given; + +public class Steps { + + @Given("I have {int} cukes in the belly") + public void I_have_cukes_in_the_belly(int arg1) { + } + +} diff --git a/java/src/test/java/io/cucumber/java/annotation/SubstitutionStepdefs.java b/java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java similarity index 97% rename from java/src/test/java/io/cucumber/java/annotation/SubstitutionStepdefs.java rename to java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java index c87293a36d..190a983cc7 100644 --- a/java/src/test/java/io/cucumber/java/annotation/SubstitutionStepdefs.java +++ b/java/src/test/java/io/cucumber/java/annotation/SubstitutionSteps.java @@ -9,7 +9,7 @@ import static org.junit.Assert.assertEquals; -public class SubstitutionStepdefs { +public class SubstitutionSteps { private static final Map ROLES = new HashMap() {{ put("Manager", "now able to manage your employee accounts"); put("Admin", "able to manage any user account on the system"); diff --git a/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java b/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java index fd7dce76cc..8e1740902f 100644 --- a/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java +++ b/java/src/test/java/io/cucumber/java/annotation/TypeRegistryConfiguration.java @@ -1,63 +1,27 @@ package io.cucumber.java.annotation; -import io.cucumber.core.api.TypeRegistryConfigurer; import io.cucumber.core.api.TypeRegistry; -import io.cucumber.datatable.DataTable; -import io.cucumber.datatable.DataTableType; -import io.cucumber.datatable.TableEntryTransformer; -import io.cucumber.java.annotation.Authors.Author; -import io.cucumber.datatable.TableTransformer; - -import java.util.Locale; -import java.util.Map; +import io.cucumber.core.api.TypeRegistryConfigurer; +import io.cucumber.cucumberexpressions.CaptureGroupTransformer; +import io.cucumber.cucumberexpressions.ParameterType; -import static java.util.Locale.ENGLISH; +import java.time.LocalDate; public class TypeRegistryConfiguration implements TypeRegistryConfigurer { - - private final TableEntryTransformer personEntryTransformer = new TableEntryTransformer() { - @Override - public Stepdefs.Person transform(Map tableEntry) { - Stepdefs.Person person = new Stepdefs.Person(); - person.first = tableEntry.get("first"); - person.last = tableEntry.get("last"); - return person; - } - }; - private final TableEntryTransformer authorEntryTransformer = new TableEntryTransformer() { - @Override - public Author transform(Map tableEntry) { - return new Author( - tableEntry.get("firstName"), - tableEntry.get("lastName"), - tableEntry.get("birthDate")); - } - }; - private final TableTransformer singleAuthorTransformer = new TableTransformer() { - @Override - public Author transform(DataTable table) throws Throwable { - Map tableEntry = table.asMaps().get(0); - return authorEntryTransformer.transform(tableEntry); - } - }; - - @Override - public Locale locale() { - return ENGLISH; - } + private final CaptureGroupTransformer localDateParameterType = + (String[] args) -> LocalDate.of( + Integer.parseInt(args[0]), + Integer.parseInt(args[1]), + Integer.parseInt(args[2]) + ); @Override public void configureTypeRegistry(TypeRegistry typeRegistry) { - typeRegistry.defineDataTableType(new DataTableType( - Author.class, - authorEntryTransformer)); - - typeRegistry.defineDataTableType(new DataTableType( - Author.class, - singleAuthorTransformer)); - - typeRegistry.defineDataTableType(new DataTableType( - Stepdefs.Person.class, - personEntryTransformer)); + typeRegistry.defineParameterType(new ParameterType<>( + "parameterTypeRegistryIso8601Date", + "([0-9]{4})/([0-9]{2})/([0-9]{2})", + LocalDate.class, + localDateParameterType + )); } } diff --git a/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java b/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java new file mode 100644 index 0000000000..11734da0b1 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/defaultstransformer/DataTableSteps.java @@ -0,0 +1,117 @@ +package io.cucumber.java.defaultstransformer; + +import io.cucumber.datatable.dependency.com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.DefaultDataTableCellTransformer; +import io.cucumber.java.DefaultDataTableEntryTransformer; +import io.cucumber.java.DefaultParameterTransformer; +import io.cucumber.java.Transpose; +import io.cucumber.java.en.Given; +import org.hamcrest.CoreMatchers; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.util.Currency; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class DataTableSteps { + + private final Author expectedAuthor = new Author("Annie M. G.", "Schmidt", "1911-03-20"); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @DefaultParameterTransformer + @DefaultDataTableEntryTransformer + @DefaultDataTableCellTransformer + public Object defaultTransformer(Object fromValue, Type toValueType) { + return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType)); + } + + @Given("a list of authors in a table") + public void aListOfAuthorsInATable(List authors) { + assertTrue(authors.contains(expectedAuthor)); + } + + @Given("a single currency in a table") + public void aSingleCurrencyInATable(Currency currency) { + assertThat(currency, is(Currency.getInstance("EUR"))); + } + + @Given("a currency in a parameter {}") + public void aCurrencyInAParameter(Currency currency) { + assertThat(currency, is(Currency.getInstance("EUR"))); + } + + + public static class Author { + String firstName; + String lastName; + String birthDate; + + Author() { + } + + public Author(String firstName, String lastName, String birthDate) { + this.firstName = firstName; + this.lastName = lastName; + this.birthDate = birthDate; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getBirthDate() { + return birthDate; + } + + public void setBirthDate(String birthDate) { + this.birthDate = birthDate; + } + + @Override + public String toString() { + return "Author{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate='" + birthDate + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + return birthDate.equals(author.birthDate); + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + result = 31 * result + birthDate.hashCode(); + return result; + } + } + +} diff --git a/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java b/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java new file mode 100644 index 0000000000..91c08d3924 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/defaultstransformer/RunCucumberTest.java @@ -0,0 +1,8 @@ +package io.cucumber.java.defaultstransformer; + +import io.cucumber.junit.Cucumber; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +public class RunCucumberTest { +} diff --git a/java/src/test/java/io/cucumber/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java b/java/src/test/java/io/cucumber/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java deleted file mode 100644 index da629b3506..0000000000 --- a/java/src/test/java/io/cucumber/java/incorrectlysubclassedstepdefs/SubclassesStepdefs.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.cucumber.java.incorrectlysubclassedstepdefs; - -import io.cucumber.java.stepdefs.Stepdefs; - -public class SubclassesStepdefs extends Stepdefs { -} diff --git a/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java b/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java new file mode 100644 index 0000000000..b13228ab28 --- /dev/null +++ b/java/src/test/java/io/cucumber/java/incorrectlysubclassedsteps/SubclassesSteps.java @@ -0,0 +1,6 @@ +package io.cucumber.java.incorrectlysubclassedsteps; + +import io.cucumber.java.steps.Steps; + +public class SubclassesSteps extends Steps { +} diff --git a/java/src/test/java/io/cucumber/java/repeatable/Stepdefs.java b/java/src/test/java/io/cucumber/java/repeatable/Steps.java similarity index 86% rename from java/src/test/java/io/cucumber/java/repeatable/Stepdefs.java rename to java/src/test/java/io/cucumber/java/repeatable/Steps.java index 7f25d583d7..7fa5f3081a 100644 --- a/java/src/test/java/io/cucumber/java/repeatable/Stepdefs.java +++ b/java/src/test/java/io/cucumber/java/repeatable/Steps.java @@ -2,7 +2,7 @@ import io.cucumber.java.en.Given; -public class Stepdefs { +public class Steps { @Given("test") @Given("test again") diff --git a/java/src/test/java/io/cucumber/java/stepdefs/Stepdefs.java b/java/src/test/java/io/cucumber/java/steps/Steps.java similarity index 60% rename from java/src/test/java/io/cucumber/java/stepdefs/Stepdefs.java rename to java/src/test/java/io/cucumber/java/steps/Steps.java index e8e50648ab..109bdc4c89 100644 --- a/java/src/test/java/io/cucumber/java/stepdefs/Stepdefs.java +++ b/java/src/test/java/io/cucumber/java/steps/Steps.java @@ -1,8 +1,8 @@ -package io.cucumber.java.stepdefs; +package io.cucumber.java.steps; import io.cucumber.java.en.Given; -public class Stepdefs { +public class Steps { @Given("test") public void test() { diff --git a/java/src/test/resources/io/cucumber/java/annotation/authors.feature b/java/src/test/resources/io/cucumber/java/annotation/data-table.feature similarity index 63% rename from java/src/test/resources/io/cucumber/java/annotation/authors.feature rename to java/src/test/resources/io/cucumber/java/annotation/data-table.feature index 7b2c57d8d0..bcf1b280a6 100644 --- a/java/src/test/resources/io/cucumber/java/annotation/authors.feature +++ b/java/src/test/resources/io/cucumber/java/annotation/data-table.feature @@ -1,6 +1,6 @@ -Feature: Authors and tables +Feature: Datatable - Scenario: Some authors and tables + Scenario: Convert a table to a generic list via the ParameterTypeRegistry Given a list of authors in a table | firstName | lastName | birthDate | | Annie M. G. | Schmidt | 1911-03-20 | @@ -11,6 +11,8 @@ Feature: Authors and tables | lastName | Schmidt | Dahl | | birthDate | 1911-03-20 | 1916-09-13 | + Scenario: Convert a table to a single object via the ParameterTypeRegistry + Given a single author in a table | firstName | lastName | birthDate | | Annie M. G. | Schmidt | 1911-03-20 | @@ -20,3 +22,10 @@ Feature: Authors and tables | lastName | Schmidt | | birthDate | 1911-03-20 | + + Scenario: Convert a table to a generic list via the @DataTableType Annotation + + Given a list of people in a table + | first | last | + | Astrid | Lindgren | + | Roald | Dahl | \ No newline at end of file diff --git a/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature b/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature index 91254aead4..8a270c523e 100644 --- a/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature +++ b/java/src/test/resources/io/cucumber/java/annotation/french-iso-8859-1-cukes.feature @@ -1,5 +1,6 @@ # language: fr # encoding: ISO-8859-1 -Fonctionnalité: Cukes - Scénario: in the belly - Étant donné I have 5 cukes in the belly +Fonctionnalité: Concombres dans ISO-8859-1 + + Scénario: dans la ventre + Étant donné j'ai 5 concombres diff --git a/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature b/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature new file mode 100644 index 0000000000..a85d3174bb --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/annotation/french-numbers.feature @@ -0,0 +1,5 @@ +# language: fr +Fonctionnalité: Concombres fractionnaires + + Scénario: dans la ventre + Étant donné j'ai 5,5 concombres fractionnaires diff --git a/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature b/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature new file mode 100644 index 0000000000..84894790fd --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/annotation/parameter-types.feature @@ -0,0 +1,7 @@ +Feature: ParameterTypes + + Scenario: Convert a parameter to date via the ParameterTypeRegistry + Given tomorrow is 1907/11/14 + + Scenario: Convert a parameter to date via the @ParameterType Annotation + Given today is 1907-11-14 diff --git a/java/src/test/resources/io/cucumber/java/annotation/table_conversion.feature b/java/src/test/resources/io/cucumber/java/annotation/table_conversion.feature deleted file mode 100644 index 7a4881ae4b..0000000000 --- a/java/src/test/resources/io/cucumber/java/annotation/table_conversion.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Table Conversion - - Scenario: use a table - Given this data table: - | first | last | - | Aslak | Hellesøy | - | Donald | Duck | diff --git a/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature b/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature new file mode 100644 index 0000000000..003d72051f --- /dev/null +++ b/java/src/test/resources/io/cucumber/java/defaultstransformer/default-transformer.feature @@ -0,0 +1,17 @@ +Feature: Datatable + + Scenario: Convert a table to a generic list via default transformer + Given a list of authors in a table + | firstName | lastName | birthDate | + | Annie M. G. | Schmidt | 1911-03-20 | + | Roald | Dahl | 1916-09-13 | + + + Scenario: Convert a table to a single object via the default transformer + + Given a single currency in a table + | EUR | + + Scenario: Convert an anonymous parameter to a single object via default transformer + + Given a currency in a parameter EUR diff --git a/java8/src/main/java/io/cucumber/java8/Java8Backend.java b/java8/src/main/java/io/cucumber/java8/Java8Backend.java index 2e43c35baf..45ebf28b4f 100644 --- a/java8/src/main/java/io/cucumber/java8/Java8Backend.java +++ b/java8/src/main/java/io/cucumber/java8/Java8Backend.java @@ -10,24 +10,22 @@ import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.snippets.Snippet; -import io.cucumber.core.stepexpression.TypeRegistry; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.function.Function; import static java.lang.Thread.currentThread; -final class Java8Backend implements Backend, LambdaGlueRegistry { +final class Java8Backend implements Backend { private final Lookup lookup; private final Container container; private final ClassFinder classFinder; - private Glue glue; private List> lambdaGlueClasses = new ArrayList<>(); + private Glue glue; Java8Backend(Lookup lookup, Container container, ResourceLoader resourceLoader) { this.classFinder = new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader()); @@ -55,15 +53,15 @@ public void loadGlue(Glue glue, List gluePaths) { public void buildWorld() { // Instantiate all the stepdef classes for java8 - the stepdef will be initialised // in the constructor. - INSTANCE.set(this); - for (Class lambdaGlueClass: lambdaGlueClasses) { + LambdaGlueRegistry.INSTANCE.set(new GlueAdaptor(glue)); + for (Class lambdaGlueClass : lambdaGlueClasses) { lookup.getInstance(lambdaGlueClass); } } @Override public void disposeWorld() { - INSTANCE.remove(); + LambdaGlueRegistry.INSTANCE.remove(); } @Override @@ -71,29 +69,42 @@ public Snippet getSnippet() { return new Java8Snippet(); } - @Override - public void addStepDefinition(StepDefinition stepDefinition) { - glue.addStepDefinition(stepDefinition); - } - @Override - public void addBeforeHookDefinition(HookDefinition beforeHook) { - glue.addBeforeHook(beforeHook); - } + private static final class GlueAdaptor implements LambdaGlueRegistry { - @Override - public void addAfterHookDefinition(HookDefinition afterHook) { - glue.addAfterHook(afterHook); - } + private final Glue glue; - @Override - public void addAfterStepHookDefinition(HookDefinition afterStepHook) { - glue.addAfterStepHook(afterStepHook); - } + private GlueAdaptor(Glue glue) { + this.glue = glue; + } - @Override - public void addBeforeStepHookDefinition(HookDefinition beforeStepHook) { - glue.addBeforeStepHook(beforeStepHook); + @Override + public void addStepDefinition(StepDefinition stepDefinition) { + glue.addStepDefinition(stepDefinition); + } + + @Override + public void addBeforeStepHookDefinition(HookDefinition beforeStepHook) { + glue.addBeforeStepHook(beforeStepHook); + + } + + @Override + public void addAfterStepHookDefinition(HookDefinition afterStepHook) { + glue.addAfterStepHook(afterStepHook); + + } + + @Override + public void addBeforeHookDefinition(HookDefinition beforeHook) { + glue.addBeforeHook(beforeHook); + + } + + @Override + public void addAfterHookDefinition(HookDefinition afterHook) { + glue.addAfterHook(afterHook); + } } } diff --git a/java8/src/main/java/io/cucumber/java8/LambdaGlue.java b/java8/src/main/java/io/cucumber/java8/LambdaGlue.java index 2f86e7422d..90a92b908e 100644 --- a/java8/src/main/java/io/cucumber/java8/LambdaGlue.java +++ b/java8/src/main/java/io/cucumber/java8/LambdaGlue.java @@ -16,7 +16,7 @@ public interface LambdaGlue { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -26,7 +26,7 @@ default void Before(final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(String tagExpression, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -36,7 +36,7 @@ default void Before(String tagExpression, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(long timeoutMillis, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -46,7 +46,7 @@ default void Before(long timeoutMillis, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(int order, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -58,7 +58,7 @@ default void Before(int order, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(String tagExpression, long timeoutMillis, int order, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -67,7 +67,7 @@ default void Before(String tagExpression, long timeoutMillis, int order, final H * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void Before(final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -77,7 +77,7 @@ default void Before(final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(String tagExpression, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -87,7 +87,7 @@ default void Before(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(long timeoutMillis, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -97,7 +97,7 @@ default void Before(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -109,7 +109,7 @@ default void Before(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void Before(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -118,7 +118,7 @@ default void Before(String tagExpression, long timeoutMillis, int order, final H * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -128,7 +128,7 @@ default void BeforeStep(final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(String tagExpression, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } /** @@ -138,7 +138,7 @@ default void BeforeStep(String tagExpression, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(long timeoutMillis, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -148,7 +148,7 @@ default void BeforeStep(long timeoutMillis, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(int order, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -160,7 +160,7 @@ default void BeforeStep(int order, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void BeforeStep(String tagExpression, long timeoutMillis, int order, final HookBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -169,7 +169,7 @@ default void BeforeStep(String tagExpression, long timeoutMillis, int order, fin * @param body lambda to execute */ default void BeforeStep(final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } @@ -180,7 +180,7 @@ default void BeforeStep(final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(String tagExpression, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_BEFORE_ORDER, NO_TIMEOUT, body)); } @@ -191,7 +191,7 @@ default void BeforeStep(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(long timeoutMillis, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_BEFORE_ORDER, timeoutMillis, body)); } /** @@ -201,7 +201,7 @@ default void BeforeStep(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -213,7 +213,7 @@ default void BeforeStep(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void BeforeStep(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addBeforeStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -222,7 +222,7 @@ default void BeforeStep(String tagExpression, long timeoutMillis, int order, fin * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(final HookBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -232,7 +232,7 @@ default void After(final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(String tagExpression, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -242,7 +242,7 @@ default void After(String tagExpression, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(long timeoutMillis, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -252,7 +252,7 @@ default void After(long timeoutMillis, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(int order, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -264,7 +264,7 @@ default void After(int order, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void After(String tagExpression, long timeoutMillis, int order, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -273,7 +273,7 @@ default void After(String tagExpression, long timeoutMillis, int order, final Ho * @param body lambda to execute */ default void After(final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -283,7 +283,7 @@ default void After(final HookNoArgsBody body) { * @param body lambda to execute */ default void After(String tagExpression, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -293,7 +293,7 @@ default void After(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(long timeoutMillis, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -303,7 +303,7 @@ default void After(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -315,7 +315,7 @@ default void After(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void After(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -324,7 +324,7 @@ default void After(String tagExpression, long timeoutMillis, int order, final Ho * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(final HookBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -334,7 +334,7 @@ default void AfterStep(final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(String tagExpression, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -344,7 +344,7 @@ default void AfterStep(String tagExpression, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(long timeoutMillis, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -354,7 +354,7 @@ default void AfterStep(long timeoutMillis, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(int order, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -366,7 +366,7 @@ default void AfterStep(int order, final HookBody body) { * @param body lambda to execute, takes {@link io.cucumber.core.api.Scenario} as an argument */ default void AfterStep(String tagExpression, long timeoutMillis, int order, final HookBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } /** @@ -375,7 +375,7 @@ default void AfterStep(String tagExpression, long timeoutMillis, int order, fina * @param body lambda to execute */ default void AfterStep(final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -385,7 +385,7 @@ default void AfterStep(final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(String tagExpression, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, DEFAULT_AFTER_ORDER, NO_TIMEOUT, body)); } /** @@ -395,7 +395,7 @@ default void AfterStep(String tagExpression, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(long timeoutMillis, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, DEFAULT_AFTER_ORDER, timeoutMillis, body)); } /** @@ -405,7 +405,7 @@ default void AfterStep(long timeoutMillis, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(EMPTY_TAG_EXPRESSIONS, order, NO_TIMEOUT, body)); } /** @@ -417,7 +417,7 @@ default void AfterStep(int order, final HookNoArgsBody body) { * @param body lambda to execute */ default void AfterStep(String tagExpression, long timeoutMillis, int order, final HookNoArgsBody body) { - Java8Backend.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); + LambdaGlueRegistry.INSTANCE.get().addAfterStepHookDefinition(new Java8HookDefinition(tagExpression, order, timeoutMillis, body)); } } diff --git a/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java index f4ec246e76..e604320659 100644 --- a/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java +++ b/java8/src/test/java/io/cucumber/java8/AnonInnerClassStepdefs.java @@ -5,7 +5,7 @@ public class AnonInnerClassStepdefs implements LambdaGlue { public AnonInnerClassStepdefs() { - Java8Backend.INSTANCE.get().addStepDefinition( + LambdaGlueRegistry.INSTANCE.get().addStepDefinition( Java8StepDefinition.create( "I have {int} java7 beans in my {word}", StepdefBody.A2.class, new StepdefBody.A2() { diff --git a/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java b/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java index 656715fd7c..983dba2ca3 100644 --- a/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java +++ b/java8/src/test/java/io/cucumber/java8/Java8BackendTest.java @@ -40,7 +40,7 @@ public void createBackend() { @Test public void finds_step_definitions_by_classpath_url() { - backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java8/stepdefs"))); + backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java8/steps"))); backend.buildWorld(); verify(factory).addClass(Stepdefs.class); } diff --git a/junit/README.md b/junit/README.md index a03994f8c8..417cc79dcc 100644 --- a/junit/README.md +++ b/junit/README.md @@ -1,9 +1,7 @@ Cucumber JUnit ============== -Use JUnit to execute cucumber scenarios. - -Add the `cucumber-junit` dependency to your pom.xml: +Use JUnit to execute cucumber scenarios. To use add the `cucumber-junit` dependency to your pom.xml: ```xml diff --git a/junit/src/main/java/io/cucumber/junit/Cucumber.java b/junit/src/main/java/io/cucumber/junit/Cucumber.java index b8e64438f4..719e32eb22 100644 --- a/junit/src/main/java/io/cucumber/junit/Cucumber.java +++ b/junit/src/main/java/io/cucumber/junit/Cucumber.java @@ -9,7 +9,7 @@ import io.cucumber.core.options.CucumberProperties; import io.cucumber.core.options.CucumberPropertiesParser; import io.cucumber.core.options.RuntimeOptions; -import io.cucumber.core.runtime.ConfiguringTypeRegistrySupplier; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; import io.cucumber.core.runtime.ObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; import io.cucumber.core.eventbus.EventBus; @@ -27,7 +27,7 @@ import io.cucumber.core.io.ResourceLoader; import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.feature.CucumberFeature; -import io.cucumber.core.runtime.TypeRegistrySupplier; +import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; import org.apiguardian.api.API; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -146,8 +146,8 @@ public Cucumber(Class clazz) throws InitializationError { ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(runtimeOptions); ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader); BackendSupplier backendSupplier = new BackendServiceLoader(resourceLoader, objectFactorySupplier); - TypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); - ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistrySupplier); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); Filters filters = new Filters(runtimeOptions); for (CucumberFeature cucumberFeature : features) { FeatureRunner featureRunner = new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOptions); diff --git a/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java b/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java index 857fa645e5..44de8db0ec 100644 --- a/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java +++ b/junit/src/test/java/io/cucumber/junit/FeatureRunnerTest.java @@ -10,7 +10,7 @@ import io.cucumber.core.io.ResourceLoaderClassFinder; import io.cucumber.core.options.RuntimeOptions; import io.cucumber.core.runtime.BackendSupplier; -import io.cucumber.core.runtime.ConfiguringTypeRegistrySupplier; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; import io.cucumber.core.runtime.ObjectFactorySupplier; import io.cucumber.core.runtime.RunnerSupplier; import io.cucumber.core.runtime.SingletonObjectFactorySupplier; @@ -181,7 +181,7 @@ public Clock withZone(ZoneId zone) { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); ResourceLoader resourceLoader = new MultiLoader(classLoader); ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - ConfiguringTypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); + ScanningTypeRegistryConfigurerSupplier typeRegistrySupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); ThreadLocalRunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactory, typeRegistrySupplier); return new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOption); } diff --git a/kotlin-java8/pom.xml b/kotlin-java8/pom.xml index 4a494375a4..b6294b0a3e 100644 --- a/kotlin-java8/pom.xml +++ b/kotlin-java8/pom.xml @@ -12,6 +12,7 @@ Cucumber-JVM: Kotlin Java8 + io.cucumber.kotlin.java8 1.3.0 diff --git a/testng/README.md b/testng/README.md index e4ff4ef645..00c5005af6 100644 --- a/testng/README.md +++ b/testng/README.md @@ -1,9 +1,7 @@ Cucumber TestNG ============== -Use TestNG to execute cucumber scenarios. - -Add the `cucumber-testng` dependency to your pom. +Use TestNG to execute cucumber scenarios. To use add the `cucumber-testng` dependency to your pom. ```xml diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java index 43092f2bdd..4fc0206404 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java @@ -23,13 +23,13 @@ import io.cucumber.core.plugin.Plugins; import io.cucumber.core.runner.Runner; import io.cucumber.core.runtime.BackendServiceLoader; -import io.cucumber.core.runtime.ConfiguringTypeRegistrySupplier; +import io.cucumber.core.runtime.ScanningTypeRegistryConfigurerSupplier; import io.cucumber.core.runtime.FeaturePathFeatureSupplier; import io.cucumber.core.runtime.ObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.core.runtime.TypeRegistrySupplier; +import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; import org.apiguardian.api.API; import java.time.Clock; @@ -95,8 +95,8 @@ public TestNGCucumberRunner(Class clazz) { ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader); BackendServiceLoader backendSupplier = new BackendServiceLoader(resourceLoader, objectFactorySupplier); this.filters = new Filters(runtimeOptions); - TypeRegistrySupplier typeRegistrySupplier = new ConfiguringTypeRegistrySupplier(classFinder, runtimeOptions); - this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistrySupplier); + TypeRegistryConfigurerSupplier typeRegistryConfigurerSupplier = new ScanningTypeRegistryConfigurerSupplier(classFinder, runtimeOptions); + this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, objectFactorySupplier, typeRegistryConfigurerSupplier); } public void runScenario(PickleEvent pickle) throws Throwable {