From a4a3ad59d6a3769357d0a42b141c2fce444edd63 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 May 2018 22:52:00 +0200 Subject: [PATCH] [Core] Implement cucumber expressions (#1248) * Implements Cucumber expressions * Removes XStream * Adds DataTables from cucumber/cucumber#291 * Introduces the concept of StepExpressions which combines a CucumberExpression and DataTable expression. Currently data table expressions are limited to a java type. Currently only the implicit type derived from the method argument is used. For a working example check the calculator examples. --- README.md | 2 +- .../runtime/android/CucumberExecutor.java | 16 +- core/pom.xml | 19 +- core/src/main/java/cucumber/api/Argument.java | 14 +- .../src/main/java/cucumber/api/DataTable.java | 271 ---------- core/src/main/java/cucumber/api/Format.java | 43 -- .../java/cucumber/api/PickleStepTestStep.java | 2 +- .../java/cucumber/api/TableConverter.java | 19 - core/src/main/java/cucumber/api/TestStep.java | 1 + .../src/main/java/cucumber/api/Transform.java | 19 - .../main/java/cucumber/api/Transformer.java | 111 ----- .../main/java/cucumber/api/TypeRegistry.java | 17 + .../cucumber/api/TypeRegistryConfigurer.java | 10 + .../cucumber/runner/PickleStepTestStep.java | 3 +- .../AmbiguousPickleStepDefinitionsMatch.java | 8 +- .../main/java/cucumber/runtime/Arguments.java | 29 -- .../DefaultTypeRegistryConfiguration.java | 20 + .../cucumber/runtime/DefinitionArgument.java | 41 ++ .../FailedPickleStepInstantiationMatch.java | 4 +- .../runtime/JdkPatternArgumentMatcher.java | 31 -- .../src/main/java/cucumber/runtime/Match.java | 3 +- .../cucumber/runtime/NoStepDefinition.java | 6 +- .../java/cucumber/runtime/ParameterInfo.java | 134 +---- .../runtime/PickleStepDefinitionMatch.java | 177 +++---- .../java/cucumber/runtime/Reflections.java | 18 +- .../main/java/cucumber/runtime/Runtime.java | 18 +- .../java/cucumber/runtime/RuntimeGlue.java | 38 +- .../java/cucumber/runtime/RuntimeOptions.java | 17 +- .../runtime/RuntimeOptionsFactory.java | 27 +- .../java/cucumber/runtime/StepDefinition.java | 2 +- .../UndefinedPickleStepDefinitionMatch.java | 4 +- .../src/main/java/cucumber/runtime/Utils.java | 35 -- .../autocomplete/StepdefGenerator.java | 61 --- .../runtime/formatter/JSONFormatter.java | 8 +- .../runtime/formatter/PrettyFormatter.java | 15 +- .../java/cucumber/runtime/io/MultiLoader.java | 11 + .../runtime/model/CucumberFeature.java | 1 - .../runtime/{ => model}/FeatureBuilder.java | 4 +- .../runtime/snippets/ArgumentPattern.java | 22 +- .../cucumber/runtime/snippets/Snippet.java | 45 +- .../runtime/snippets/SnippetGenerator.java | 144 ++---- .../table/CamelCaseStringConverter.java | 45 -- .../cucumber/runtime/table/DataTableDiff.java | 40 -- .../runtime/table/DiffTablePrinter.java | 29 -- .../cucumber/runtime/table/DiffableRow.java | 29 -- .../table/PascalCaseStringConverter.java | 35 -- .../runtime/table/StringConverter.java | 5 - .../runtime/table/TableConverter.java | 315 ------------ .../runtime/table/TableDiffException.java | 37 -- .../cucumber/runtime/table/TableDiffer.java | 148 ------ .../cucumber/runtime/table/TablePrinter.java | 73 --- .../runtime/xstream/BigDecimalConverter.java | 18 - .../runtime/xstream/BigIntegerConverter.java | 17 - .../runtime/xstream/ByteConverter.java | 16 - .../runtime/xstream/CalendarConverter.java | 20 - .../cucumber/runtime/xstream/CellWriter.java | 11 - ...hStringAssignableConstructorConverter.java | 39 -- .../runtime/xstream/ComplexTypeWriter.java | 134 ----- .../xstream/ConverterWithEnumFormat.java | 91 ---- .../runtime/xstream/ConverterWithFormat.java | 70 --- .../xstream/ConverterWithNumberFormat.java | 28 -- .../runtime/xstream/DateConverter.java | 10 - .../runtime/xstream/DoubleConverter.java | 16 - ...DynamicClassBasedSingleValueConverter.java | 23 - ...micClassWithStringAssignableConverter.java | 28 -- .../runtime/xstream/DynamicEnumConverter.java | 27 - .../runtime/xstream/EnumConverter.java | 10 - .../runtime/xstream/FloatConverter.java | 16 - .../runtime/xstream/IntegerConverter.java | 16 - .../runtime/xstream/ListConverter.java | 53 -- .../xstream/ListOfComplexTypeReader.java | 133 ----- .../xstream/ListOfSingleValueWriter.java | 53 -- .../runtime/xstream/LocalizedXStreams.java | 145 ------ .../runtime/xstream/LongConverter.java | 16 - .../cucumber/runtime/xstream/MapWriter.java | 67 --- .../runtime/xstream/PatternConverter.java | 74 --- .../runtime/xstream/ShortConverter.java | 16 - .../SingleValueConverterWrapperExt.java | 20 - .../runtime/xstream/TimeConverter.java | 77 --- .../cucumber/runtime/xstream/package.html | 7 - .../io/cucumber/stepexpression/Argument.java | 7 + .../stepexpression/ArgumentMatcher.java | 9 + .../stepexpression/DataTableArgument.java | 30 ++ .../stepexpression/DocStringArgument.java | 22 + .../stepexpression/DocStringTransformer.java | 6 + .../stepexpression/ExpressionArgument.java | 26 + .../ExpressionArgumentMatcher.java | 36 ++ .../stepexpression/PickleTableConverter.java | 22 + .../stepexpression/RawTableTransformer.java | 7 + .../stepexpression/StepExpression.java | 65 +++ .../stepexpression/StepExpressionFactory.java | 109 ++++ .../cucumber/stepexpression/TypeRegistry.java | 38 ++ .../cucumber/stepexpression/TypeResolver.java | 17 + .../test/java/cucumber/runner/RunnerTest.java | 2 +- .../java/cucumber/runtime/DummyConverter.java | 26 - .../java/cucumber/runtime/HookOrderTest.java | 2 +- .../test/java/cucumber/runtime/HookTest.java | 4 +- .../JdkPatternArgumentMatcherTest.java | 78 --- .../cucumber/runtime/ParameterInfoTest.java | 152 ------ .../cucumber/runtime/RuntimeGlueTest.java | 20 +- .../runtime/RuntimeOptionsFactoryTest.java | 59 --- .../java/cucumber/runtime/RuntimeTest.java | 9 +- .../runtime/StepDefinitionMatchTest.java | 321 ++++++------ .../cucumber/runtime/StubStepDefinition.java | 41 +- .../runtime/annotations/CustomDelimiter.java | 16 - .../runtime/annotations/SampleDateFormat.java | 16 - .../annotations/TransformToFortyTwo.java | 18 - .../autocomplete/StepdefGeneratorTest.java | 172 ------- .../formatter/PrettyFormatterTest.java | 67 +-- .../{ => model}/FeatureBuilderTest.java | 3 +- .../runtime/snippets/ArgumentPatternTest.java | 3 +- .../table/CamelCaseStringConverterTest.java | 25 - .../cucumber/runtime/table/DataTableTest.java | 129 ----- .../runtime/table/FromDataTableTest.java | 358 -------------- .../runtime/table/TableConverterTest.java | 447 ----------------- .../runtime/table/TableDifferTest.java | 467 ------------------ .../cucumber/runtime/table/TableParser.java | 34 -- .../runtime/table/ToDataTableTest.java | 328 ------------ .../runtime/xstream/ConvertersTest.java | 89 ---- .../xstream/ExternalConverterTest.java | 69 --- .../xstream/StandardConvertersTest.java | 150 ------ .../StepExpressionFactoryTest.java | 113 +++++ .../cucumber/stepexpression/TableParser.java | 40 ++ .../test/CalculatorActivitySteps.java | 8 +- .../cukeulator/test/ParameterTypes.java | 44 ++ .../android/test/CucumberActivitySteps.java | 6 +- .../test/CalculatorActivitySteps.java | 8 +- .../test/TypeRegistryConfiguration.java | 45 ++ examples/java-calculator-testng/pom.xml | 14 + .../java/calculator/DateStepdefs.java | 17 +- .../java/calculator/ParameterTypes.java | 62 +++ .../calculator/RpnCalculatorStepdefs.java | 24 +- .../java/calculator/ShoppingStepdefs.java | 34 +- examples/java-calculator/pom.xml | 15 +- .../java/calculator/DateStepdefs.java | 17 +- .../java/calculator/ParameterTypes.java | 60 +++ .../calculator/RpnCalculatorStepdefs.java | 24 +- .../java/calculator/RunCukesTest.java | 2 +- .../java/calculator/ShoppingStepdefs.java | 34 +- .../java/gradle/cucumber/BasicStepdefs.java | 2 +- .../java/websockets/NavigationStepdefs.java | 2 +- .../java/websockets/TemperatureStepdefs.java | 4 +- .../java/wicket/steps/RentStepdefs.java | 6 +- .../calculator/RpnCalculatorStepdefs.java | 28 +- .../java/calculator/ShoppingStepdefs.java | 36 +- .../calculator/TypeRegistryConfiguration.java | 40 ++ .../java/paxexam/test/CalculatorSteps.java | 8 +- .../java/paxexam/test/CalculatorTest.java | 9 +- examples/pom.xml | 2 +- examples/spring-txn/pom.xml | 5 - .../spring/txn/SeeMessagesStepdefs.java | 8 +- .../spring/txn/TypeRegistryConfiguration.java | 34 ++ .../examples/spring/txn/UserStepdefs.java | 2 +- .../guice/integration/HelloWorldSteps.java | 2 +- .../integration/ScenarioScopedSteps.java | 10 +- .../integration/SingletonScopedSteps.java | 10 +- .../java/guice/integration/UnScopedSteps.java | 6 +- java/pom.xml | 2 +- java/src/main/code_generator/I18n.java.txt | 20 +- .../runtime/java/AbstractJavaSnippet.java | 49 +- .../java/cucumber/runtime/java/Function.java | 7 + .../cucumber/runtime/java/Java8Snippet.java | 33 +- .../cucumber/runtime/java/JavaBackend.java | 41 +- .../cucumber/runtime/java/JavaSnippet.java | 18 +- .../runtime/java/JavaStepDefinition.java | 36 +- .../runtime/java/LambdaGlueRegistry.java | 3 +- .../runtime/java/ObjectFactoryLoader.java | 4 +- .../runtime/java/Java8SnippetTest.java | 6 +- .../runtime/java/JavaBackendTest.java | 5 +- .../cucumber/runtime/java/JavaHookTest.java | 10 +- .../runtime/java/JavaSnippetTest.java | 310 ++++++++---- .../runtime/java/JavaStepDefinitionTest.java | 5 +- .../java/JavaStepDefinitionTransposeTest.java | 152 ++++++ .../runtime/java/MethodScannerTest.java | 6 +- .../cucumber/runtime/java/test/Authors.java | 75 +++ .../runtime/java/test/ScenarioStepDefs.java | 6 +- .../cucumber/runtime/java/test/Stepdefs.java | 4 +- .../java/test/SubstitutionStepdefs.java | 6 +- .../java/test/TypeRegistryConfiguration.java | 64 +++ .../runtime/java/test/authors.feature | 22 + java8/src/main/code_generator/I18n.java8.txt | 84 +++- .../runtime/java8/Java8StepDefinition.java | 104 ++-- ...Java8AnonInnerClassStepDefinitionTest.java | 35 +- ...efinitionMarksCorrectStackElementTest.java | 8 +- .../java8/Java8LambdaStepDefinitionTest.java | 20 +- .../java8/test/AnonInnerClassStepdefs.java | 27 +- .../runtime/java8/test/LambdaStepdefs.java | 28 +- .../java8/test/TypeRegistryConfiguration.java | 31 ++ .../test/lambda-step-definitions.feature | 7 +- .../cucumber/runtime/stub/StubBackend.java | 4 +- .../runtime/kotlin/test/LambdaStepdefs.kt | 22 +- .../kotlin/test/TypeRegistryConfiguration.kt | 27 + .../java/needle/test/AtmWithdrawalSteps.java | 6 +- .../runtime/java/needle/test/MoreSteps.java | 2 +- .../runtime/java/openejb/BellyStepdefs.java | 4 +- .../java/runtime/osgi/OsgiClassFinder.java | 2 + picocontainer/pom.xml | 4 - .../java/picocontainer/DatesSteps.java | 50 -- .../java/picocontainer/EnumsSteps.java | 38 -- .../java/picocontainer/RunCukesTest.java | 1 + .../java/picocontainer/SanityTest.java | 1 + .../runtime/java/picocontainer/StepDefs.java | 12 +- .../runtime/java/picocontainer/dates.feature | 26 - .../runtime/java/picocontainer/enums.feature | 16 - pom.xml | 50 +- .../spring/commonglue/AnotherStepDef.java | 2 +- .../java/spring/commonglue/OneStepDef.java | 4 +- .../java/spring/commonglue/ThirdStepDef.java | 2 +- .../commonglue/TransactionStepDefs.java | 4 +- .../spring/contextconfig/BellyStepdefs.java | 4 +- .../DirtiesContextBellyStepDefs.java | 8 +- .../DirtiesContextBellyMetaStepDefs.java | 8 +- .../metaconfig/general/BellyMetaStepdefs.java | 4 +- .../spring/threading/ThreadingStepDefs.java | 6 +- .../webappconfig/SpringInjectionStepDefs.java | 6 +- .../cucumber/runtime/stub/StubBackend.java | 4 +- .../runtime/java/weld/BellyStepdefs.java | 4 +- 217 files changed, 2647 insertions(+), 6659 deletions(-) delete mode 100644 core/src/main/java/cucumber/api/DataTable.java delete mode 100644 core/src/main/java/cucumber/api/Format.java delete mode 100644 core/src/main/java/cucumber/api/TableConverter.java delete mode 100644 core/src/main/java/cucumber/api/Transform.java delete mode 100644 core/src/main/java/cucumber/api/Transformer.java create mode 100644 core/src/main/java/cucumber/api/TypeRegistry.java create mode 100644 core/src/main/java/cucumber/api/TypeRegistryConfigurer.java delete mode 100644 core/src/main/java/cucumber/runtime/Arguments.java create mode 100644 core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java create mode 100644 core/src/main/java/cucumber/runtime/DefinitionArgument.java delete mode 100644 core/src/main/java/cucumber/runtime/JdkPatternArgumentMatcher.java delete mode 100644 core/src/main/java/cucumber/runtime/autocomplete/StepdefGenerator.java rename core/src/main/java/cucumber/runtime/{ => model}/FeatureBuilder.java (97%) delete mode 100644 core/src/main/java/cucumber/runtime/table/CamelCaseStringConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/table/DataTableDiff.java delete mode 100644 core/src/main/java/cucumber/runtime/table/DiffTablePrinter.java delete mode 100644 core/src/main/java/cucumber/runtime/table/DiffableRow.java delete mode 100644 core/src/main/java/cucumber/runtime/table/PascalCaseStringConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/table/StringConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/table/TableConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/table/TableDiffException.java delete mode 100644 core/src/main/java/cucumber/runtime/table/TableDiffer.java delete mode 100644 core/src/main/java/cucumber/runtime/table/TablePrinter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/BigDecimalConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/BigIntegerConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ByteConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/CalendarConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/CellWriter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ClassWithStringAssignableConstructorConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ComplexTypeWriter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ConverterWithEnumFormat.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ConverterWithFormat.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ConverterWithNumberFormat.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/DateConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/DoubleConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/DynamicClassBasedSingleValueConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/DynamicClassWithStringAssignableConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/DynamicEnumConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/EnumConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/FloatConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/IntegerConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ListConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ListOfComplexTypeReader.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ListOfSingleValueWriter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/LocalizedXStreams.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/LongConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/MapWriter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/PatternConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/ShortConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/SingleValueConverterWrapperExt.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/TimeConverter.java delete mode 100644 core/src/main/java/cucumber/runtime/xstream/package.html create mode 100644 core/src/main/java/io/cucumber/stepexpression/Argument.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/StepExpression.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java create mode 100644 core/src/main/java/io/cucumber/stepexpression/TypeResolver.java delete mode 100644 core/src/test/java/cucumber/runtime/DummyConverter.java delete mode 100644 core/src/test/java/cucumber/runtime/JdkPatternArgumentMatcherTest.java delete mode 100644 core/src/test/java/cucumber/runtime/ParameterInfoTest.java delete mode 100644 core/src/test/java/cucumber/runtime/annotations/CustomDelimiter.java delete mode 100644 core/src/test/java/cucumber/runtime/annotations/SampleDateFormat.java delete mode 100644 core/src/test/java/cucumber/runtime/annotations/TransformToFortyTwo.java delete mode 100644 core/src/test/java/cucumber/runtime/autocomplete/StepdefGeneratorTest.java rename core/src/test/java/cucumber/runtime/{ => model}/FeatureBuilderTest.java (96%) delete mode 100644 core/src/test/java/cucumber/runtime/table/CamelCaseStringConverterTest.java delete mode 100644 core/src/test/java/cucumber/runtime/table/DataTableTest.java delete mode 100755 core/src/test/java/cucumber/runtime/table/FromDataTableTest.java delete mode 100644 core/src/test/java/cucumber/runtime/table/TableConverterTest.java delete mode 100755 core/src/test/java/cucumber/runtime/table/TableDifferTest.java delete mode 100644 core/src/test/java/cucumber/runtime/table/TableParser.java delete mode 100644 core/src/test/java/cucumber/runtime/table/ToDataTableTest.java delete mode 100644 core/src/test/java/cucumber/runtime/xstream/ConvertersTest.java delete mode 100644 core/src/test/java/cucumber/runtime/xstream/ExternalConverterTest.java delete mode 100644 core/src/test/java/cucumber/runtime/xstream/StandardConvertersTest.java create mode 100644 core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java create mode 100644 core/src/test/java/io/cucumber/stepexpression/TableParser.java create mode 100644 examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/ParameterTypes.java create mode 100644 examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/TypeRegistryConfiguration.java create mode 100644 examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java create mode 100644 examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java create mode 100644 examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/TypeRegistryConfiguration.java create mode 100644 examples/spring-txn/src/test/java/cucumber/examples/spring/txn/TypeRegistryConfiguration.java create mode 100644 java/src/main/java/cucumber/runtime/java/Function.java create mode 100755 java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java create mode 100644 java/src/test/java/cucumber/runtime/java/test/Authors.java create mode 100644 java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java create mode 100644 java/src/test/resources/cucumber/runtime/java/test/authors.feature create mode 100644 java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java create mode 100644 kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt delete mode 100644 picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java delete mode 100644 picocontainer/src/test/java/cucumber/runtime/java/picocontainer/EnumsSteps.java delete mode 100644 picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/dates.feature delete mode 100644 picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/enums.feature diff --git a/README.md b/README.md index 61ba408312..890dbf2ab0 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You're most likely going to paste code and output, so familiarise yourself with
 ```java
 // Why doesn't this work?
-@Given("I have (\\d+) cukes in my (.*)")
+@Given("I have {int} cukes in my {string}")
 public void some_cukes(int howMany, String what) {
     // HALP!
 }
diff --git a/android/src/main/java/cucumber/runtime/android/CucumberExecutor.java b/android/src/main/java/cucumber/runtime/android/CucumberExecutor.java
index 08aeea3c10..5fa51a4090 100644
--- a/android/src/main/java/cucumber/runtime/android/CucumberExecutor.java
+++ b/android/src/main/java/cucumber/runtime/android/CucumberExecutor.java
@@ -3,19 +3,24 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.util.Log;
+import cucumber.api.TypeRegistryConfigurer;
 import cucumber.api.CucumberOptions;
 import cucumber.api.StepDefinitionReporter;
+import io.cucumber.stepexpression.TypeRegistry;
 import cucumber.api.event.TestRunFinished;
 import cucumber.api.java.ObjectFactory;
 import cucumber.runtime.Backend;
 import cucumber.runtime.ClassFinder;
 import cucumber.runtime.CucumberException;
+import cucumber.runtime.DefaultTypeRegistryConfiguration;
 import cucumber.runtime.Env;
+import cucumber.runtime.Reflections;
 import cucumber.runtime.Runtime;
 import cucumber.runtime.RuntimeOptions;
 import cucumber.runtime.RuntimeOptionsFactory;
 import cucumber.runtime.formatter.AndroidInstrumentationReporter;
 import cucumber.runtime.formatter.AndroidLogcatReporter;
+import cucumber.runtime.io.MultiLoader;
 import cucumber.runtime.io.ResourceLoader;
 import cucumber.runtime.java.JavaBackend;
 import cucumber.runtime.java.ObjectFactoryLoader;
@@ -24,10 +29,11 @@
 import gherkin.events.PickleEvent;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import static java.util.Collections.singletonList;
+
 /**
  * Executes the cucumber scenarios.
  */
@@ -159,10 +165,12 @@ private RuntimeOptions createRuntimeOptions(final Context context) {
     }
 
     private Collection createBackends() {
+        final Reflections reflections = new Reflections(classFinder);
         final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName()));
         final AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, instrumentation);
-        final List backends = new ArrayList();
-        backends.add(new JavaBackend(objectFactory, classFinder));
-        return backends;
+        final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
+        final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
+        typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
+        return singletonList(new JavaBackend(objectFactory, classFinder, typeRegistry));
     }
 }
diff --git a/core/pom.xml b/core/pom.xml
index 62a6a52cd0..76f185fe13 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -18,17 +18,20 @@
         
         
             io.cucumber
-            cucumber-jvm-deps
+            gherkin
         
         
             io.cucumber
-            gherkin
+            tag-expressions
         
         
             io.cucumber
-            tag-expressions
+            cucumber-expressions
+        
+        
+            io.cucumber
+            datatable
         
-
         
             junit
             junit
@@ -49,11 +52,7 @@
             jsoup
             test
         
-        
-            joda-time
-            joda-time
-            test
-        
+
         
             org.webbitserver
             webbit
@@ -64,6 +63,7 @@
             webbit-rest
             test
         
+
     
 
     
@@ -101,7 +101,6 @@
                 
                     
                         
-                        cucumber.*
                         *
                     
                 
diff --git a/core/src/main/java/cucumber/api/Argument.java b/core/src/main/java/cucumber/api/Argument.java
index e49bae6bc5..13906ace1f 100644
--- a/core/src/main/java/cucumber/api/Argument.java
+++ b/core/src/main/java/cucumber/api/Argument.java
@@ -1,7 +1,17 @@
 package cucumber.api;
 
+/**
+ * Represents an argument in for a step definition.
+ * 

+ * The step definition {@code I have {long} cukes in my belly} + * when matched with {@code I have 7 cukes in my belly} will produce + * one argument with value {@code "4"}, starting at {@code 7} and + * ending at {@code 8}. + */ public interface Argument { - Integer getOffset(); + String getValue(); - String getVal(); + int getStart(); + + int getEnd(); } diff --git a/core/src/main/java/cucumber/api/DataTable.java b/core/src/main/java/cucumber/api/DataTable.java deleted file mode 100644 index f3fb4c4bae..0000000000 --- a/core/src/main/java/cucumber/api/DataTable.java +++ /dev/null @@ -1,271 +0,0 @@ -package cucumber.api; - -import cucumber.runtime.CucumberException; -import cucumber.runtime.ParameterInfo; -import cucumber.runtime.table.DiffableRow; -import cucumber.runtime.table.TableDiffException; -import cucumber.runtime.table.TableDiffer; -import cucumber.runtime.table.TablePrinter; -import cucumber.runtime.xstream.LocalizedXStreams; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleTable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * Represents the data from a Gherkin DataTable. Cucumber will convert the table in Gherkin - * to a DataTable instance and pass it to a step definition. - */ -public class DataTable { - - private final List> raw; - private final PickleTable pickleTable; - private final TableConverter tableConverter; - - public static DataTable create(List raw) { - return create(raw, Locale.getDefault(), null, new String[0]); - } - - public static DataTable create(List raw, String format, String... columnNames) { - return create(raw, Locale.getDefault(), format, columnNames); - } - - public static DataTable create(List raw, Locale locale, String... columnNames) { - return create(raw, locale, null, columnNames); - } - - private static DataTable create(List raw, Locale locale, String format, String... columnNames) { - ParameterInfo parameterInfo = new ParameterInfo(null, format, null, null); - TableConverter tableConverter = new cucumber.runtime.table.TableConverter(new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(locale), parameterInfo); - return tableConverter.toTable(raw, columnNames); - } - - /** - * Creates a new DataTable. This constructor should not be called by Cucumber users - it's used internally only. - * - * @param pickleTable the underlying table. - * @param tableConverter how to convert the rows. - */ - public DataTable(PickleTable pickleTable, TableConverter tableConverter) { - this.pickleTable = pickleTable; - this.tableConverter = tableConverter; - int columns = pickleTable.getRows().isEmpty() ? 0 : pickleTable.getRows().get(0).getCells().size(); - List> raw = new ArrayList>(); - for (PickleRow row : pickleTable.getRows()) { - List list = new ArrayList(); - for (PickleCell cell : row.getCells()) { - list.add(cell.getValue()); - } - if (columns != row.getCells().size()) { - throw new CucumberException(String.format("Table is unbalanced: expected %s column(s) but found %s.", columns, row.getCells().size())); - } - raw.add(Collections.unmodifiableList(list)); - } - this.raw = Collections.unmodifiableList(raw); - } - - private DataTable(PickleTable pickleTable, List> raw, TableConverter tableConverter) { - this.pickleTable = pickleTable; - this.tableConverter = tableConverter; - this.raw = Collections.unmodifiableList(raw); - } - - /** - * @return a List of List of String. - */ - public List> raw() { - return this.raw; - } - - /** - * Converts the table to a List of Map. The top row is used as keys in the maps, - * and the rows below are used as values. - * - * @param key type - * @param value type - * @param keyType key type - * @param valueType value type - * - * @return a List of Map. - */ - public List> asMaps(Class keyType, Class valueType) { - return tableConverter.toMaps(this, keyType, valueType); - } - - /** - * Converts the table to a single Map. The left column is used as keys, the right column as values. - * - * @param key type - * @param value type - * @param keyType key type - * @param valueType value type - * @return a Map. - * @throws cucumber.runtime.CucumberException if the table doesn't have 2 columns. - */ - public Map asMap(Class keyType, Class valueType) { - return tableConverter.toMap(this, keyType, valueType); - } - - /** - * Converts the table to a List. - * - * If {@code itemType} is a scalar type the table is flattened. - * - * Otherwise, the top row is used to name the fields/properties and the remaining - * rows are turned into list items. - * - * @param itemType the type of the list items - * @param the type of the list items - * @return a List of objects - */ - public List asList(Class itemType) { - return tableConverter.toList(this, itemType); - } - - /** - * Converts the table to a List of List of scalar. - * - * @param itemType the type of the list items - * @param the type of the list items - * @return a List of List of objects - */ - public List> asLists(Class itemType) { - return tableConverter.toLists(this, itemType); - } - - public List topCells() { - return raw.isEmpty() ? Collections.emptyList() : raw.get(0); - } - - public List> cells(int firstRow) { - return raw.subList(firstRow, raw.size()); - } - - /** - * Creates another table using the same {@link Locale} and {@link Format} that was used to create this table. - * - * @param raw a list of objects - * @param columnNames optional explicit header columns - * @return a new table - */ - public DataTable toTable(List raw, String... columnNames) { - return tableConverter.toTable(raw, columnNames); - } - - /** - * Diffs this table with {@code other}, which can be a {@code List<List<String>>} or a - * {@code List<YourType>}. - * - * @param other the other table to diff with. - * @throws cucumber.runtime.table.TableDiffException if the tables are different. - */ - public void diff(List other) throws TableDiffException { - List topCells = topCells(); - DataTable otherTable = toTable(other, topCells.toArray(new String[topCells.size()])); - diff(otherTable); - } - - /** - * Diffs this table with {@code other}. - * - * @param other the other table to diff with. - * @throws TableDiffException if the tables are different. - */ - public void diff(DataTable other) throws TableDiffException { - new TableDiffer(this, other).calculateDiffs(); - } - - /** - * Diffs this table with {@code other}. - * The order is not important. A set-difference is applied. - * @param other the other table to diff with. - * @throws TableDiffException if the tables are different. - */ - public void unorderedDiff(DataTable other) throws TableDiffException { - new TableDiffer(this, other).calculateUnorderedDiffs(); - } - - /** - * Diffs this table with {@code other}, which can be a {@code List<List<String>>} or a - * {@code List<YourType>}. - * - * @param other the other table to diff with. - * @throws cucumber.runtime.table.TableDiffException if the tables are different. - */ - public void unorderedDiff(List other) throws TableDiffException { - List topCells = topCells(); - DataTable otherTable = toTable(other, topCells.toArray(new String[topCells.size()])); - unorderedDiff(otherTable); - } - - /** - * Internal method. Do not use. - * - * @return a list of raw rows. - */ - public List getPickleRows() { - return Collections.unmodifiableList(pickleTable.getRows()); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - TablePrinter printer = createTablePrinter(); - printer.printTable(raw, result); - return result.toString(); - } - - public List diffableRows() { - List result = new ArrayList(); - List> convertedRows = raw(); - for (int i = 0; i < convertedRows.size(); i++) { - result.add(new DiffableRow(getPickleRows().get(i), convertedRows.get(i))); - } - return result; - } - - public TableConverter getTableConverter() { - return tableConverter; - } - - public DataTable transpose() { - List> transposed = new ArrayList>(); - for (int i = 0; i < pickleTable.getRows().size(); i++) { - PickleRow pickleRow = pickleTable.getRows().get(i); - for (int j = 0; j < pickleRow.getCells().size(); j++) { - List row = null; - if (j < transposed.size()) { - row = transposed.get(j); - } - if (row == null) { - row = new ArrayList(); - transposed.add(row); - } - row.add(pickleRow.getCells().get(j).getValue()); - } - } - return new DataTable(this.pickleTable, transposed, this.tableConverter); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof DataTable)) return false; - DataTable dataTable = (DataTable) o; - return raw.equals(dataTable.raw); - } - - @Override - public int hashCode() { - return raw.hashCode(); - } - - protected TablePrinter createTablePrinter() { - return new TablePrinter(); - } -} diff --git a/core/src/main/java/cucumber/api/Format.java b/core/src/main/java/cucumber/api/Format.java deleted file mode 100644 index dabbfc646a..0000000000 --- a/core/src/main/java/cucumber/api/Format.java +++ /dev/null @@ -1,43 +0,0 @@ -package cucumber.api; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

- * This annotation can be specified on step definition method parameters to give Cucumber a hint - * about how to transform a String into an object such as a Date or a Calendar. For example, if you have the following Gherkin step with - * a ISO 8601 date: - *

- *
- * Given the date is 2012-03-01T06:54:12
- * 
- *

- * Then the following Java Step Definition would convert that into a Date: - *

- *
- * @Given("^the date is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})$")
- * public void the_date_is(@Format("yyyy-MM-dd'T'HH:mm:ss") Date date) {
- *     this.date = date;
- * }
- * 
- *

- * Or a Calendar: - *

- *
- * @Given("^the date is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})$")
- * public void the_date_is(@Format("yyyy-MM-dd'T'HH:mm:ss") Calendar cal) {
- *     this.cal = cal;
- * }
- * 
- *

- * This annotation also works for data tables that are transformed to a list of beans with Date or Calendar fields. - *

- */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) -public @interface Format { - String value(); -} diff --git a/core/src/main/java/cucumber/api/PickleStepTestStep.java b/core/src/main/java/cucumber/api/PickleStepTestStep.java index 60e0aab04e..3ad92d4d17 100644 --- a/core/src/main/java/cucumber/api/PickleStepTestStep.java +++ b/core/src/main/java/cucumber/api/PickleStepTestStep.java @@ -31,7 +31,7 @@ public interface PickleStepTestStep extends TestStep { * * @return argument provided to the step definition */ - List getDefinitionArgument(); + List getDefinitionArgument(); /** * Returns arguments provided to the Gherkin step. E.g: diff --git a/core/src/main/java/cucumber/api/TableConverter.java b/core/src/main/java/cucumber/api/TableConverter.java deleted file mode 100644 index 2a2b81450c..0000000000 --- a/core/src/main/java/cucumber/api/TableConverter.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.api; - -import java.lang.reflect.Type; -import java.util.List; -import java.util.Map; - -public interface TableConverter { - T convert(DataTable dataTable, Type type, boolean transposed); - - List toList(DataTable dataTable, Type itemType); - - List> toLists(DataTable dataTable, Type itemType); - - Map toMap(DataTable dataTable, Type keyType, Type valueType); - - List> toMaps(DataTable dataTable, Type keyType, Type valueType); - - DataTable toTable(List objects, String... columnNames); -} diff --git a/core/src/main/java/cucumber/api/TestStep.java b/core/src/main/java/cucumber/api/TestStep.java index 61fe4ff6c9..8747b71ad3 100644 --- a/core/src/main/java/cucumber/api/TestStep.java +++ b/core/src/main/java/cucumber/api/TestStep.java @@ -1,5 +1,6 @@ package cucumber.api; + import java.util.List; /** diff --git a/core/src/main/java/cucumber/api/Transform.java b/core/src/main/java/cucumber/api/Transform.java deleted file mode 100644 index 94ea372975..0000000000 --- a/core/src/main/java/cucumber/api/Transform.java +++ /dev/null @@ -1,19 +0,0 @@ -package cucumber.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An annotation to specify how a Step Definition argument is transformed. - * - * @see Transformer - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) -@Documented -public @interface Transform { - Class> value(); -} diff --git a/core/src/main/java/cucumber/api/Transformer.java b/core/src/main/java/cucumber/api/Transformer.java deleted file mode 100644 index 3ffbe4a841..0000000000 --- a/core/src/main/java/cucumber/api/Transformer.java +++ /dev/null @@ -1,111 +0,0 @@ -package cucumber.api; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.runtime.ParameterInfo; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Locale; - -/** - *

- * Allows transformation of a step definition argument to a custom type, giving you full control - * over how that type is instantiated. - *

- *

- * Consider the following Gherkin step: - *

- *
Given today's date is "10/03/1985"
- *

- * As an example, let's assume we want Cucumber to transform the substring "10/03/1985" into an instance of - * org.joda.time.LocalDate class: - *

- *
- *     @Given("today's date is \"(.*)\"")
- *     public void todays_date_is(LocalDate d) {
- *     }
- * 
- *

- * If the parameter's class has a constructor with a single String or Object argument, then - * Cucumber will instantiate it without any further ado. However, in this case that might not give you what you - * want. Depending on your Locale, the date may be Oct 3 or March 10! - * - *

- *

- * This is when you can use a custom transformer. You'll also have to do that if your parameter class doesn't - * have a constructor with a single String or Object argument. For the JODA Time - * example: - *

- * - *
- *     @Given("today's date is \"(.*)\"")
- *     public void todays_date_is(@Transform(JodaTimeConverter.class) LocalDate d) {
- *     }
- * 
- *

- * And then a JodaTimeConverter class: - *

- *
{@code
- *     public static class JodaTimeConverter extends Transformer {
- *         private static DateTimeFormatter FORMATTER = DateTimeFormat.forStyle("S-");
- *
- *         @Override
- *         public LocalDate transform(String value) {
- *             return FORMATTER.withLocale(getLocale()).parseLocalDate(value);
- *         }
- *     }
- * }
- *

- * An alternative to annotating parameters with {@link Transform} is to annotate your class with - * {@link cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter}: - *

- *
- *     @XStreamConverter(MyConverter.class)
- *     public class MyClass {
- *     }
- * 
- *

- * This will also enable a {@link DataTable} to be transformed to - * a List<MyClass;> - *

- * - * @param the type to be instantiated - * @see Transform - */ -public abstract class Transformer implements SingleValueConverter { - private final Type type; - private Locale locale; - - public Transformer() { - ParameterizedType ptype = (ParameterizedType) getClass().getGenericSuperclass(); - this.type = ptype.getActualTypeArguments()[0]; - } - - @Override - public String toString(Object o) { - return o.toString(); - } - - @Override - public final Object fromString(String s) { - return transform(s); - } - - @Override - public boolean canConvert(Class type) { - return type.equals(this.type); - } - - public abstract T transform(String value); - - public void setParameterInfoAndLocale(ParameterInfo parameterInfo, Locale locale) { - this.locale = locale; - } - - /** - * @return the current locale - */ - protected Locale getLocale() { - return locale; - } -} diff --git a/core/src/main/java/cucumber/api/TypeRegistry.java b/core/src/main/java/cucumber/api/TypeRegistry.java new file mode 100644 index 0000000000..a8d288deb3 --- /dev/null +++ b/core/src/main/java/cucumber/api/TypeRegistry.java @@ -0,0 +1,17 @@ +package cucumber.api; + +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.DataTableTypeRegistry; + +import java.lang.reflect.Type; +import java.util.Locale; + +public interface TypeRegistry { + + void defineParameterType(ParameterType parameterType); + + void defineDataTableType(DataTableType tableType); + +} diff --git a/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java b/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java new file mode 100644 index 0000000000..865ce7331a --- /dev/null +++ b/core/src/main/java/cucumber/api/TypeRegistryConfigurer.java @@ -0,0 +1,10 @@ +package cucumber.api; + +import java.util.Locale; + +public interface TypeRegistryConfigurer { + + Locale locale(); + + void configureTypeRegistry(TypeRegistry typeRegistry); +} diff --git a/core/src/main/java/cucumber/runner/PickleStepTestStep.java b/core/src/main/java/cucumber/runner/PickleStepTestStep.java index 0af7dbee91..1052e23eca 100644 --- a/core/src/main/java/cucumber/runner/PickleStepTestStep.java +++ b/core/src/main/java/cucumber/runner/PickleStepTestStep.java @@ -3,6 +3,7 @@ import cucumber.api.HookType; import cucumber.api.Result; import cucumber.api.Scenario; +import cucumber.runtime.DefinitionArgument; import cucumber.runtime.PickleStepDefinitionMatch; import gherkin.pickles.PickleStep; @@ -88,7 +89,7 @@ public String getStepText() { @Override public List getDefinitionArgument() { - return definitionMatch.getArguments(); + return DefinitionArgument.createArguments(definitionMatch.getArguments()); } @Override diff --git a/core/src/main/java/cucumber/runtime/AmbiguousPickleStepDefinitionsMatch.java b/core/src/main/java/cucumber/runtime/AmbiguousPickleStepDefinitionsMatch.java index c79bb10b2f..4e2da40605 100644 --- a/core/src/main/java/cucumber/runtime/AmbiguousPickleStepDefinitionsMatch.java +++ b/core/src/main/java/cucumber/runtime/AmbiguousPickleStepDefinitionsMatch.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.Scenario; import gherkin.pickles.PickleStep; @@ -10,7 +10,7 @@ public class AmbiguousPickleStepDefinitionsMatch extends PickleStepDefinitionMat private AmbiguousStepDefinitionsException exception; public AmbiguousPickleStepDefinitionsMatch(String uri, PickleStep step, AmbiguousStepDefinitionsException e) { - super(Collections.emptyList(), new NoStepDefinition(), uri, step, null); + super(Collections.emptyList(), new NoStepDefinition(), uri, step); this.exception = e; } @@ -24,4 +24,8 @@ public void dryRunStep(String language, Scenario scenario) throws Throwable { runStep(language, scenario); } + @Override + public Match getMatch() { + return exception.getMatches().get(0); + } } diff --git a/core/src/main/java/cucumber/runtime/Arguments.java b/core/src/main/java/cucumber/runtime/Arguments.java deleted file mode 100644 index c79982b428..0000000000 --- a/core/src/main/java/cucumber/runtime/Arguments.java +++ /dev/null @@ -1,29 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.Argument; - -public final class Arguments { - - private Arguments() { - //Not for construction - } - - public static Argument createArgument(final Integer offset, final String val) { - return new Argument() { - @Override - public Integer getOffset() { - return offset; - } - - @Override - public String getVal() { - return val; - } - - @Override - public String toString() { - return getVal(); - } - }; - } -} diff --git a/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java b/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java new file mode 100644 index 0000000000..731c1c818d --- /dev/null +++ b/core/src/main/java/cucumber/runtime/DefaultTypeRegistryConfiguration.java @@ -0,0 +1,20 @@ +package cucumber.runtime; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; + +import java.util.Locale; + +public class DefaultTypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return Locale.ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + //noop + } + +} diff --git a/core/src/main/java/cucumber/runtime/DefinitionArgument.java b/core/src/main/java/cucumber/runtime/DefinitionArgument.java new file mode 100644 index 0000000000..b263a0f4a5 --- /dev/null +++ b/core/src/main/java/cucumber/runtime/DefinitionArgument.java @@ -0,0 +1,41 @@ +package cucumber.runtime; + +import cucumber.api.Argument; +import io.cucumber.stepexpression.ExpressionArgument; + +import java.util.ArrayList; +import java.util.List; + +public final class DefinitionArgument implements Argument { + + private final io.cucumber.cucumberexpressions.Group group; + + private DefinitionArgument(ExpressionArgument expressionArgument) { + group = expressionArgument.getGroup(); + } + + public static List createArguments(List match) { + List args = new ArrayList(); + for (io.cucumber.stepexpression.Argument argument : match) { + if (argument instanceof ExpressionArgument) { + args.add(new DefinitionArgument((ExpressionArgument) argument)); + } + } + return args; + } + + @Override + public String getValue() { + return group == null ? null : group.getValue(); + } + + @Override + public int getStart() { + return group == null ? -1 : group.getStart(); + } + + @Override + public int getEnd() { + return group == null ? -1 : group.getEnd(); + } +} diff --git a/core/src/main/java/cucumber/runtime/FailedPickleStepInstantiationMatch.java b/core/src/main/java/cucumber/runtime/FailedPickleStepInstantiationMatch.java index da9fd85347..71f071d850 100644 --- a/core/src/main/java/cucumber/runtime/FailedPickleStepInstantiationMatch.java +++ b/core/src/main/java/cucumber/runtime/FailedPickleStepInstantiationMatch.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.Scenario; import gherkin.pickles.PickleStep; @@ -10,7 +10,7 @@ public class FailedPickleStepInstantiationMatch extends PickleStepDefinitionMatc private final Throwable throwable; public FailedPickleStepInstantiationMatch(String uri, PickleStep step, Throwable throwable) { - super(Collections.emptyList(), new NoStepDefinition(), uri, step, null); + super(Collections.emptyList(), new NoStepDefinition(), uri, step); this.throwable = removeFrameworkFramesAndAppendStepLocation(throwable, getStepLocation()); } diff --git a/core/src/main/java/cucumber/runtime/JdkPatternArgumentMatcher.java b/core/src/main/java/cucumber/runtime/JdkPatternArgumentMatcher.java deleted file mode 100644 index bef44df6e8..0000000000 --- a/core/src/main/java/cucumber/runtime/JdkPatternArgumentMatcher.java +++ /dev/null @@ -1,31 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.Argument; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class JdkPatternArgumentMatcher { - private final Pattern pattern; - - public JdkPatternArgumentMatcher(Pattern pattern) { - this.pattern = pattern; - } - - public List argumentsFrom(String stepName) { - Matcher matcher = pattern.matcher(stepName); - if (matcher.lookingAt()) { - List arguments = new ArrayList(matcher.groupCount()); - for (int i = 1; i <= matcher.groupCount(); i++) { - int startIndex = matcher.start(i); - arguments.add(Arguments.createArgument(startIndex == -1 ? null : startIndex, matcher.group(i))); - } - return arguments; - } else { - return null; - } - } - -} diff --git a/core/src/main/java/cucumber/runtime/Match.java b/core/src/main/java/cucumber/runtime/Match.java index 769ed262c3..c0800320e7 100644 --- a/core/src/main/java/cucumber/runtime/Match.java +++ b/core/src/main/java/cucumber/runtime/Match.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import java.util.Collections; import java.util.List; @@ -13,6 +13,7 @@ public class Match { public static final Match UNDEFINED = new Match(Collections.emptyList(), null); Match(List arguments, String location) { + if(arguments == null) throw new NullPointerException("argument may not be null"); this.arguments = arguments; this.location = location; } diff --git a/core/src/main/java/cucumber/runtime/NoStepDefinition.java b/core/src/main/java/cucumber/runtime/NoStepDefinition.java index 2af95ed596..8e7ad34481 100644 --- a/core/src/main/java/cucumber/runtime/NoStepDefinition.java +++ b/core/src/main/java/cucumber/runtime/NoStepDefinition.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import gherkin.pickles.PickleStep; import java.lang.reflect.Type; @@ -24,12 +24,12 @@ public Integer getParameterCount() { } @Override - public ParameterInfo getParameterType(int n, Type argumentType) throws IndexOutOfBoundsException { + public ParameterInfo getParameterType(int n, Type argumentType) { return null; } @Override - public void execute(String language, Object[] args) throws Throwable { + public void execute(String language, Object[] args) { } @Override diff --git a/core/src/main/java/cucumber/runtime/ParameterInfo.java b/core/src/main/java/cucumber/runtime/ParameterInfo.java index 8dc92c6ecb..702f651968 100644 --- a/core/src/main/java/cucumber/runtime/ParameterInfo.java +++ b/core/src/main/java/cucumber/runtime/ParameterInfo.java @@ -1,17 +1,9 @@ package cucumber.runtime; -import cucumber.api.Delimiter; -import cucumber.api.Format; -import cucumber.api.Transform; -import cucumber.api.Transformer; import cucumber.api.Transpose; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.runtime.xstream.LocalizedXStreams; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -20,101 +12,40 @@ * This class composes all interesting parameter information into one object. */ public class ParameterInfo { - public static final String DEFAULT_DELIMITER = ",\\s?"; - private final Type type; - private final String format; - private final String delimiter; private final boolean transposed; - private final Transformer transformer; public static List fromMethod(Method method) { List result = new ArrayList(); Type[] genericParameterTypes = method.getGenericParameterTypes(); Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < genericParameterTypes.length; i++) { - String format = null; - String delimiter = DEFAULT_DELIMITER; boolean transposed = false; - Transformer transformer = null; for (Annotation annotation : annotations[i]) { - if (annotation instanceof Format) { - format = ((Format) annotation).value(); - } else if (isAnnotatedWith(annotation, Format.class)) { - format = getAnnotationForAnnotation(annotation, Format.class).value(); - } - - if (annotation instanceof Delimiter) { - delimiter = ((Delimiter) annotation).value(); - } else if (isAnnotatedWith(annotation, Delimiter.class)) { - delimiter = getAnnotationForAnnotation(annotation, Delimiter.class).value(); - } if (annotation instanceof Transpose) { transposed = ((Transpose) annotation).value(); } - if (annotation instanceof Transform) { - transformer = getTransformer(annotation); - } else if (isAnnotatedWith(annotation, Transform.class)) { - transformer = getTransformer(getAnnotationForAnnotation(annotation, Transform.class)); - } } - result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transposed, transformer)); + result.add(new ParameterInfo(genericParameterTypes[i], transposed)); } return result; } public static List fromTypes(Type[] genericParameterTypes) { List result = new ArrayList(); - for (int i = 0; i < genericParameterTypes.length; i++) { - String format = null; - String delimiter = DEFAULT_DELIMITER; - boolean transposed = false; - Transformer transformer = null; - result.add(new ParameterInfo(genericParameterTypes[i], format, delimiter, transposed, transformer)); + for (Type genericParameterType : genericParameterTypes) { + result.add(new ParameterInfo(genericParameterType, false)); } return result; } - private static boolean isAnnotatedWith(Annotation source, Class requiredAnnotation) { - return getAnnotationForAnnotation(source, requiredAnnotation) != null; - } - - private static T getAnnotationForAnnotation(Annotation source, Class requiredAnnotation) { - return source.annotationType().getAnnotation(requiredAnnotation); - } - - private static Transformer getTransformer(Annotation annotation) { - try { - return ((Transform) annotation).value().newInstance(); - } catch (InstantiationException e) { - throw new CucumberException(e); - } catch (IllegalAccessException e) { - throw new CucumberException(e); - } - } - - public ParameterInfo(Type type, String format, String delimiter, Transformer transformer) { - this(type, format, delimiter, false, transformer); + ParameterInfo(Type type) { + this(type, false); } - public ParameterInfo(Type type, String format, String delimiter, boolean transposed, Transformer transformer) { + private ParameterInfo(Type type, boolean transposed) { this.type = type; - this.format = format; - this.delimiter = delimiter; this.transposed = transposed; - this.transformer = transformer; - } - - public Class getRawType() { - return getRawType(type); - } - - private Class getRawType(Type type) { - if (type instanceof ParameterizedType) { - return (Class) ((ParameterizedType) type).getRawType(); - } else { - return (Class) type; - } } public Type getType() { @@ -130,57 +61,4 @@ public String toString() { return type.toString(); } - public Object convert(String value, LocalizedXStreams.LocalizedXStream xStream) { - try { - xStream.setParameterInfo(this); - SingleValueConverter converter; - xStream.processAnnotations(getRawType()); - xStream.autodetectAnnotations(true); // Needed to unlock annotation processing - - if (transformer != null) { - transformer.setParameterInfoAndLocale(this, xStream.getLocale()); - converter = transformer; - } else { - if (List.class.isAssignableFrom(getRawType())) { - converter = getListConverter(type, xStream); - } else { - converter = xStream.getSingleValueConverter(getRawType()); - } - if (converter == null) { - throw new CucumberException(String.format( - "Don't know how to convert \"%s\" into %s.\n" + - "Try writing your own converter:\n" + - "\n" + - "@%s(%sConverter.class)\n" + - "public class %s {}\n", - value, - getRawType().getName(), - XStreamConverter.class.getName(), - getRawType().getSimpleName(), - getRawType().getSimpleName() - )); - } - } - return converter.fromString(value); - } finally { - xStream.unsetParameterInfo(); - } - } - - private SingleValueConverter getListConverter(Type type, LocalizedXStreams.LocalizedXStream xStream) { - Class elementType = type instanceof ParameterizedType - ? getRawType(((ParameterizedType) type).getActualTypeArguments()[0]) - : Object.class; - - SingleValueConverter elementConverter = xStream.getSingleValueConverter(elementType); - if (elementConverter == null) { - return null; - } else { - return xStream.createListConverter(delimiter, elementConverter); - } - } - - public String getFormat() { - return format; - } } diff --git a/core/src/main/java/cucumber/runtime/PickleStepDefinitionMatch.java b/core/src/main/java/cucumber/runtime/PickleStepDefinitionMatch.java index 06af97d369..db21d34928 100644 --- a/core/src/main/java/cucumber/runtime/PickleStepDefinitionMatch.java +++ b/core/src/main/java/cucumber/runtime/PickleStepDefinitionMatch.java @@ -1,150 +1,119 @@ package cucumber.runtime; -import cucumber.api.Argument; -import cucumber.api.DataTable; +import io.cucumber.cucumberexpressions.CucumberExpressionException; +import io.cucumber.datatable.CucumberDataTableException; +import io.cucumber.datatable.UndefinedDataTableTypeException; +import io.cucumber.stepexpression.Argument; import cucumber.api.Scenario; -import cucumber.api.TableConverter; -import cucumber.runtime.xstream.LocalizedXStreams; -import cucumber.util.Mapper; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleString; -import gherkin.pickles.PickleTable; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import static cucumber.runtime.Arguments.createArgument; -import static cucumber.util.FixJava.map; - public class PickleStepDefinitionMatch extends Match implements StepDefinitionMatch { private final StepDefinition stepDefinition; private final transient String featurePath; // The official JSON gherkin format doesn't have a step attribute, so we're marking this as transient // to prevent it from ending up in the JSON. private final transient PickleStep step; - private final LocalizedXStreams localizedXStreams; - public PickleStepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, PickleStep step, LocalizedXStreams localizedXStreams) { + public PickleStepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, PickleStep step) { super(arguments, stepDefinition.getLocation(false)); this.stepDefinition = stepDefinition; this.featurePath = featurePath; this.step = step; - this.localizedXStreams = localizedXStreams; } @Override public void runStep(String language, Scenario scenario) throws Throwable { - try { - stepDefinition.execute(language, transformedArgs(step, localizedXStreams.get(localeFor(language)))); - } catch (CucumberException e) { - throw e; - } catch (Throwable t) { - throw removeFrameworkFramesAndAppendStepLocation(t, getStepLocation()); - } - } - - @Override - public void dryRunStep(String language, Scenario scenario) throws Throwable { - // Do nothing - } - - /** - * @param step the step to run - * @param xStream used to convert a string to declared stepdef arguments - * @return an Array matching the types or {@code parameterTypes}, or an array of String if {@code parameterTypes} is null - */ - private Object[] transformedArgs(PickleStep step, LocalizedXStreams.LocalizedXStream xStream) { int argumentCount = getArguments().size(); - if (!step.getArgument().isEmpty()) { - argumentCount++; - } Integer parameterCount = stepDefinition.getParameterCount(); if (parameterCount != null && argumentCount != parameterCount) { throw arityMismatch(parameterCount); } - List result = new ArrayList(); - - int n = 0; - for (Argument a : getArguments()) { - ParameterInfo parameterInfo = getParameterType(n, String.class); - Object arg = parameterInfo.convert(a.getVal(), xStream); - result.add(arg); - n++; + try { + for (Argument argument : getArguments()) { + result.add(argument.getValue()); + } + } catch (UndefinedDataTableTypeException e) { + throw registerTypeInConfiguration(e); + } catch (CucumberExpressionException e) { + throw couldNotConvertArguments(e); + } catch (CucumberDataTableException e) { + throw couldNotConvertArguments(e); } - if (!step.getArgument().isEmpty()) { - gherkin.pickles.Argument stepArgument = step.getArgument().get(0); - if (stepArgument instanceof PickleTable) { - result.add(tableArgument((PickleTable) stepArgument, n, xStream)); - } else if (stepArgument instanceof PickleString) { - ParameterInfo parameterInfo = getParameterType(n, String.class); - Object arg = parameterInfo.convert(((PickleString) stepArgument).getContent(), xStream); - result.add(arg); - } + try { + stepDefinition.execute(language, result.toArray(new Object[result.size()])); + } catch (CucumberException e) { + throw e; + } catch (Throwable t) { + throw removeFrameworkFramesAndAppendStepLocation(t, getStepLocation()); } - return result.toArray(new Object[result.size()]); } - private ParameterInfo getParameterType(int n, Type argumentType) { - ParameterInfo parameterInfo = stepDefinition.getParameterType(n, argumentType); - if (parameterInfo == null) { - // Some backends return null because they don't know - parameterInfo = new ParameterInfo(argumentType, null, null, false, null); - } - return parameterInfo; + private CucumberException registerTypeInConfiguration(Exception e) { + return new CucumberException(String.format("" + + "Could not convert arguments for step [%s] defined at '%s'.\n" + + "It appears you did not register a data table type. The details are in the stacktrace below.", //TODO: Add doc URL + stepDefinition.getPattern(), + stepDefinition.getLocation(true) + ), e); } - private Object tableArgument(PickleTable stepArgument, int argIndex, LocalizedXStreams.LocalizedXStream xStream) { - ParameterInfo parameterInfo = getParameterType(argIndex, DataTable.class); - TableConverter tableConverter = new cucumber.runtime.table.TableConverter(xStream, parameterInfo); - DataTable table = new DataTable(stepArgument, tableConverter); - Type type = parameterInfo.getType(); - return tableConverter.convert(table, type, parameterInfo.isTransposed()); + + private CucumberException couldNotConvertArguments(Exception e) { + return new CucumberException(String.format( + "Could not convert arguments for step [%s] defined at '%s'.\n" + + "The details are in the stacktrace below.", + stepDefinition.getPattern(), + stepDefinition.getLocation(true) + ), e); + } + + @Override + public void dryRunStep(String language, Scenario scenario) throws Throwable { + // Do nothing } private CucumberException arityMismatch(int parameterCount) { - List arguments = createArgumentsForErrorMessage(step); + List arguments = createArgumentsForErrorMessage(); return new CucumberException(String.format( - "Arity mismatch: Step Definition '%s' with pattern [%s] is declared with %s parameters. However, the gherkin step has %s arguments %s. \nStep text: %s", - stepDefinition.getLocation(true), - stepDefinition.getPattern(), - parameterCount, - arguments.size(), - arguments, - step.getText() + "Step [%s] is defined with %s parameters at '%s'.\n" + + "However, the gherkin step has %s arguments%sStep text: %s", + stepDefinition.getPattern(), + parameterCount, + stepDefinition.getLocation(true), + arguments.size(), + formatArguments(arguments), + step.getText() )); } - private List createArgumentsForErrorMessage(PickleStep step) { - List arguments = new ArrayList(getArguments()); - if (!step.getArgument().isEmpty()) { - gherkin.pickles.Argument stepArgument = step.getArgument().get(0); - if (stepArgument instanceof PickleString) { - arguments.add(createArgument(-1, "DocString:" + ((PickleString) stepArgument).getContent())); - } else if (stepArgument instanceof PickleTable) { - List> rows = map(((PickleTable) stepArgument).getRows(), new Mapper>() { - @Override - public List map(PickleRow row) { - List raw = new ArrayList(row.getCells().size()); - for (PickleCell pickleCell : row.getCells()) { - raw.add(pickleCell.getValue()); - } - return raw; - } - }); - arguments.add(createArgument(-1, "Table:" + rows.toString())); - } + private String formatArguments(List arguments) { + if (arguments.isEmpty()) { + return ".\n"; + } + + StringBuilder formatted = new StringBuilder(":\n"); + for (String argument : arguments) { + formatted.append(" * ").append(argument).append("\n"); + } + return formatted.toString(); + } + + private List createArgumentsForErrorMessage() { + List arguments = new ArrayList(getArguments().size()); + for (Argument argument : getArguments()) { + arguments.add(argument.toString()); } return arguments; } - Throwable removeFrameworkFramesAndAppendStepLocation(Throwable error, StackTraceElement stepLocation) { + protected Throwable removeFrameworkFramesAndAppendStepLocation(Throwable error, StackTraceElement stepLocation) { StackTraceElement[] stackTraceElements = error.getStackTrace(); if (stackTraceElements.length == 0 || stepLocation == null) { return error; @@ -176,9 +145,12 @@ public String getPattern() { return stepDefinition.getPattern(); } - StackTraceElement getStepLocation() { - int stepLine = step.getLocations().get(step.getLocations().size() - 1).getLine(); - return new StackTraceElement("✽", step.getText(), featurePath, stepLine); + public StackTraceElement getStepLocation() { + return new StackTraceElement("✽", step.getText(), featurePath, getStepLine(step)); + } + + public Match getMatch() { + return this; } StepDefinition getStepDefinition() { @@ -190,4 +162,7 @@ public String getCodeLocation() { return stepDefinition.getLocation(false); } + public static int getStepLine(PickleStep step) { + return step.getLocations().get(step.getLocations().size() - 1).getLine(); + } } diff --git a/core/src/main/java/cucumber/runtime/Reflections.java b/core/src/main/java/cucumber/runtime/Reflections.java index 04918bcd0e..721c2df9d1 100644 --- a/core/src/main/java/cucumber/runtime/Reflections.java +++ b/core/src/main/java/cucumber/runtime/Reflections.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; public class Reflections { private final ClassFinder classFinder; @@ -12,22 +13,27 @@ public Reflections(ClassFinder classFinder) { this.classFinder = classFinder; } - public T instantiateExactlyOneSubclass(Class parentType, String packageName, Class[] constructorParams, Object[] constructorArgs) { - Collection instances = instantiateSubclasses(parentType, packageName, constructorParams, constructorArgs); + public T instantiateExactlyOneSubclass(Class parentType, List packageNames, Class[] constructorParams, Object[] constructorArgs, T fallback) { + Collection instances = instantiateSubclasses(parentType, packageNames, constructorParams, constructorArgs); if (instances.size() == 1) { return instances.iterator().next(); } else if (instances.isEmpty()) { + if(fallback != null) { + return fallback; + } throw new NoInstancesException(parentType); } else { throw new TooManyInstancesException(instances); } } - public Collection instantiateSubclasses(Class parentType, String packageName, Class[] constructorParams, Object[] constructorArgs) { + public Collection instantiateSubclasses(Class parentType, List packageNames, Class[] constructorParams, Object[] constructorArgs) { Collection result = new HashSet(); - for (Class clazz : classFinder.getDescendants(parentType, packageName)) { - if (Utils.isInstantiable(clazz) && hasConstructor(clazz, constructorParams)) { - result.add(newInstance(constructorParams, constructorArgs, clazz)); + for (String packageName : packageNames) { + for (Class clazz : classFinder.getDescendants(parentType, packageName)) { + if (Utils.isInstantiable(clazz)) { + result.add(newInstance(constructorParams, constructorArgs, clazz)); + } } } return result; diff --git a/core/src/main/java/cucumber/runtime/Runtime.java b/core/src/main/java/cucumber/runtime/Runtime.java index 9c34ddde43..b1d756a625 100644 --- a/core/src/main/java/cucumber/runtime/Runtime.java +++ b/core/src/main/java/cucumber/runtime/Runtime.java @@ -1,17 +1,19 @@ package cucumber.runtime; +import cucumber.api.TypeRegistryConfigurer; import cucumber.api.StepDefinitionReporter; import cucumber.api.SummaryPrinter; import cucumber.api.event.TestRunFinished; import cucumber.runner.EventBus; import cucumber.runner.Runner; import cucumber.runner.TimeService; +import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.xstream.LocalizedXStreams; import gherkin.events.PickleEvent; import gherkin.pickles.Compiler; import gherkin.pickles.Pickle; +import io.cucumber.stepexpression.TypeRegistry; import java.io.IOException; import java.io.PrintStream; @@ -21,6 +23,8 @@ import java.util.Map; import java.util.regex.Pattern; +import static java.util.Collections.singletonList; + /** * This is the main entry point for running Cucumber features. */ @@ -37,8 +41,9 @@ public class Runtime { private final List filters; private final EventBus bus; private final Compiler compiler = new Compiler(); + public Runtime(ResourceLoader resourceLoader, ClassFinder classFinder, ClassLoader classLoader, RuntimeOptions runtimeOptions) { - this(resourceLoader, classLoader, loadBackends(resourceLoader, classFinder), runtimeOptions); + this(resourceLoader, classLoader, loadBackends(resourceLoader, classFinder, runtimeOptions), runtimeOptions); } public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collection backends, RuntimeOptions runtimeOptions) { @@ -59,7 +64,7 @@ public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collectio this.classLoader = classLoader; this.runtimeOptions = runtimeOptions; final Glue glue; - glue = optionalGlue == null ? new RuntimeGlue(new LocalizedXStreams(classLoader, runtimeOptions.getConverters())) : optionalGlue; + glue = optionalGlue == null ? new RuntimeGlue() : optionalGlue; this.stats = new Stats(runtimeOptions.isMonochrome()); this.bus = new EventBus(stopWatch); this.runner = new Runner(glue, bus, backends, runtimeOptions); @@ -82,9 +87,12 @@ public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collectio runtimeOptions.setEventBus(bus); } - private static Collection loadBackends(ResourceLoader resourceLoader, ClassFinder classFinder) { + private static Collection loadBackends(ResourceLoader resourceLoader, ClassFinder classFinder, RuntimeOptions runtimeOptions) { Reflections reflections = new Reflections(classFinder); - return reflections.instantiateSubclasses(Backend.class, "cucumber.runtime", new Class[]{ResourceLoader.class}, new Object[]{resourceLoader}); + TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration()); + TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale()); + typeRegistryConfigurer.configureTypeRegistry(typeRegistry); + return reflections.instantiateSubclasses(Backend.class, singletonList("cucumber.runtime"), new Class[]{ResourceLoader.class, TypeRegistry.class}, new Object[]{resourceLoader, typeRegistry}); } /** diff --git a/core/src/main/java/cucumber/runtime/RuntimeGlue.java b/core/src/main/java/cucumber/runtime/RuntimeGlue.java index f50162cbdf..f7163b60fe 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeGlue.java +++ b/core/src/main/java/cucumber/runtime/RuntimeGlue.java @@ -1,8 +1,7 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.StepDefinitionReporter; -import cucumber.runtime.xstream.LocalizedXStreams; import gherkin.pickles.PickleStep; import java.util.ArrayList; @@ -20,16 +19,6 @@ public class RuntimeGlue implements Glue { final List afterHooks = new ArrayList(); final List afterStepHooks = new ArrayList(); final Map matchedStepDefinitionsCache = new HashMap(); - private final LocalizedXStreams localizedXStreams; - - public RuntimeGlue(LocalizedXStreams localizedXStreams) { - this(null, localizedXStreams); - } - - @Deprecated - public RuntimeGlue(UndefinedStepsTracker tracker, LocalizedXStreams localizedXStreams) { - this.localizedXStreams = localizedXStreams; - } @Override public void addStepDefinition(StepDefinition stepDefinition) { @@ -89,19 +78,26 @@ public PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleS CacheEntry cacheEntry = matchedStepDefinitionsCache.get(stepText); if (cacheEntry != null) { - return new PickleStepDefinitionMatch(cacheEntry.arguments, cacheEntry.stepDefinition, featurePath, step, localizedXStreams); + return new PickleStepDefinitionMatch(Collections.emptyList(), cacheEntry.stepDefinition, featurePath, step); } List matches = stepDefinitionMatches(featurePath, step); if (matches.isEmpty()) { return null; } - if (matches.size() == 1) { - PickleStepDefinitionMatch match = matches.get(0); - matchedStepDefinitionsCache.put(stepText, new CacheEntry(match.getStepDefinition(), match.getArguments())); - return match; + if (matches.size() > 1) { + throw new AmbiguousStepDefinitionsException(step, matches); } - throw new AmbiguousStepDefinitionsException(step, matches); + + PickleStepDefinitionMatch match = matches.get(0); + + // We can only cache step definitions without arguments. + // DocString and TableArguments are not included in the stepText used as the cache key. + if(match.getArguments().isEmpty()) { + matchedStepDefinitionsCache.put(stepText, new CacheEntry(match.getStepDefinition())); + } + + return match; } private List stepDefinitionMatches(String featurePath, PickleStep step) { @@ -109,7 +105,7 @@ private List stepDefinitionMatches(String featurePath for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) { List arguments = stepDefinition.matchedArguments(step); if (arguments != null) { - result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step, localizedXStreams)); + result.add(new PickleStepDefinitionMatch(arguments, stepDefinition, featurePath, step)); } } return result; @@ -162,11 +158,9 @@ private void removeScenarioScopedStepdefs() { static final class CacheEntry { StepDefinition stepDefinition; - List arguments; - private CacheEntry(StepDefinition stepDefinition, List arguments) { + private CacheEntry(StepDefinition stepDefinition) { this.stepDefinition = stepDefinition; - this.arguments = arguments; } } } diff --git a/core/src/main/java/cucumber/runtime/RuntimeOptions.java b/core/src/main/java/cucumber/runtime/RuntimeOptions.java index a7611362b5..61da99c1d9 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeOptions.java +++ b/core/src/main/java/cucumber/runtime/RuntimeOptions.java @@ -4,17 +4,16 @@ import cucumber.api.SnippetType; import cucumber.api.StepDefinitionReporter; import cucumber.api.SummaryPrinter; +import io.cucumber.datatable.DataTable; import cucumber.api.event.TestRunStarted; import cucumber.api.formatter.ColorAware; import cucumber.api.formatter.Formatter; import cucumber.api.formatter.StrictAware; import cucumber.runner.EventBus; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; import cucumber.runtime.formatter.PluginFactory; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.model.CucumberFeature; import cucumber.runtime.model.PathWithLines; -import cucumber.runtime.table.TablePrinter; import cucumber.util.FixJava; import cucumber.util.Mapper; import gherkin.GherkinDialect; @@ -37,7 +36,6 @@ import static cucumber.util.FixJava.join; import static cucumber.util.FixJava.map; import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; // IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes. public class RuntimeOptions { @@ -70,7 +68,6 @@ public String map(String keyword) { private final List junitOptions = new ArrayList(); private final PluginFactory pluginFactory; private final List plugins = new ArrayList(); - private final List converters = new ArrayList(); private boolean dryRun; private boolean strict = false; private boolean monochrome = false; @@ -213,11 +210,6 @@ private void parse(List args) { parsedPluginData.updatePluginSummaryPrinterNames(pluginSummaryPrinterNames); } - RuntimeOptions withConverters(List converters) { - this.converters.addAll(converters); - return this; - } - private void addLineFilters(Map> parsedLineFilters, String key, List lines) { if (parsedLineFilters.containsKey(key)) { parsedLineFilters.get(key).addAll(lines); @@ -271,7 +263,6 @@ private int printI18n(String language) { private int printKeywordsFor(GherkinDialect dialect) { StringBuilder builder = new StringBuilder(); - TablePrinter printer = new TablePrinter(); List> table = new ArrayList>(); addKeywordRow(table, "feature", dialect.getFeatureKeywords()); addKeywordRow(table, "background", dialect.getBackgroundKeywords()); @@ -288,7 +279,7 @@ private int printKeywordsFor(GherkinDialect dialect) { addCodeKeywordRow(table, "then", dialect.getThenKeywords()); addCodeKeywordRow(table, "and", dialect.getAndKeywords()); addCodeKeywordRow(table, "but", dialect.getButKeywords()); - printer.printTable(table, builder); + DataTable.create(table).print(builder); System.out.println(builder.toString()); return 0; } @@ -336,10 +327,6 @@ public List getPlugins() { return plugins; } - List getConverters() { - return unmodifiableList(converters); - } - public Formatter formatter(ClassLoader classLoader) { return pluginProxy(classLoader, Formatter.class); } diff --git a/core/src/main/java/cucumber/runtime/RuntimeOptionsFactory.java b/core/src/main/java/cucumber/runtime/RuntimeOptionsFactory.java index 772e48d674..8c51988a05 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeOptionsFactory.java +++ b/core/src/main/java/cucumber/runtime/RuntimeOptionsFactory.java @@ -1,8 +1,6 @@ package cucumber.runtime; import cucumber.api.CucumberOptions; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverters; import cucumber.runtime.formatter.PluginFactory; import cucumber.runtime.io.MultiLoader; @@ -24,9 +22,7 @@ public RuntimeOptionsFactory(Class clazz) { public RuntimeOptions create() { List args = buildArgsFromOptions(); - List converters = buildConverters(); - return new RuntimeOptions(args) - .withConverters(converters); + return new RuntimeOptions(args); } private List buildArgsFromOptions() { @@ -144,27 +140,6 @@ private void addJunitOptions(CucumberOptions options, List args) { } } - private List buildConverters() { - List converters = new ArrayList(); - - for (Class classWithConverters = clazz; - hasSuperClass(classWithConverters); - classWithConverters = classWithConverters.getSuperclass() - ) { - XStreamConverters xstreamConverters = classWithConverters.getAnnotation(XStreamConverters.class); - if (xstreamConverters != null) { - Collections.addAll(converters, xstreamConverters.value()); - } - - XStreamConverter xstreamConverter = classWithConverters.getAnnotation(XStreamConverter.class); - if (xstreamConverter != null) { - converters.add(xstreamConverter); - } - } - - return converters; - } - static String packagePath(Class clazz) { return packagePath(packageName(clazz.getName())); } diff --git a/core/src/main/java/cucumber/runtime/StepDefinition.java b/core/src/main/java/cucumber/runtime/StepDefinition.java index 3d40cba674..fcd94b8a0a 100644 --- a/core/src/main/java/cucumber/runtime/StepDefinition.java +++ b/core/src/main/java/cucumber/runtime/StepDefinition.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import gherkin.pickles.PickleStep; import java.lang.reflect.Type; diff --git a/core/src/main/java/cucumber/runtime/UndefinedPickleStepDefinitionMatch.java b/core/src/main/java/cucumber/runtime/UndefinedPickleStepDefinitionMatch.java index f5a8fbf4a4..e391e132de 100644 --- a/core/src/main/java/cucumber/runtime/UndefinedPickleStepDefinitionMatch.java +++ b/core/src/main/java/cucumber/runtime/UndefinedPickleStepDefinitionMatch.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.Scenario; import gherkin.pickles.PickleStep; @@ -9,7 +9,7 @@ public class UndefinedPickleStepDefinitionMatch extends PickleStepDefinitionMatch { public UndefinedPickleStepDefinitionMatch(PickleStep step) { - super(Collections.emptyList(), new NoStepDefinition(), null, step, null); + super(Collections.emptyList(), new NoStepDefinition(), null, step); } @Override diff --git a/core/src/main/java/cucumber/runtime/Utils.java b/core/src/main/java/cucumber/runtime/Utils.java index 76f3000bab..33f998204a 100644 --- a/core/src/main/java/cucumber/runtime/Utils.java +++ b/core/src/main/java/cucumber/runtime/Utils.java @@ -3,13 +3,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.net.MalformedURLException; import java.net.URL; -import java.util.List; -import java.util.Map; public class Utils { private Utils() { @@ -72,36 +67,6 @@ private static Method targetMethod(final Object target, final Method method) thr } } - public static Type listItemType(Type type) { - return typeArg(type, List.class, 0); - } - - public static Type mapKeyType(Type type) { - return typeArg(type, Map.class, 0); - } - - public static Type mapValueType(Type type) { - return typeArg(type, Map.class, 1); - } - - private static Type typeArg(Type type, Class wantedRawType, int index) { - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - Type rawType = parameterizedType.getRawType(); - if (rawType instanceof Class && wantedRawType.isAssignableFrom((Class) rawType)) { - Type result = parameterizedType.getActualTypeArguments()[index]; - if(result instanceof TypeVariable) { - throw new CucumberException("Generic types must be explicit"); - } - return result; - } else { - return null; - } - } else { - return null; - } - } - public static URL toURL(String pathOrUrl) { try { if (!pathOrUrl.endsWith("/")) { diff --git a/core/src/main/java/cucumber/runtime/autocomplete/StepdefGenerator.java b/core/src/main/java/cucumber/runtime/autocomplete/StepdefGenerator.java deleted file mode 100644 index 719fe09ae8..0000000000 --- a/core/src/main/java/cucumber/runtime/autocomplete/StepdefGenerator.java +++ /dev/null @@ -1,61 +0,0 @@ -package cucumber.runtime.autocomplete; - -import cucumber.api.Argument; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.model.CucumberFeature; -import gherkin.pickles.Compiler; -import gherkin.pickles.Pickle; -import gherkin.pickles.PickleStep; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Generates metadata to be used for Code Completion: https://github.com/cucumber/gherkin/wiki/Code-Completion - */ -public class StepdefGenerator { - private static final Comparator STEP_DEFINITION_COMPARATOR = new Comparator() { - @Override - public int compare(StepDefinition a, StepDefinition b) { - return a.getPattern().compareTo(b.getPattern()); - } - }; - - public List generate(Collection stepDefinitions, List features) { - List result = new ArrayList(); - - List sortedStepdefs = new ArrayList(); - sortedStepdefs.addAll(stepDefinitions); - Collections.sort(sortedStepdefs, STEP_DEFINITION_COMPARATOR); - Compiler compiler = new Compiler(); - for (StepDefinition stepDefinition : sortedStepdefs) { - MetaStepdef metaStepdef = new MetaStepdef(); - metaStepdef.source = stepDefinition.getPattern(); - metaStepdef.flags = ""; // TODO = get the flags too - for (CucumberFeature feature : features) { - for (Pickle pickle : compiler.compile(feature.getGherkinFeature())) { - for (PickleStep step : pickle.getSteps()) { - List arguments = stepDefinition.matchedArguments(step); - if (arguments != null) { - MetaStepdef.MetaStep ms = new MetaStepdef.MetaStep(); - ms.name = step.getText(); - for (Argument argument : arguments) { - MetaStepdef.MetaArgument ma = new MetaStepdef.MetaArgument(); - ma.offset = argument.getOffset(); - ma.val = argument.getVal(); - ms.args.add(ma); - } - metaStepdef.steps.add(ms); - } - } - } - } - result.add(metaStepdef); - } - return result; - } - -} diff --git a/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java b/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java index 07d06014e4..8d77e55a9f 100644 --- a/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java +++ b/core/src/main/java/cucumber/runtime/formatter/JSONFormatter.java @@ -56,7 +56,7 @@ public void receive(TestSourceRead event) { handleTestSourceRead(event); } }; - private EventHandler caseStartedHandler= new EventHandler() { + private EventHandler caseStartedHandler = new EventHandler() { @Override public void receive(TestCaseStarted event) { handleTestCaseStarted(event); @@ -356,8 +356,10 @@ private Map createMatchMap(TestStep step, Result result) { List> argumentList = new ArrayList>(); for (cucumber.api.Argument argument : testStep.getDefinitionArgument()) { Map argumentMap = new HashMap(); - argumentMap.put("val", argument.getVal()); - argumentMap.put("offset", argument.getOffset()); + if (argument.getValue() != null) { + argumentMap.put("val", argument.getValue()); + argumentMap.put("offset", argument.getStart()); + } argumentList.add(argumentMap); } matchMap.put("arguments", argumentList); diff --git a/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java b/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java index 5430522ce4..28161934dd 100644 --- a/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java +++ b/core/src/main/java/cucumber/runtime/formatter/PrettyFormatter.java @@ -60,7 +60,7 @@ public void receive(TestSourceRead event) { handleTestSourceRead(event); } }; - private EventHandler caseStartedHandler= new EventHandler() { + private EventHandler caseStartedHandler = new EventHandler() { @Override public void receive(TestCaseStarted event) { handleTestCaseStarted(event); @@ -213,20 +213,21 @@ String formatStepText(String keyword, String stepText, Format textFormat, Format StringBuilder result = new StringBuilder(textFormat.text(keyword)); for (Argument argument : arguments) { // can be null if the argument is missing. - if (argument.getOffset() != null) { - int argumentOffset = argument.getOffset(); + if (argument.getValue() != null) { + int argumentOffset = argument.getStart(); // a nested argument starts before the enclosing argument ends; ignore it when formatting - if (argumentOffset < beginIndex ) { + if (argumentOffset < beginIndex) { continue; } String text = stepText.substring(beginIndex, argumentOffset); result.append(textFormat.text(text)); } // val can be null if the argument isn't there, for example @And("(it )?has something") - if (argument.getVal() != null) { - result.append(argFormat.text(argument.getVal())); + if (argument.getValue() != null) { + String text = stepText.substring(argument.getStart(), argument.getEnd()); + result.append(argFormat.text(text)); // set beginIndex to end of argument - beginIndex = argument.getOffset() + argument.getVal().length(); + beginIndex = argument.getEnd(); } } if (beginIndex != stepText.length()) { diff --git a/core/src/main/java/cucumber/runtime/io/MultiLoader.java b/core/src/main/java/cucumber/runtime/io/MultiLoader.java index db33eb6588..f4856b471d 100644 --- a/core/src/main/java/cucumber/runtime/io/MultiLoader.java +++ b/core/src/main/java/cucumber/runtime/io/MultiLoader.java @@ -1,5 +1,8 @@ package cucumber.runtime.io; +import java.util.ArrayList; +import java.util.List; + public class MultiLoader implements ResourceLoader { public static final String CLASSPATH_SCHEME = "classpath:"; @@ -20,6 +23,14 @@ public Iterable resources(String path, String suffix) { } } + public static List packageName(List glue) { + List packageNames = new ArrayList(glue.size()); + for (String gluePath : glue) { + packageNames.add(packageName(gluePath)); + } + return packageNames; + } + public static String packageName(String gluePath) { if (isClasspathPath(gluePath)) { gluePath = stripClasspathPrefix(gluePath); diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java index 49d4528421..493b5f720a 100644 --- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java +++ b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java @@ -3,7 +3,6 @@ import cucumber.api.event.TestSourceRead; import cucumber.runner.EventBus; import cucumber.runtime.CucumberException; -import cucumber.runtime.FeatureBuilder; import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.Resource; import cucumber.runtime.io.ResourceLoader; diff --git a/core/src/main/java/cucumber/runtime/FeatureBuilder.java b/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java similarity index 97% rename from core/src/main/java/cucumber/runtime/FeatureBuilder.java rename to core/src/main/java/cucumber/runtime/model/FeatureBuilder.java index ed64ba05e3..728c2e1c69 100644 --- a/core/src/main/java/cucumber/runtime/FeatureBuilder.java +++ b/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java @@ -1,7 +1,7 @@ -package cucumber.runtime; +package cucumber.runtime.model; +import cucumber.runtime.CucumberException; import cucumber.runtime.io.Resource; -import cucumber.runtime.model.CucumberFeature; import cucumber.util.Encoding; import gherkin.AstBuilder; import gherkin.Parser; diff --git a/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java b/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java index 9194831058..83b378b5cf 100644 --- a/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java +++ b/core/src/main/java/cucumber/runtime/snippets/ArgumentPattern.java @@ -3,35 +3,25 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class ArgumentPattern { +class ArgumentPattern { private final Pattern pattern; - private final Class type; private final String replacement; - public ArgumentPattern(Pattern pattern, Class type) { - this(pattern, pattern.pattern(), type); + ArgumentPattern(Pattern pattern) { + this(pattern, pattern.pattern()); } - public ArgumentPattern(Pattern pattern, String replacement, Class type) { + private ArgumentPattern(Pattern pattern, String replacement) { this.pattern = pattern; this.replacement = replacement; - this.type = type; } - public Pattern pattern() { - return pattern; - } - - public Class type() { - return type; - } - - public String replaceMatchesWithGroups(String name) { + String replaceMatchesWithGroups(String name) { return replaceMatchWith(name, replacement); } - public String replaceMatchesWithSpace(String name) { + String replaceMatchesWithSpace(String name) { return replaceMatchWith(name, " "); } diff --git a/core/src/main/java/cucumber/runtime/snippets/Snippet.java b/core/src/main/java/cucumber/runtime/snippets/Snippet.java index 0d24446916..91805708d5 100644 --- a/core/src/main/java/cucumber/runtime/snippets/Snippet.java +++ b/core/src/main/java/cucumber/runtime/snippets/Snippet.java @@ -1,19 +1,21 @@ package cucumber.runtime.snippets; -import java.util.List; +import java.lang.reflect.Type; +import java.util.Map; public interface Snippet { /** - * @return a {@link java.text.MessageFormat} template used to generate a snippet. The template can access the following variables: - *

- *

    - *
  • {0} : Step Keyword
  • - *
  • {1} : Value of {@link #escapePattern(String)}
  • - *
  • {2} : Function name
  • - *
  • {3} : Value of {@link #arguments(java.util.List)}
  • - *
  • {4} : Regexp hint comment
  • - *
  • {5} : value of {@link #tableHint()} if the step has a table
  • - *
+ * @return a {@link java.text.MessageFormat} template used to generate a snippet. The template can access the + * following variables: + *

+ *

    + *
  • {0} : Step Keyword
  • + *
  • {1} : Value of {@link #escapePattern(String)}
  • + *
  • {2} : Function name
  • + *
  • {3} : Value of {@link #arguments(Map)}
  • + *
  • {4} : Regexp hint comment
  • + *
  • {5} : value of {@link #tableHint()} if the step has a table
  • + *
*/ String template(); @@ -23,24 +25,13 @@ public interface Snippet { String tableHint(); /** - * @param argumentTypes the types the snippet's argument should accept - * @return a string representation of the arguments - */ - String arguments(List> argumentTypes); - - /** - * Langauges that don't support named capture groups should return null. + * Constructs a string representation of the arguments a step definition should accept. The arguments are + * provided a map of (suggested) names and types. The arguments are ordered by their position. * - * @return the start of a named capture group - */ - String namedGroupStart(); - - /** - * Langauges that don't support named capture groups should return null. - * - * @return the end of a named capture group + * @param arguments ordered pairs of names and types + * @return a string representation of the arguments */ - String namedGroupEnd(); + String arguments(Map arguments); /** * @param pattern the computed pattern that will match an undefined step diff --git a/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java b/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java index 1291f4534b..08b1e72987 100644 --- a/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java +++ b/core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java @@ -1,76 +1,54 @@ package cucumber.runtime.snippets; -import cucumber.api.DataTable; +import io.cucumber.cucumberexpressions.GeneratedExpression; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.datatable.DataTable; import gherkin.pickles.Argument; import gherkin.pickles.PickleStep; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; +import io.cucumber.cucumberexpressions.CucumberExpressionGenerator; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import java.lang.reflect.Type; import java.text.MessageFormat; -import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.regex.Matcher; +import java.util.Map; import java.util.regex.Pattern; public class SnippetGenerator { private static final ArgumentPattern[] DEFAULT_ARGUMENT_PATTERNS = new ArgumentPattern[]{ - new ArgumentPattern(Pattern.compile("\"([^\"]*)\""), String.class), - new ArgumentPattern(Pattern.compile("(\\d+)"), Integer.TYPE), - new ArgumentPattern(Pattern.compile("<([^>]*)>"), "(.*)", String.class) - }; - private static final Pattern GROUP_PATTERN = Pattern.compile("\\("); - private static final Pattern[] ESCAPE_PATTERNS = new Pattern[]{ - Pattern.compile("\\$"), - Pattern.compile("\\("), - Pattern.compile("\\)"), - Pattern.compile("\\["), - Pattern.compile("\\]"), - Pattern.compile("\\?"), - Pattern.compile("\\*"), - Pattern.compile("\\+"), - Pattern.compile("\\."), - Pattern.compile("\\^") + new ArgumentPattern(Pattern.compile("\\{.*?}")), }; private static final String REGEXP_HINT = "Write code here that turns the phrase above into concrete actions"; private final Snippet snippet; + private final CucumberExpressionGenerator generator; - public SnippetGenerator(Snippet snippet) { + public SnippetGenerator(Snippet snippet, ParameterTypeRegistry parameterTypeRegistry) { this.snippet = snippet; + this.generator = new CucumberExpressionGenerator(parameterTypeRegistry); } public String getSnippet(PickleStep step, String keyword, FunctionNameGenerator functionNameGenerator) { + List expressions = generator.generateExpressions(step.getText()); + GeneratedExpression expression = expressions.get(0); + return MessageFormat.format( - snippet.template(), - keyword, - snippet.escapePattern(patternFor(step.getText())), - functionName(step.getText(), functionNameGenerator), - snippet.arguments(argumentTypes(step)), - REGEXP_HINT, - !step.getArgument().isEmpty() && step.getArgument().get(0) instanceof PickleTable ? snippet.tableHint() : "" + snippet.template(), + keyword, + snippet.escapePattern(expression.getSource()), + functionName(expression.getSource(), functionNameGenerator), + snippet.arguments(arguments(step, expression.getParameterNames(), expression.getParameterTypes())), + REGEXP_HINT, + !step.getArgument().isEmpty() && step.getArgument().get(0) instanceof PickleTable ? snippet.tableHint() : "" ); } - String patternFor(String stepName) { - String pattern = stepName; - for (Pattern escapePattern : ESCAPE_PATTERNS) { - Matcher m = escapePattern.matcher(pattern); - String replacement = Matcher.quoteReplacement(escapePattern.toString()); - pattern = m.replaceAll(replacement); - } - for (ArgumentPattern argumentPattern : argumentPatterns()) { - pattern = argumentPattern.replaceMatchesWithGroups(pattern); - } - if (snippet.namedGroupStart() != null) { - pattern = withNamedGroups(pattern); - } - - return "^" + pattern + "$"; - } - private String functionName(String sentence, FunctionNameGenerator functionNameGenerator) { - if(functionNameGenerator == null) { + if (functionNameGenerator == null) { return null; } for (ArgumentPattern argumentPattern : argumentPatterns()) { @@ -80,72 +58,44 @@ private String functionName(String sentence, FunctionNameGenerator functionNameG } - private String withNamedGroups(String snippetPattern) { - Matcher m = GROUP_PATTERN.matcher(snippetPattern); + private Map arguments(PickleStep step, List parameterNames, List> parameterTypes) { + Map arguments = new LinkedHashMap(parameterTypes.size() + 1); - StringBuffer sb = new StringBuffer(); - int n = 1; - while (m.find()) { - m.appendReplacement(sb, "(" + snippet.namedGroupStart() + n++ + snippet.namedGroupEnd()); + for (int i = 0; i < parameterTypes.size(); i++) { + ParameterType parameterType = parameterTypes.get(i); + String parameterName = parameterNames.get(i); + arguments.put(parameterName, parameterType.getType()); } - m.appendTail(sb); - - return sb.toString(); - } + if (step.getArgument().isEmpty()) { + return arguments; + } - private List> argumentTypes(PickleStep step) { - String name = step.getText(); - List> argTypes = new ArrayList>(); - Matcher[] matchers = new Matcher[argumentPatterns().length]; - for (int i = 0; i < argumentPatterns().length; i++) { - matchers[i] = argumentPatterns()[i].pattern().matcher(name); + Argument arg = step.getArgument().get(0); + if (arg instanceof PickleString) { + arguments.put(parameterName("docString", parameterNames), String.class); + } + if (arg instanceof PickleTable) { + arguments.put(parameterName("dataTable", parameterNames), DataTable.class); } - int pos = 0; - while (true) { - int matchedLength = 1; - - for (int i = 0; i < matchers.length; i++) { - Matcher m = matchers[i].region(pos, name.length()); - if (m.lookingAt()) { - Class typeForSignature = argumentPatterns()[i].type(); - argTypes.add(typeForSignature); - - matchedLength = m.group().length(); - break; - } - } - pos += matchedLength; + return arguments; + } - if (pos == name.length()) { - break; - } + private String parameterName(String name, List parameterNames) { + if (!parameterNames.contains(name)) { + return name; } - if (!step.getArgument().isEmpty()) { - Argument arg = step.getArgument().get(0); - if (arg instanceof PickleString) { - argTypes.add(String.class); - } - if (arg instanceof PickleTable) { - argTypes.add(DataTable.class); + + for (int i = 1; ; i++) { + if (!parameterNames.contains(name + i)) { + return name + i; } } - return argTypes; } ArgumentPattern[] argumentPatterns() { return DEFAULT_ARGUMENT_PATTERNS; } - public static String untypedArguments(List> argumentTypes) { - StringBuilder sb = new StringBuilder(); - for (int n = 0; n < argumentTypes.size(); n++) { - if (n > 0) { - sb.append(", "); - } - sb.append("arg").append(n + 1); - } - return sb.toString(); - } } diff --git a/core/src/main/java/cucumber/runtime/table/CamelCaseStringConverter.java b/core/src/main/java/cucumber/runtime/table/CamelCaseStringConverter.java deleted file mode 100644 index 8bcee61934..0000000000 --- a/core/src/main/java/cucumber/runtime/table/CamelCaseStringConverter.java +++ /dev/null @@ -1,45 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.runtime.CucumberException; - -import java.util.regex.Pattern; - -public class CamelCaseStringConverter implements StringConverter { - - private static final String WHITESPACE = " "; - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - - @Override - public String map(String string) { - String[] splitted = normalizeSpace(string).split(WHITESPACE); - splitted[0] = uncapitalize(splitted[0]); - for (int i = 1; i < splitted.length; i++) { - splitted[i] = capitalize(splitted[i]); - } - return join(splitted); - } - - private String join(String[] splitted) { - StringBuilder sb = new StringBuilder(); - for (String s : splitted) { - sb.append(s); - } - return sb.toString(); - } - - private String normalizeSpace(String originalHeaderName) { - return WHITESPACE_PATTERN.matcher(originalHeaderName.trim()).replaceAll(WHITESPACE); - } - - private String capitalize(String string) { - return new StringBuilder(string.length()).append(Character.toTitleCase(string.charAt(0))).append(string.substring(1)).toString(); - } - - private String uncapitalize(String string) { - if (string.isEmpty()) { - throw new CucumberException("Field name cannot be empty. Please check the table header."); - } - return new StringBuilder(string.length()).append(Character.toLowerCase(string.charAt(0))).append(string.substring(1)).toString(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/table/DataTableDiff.java b/core/src/main/java/cucumber/runtime/table/DataTableDiff.java deleted file mode 100644 index 5d60a768c1..0000000000 --- a/core/src/main/java/cucumber/runtime/table/DataTableDiff.java +++ /dev/null @@ -1,40 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.api.TableConverter; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleTable; - -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.List; - -public class DataTableDiff extends DataTable { - public enum DiffType { - NONE, DELETE, INSERT - } - - private List diffTypes; - - public static DataTableDiff create(List> diffTableRows, TableConverter tableConverter) { - List rows = new ArrayList(diffTableRows.size()); - List diffTypes = new ArrayList(diffTableRows.size()); - for (SimpleEntry row : diffTableRows) { - rows.add(row.getKey()); - diffTypes.add(row.getValue()); - } - return new DataTableDiff(new PickleTable(rows), diffTypes, tableConverter); - } - - public DataTableDiff(PickleTable pickleTable, List diffTypes, TableConverter tableConverter) { - super(pickleTable, tableConverter); - this.diffTypes = diffTypes; - - } - - @Override - protected TablePrinter createTablePrinter() { - return new DiffTablePrinter(diffTypes); - } - -} diff --git a/core/src/main/java/cucumber/runtime/table/DiffTablePrinter.java b/core/src/main/java/cucumber/runtime/table/DiffTablePrinter.java deleted file mode 100644 index a91b830aeb..0000000000 --- a/core/src/main/java/cucumber/runtime/table/DiffTablePrinter.java +++ /dev/null @@ -1,29 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.runtime.table.DataTableDiff.DiffType; - -import java.util.List; - -public class DiffTablePrinter extends TablePrinter { - private final List diffTypes; - - public DiffTablePrinter(List diffTypes) { - this.diffTypes = diffTypes; - } - - @Override - protected void printStartIndent(StringBuilder buffer, int rowIndex) { - switch (diffTypes.get(rowIndex)) { - case NONE: - buffer.append(" "); - break; - case DELETE: - buffer.append(" - "); - break; - case INSERT: - buffer.append(" + "); - break; - } - } - -} diff --git a/core/src/main/java/cucumber/runtime/table/DiffableRow.java b/core/src/main/java/cucumber/runtime/table/DiffableRow.java deleted file mode 100644 index fbdcf89627..0000000000 --- a/core/src/main/java/cucumber/runtime/table/DiffableRow.java +++ /dev/null @@ -1,29 +0,0 @@ -package cucumber.runtime.table; - -import gherkin.pickles.PickleRow; - -import java.util.List; - -public class DiffableRow { - public final PickleRow row; - public final List convertedRow; - - public DiffableRow(PickleRow row, List convertedRow) { - this.row = row; - this.convertedRow = convertedRow; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DiffableRow that = (DiffableRow) o; - return convertedRow.equals(that.convertedRow); - - } - - @Override - public int hashCode() { - return convertedRow.hashCode(); - } -} diff --git a/core/src/main/java/cucumber/runtime/table/PascalCaseStringConverter.java b/core/src/main/java/cucumber/runtime/table/PascalCaseStringConverter.java deleted file mode 100644 index 1455078458..0000000000 --- a/core/src/main/java/cucumber/runtime/table/PascalCaseStringConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -package cucumber.runtime.table; - -import java.util.regex.Pattern; - -public class PascalCaseStringConverter implements StringConverter { - - private static final String WHITESPACE = " "; - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - - @Override - public String map(String string) { - String[] splitted = normalizeSpace(string).split(WHITESPACE); - for (int i = 0; i < splitted.length; i++) { - splitted[i] = capitalize(splitted[i]); - } - return join(splitted); - } - - private String join(String[] splitted) { - StringBuilder sb = new StringBuilder(); - for (String s : splitted) { - sb.append(s); - } - return sb.toString(); - } - - private String normalizeSpace(String originalHeaderName) { - return WHITESPACE_PATTERN.matcher(originalHeaderName.trim()).replaceAll(WHITESPACE); - } - - private String capitalize(String string) { - return new StringBuilder(string.length()).append(Character.toTitleCase(string.charAt(0))).append(string.substring(1)).toString(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/table/StringConverter.java b/core/src/main/java/cucumber/runtime/table/StringConverter.java deleted file mode 100644 index 850aa8ec0e..0000000000 --- a/core/src/main/java/cucumber/runtime/table/StringConverter.java +++ /dev/null @@ -1,5 +0,0 @@ -package cucumber.runtime.table; - -public interface StringConverter { - String map(String string); -} diff --git a/core/src/main/java/cucumber/runtime/table/TableConverter.java b/core/src/main/java/cucumber/runtime/table/TableConverter.java deleted file mode 100644 index 87c633d052..0000000000 --- a/core/src/main/java/cucumber/runtime/table/TableConverter.java +++ /dev/null @@ -1,315 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.deps.com.thoughtworks.xstream.converters.ConversionException; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamReader; -import cucumber.runtime.CucumberException; -import cucumber.runtime.ParameterInfo; -import cucumber.runtime.xstream.CellWriter; -import cucumber.runtime.xstream.ComplexTypeWriter; -import cucumber.runtime.xstream.ListOfComplexTypeReader; -import cucumber.runtime.xstream.ListOfSingleValueWriter; -import cucumber.runtime.xstream.LocalizedXStreams; -import cucumber.runtime.xstream.MapWriter; -import cucumber.util.Mapper; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleTable; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static cucumber.runtime.Utils.listItemType; -import static cucumber.runtime.Utils.mapKeyType; -import static cucumber.runtime.Utils.mapValueType; -import static cucumber.util.FixJava.map; -import static java.util.Arrays.asList; - -/** - * This class converts a {@link cucumber.api.DataTable} to various other types. - */ -public class TableConverter implements cucumber.api.TableConverter { - private final LocalizedXStreams.LocalizedXStream xStream; - private final ParameterInfo parameterInfo; - - public TableConverter(LocalizedXStreams.LocalizedXStream xStream, ParameterInfo parameterInfo) { - this.xStream = xStream; - this.parameterInfo = parameterInfo; - } - - /** - * This method converts a {@link cucumber.api.DataTable} to another type. - * When a Step Definition is passed a Gherkin Data Table, the runtime will use this method to convert the - * {@link cucumber.api.DataTable} to the declared type before invoking the Step Definition. - *

- * This method uses reflection to inspect the type and delegates to the appropriate {@code toXxx} method. - * - * @param dataTable the table to convert - * @param type the type to convert to - * @param transposed whether the table should be transposed first. - * @return the transformed object. - */ - @Override - public T convert(DataTable dataTable, Type type, boolean transposed) { - if (transposed) { - dataTable = dataTable.transpose(); - } - - if (type == null || (type instanceof Class && ((Class) type).isAssignableFrom(DataTable.class))) { - return (T) dataTable; - } - - Type mapKeyType = mapKeyType(type); - if (mapKeyType != null) { - Type mapValueType = mapValueType(type); - return (T) toMap(dataTable, mapKeyType, mapValueType); - } - - Type itemType = listItemType(type); - if (itemType == null) { - throw new CucumberException("Not a Map or List type: " + type); - } - - Type listItemType = listItemType(itemType); - if (listItemType != null) { - return (T) toLists(dataTable, listItemType); - } else { - SingleValueConverter singleValueConverter = xStream.getSingleValueConverter(itemType); - if (singleValueConverter != null) { - return (T) toList(dataTable, singleValueConverter); - } else { - if (itemType instanceof Class) { - if (Map.class.equals(itemType)) { - // Non-generic map - return (T) toMaps(dataTable, String.class, String.class); - } else { - return (T) toListOfComplexType(dataTable, (Class) itemType); - } - } else { - return (T) toMaps(dataTable, mapKeyType(itemType), mapValueType(itemType)); - } - } - } - } - - private List toListOfComplexType(DataTable dataTable, Class itemType) { - HierarchicalStreamReader reader = new ListOfComplexTypeReader(itemType, convertTopCellsToFieldNames(dataTable), dataTable.cells(1)); - try { - xStream.setParameterInfo(parameterInfo); - return Collections.unmodifiableList((List) xStream.unmarshal(reader)); - } catch (AbstractReflectionConverter.UnknownFieldException e) { - throw new CucumberException(e.getShortMessage()); - } catch (AbstractReflectionConverter.DuplicateFieldException e) { - throw new CucumberException(e.getShortMessage()); - } catch (ConversionException e) { - if (e.getCause() instanceof NullPointerException) { - throw new CucumberException(String.format("Can't assign null value to one of the primitive fields in %s. Please use boxed types.", e.get("class"))); - } else { - throw new CucumberException(e); - } - } finally { - xStream.unsetParameterInfo(); - } - } - - @Override - public List toList(DataTable dataTable, Type itemType) { - SingleValueConverter itemConverter = xStream.getSingleValueConverter(itemType); - if (itemConverter != null) { - return toList(dataTable, itemConverter); - } else { - if (itemType instanceof Class) { - return toListOfComplexType(dataTable, (Class) itemType); - } else { - throw new CucumberException(String.format("Can't convert DataTable to List<%s>", itemType)); - } - } - } - - private List toList(DataTable dataTable, SingleValueConverter itemConverter) { - List result = new ArrayList(); - - for (List row : dataTable.raw()) { - for (String cell : row) { - result.add((T) itemConverter.fromString(cell)); - } - } - return Collections.unmodifiableList(result); - } - - @Override - public List> toLists(DataTable dataTable, Type itemType) { - try { - xStream.setParameterInfo(parameterInfo); - SingleValueConverter itemConverter = xStream.getSingleValueConverter(itemType); - if (itemConverter == null) { - throw new CucumberException(String.format("Can't convert DataTable to List>", itemType)); - } - - List> result = new ArrayList>(); - for (List row : dataTable.raw()) { - List convertedRow = new ArrayList(); - for (String cell : row) { - convertedRow.add((T) itemConverter.fromString(cell)); - } - result.add(Collections.unmodifiableList(convertedRow)); - } - return Collections.unmodifiableList(result); - } finally { - xStream.unsetParameterInfo(); - } - } - - @Override - public Map toMap(DataTable dataTable, Type keyType, Type valueType) { - try { - xStream.setParameterInfo(parameterInfo); - SingleValueConverter keyConverter = xStream.getSingleValueConverter(keyType); - SingleValueConverter valueConverter = xStream.getSingleValueConverter(valueType); - - if (keyConverter == null || valueConverter == null) { - throw new CucumberException(String.format("Can't convert DataTable to Map<%s,%s>", keyType, valueType)); - } - - Map result = new LinkedHashMap(); - for (List row : dataTable.raw()) { - if (row.size() != 2) { - throw new CucumberException("A DataTable can only be converted to a Map when there are 2 columns"); - } - K key = (K) keyConverter.fromString(row.get(0)); - V value = (V) valueConverter.fromString(row.get(1)); - result.put(key, value); - } - return Collections.unmodifiableMap(result); - } finally { - xStream.unsetParameterInfo(); - } - } - - @Override - public List> toMaps(DataTable dataTable, Type keyType, Type valueType) { - try { - xStream.setParameterInfo(parameterInfo); - SingleValueConverter keyConverter = xStream.getSingleValueConverter(keyType); - SingleValueConverter valueConverter = xStream.getSingleValueConverter(valueType); - - if (keyConverter == null || valueConverter == null) { - throw new CucumberException(String.format("Can't convert DataTable to List>", keyType, valueType)); - } - - List> result = new ArrayList>(); - List keyStrings = dataTable.topCells(); - List keys = new ArrayList(); - for (String keyString : keyStrings) { - keys.add((K) keyConverter.fromString(keyString)); - } - List> valueRows = dataTable.cells(1); - for (List valueRow : valueRows) { - Map map = new LinkedHashMap(); - int i = 0; - for (String cell : valueRow) { - map.put(keys.get(i), (V) valueConverter.fromString(cell)); - i++; - } - result.add(Collections.unmodifiableMap(map)); - } - return Collections.unmodifiableList(result); - } finally { - xStream.unsetParameterInfo(); - } - } - - /** - * Converts a List of objects to a DataTable. - * - * @param objects the objects to convert - * @param columnNames an explicit list of column names - * @return a DataTable - */ - @Override - public DataTable toTable(List objects, String... columnNames) { - try { - xStream.setParameterInfo(parameterInfo); - - List header = null; - List> valuesList = new ArrayList>(); - for (Object object : objects) { - CellWriter writer; - if (isListOfSingleValue(object)) { - // XStream needs an instance of ArrayList - object = new ArrayList((List) object); - writer = new ListOfSingleValueWriter(); - } else if (isArrayOfSingleValue(object)) { - // XStream needs an instance of ArrayList - object = new ArrayList(asList((Object[]) object)); - writer = new ListOfSingleValueWriter(); - } else if (object instanceof Map) { - writer = new MapWriter(asList(columnNames)); - } else { - writer = new ComplexTypeWriter(asList(columnNames)); - } - xStream.marshal(object, writer); - if (header == null) { - header = writer.getHeader(); - } - List values = writer.getValues(); - valuesList.add(values); - } - return createDataTable(header, valuesList); - } finally { - xStream.unsetParameterInfo(); - } - } - - private DataTable createDataTable(List header, List> valuesList) { - List gherkinRows = new ArrayList(); - if (header != null) { - gherkinRows.add(gherkinRow(header)); - } - for (List values : valuesList) { - gherkinRows.add(gherkinRow(values)); - } - return new DataTable(new PickleTable(gherkinRows), this); - } - - private PickleRow gherkinRow(List cells) { - List pickleCells = new ArrayList(cells.size()); - for (String cell : cells) { - PickleCell pickleCell = new PickleCell(null, cell); - pickleCells.add(pickleCell); - } - return new PickleRow(pickleCells); - } - - private List convertTopCellsToFieldNames(DataTable dataTable) { - final StringConverter mapper = new CamelCaseStringConverter(); - return map(dataTable.topCells(), new Mapper() { - @Override - public String map(String attributeName) { - return mapper.map(attributeName); - } - }); - } - - private boolean isListOfSingleValue(Object object) { - if (object instanceof List) { - List list = (List) object; - return !list.isEmpty() && xStream.getSingleValueConverter(list.get(0).getClass()) != null; - } - return false; - } - - private boolean isArrayOfSingleValue(Object object) { - if (object.getClass().isArray()) { - Object[] array = (Object[]) object; - return array.length > 0 && xStream.getSingleValueConverter(array[0].getClass()) != null; - } - return false; - } -} diff --git a/core/src/main/java/cucumber/runtime/table/TableDiffException.java b/core/src/main/java/cucumber/runtime/table/TableDiffException.java deleted file mode 100644 index b7b025f512..0000000000 --- a/core/src/main/java/cucumber/runtime/table/TableDiffException.java +++ /dev/null @@ -1,37 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; - -public class TableDiffException extends RuntimeException { - private final DataTable from; - private final DataTable to; - private final DataTable diff; - - public TableDiffException(DataTable from, DataTable to, DataTable diff) { - super("Tables were not identical:\n" + diff.toString()); - this.from = from; - this.to = to; - this.diff = diff; - } - - /** - * @return the left side of the diff - */ - public DataTable getFrom() { - return from; - } - - /** - * @return the right side of the diff - */ - public DataTable getTo() { - return to; - } - - /** - * @return the diff itself - represented as a table - */ - public DataTable getDiff() { - return diff; - } -} diff --git a/core/src/main/java/cucumber/runtime/table/TableDiffer.java b/core/src/main/java/cucumber/runtime/table/TableDiffer.java deleted file mode 100644 index 3e035934ce..0000000000 --- a/core/src/main/java/cucumber/runtime/table/TableDiffer.java +++ /dev/null @@ -1,148 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.deps.difflib.Delta; -import cucumber.deps.difflib.DiffUtils; -import cucumber.deps.difflib.Patch; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cucumber.runtime.table.DataTableDiff.DiffType; -import static java.util.AbstractMap.SimpleEntry; - -public class TableDiffer { - - private final DataTable from; - private final DataTable to; - - public TableDiffer(DataTable fromTable, DataTable toTable) { - checkColumns(fromTable, toTable); - this.from = fromTable; - this.to = toTable; - } - - private void checkColumns(DataTable a, DataTable b) { - if (a.topCells().size() != b.topCells().size() && !b.topCells().isEmpty()) { - throw new IllegalArgumentException("Tables must have equal number of columns:\n" + a + "\n" + b); - } - } - - public void calculateDiffs() throws TableDiffException { - Patch patch = DiffUtils.diff(from.diffableRows(), to.diffableRows()); - List deltas = patch.getDeltas(); - if (!deltas.isEmpty()) { - Map deltasByLine = createDeltasByLine(deltas); - throw new TableDiffException(from, to, createTableDiff(deltasByLine)); - } - } - - public void calculateUnorderedDiffs() throws TableDiffException { - boolean isDifferent = false; - List> diffTableRows = new ArrayList>(); - List> missingRow = new ArrayList>(); - - ArrayList> extraRows = new ArrayList>(); - - // 1. add all "to" row in extra table - // 2. iterate over "from", when a common row occurs, remove it from extraRows - // finally, only extra rows are kept and in same order that in "to". - extraRows.addAll(to.raw()); - - for (PickleRow r : from.getPickleRows()) { - if (!to.raw().contains(getCellValues(r))) { - missingRow.add(getCellValues(r)); - diffTableRows.add( - new SimpleEntry(new PickleRow(r.getCells()), DiffType.DELETE)); - isDifferent = true; - } else { - diffTableRows.add( - new SimpleEntry(new PickleRow(r.getCells()), DiffType.NONE)); - extraRows.remove(getCellValues(r)); - } - } - - for (List e : extraRows) { - diffTableRows.add( - new SimpleEntry(new PickleRow(convertToPickleCells(e)), DiffType.INSERT)); - isDifferent = true; - } - - if (isDifferent) { - throw new TableDiffException(from, to, DataTableDiff.create(diffTableRows, from.getTableConverter())); - } - } - - private List convertToPickleCells(List e) { - List cells = new ArrayList(e.size()); - for (String value : e) { - cells.add(new PickleCell(null, value)); - } - return cells; - } - - private List getCellValues(PickleRow r) { - List values = new ArrayList(r.getCells().size()); - for (PickleCell cell : r.getCells()) { - values.add(cell.getValue()); - } - return values; - } - - private Map createDeltasByLine(List deltas) { - Map deltasByLine = new HashMap(); - for (Delta delta : deltas) { - deltasByLine.put(delta.getOriginal().getPosition(), delta); - } - return deltasByLine; - } - - private DataTable createTableDiff(Map deltasByLine) { - List> diffTableRows = new ArrayList>(); - List> rows = from.raw(); - for (int i = 0; i < rows.size(); i++) { - Delta delta = deltasByLine.get(i); - if (delta == null) { - diffTableRows.add(new SimpleEntry(from.getPickleRows().get(i), DiffType.NONE)); - } else { - addRowsToTableDiff(diffTableRows, delta); - // skipping lines involved in a delta - if (delta.getType() == Delta.TYPE.CHANGE || delta.getType() == Delta.TYPE.DELETE) { - i += delta.getOriginal().getLines().size() - 1; - } else { - diffTableRows.add(new SimpleEntry(from.getPickleRows().get(i), DiffType.NONE)); - } - } - } - // Can have new lines at end - Delta remainingDelta = deltasByLine.get(rows.size()); - if (remainingDelta != null) { - addRowsToTableDiff(diffTableRows, remainingDelta); - } - return DataTableDiff.create(diffTableRows, from.getTableConverter()); - } - - private void addRowsToTableDiff(List> diffTableRows, Delta delta) { - markChangedAndDeletedRowsInOriginalAsMissing(diffTableRows, delta); - markChangedAndInsertedRowsInRevisedAsNew(diffTableRows, delta); - } - - private void markChangedAndDeletedRowsInOriginalAsMissing(List> diffTableRows, Delta delta) { - List deletedLines = (List) delta.getOriginal().getLines(); - for (DiffableRow row : deletedLines) { - diffTableRows.add(new SimpleEntry(new PickleRow(row.row.getCells()), DiffType.DELETE)); - } - } - - private void markChangedAndInsertedRowsInRevisedAsNew(List> diffTableRows, Delta delta) { - List insertedLines = (List) delta.getRevised().getLines(); - for (DiffableRow row : insertedLines) { - diffTableRows.add(new SimpleEntry(new PickleRow(row.row.getCells()), DiffType.INSERT)); - } - } -} diff --git a/core/src/main/java/cucumber/runtime/table/TablePrinter.java b/core/src/main/java/cucumber/runtime/table/TablePrinter.java deleted file mode 100644 index 197d6be527..0000000000 --- a/core/src/main/java/cucumber/runtime/table/TablePrinter.java +++ /dev/null @@ -1,73 +0,0 @@ -package cucumber.runtime.table; - -import java.util.List; - -public class TablePrinter { - private int[][] cellLengths; - private int[] maxLengths; - - public void printTable(List> table, StringBuilder result) { - calculateColumnAndMaxLengths(table); - for (int i = 0; i < table.size(); ++i) { - printRow(table.get(i), i, result); - result.append("\n"); - } - - } - - protected void printStartIndent(StringBuilder buffer, int rowIndex) { - buffer.append(" "); - } - - private void calculateColumnAndMaxLengths(List> rows) { - // find the largest row - int columnCount = 0; - for (List row : rows) { - if (columnCount < row.size()) { - columnCount = row.size(); - } - } - - cellLengths = new int[rows.size()][columnCount]; - maxLengths = new int[columnCount]; - for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { - final List cells = rows.get(rowIndex); - for (int colIndex = 0; colIndex < columnCount; colIndex++) { - final String cell = getCellSafely(cells, colIndex); - final int length = escapeCell(cell).length(); - cellLengths[rowIndex][colIndex] = length; - maxLengths[colIndex] = Math.max(maxLengths[colIndex], length); - } - } - } - - private String getCellSafely(final List cells, final int colIndex) { - return (colIndex < cells.size()) ? cells.get(colIndex) : ""; - } - - private void printRow(List cells, int rowIndex, StringBuilder buffer) { - printStartIndent(buffer, rowIndex); - buffer.append("| "); - for (int colIndex = 0; colIndex < maxLengths.length; colIndex++) { - String cellText = escapeCell(getCellSafely(cells, colIndex)); - buffer.append(cellText); - int padding = maxLengths[colIndex] - cellLengths[rowIndex][colIndex]; - padSpace(buffer, padding); - if (colIndex < maxLengths.length - 1) { - buffer.append(" | "); - } else { - buffer.append(" |"); - } - } - } - - private String escapeCell(String cell) { - return cell.replaceAll("\\\\(?!\\|)", "\\\\\\\\").replaceAll("\\n", "\\\\n").replaceAll("\\|", "\\\\|"); - } - - private void padSpace(StringBuilder buffer, int indent) { - for (int i = 0; i < indent; i++) { - buffer.append(" "); - } - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/BigDecimalConverter.java b/core/src/main/java/cucumber/runtime/xstream/BigDecimalConverter.java deleted file mode 100644 index 989651c501..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/BigDecimalConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.runtime.xstream; - -import java.math.BigDecimal; -import java.util.Locale; - -class BigDecimalConverter extends ConverterWithNumberFormat { - - public BigDecimalConverter(Locale locale) { - super(locale, new Class[]{BigDecimal.class}); - } - - @Override - protected BigDecimal downcast(Number argument) { - // See http://java.sun.com/j2se/6/docs/api/java/math/BigDecimal.html#BigDecimal%28double%29 - return new BigDecimal(Double.toString(argument.doubleValue())); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/BigIntegerConverter.java b/core/src/main/java/cucumber/runtime/xstream/BigIntegerConverter.java deleted file mode 100644 index 22e6a09bd0..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/BigIntegerConverter.java +++ /dev/null @@ -1,17 +0,0 @@ -package cucumber.runtime.xstream; - -import java.math.BigInteger; -import java.util.Locale; - -class BigIntegerConverter extends ConverterWithNumberFormat { - - public BigIntegerConverter(Locale locale) { - super(locale, new Class[]{BigInteger.class}); - } - - @Override - protected BigInteger downcast(Number argument) { - return BigInteger.valueOf(argument.longValue()); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ByteConverter.java b/core/src/main/java/cucumber/runtime/xstream/ByteConverter.java deleted file mode 100644 index 272171a021..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ByteConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class ByteConverter extends ConverterWithNumberFormat { - - public ByteConverter(Locale locale) { - super(locale, new Class[]{Byte.class, Byte.TYPE}); - } - - @Override - protected Byte downcast(Number argument) { - return argument.byteValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/CalendarConverter.java b/core/src/main/java/cucumber/runtime/xstream/CalendarConverter.java deleted file mode 100644 index 38084fd2ef..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/CalendarConverter.java +++ /dev/null @@ -1,20 +0,0 @@ -package cucumber.runtime.xstream; - -import java.text.Format; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; - -class CalendarConverter extends TimeConverter { - public CalendarConverter(Locale locale) { - super(locale, new Class[]{Calendar.class}); - } - - @Override - protected Object transform(Format format, String argument) { - Date date = (Date) super.transform(format, argument); - Calendar cal = Calendar.getInstance(getLocale()); - cal.setTime(date); - return cal; - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/CellWriter.java b/core/src/main/java/cucumber/runtime/xstream/CellWriter.java deleted file mode 100644 index 0ab5e9e0b5..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/CellWriter.java +++ /dev/null @@ -1,11 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.io.AbstractWriter; - -import java.util.List; - -public abstract class CellWriter extends AbstractWriter { - public abstract List getHeader(); - - public abstract List getValues(); -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ClassWithStringAssignableConstructorConverter.java b/core/src/main/java/cucumber/runtime/xstream/ClassWithStringAssignableConstructorConverter.java deleted file mode 100644 index 4f3c9b8005..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ClassWithStringAssignableConstructorConverter.java +++ /dev/null @@ -1,39 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.runtime.CucumberException; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -class ClassWithStringAssignableConstructorConverter implements SingleValueConverter { - private final Constructor ctor; - - ClassWithStringAssignableConstructorConverter(Constructor constructor) { - this.ctor = constructor; - } - - @Override - public String toString(Object obj) { - return obj.toString(); - } - - @Override - public Object fromString(String str) { - try { - return ctor.newInstance(str); - } catch (InstantiationException e) { - throw new CucumberException(e); - } catch (IllegalAccessException e) { - throw new CucumberException(e); - } catch (InvocationTargetException e) { - throw new CucumberException(e.getTargetException()); - } - } - - @Override - public boolean canConvert(Class type) { - return ctor.getDeclaringClass().equals(type); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ComplexTypeWriter.java b/core/src/main/java/cucumber/runtime/xstream/ComplexTypeWriter.java deleted file mode 100644 index 35b8f09441..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ComplexTypeWriter.java +++ /dev/null @@ -1,134 +0,0 @@ -package cucumber.runtime.xstream; - - -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.runtime.CucumberException; -import cucumber.runtime.table.CamelCaseStringConverter; -import cucumber.runtime.table.PascalCaseStringConverter; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; - -import static java.util.Arrays.asList; - -public class ComplexTypeWriter extends CellWriter { - private final List columnNames; - private final Map fields = new LinkedHashMap(); - private final Stack currentKey = new Stack(); - - public ComplexTypeWriter(List columnNames) { - this.columnNames = columnNames; - } - - @Override - public List getHeader() { - return columnNames.isEmpty() ? new ArrayList(fields.keySet()) : columnNames; - } - - @Override - public List getValues() { - CamelCaseStringConverter converter = new CamelCaseStringConverter(); - if (!columnNames.isEmpty()) { - String[] explicitFieldValues = new String[columnNames.size()]; - int n = 0; - for (String columnName : columnNames) { - final String convertedColumnName = converter.map(columnName); - if (fields.containsKey(convertedColumnName)) { - explicitFieldValues[n] = fields.get(convertedColumnName); - } else { - explicitFieldValues[n] = ""; - } - n++; - } - return asList(explicitFieldValues); - } else { - return new ArrayList(fields.values()); - } - } - - @Override - public void startNode(String name) { - currentKey.push(name); - if (currentKey.size() == 2) { - fields.put(name, ""); - } - } - - @Override - public void addAttribute(String name, String value) { - } - - @Override - public void setValue(String value) { - // Add all simple types at level 2. nodeDepth 1 is the root node. - if(currentKey.size() < 2){ - return; - } - - if (currentKey.size() == 2) { - fields.put(currentKey.peek(), value == null ? "" : value); - return; - } - - final String clazz = currentKey.get(0); - final String field = currentKey.get(1); - if ((columnNames.isEmpty() || columnNames.contains(field))) { - throw createMissingConverterException(clazz, field); - } - } - - @Override - public void endNode() { - currentKey.pop(); - } - - @Override - public void flush() { - throw new UnsupportedOperationException(); - } - - @Override - public void close() { - throw new UnsupportedOperationException(); - } - - private static CucumberException createMissingConverterException(String clazz, String field) { - PascalCaseStringConverter converter = new PascalCaseStringConverter(); - return new CucumberException(String.format( - "Don't know how to convert \"%s.%s\" into a table entry.\n" + - "Either exclude %s from the table by selecting the fields to include:\n" + - "\n" + - "DataTable.create(entries, \"Field\", \"Other Field\")\n" + - "\n" + - "Or try writing your own converter:\n" + - "\n" + - "@%s(%sConverter.class)\n" + - "%s %s;\n", - clazz, - field, - field, - XStreamConverter.class.getName(), - converter.map(field), - modifierAndTypeOfField(clazz, field), - field - )); - } - - private static String modifierAndTypeOfField(String clazz, String fieldName) { - try { - Field field = Class.forName(clazz).getDeclaredField(fieldName); - String simpleTypeName = field.getType().getSimpleName(); - String modifiers = Modifier.toString(field.getModifiers()); - return modifiers + " " + simpleTypeName; - } catch (NoSuchFieldException e) { - return "private Object"; - } catch (ClassNotFoundException e) { - return "private Object"; - } - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ConverterWithEnumFormat.java b/core/src/main/java/cucumber/runtime/xstream/ConverterWithEnumFormat.java deleted file mode 100644 index dc37001ae3..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ConverterWithEnumFormat.java +++ /dev/null @@ -1,91 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.ConversionException; - -import java.text.FieldPosition; -import java.text.Format; -import java.text.ParsePosition; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import static java.util.Arrays.asList; - -class ConverterWithEnumFormat extends ConverterWithFormat { - - private final List formats = new ArrayList(); - private final Locale locale; - private final Class typeClass; - - ConverterWithEnumFormat(Locale locale, Class enumClass) { - super(new Class[]{enumClass}); - this.locale = locale; - this.typeClass = enumClass; - formats.add(new OriginalFormat()); - formats.add(new LowercaseFormat()); - formats.add(new UppercaseFormat()); - formats.add(new CapitalizeFormat()); - } - - - @Override - public T transform(String string) { - try { - return super.transform(string); - } catch (ConversionException e) { - String allowed = asList(typeClass.getEnumConstants()).toString(); - throw new ConversionException(String.format("Couldn't convert %s to %s. Legal values are %s", string, typeClass.getName(), allowed)); - } - } - - @Override - public List getFormats() { - return formats; - } - - private class OriginalFormat extends AbstractEnumFormat { - @Override - protected String transformSource(String source) { - return source; - } - } - - private class LowercaseFormat extends AbstractEnumFormat { - @Override - protected String transformSource(String source) { - return source.toLowerCase(locale); - } - } - - private class UppercaseFormat extends AbstractEnumFormat { - @Override - protected String transformSource(String source) { - return source.toUpperCase(locale); - } - } - - private class CapitalizeFormat extends AbstractEnumFormat { - @Override - protected String transformSource(String source) { - String firstLetter = source.substring(0, 1); - String restOfTheString = source.substring(1, source.length()); - return firstLetter.toUpperCase(locale) + restOfTheString; - } - } - - private abstract class AbstractEnumFormat extends Format { - @Override - public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { - return toAppendTo.append(String.valueOf(obj)); - } - - @Override - public Object parseObject(String source, ParsePosition pos) { - return source == null ? null : Enum.valueOf(typeClass, transformSource(source)); - } - - protected abstract String transformSource(String source); - - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ConverterWithFormat.java b/core/src/main/java/cucumber/runtime/xstream/ConverterWithFormat.java deleted file mode 100644 index e92eaabc31..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ConverterWithFormat.java +++ /dev/null @@ -1,70 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.api.Transformer; -import cucumber.deps.com.thoughtworks.xstream.converters.ConversionException; -import cucumber.runtime.CucumberException; - -import java.text.Format; -import java.text.ParsePosition; -import java.util.List; - -import static java.util.Arrays.asList; - -abstract class ConverterWithFormat extends Transformer { - private final Class[] convertibleTypes; - - ConverterWithFormat(Class[] convertibleTypes) { - this.convertibleTypes = convertibleTypes; - } - - public T transform(String string) { - if (string == null || string.isEmpty()) { - return null; - } - for (Format format : getFormats()) { - try { - return (T) transform(format, string); - } catch (Exception ignore) { - // no worries, let's try the next format. - } - } - throw new ConversionException("Couldn't convert \"" + string + "\" to an instance of: " + asList(convertibleTypes)); - } - - /** - * @return A Format to parse the argument - */ - protected abstract List getFormats(); - - /** - * Parses a value using one of the java.util.text format classes. - * - * @param format The format to use - * @param argument The object to parse - * @return The object - */ - @SuppressWarnings("unchecked") - Object transform(final Format format, final String argument) { - ParsePosition position = new ParsePosition(0); - Object result = format.parseObject(argument, position); - if (position.getErrorIndex() != -1) { - throw new CucumberException("Can't parse '" + argument + "' using format " + format); - } - return result; - } - - @Override - public String toString(Object obj) { - return getFormats().get(0).format(obj); - } - - @Override - public boolean canConvert(Class type) { - for (Class convertibleType : convertibleTypes) { - if (convertibleType.isAssignableFrom(type)) { - return true; - } - } - return false; - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ConverterWithNumberFormat.java b/core/src/main/java/cucumber/runtime/xstream/ConverterWithNumberFormat.java deleted file mode 100644 index 52995b04ec..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ConverterWithNumberFormat.java +++ /dev/null @@ -1,28 +0,0 @@ -package cucumber.runtime.xstream; - -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -abstract class ConverterWithNumberFormat extends ConverterWithFormat { - private final List formats = new ArrayList(); - - ConverterWithNumberFormat(Locale locale, Class[] convertibleTypes) { - super(convertibleTypes); - formats.add(NumberFormat.getNumberInstance(locale)); - } - - @Override - public T transform(String string) { - T number = super.transform(string); - return number == null ? null : downcast(number); - } - - @Override - public List getFormats() { - return formats; - } - - protected abstract T downcast(Number argument); -} diff --git a/core/src/main/java/cucumber/runtime/xstream/DateConverter.java b/core/src/main/java/cucumber/runtime/xstream/DateConverter.java deleted file mode 100644 index ab96d7fddd..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/DateConverter.java +++ /dev/null @@ -1,10 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Date; -import java.util.Locale; - -class DateConverter extends TimeConverter { - public DateConverter(Locale locale) { - super(locale, new Class[]{Date.class}); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/DoubleConverter.java b/core/src/main/java/cucumber/runtime/xstream/DoubleConverter.java deleted file mode 100644 index cd4ffcc972..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/DoubleConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class DoubleConverter extends ConverterWithNumberFormat { - - public DoubleConverter(Locale locale) { - super(locale, new Class[]{Double.class, Double.TYPE}); - } - - @Override - protected Double downcast(Number argument) { - return argument.doubleValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/DynamicClassBasedSingleValueConverter.java b/core/src/main/java/cucumber/runtime/xstream/DynamicClassBasedSingleValueConverter.java deleted file mode 100644 index fe103ef482..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/DynamicClassBasedSingleValueConverter.java +++ /dev/null @@ -1,23 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.Converter; -import cucumber.deps.com.thoughtworks.xstream.converters.MarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverterWrapper; -import cucumber.deps.com.thoughtworks.xstream.converters.UnmarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamReader; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamWriter; - -abstract class DynamicClassBasedSingleValueConverter implements Converter { - @Override - public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext context) { - converterForClass(o.getClass()).marshal(o, writer, context); - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - final Class targetClass = context.getRequiredType(); - return converterForClass(targetClass).unmarshal(reader, context); - } - - public abstract SingleValueConverterWrapper converterForClass(Class type); -} diff --git a/core/src/main/java/cucumber/runtime/xstream/DynamicClassWithStringAssignableConverter.java b/core/src/main/java/cucumber/runtime/xstream/DynamicClassWithStringAssignableConverter.java deleted file mode 100644 index 252f69dcc4..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/DynamicClassWithStringAssignableConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverterWrapper; - -import java.lang.reflect.Constructor; - -class DynamicClassWithStringAssignableConverter extends DynamicClassBasedSingleValueConverter { - - @Override - public SingleValueConverterWrapper converterForClass(Class type) { - final Constructor assignableConstructor = findAssignableConstructor(type); - return new SingleValueConverterWrapperExt(new ClassWithStringAssignableConstructorConverter(assignableConstructor)); - } - - @Override - public boolean canConvert(Class type) { - return null != findAssignableConstructor(type); - } - - private static Constructor findAssignableConstructor(Class type) { - for (Constructor constructor : type.getConstructors()) { - if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0].isAssignableFrom(String.class)) { - return constructor; - } - } - return null; - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/DynamicEnumConverter.java b/core/src/main/java/cucumber/runtime/xstream/DynamicEnumConverter.java deleted file mode 100644 index 1c5497cb39..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/DynamicEnumConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverterWrapper; - -import java.util.Locale; - -/** - * Creates an instance of needed {@link cucumber.runtime.xstream.ConverterWithEnumFormat} dynamically based on required type - */ -class DynamicEnumConverter extends DynamicClassBasedSingleValueConverter { - - private final Locale locale; - - DynamicEnumConverter(Locale locale) { - this.locale = locale; - } - - @Override - public SingleValueConverterWrapper converterForClass(Class type) { - return new SingleValueConverterWrapperExt(new ConverterWithEnumFormat(locale, type)); - } - - @Override - public boolean canConvert(Class type) { - return type.isEnum(); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/EnumConverter.java b/core/src/main/java/cucumber/runtime/xstream/EnumConverter.java deleted file mode 100644 index d83ec70086..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/EnumConverter.java +++ /dev/null @@ -1,10 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class EnumConverter extends ConverterWithEnumFormat { - - public EnumConverter(Locale locale, Class enumClass) { - super(locale, enumClass); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/FloatConverter.java b/core/src/main/java/cucumber/runtime/xstream/FloatConverter.java deleted file mode 100644 index 0c43a41a0e..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/FloatConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class FloatConverter extends ConverterWithNumberFormat { - - public FloatConverter(Locale locale) { - super(locale, new Class[]{Float.class, Float.TYPE}); - } - - @Override - protected Float downcast(Number argument) { - return argument.floatValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/IntegerConverter.java b/core/src/main/java/cucumber/runtime/xstream/IntegerConverter.java deleted file mode 100644 index cf3eaa537f..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/IntegerConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class IntegerConverter extends ConverterWithNumberFormat { - - public IntegerConverter(Locale locale) { - super(locale, new Class[]{Integer.class, Integer.TYPE}); - } - - @Override - protected Integer downcast(Number argument) { - return argument.intValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ListConverter.java b/core/src/main/java/cucumber/runtime/xstream/ListConverter.java deleted file mode 100644 index 9d212cf319..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ListConverter.java +++ /dev/null @@ -1,53 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; - -import java.util.ArrayList; -import java.util.List; - -class ListConverter implements SingleValueConverter { - private final String delimiter; - private final SingleValueConverter delegate; - - public ListConverter(String delimiter, SingleValueConverter delegate) { - this.delimiter = delimiter; - this.delegate = delegate; - } - - @Override - public String toString(Object obj) { - boolean first = true; - if (obj instanceof List) { - StringBuilder sb = new StringBuilder(); - for (Object elem : (List) obj) { - if (!first) { - sb.append(delimiter); - } - sb.append(delegate.toString(elem)); - first = false; - } - return sb.toString(); - } else { - return delegate.toString(obj); - } - } - - @Override - public Object fromString(String s) { - if (s.isEmpty()) { - return new ArrayList(0); - } - - final String[] strings = s.split(delimiter); - List list = new ArrayList(strings.length); - for (String elem : strings) { - list.add(delegate.fromString(elem)); - } - return list; - } - - @Override - public boolean canConvert(Class type) { - return List.class.isAssignableFrom(type); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ListOfComplexTypeReader.java b/core/src/main/java/cucumber/runtime/xstream/ListOfComplexTypeReader.java deleted file mode 100644 index 3e4d4fa181..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ListOfComplexTypeReader.java +++ /dev/null @@ -1,133 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.ErrorWriter; -import cucumber.deps.com.thoughtworks.xstream.io.AbstractReader; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * Generates XStream XML data from table rows that will create a List of objects. Example: - *
- * 
- *     
- *         Sid Vicious
- *         1957-05-10 00:00:00.0 UTC
- *         1000
- *     
- *     
- *         Frank Zappa
- *         1940-12-21 00:00:00.0 UTC
- *         3000
- *     
- * 
- * 
- */ -public class ListOfComplexTypeReader extends AbstractReader { - private final Class itemType; - private final List attributeNames; - private final Iterator> itemIterator; - - private int depth = 0; - private Iterator attributeNameIterator; - private String attributeName; - - private Iterator attributeValueIterator; - private String attributeValue; - - public ListOfComplexTypeReader(Class itemType, List attributeNames, List> items) { - this.itemType = itemType; - this.attributeNames = attributeNames; - this.itemIterator = items.iterator(); - } - - @Override - public boolean hasMoreChildren() { - switch (depth) { - case 0: - return itemIterator.hasNext(); - case 1: - return attributeNameIterator.hasNext(); - case 2: - return false; - default: - throw new IllegalStateException("Depth is " + depth); - } - } - - @Override - public void moveDown() { - depth++; - switch (depth) { - case 1: - attributeNameIterator = attributeNames.iterator(); - attributeValueIterator = itemIterator.next().iterator(); - break; - case 2: - attributeName = attributeNameIterator.next(); - attributeValue = attributeValueIterator.next(); - break; - default: - throw new IllegalStateException("Depth is " + depth); - } - } - - @Override - public void moveUp() { - depth--; - } - - @Override - public String getNodeName() { - switch (depth) { - case 0: - return "list"; - case 1: - return itemType.getName(); - case 2: - return attributeName; - default: - throw new IllegalStateException("Depth is " + depth); - } - } - - @Override - public String getValue() { - return attributeValue; - } - - @Override - public String getAttribute(String name) { - return null; - } - - @Override - public String getAttribute(int index) { - throw new UnsupportedOperationException(); - } - - @Override - public int getAttributeCount() { - throw new UnsupportedOperationException(); - } - - @Override - public String getAttributeName(int index) { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator getAttributeNames() { - return Collections.emptyList().iterator(); - } - - @Override - public void appendErrors(ErrorWriter errorWriter) { - } - - @Override - public void close() { - throw new UnsupportedOperationException(); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ListOfSingleValueWriter.java b/core/src/main/java/cucumber/runtime/xstream/ListOfSingleValueWriter.java deleted file mode 100644 index 87101d467f..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ListOfSingleValueWriter.java +++ /dev/null @@ -1,53 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.runtime.CucumberException; - -import java.util.ArrayList; -import java.util.List; - -public class ListOfSingleValueWriter extends CellWriter { - private int nodeDepth; - private final List values = new ArrayList(); - - @Override - public List getHeader() { - return null; - } - - @Override - public List getValues() { - return values; - } - - @Override - public void startNode(String name) { - if (nodeDepth > 1) { - throw new CucumberException("Can only convert List> to a table when T is a single value (primitive, string, date etc)."); - } - nodeDepth++; - } - - @Override - public void addAttribute(String name, String value) { - } - - @Override - public void setValue(String value) { - values.add(value == null ? "" : value); - } - - @Override - public void endNode() { - nodeDepth--; - } - - @Override - public void flush() { - throw new UnsupportedOperationException(); - } - - @Override - public void close() { - throw new UnsupportedOperationException(); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/LocalizedXStreams.java b/core/src/main/java/cucumber/runtime/xstream/LocalizedXStreams.java deleted file mode 100644 index 88e39301fd..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/LocalizedXStreams.java +++ /dev/null @@ -1,145 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.XStream; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.Converter; -import cucumber.deps.com.thoughtworks.xstream.converters.ConverterLookup; -import cucumber.deps.com.thoughtworks.xstream.converters.ConverterMatcher; -import cucumber.deps.com.thoughtworks.xstream.converters.ConverterRegistry; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.deps.com.thoughtworks.xstream.core.DefaultConverterLookup; -import cucumber.runtime.CucumberException; -import cucumber.runtime.ParameterInfo; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public class LocalizedXStreams { - private final Map xStreamsByLocale = new HashMap(); - private final List extraConverters; - private final ClassLoader classLoader; - - public LocalizedXStreams(ClassLoader classLoader, List extraConverters) { - this.classLoader = classLoader; - this.extraConverters = extraConverters; - } - - public LocalizedXStreams(ClassLoader classLoader) { - this(classLoader, Collections.emptyList()); - } - - public LocalizedXStream get(Locale locale) { - LocalizedXStream xStream = xStreamsByLocale.get(locale); - if (xStream == null) { - xStream = newXStream(locale); - registerExtraConverters(xStream); - xStreamsByLocale.put(locale, xStream); - } - return xStream; - } - - private LocalizedXStream newXStream(Locale locale) { - DefaultConverterLookup lookup = new DefaultConverterLookup(); - return new LocalizedXStream(classLoader, lookup, lookup, locale); - } - - private void registerExtraConverters(LocalizedXStream xStream) { - for (XStreamConverter converter : extraConverters) { - try { - ConverterMatcher matcher = converter.value().newInstance(); - if (matcher instanceof Converter) { - xStream.registerConverter((Converter) matcher, converter.priority()); - } - } catch (InstantiationException e) { - throw new CucumberException(e.getMessage(), e); - } catch (IllegalAccessException e) { - throw new CucumberException(e.getMessage(), e); - } - } - } - - public static class LocalizedXStream extends XStream { - private final Locale locale; - private final ThreadLocal> timeConverters = new ThreadLocal>() { - @Override - protected List initialValue() { - return new ArrayList(); - } - }; - - public LocalizedXStream(ClassLoader classLoader, ConverterLookup converterLookup, ConverterRegistry converterRegistry, Locale locale) { - super(null, null, classLoader, null, converterLookup, converterRegistry); - this.locale = locale; - autodetectAnnotations(true); - - // Override with our own Locale-aware converters. - register(converterRegistry, new BigDecimalConverter(locale)); - register(converterRegistry, new BigIntegerConverter(locale)); - register(converterRegistry, new ByteConverter(locale)); - register(converterRegistry, new DateConverter(locale)); - register(converterRegistry, new CalendarConverter(locale)); - register(converterRegistry, new DoubleConverter(locale)); - register(converterRegistry, new FloatConverter(locale)); - register(converterRegistry, new IntegerConverter(locale)); - register(converterRegistry, new LongConverter(locale)); - register(converterRegistry, new PatternConverter()); - converterRegistry.registerConverter(new DynamicEnumConverter(locale), XStream.PRIORITY_VERY_HIGH); - - // Must be lower priority than the ones above, but higher than xstream's built-in ReflectionConverter - converterRegistry.registerConverter(new DynamicClassWithStringAssignableConverter(), XStream.PRIORITY_LOW); - } - - private void register(ConverterRegistry lookup, SingleValueConverter converter) { - lookup.registerConverter(new SingleValueConverterWrapperExt(converter), XStream.PRIORITY_VERY_HIGH); - } - - public void setParameterInfo(ParameterInfo parameterInfo) { - if (parameterInfo != null) { - List timeClasses = TimeConverter.getTimeClasses(); - for (Class timeClass : timeClasses) { - SingleValueConverterWrapperExt converterWrapper = (SingleValueConverterWrapperExt) getConverterLookup().lookupConverterForType(timeClass); - TimeConverter timeConverter = (TimeConverter) converterWrapper.getConverter(); - timeConverter.setParameterInfoAndLocale(parameterInfo, locale); - timeConverters.get().add(timeConverter); - } - } - } - - public void unsetParameterInfo() { - for (TimeConverter timeConverter : timeConverters.get()) { - timeConverter.removeOnlyFormat(); - } - timeConverters.get().clear(); - } - - public SingleValueConverter getSingleValueConverter(Type type) { - if (Object.class.equals(type)) { - type = String.class; - } - if (type instanceof Class) { - Class clazz = (Class) type; - ConverterLookup converterLookup = getConverterLookup(); - Converter converter = converterLookup.lookupConverterForType(clazz); - if (converter instanceof DynamicClassBasedSingleValueConverter) { - return ((DynamicClassBasedSingleValueConverter) converter).converterForClass(clazz); - } - return converter instanceof SingleValueConverter ? (SingleValueConverter) converter : null; - } else { - return null; - } - } - - public SingleValueConverter createListConverter(String delimiter, SingleValueConverter elementConverter) { - return new ListConverter(delimiter, elementConverter); - } - - public Locale getLocale() { - return locale; - } - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/LongConverter.java b/core/src/main/java/cucumber/runtime/xstream/LongConverter.java deleted file mode 100644 index b250b1e610..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/LongConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class LongConverter extends ConverterWithNumberFormat { - - public LongConverter(Locale locale) { - super(locale, new Class[]{Long.class, Long.TYPE}); - } - - @Override - protected Long downcast(Number argument) { - return argument.longValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/MapWriter.java b/core/src/main/java/cucumber/runtime/xstream/MapWriter.java deleted file mode 100644 index 41df0cf19c..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/MapWriter.java +++ /dev/null @@ -1,67 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Supports Map<String, Object> as the List item - */ -public class MapWriter extends CellWriter { - private final List columnNames; - private final Map values = new LinkedHashMap(); - - private String key; - - public MapWriter(List columnNames) { - this.columnNames = columnNames; - } - - @Override - public List getHeader() { - return columnNames.isEmpty() ? new ArrayList(values.keySet()) : columnNames; - } - - @Override - public List getValues() { - List values = new ArrayList(columnNames.size()); - for (String columnName : getHeader()) { - Object value = this.values.get(columnName); - values.add(value == null ? "" : value.toString()); - } - return values; - } - - @Override - public void setValue(String value) { - if (key == null) { - key = value; - } else { - values.put(key, value); - key = null; - } - } - - @Override - public void flush() { - throw new UnsupportedOperationException(); - } - - @Override - public void close() { - throw new UnsupportedOperationException(); - } - - @Override - public void startNode(String name) { - } - - @Override - public void addAttribute(String name, String value) { - } - - @Override - public void endNode() { - } -} \ No newline at end of file diff --git a/core/src/main/java/cucumber/runtime/xstream/PatternConverter.java b/core/src/main/java/cucumber/runtime/xstream/PatternConverter.java deleted file mode 100644 index 43fd199c44..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/PatternConverter.java +++ /dev/null @@ -1,74 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * Converts Strings of the form /hello/im to a {@link Pattern}, using a syntax - * similar to regexp engines like Ruby, Perl and JavaScript. Flags: - * - *
    - *
  • d : UNIX_LINES
  • - *
  • i : CASE_INSENSITIVE
  • - *
  • x : COMMENTS
  • - *
  • m : MULTILINE
  • - *
  • l : LITERAL
  • - *
  • s : DOTALL
  • - *
  • u : UNICODE_CASE
  • - *
  • c : CANON_EQ
  • - *
- */ -public class PatternConverter implements SingleValueConverter { - private static final Pattern PATTERN_PATTERN = Pattern.compile("/(.*)/(.+)"); - - public String toString(Object o) { - throw new UnsupportedOperationException(); - } - - @Override - public Object fromString(String pattern) { - Matcher matcher = PATTERN_PATTERN.matcher(pattern); - if (matcher.matches()) { - return Pattern.compile(matcher.group(1), flags(matcher.group(2))); - } else { - return Pattern.compile(pattern); - } - } - - private int flags(String flags) { - int result = 0; - for (char c : flags.toCharArray()) { - result |= flag(c); - } - return result; - } - - private enum FLAG { - d(Pattern.UNIX_LINES), - i(Pattern.CASE_INSENSITIVE), - x(Pattern.COMMENTS), - m(Pattern.MULTILINE), - l(Pattern.LITERAL), - s(Pattern.DOTALL), - u(Pattern.UNICODE_CASE), - c(Pattern.CANON_EQ); - - private final int modifier; - - FLAG(int modifier) { - this.modifier = modifier; - } - } - - private int flag(char c) { - return FLAG.valueOf(String.valueOf(c)).modifier; - } - - @Override - public boolean canConvert(Class type) { - return type.equals(Pattern.class); - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/ShortConverter.java b/core/src/main/java/cucumber/runtime/xstream/ShortConverter.java deleted file mode 100644 index ba7dd534ce..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/ShortConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.xstream; - -import java.util.Locale; - -class ShortConverter extends ConverterWithNumberFormat { - - public ShortConverter(Locale locale) { - super(locale, new Class[]{Short.class, Short.TYPE}); - } - - @Override - protected Short downcast(Number argument) { - return argument.shortValue(); - } - -} diff --git a/core/src/main/java/cucumber/runtime/xstream/SingleValueConverterWrapperExt.java b/core/src/main/java/cucumber/runtime/xstream/SingleValueConverterWrapperExt.java deleted file mode 100644 index 89c66d11d0..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/SingleValueConverterWrapperExt.java +++ /dev/null @@ -1,20 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverterWrapper; - -/** - * Subclass that exposes the wrapped converter - */ -class SingleValueConverterWrapperExt extends SingleValueConverterWrapper { - private final SingleValueConverter converter; - - public SingleValueConverterWrapperExt(SingleValueConverter converter) { - super(converter); - this.converter = converter; - } - - public SingleValueConverter getConverter() { - return converter; - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/TimeConverter.java b/core/src/main/java/cucumber/runtime/xstream/TimeConverter.java deleted file mode 100644 index fb58107616..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/TimeConverter.java +++ /dev/null @@ -1,77 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.runtime.ParameterInfo; - -import java.text.DateFormat; -import java.text.Format; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import static java.util.Arrays.asList; - -abstract class TimeConverter extends ConverterWithFormat { - private final List formats = new ArrayList(); - private String format; - - TimeConverter(Locale locale, Class[] convertibleTypes) { - super(convertibleTypes); - - // TODO - these are expensive to create. Cache by format+string, or use the XStream DF cache util thingy - addFormat(DateFormat.SHORT, locale); - addFormat(DateFormat.MEDIUM, locale); - addFormat(DateFormat.LONG, locale); - addFormat(DateFormat.FULL, locale); - } - - void addFormat(int style, Locale locale) { - add(DateFormat.getDateInstance(style, locale)); - } - - void add(DateFormat dateFormat) { - dateFormat.setLenient(false); - formats.add(dateFormat); - } - - public List getFormats() { - return format == null ? formats : asList(getOnlyFormat()); - } - - private Format getOnlyFormat() { - DateFormat dateFormat = new SimpleDateFormat(format, getLocale()); - dateFormat.setLenient(false); - - return dateFormat; - } - - @Override - public String toString(Object obj) { - if (obj instanceof Calendar) { - obj = ((Calendar) obj).getTime(); - } - return super.toString(obj); - } - - @Override - public void setParameterInfoAndLocale(ParameterInfo parameterInfo, Locale locale) { - super.setParameterInfoAndLocale(parameterInfo, locale); - - if (parameterInfo.getFormat() != null) { - format = parameterInfo.getFormat(); - } - } - - public void removeOnlyFormat() { - format = null; - } - - public static List getTimeClasses() { - List classes = new ArrayList(); - classes.add(Date.class); - classes.add(Calendar.class); - return classes; - } -} diff --git a/core/src/main/java/cucumber/runtime/xstream/package.html b/core/src/main/java/cucumber/runtime/xstream/package.html deleted file mode 100644 index e675918796..0000000000 --- a/core/src/main/java/cucumber/runtime/xstream/package.html +++ /dev/null @@ -1,7 +0,0 @@ - -

- This package contains Locale-aware alternatives to some of XStream's (non-Locale-aware) built-in converters. - This allows users to write numbers, dates etc. in Gherkin, using the Locale that is associated with the Gherkin - source. -

- diff --git a/core/src/main/java/io/cucumber/stepexpression/Argument.java b/core/src/main/java/io/cucumber/stepexpression/Argument.java new file mode 100644 index 0000000000..963c2b3498 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/Argument.java @@ -0,0 +1,7 @@ +package io.cucumber.stepexpression; + +public interface Argument { + + Object getValue(); + +} diff --git a/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java b/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java new file mode 100644 index 0000000000..a56d846f29 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/ArgumentMatcher.java @@ -0,0 +1,9 @@ +package io.cucumber.stepexpression; + +import gherkin.pickles.PickleStep; + +import java.util.List; + +public interface ArgumentMatcher { + List argumentsFrom(PickleStep step); +} diff --git a/core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java b/core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java new file mode 100644 index 0000000000..94304643ad --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/DataTableArgument.java @@ -0,0 +1,30 @@ +package io.cucumber.stepexpression; + +import io.cucumber.datatable.DataTable; + +import java.util.List; + +public final class DataTableArgument implements Argument { + + private final RawTableTransformer tableType; + private final List> argument; + + DataTableArgument(RawTableTransformer tableType, List> argument) { + this.tableType = tableType; + this.argument = argument; + } + + @Override + public Object getValue() { + return tableType.transform(argument); + } + + public String getText() { + return DataTable.create(argument).toString(); + } + + @Override + public String toString() { + return "Table:\n" + getText(); + } +} diff --git a/core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java b/core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java new file mode 100644 index 0000000000..c803dd09fa --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/DocStringArgument.java @@ -0,0 +1,22 @@ +package io.cucumber.stepexpression; + +public final class DocStringArgument implements Argument { + + private final DocStringTransformer docStringType; + private final String argument; + + DocStringArgument(DocStringTransformer docStringType, String argument) { + this.docStringType = docStringType; + this.argument = argument; + } + + @Override + public Object getValue() { + return docStringType.transform(argument); + } + + @Override + public String toString() { + return "DocString: " + argument; + } +} diff --git a/core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java b/core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java new file mode 100644 index 0000000000..75488a1c46 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/DocStringTransformer.java @@ -0,0 +1,6 @@ +package io.cucumber.stepexpression; + + +interface DocStringTransformer { + T transform(String docString); +} diff --git a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java b/core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java new file mode 100644 index 0000000000..c3d6eadd6c --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/ExpressionArgument.java @@ -0,0 +1,26 @@ +package io.cucumber.stepexpression; + +import io.cucumber.cucumberexpressions.Group; + +public final class ExpressionArgument implements Argument { + + private final io.cucumber.cucumberexpressions.Argument argument; + + ExpressionArgument(io.cucumber.cucumberexpressions.Argument argument) { + this.argument = argument; + } + + @Override + public Object getValue() { + return argument.getValue(); + } + + public Group getGroup() { + return argument.getGroup(); + } + + @Override + public String toString() { + return argument.getGroup() == null ? null : argument.getGroup().getValue(); + } +} diff --git a/core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java b/core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java new file mode 100644 index 0000000000..d3525ce969 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/ExpressionArgumentMatcher.java @@ -0,0 +1,36 @@ +package io.cucumber.stepexpression; + +import gherkin.pickles.PickleStep; +import gherkin.pickles.PickleString; +import gherkin.pickles.PickleTable; + +import java.util.List; + +public class ExpressionArgumentMatcher implements ArgumentMatcher { + + private final StepExpression expression; + + public ExpressionArgumentMatcher(StepExpression expression) { + this.expression = expression; + } + + @Override + public List argumentsFrom(PickleStep step) { + if (step.getArgument().isEmpty()) { + return expression.match(step.getText()); + } + + gherkin.pickles.Argument argument = step.getArgument().get(0); + + if (argument instanceof PickleString) { + return expression.match(step.getText(), ((PickleString) argument).getContent()); + } + + if (argument instanceof PickleTable) { + return expression.match(step.getText(), PickleTableConverter.toTable((PickleTable) argument)); + } + + throw new IllegalStateException("Argument was neither PickleString nor PickleTable"); + } + +} diff --git a/core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java b/core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java new file mode 100644 index 0000000000..62ea55956b --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/PickleTableConverter.java @@ -0,0 +1,22 @@ +package io.cucumber.stepexpression; + +import gherkin.pickles.PickleCell; +import gherkin.pickles.PickleRow; +import gherkin.pickles.PickleTable; + +import java.util.ArrayList; +import java.util.List; + +class PickleTableConverter { + static List> toTable(PickleTable pickleTable) { + List> table = new ArrayList>(); + for (PickleRow pickleRow : pickleTable.getRows()) { + List row = new ArrayList(); + for (PickleCell pickleCell : pickleRow.getCells()) { + row.add(pickleCell.getValue()); + } + table.add(row); + } + return table; + } +} diff --git a/core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java b/core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java new file mode 100644 index 0000000000..9071ad0938 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/RawTableTransformer.java @@ -0,0 +1,7 @@ +package io.cucumber.stepexpression; + +import java.util.List; + +interface RawTableTransformer { + T transform(List> raw); +} diff --git a/core/src/main/java/io/cucumber/stepexpression/StepExpression.java b/core/src/main/java/io/cucumber/stepexpression/StepExpression.java new file mode 100644 index 0000000000..0733aa130b --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/StepExpression.java @@ -0,0 +1,65 @@ +package io.cucumber.stepexpression; + +import io.cucumber.cucumberexpressions.Expression; + +import java.util.ArrayList; +import java.util.List; + +public final class StepExpression { + + private final Expression expression; + private final DocStringTransformer docStringType; + private final RawTableTransformer tableType; + + StepExpression(Expression expression, DocStringTransformer docStringType, RawTableTransformer tableType) { + this.expression = expression; + this.docStringType = docStringType; + this.tableType = tableType; + } + + public List match(String text) { + List> match = expression.match(text); + if (match == null) { + return null; + } + return wrapPlusOne(match); + } + + public String getSource() { + return expression.getSource(); + } + + public List match(String text, List> tableArgument) { + List list = match(text); + + if (list == null) { + return null; + } + + list.add(new DataTableArgument(tableType, tableArgument)); + + return list; + + } + + public List match(String text, String docStringArgument) { + List list = match(text); + if (list == null) { + return null; + } + + list.add(new DocStringArgument(docStringType, docStringArgument)); + + return list; + } + + + private static List wrapPlusOne(List> match) { + List copy = new ArrayList(match.size() + 1); + for (io.cucumber.cucumberexpressions.Argument argument : match) { + copy.add(new ExpressionArgument(argument)); + } + return copy; + } + +} diff --git a/core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java b/core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java new file mode 100644 index 0000000000..cd20a3d120 --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/StepExpressionFactory.java @@ -0,0 +1,109 @@ +package io.cucumber.stepexpression; + +import cucumber.runtime.CucumberException; +import io.cucumber.cucumberexpressions.UndefinedParameterTypeException; +import io.cucumber.datatable.DataTableTypeRegistryTableConverter; +import io.cucumber.datatable.DataTable; + +import io.cucumber.cucumberexpressions.Expression; + +import java.lang.reflect.Type; +import java.util.List; + +import static java.util.Collections.singletonList; + +public final class StepExpressionFactory { + + private final io.cucumber.cucumberexpressions.ExpressionFactory expressionFactory; + private final DataTableTypeRegistryTableConverter tableConverter; + + private static final DocStringTransformer DOC_STRING_IDENTITY = new DocStringTransformer() { + @Override + public String transform(String input) { + return input; + } + }; + + public StepExpressionFactory(TypeRegistry registry) { + this.expressionFactory = new io.cucumber.cucumberexpressions.ExpressionFactory(registry.parameterTypeRegistry()); + this.tableConverter = new DataTableTypeRegistryTableConverter(registry.dataTableTypeRegistry()); + } + + public StepExpression createExpression(String expressionString) { + if (expressionString == null) throw new NullPointerException("expression can not be null"); + Expression expression = expressionFactory.createExpression(expressionString); + + RawTableTransformer toDataTable = new RawTableTransformer() { + @Override + public DataTable transform(List> raw) { + return DataTable.create(raw, tableConverter); + } + }; + return new StepExpression(expression, DOC_STRING_IDENTITY, toDataTable); + } + + public StepExpression createExpression(String expressionString, Type tableOrDocStringType) { + return createExpression(expressionString, new ResolvedType(tableOrDocStringType), false); + } + + public StepExpression createExpression(String expressionString, TypeResolver tableOrDocStringType) { + return createExpression(expressionString, tableOrDocStringType, false); + } + + public StepExpression createExpression(String expressionString, final Type tableOrDocStringType, final boolean transpose) { + return createExpression(expressionString, new ResolvedType(tableOrDocStringType), transpose); + } + + public StepExpression createExpression(String expressionString, final TypeResolver tableOrDocStringType, final boolean transpose) { + if (expressionString == null) throw new NullPointerException("expressionString can not be null"); + if (tableOrDocStringType == null) throw new NullPointerException("tableOrDocStringType can not be null"); + + final Expression expression; + try { + expression = expressionFactory.createExpression(expressionString); + } catch (UndefinedParameterTypeException e) { + throw registerTypeInConfiguration(expressionString, e); + } + + RawTableTransformer tableTransform = new RawTableTransformer() { + @Override + public Object transform(List> raw) { + return DataTable.create(raw, StepExpressionFactory.this.tableConverter) + .convert(tableOrDocStringType.resolve(), transpose); + } + }; + + DocStringTransformer docStringTransform = new DocStringTransformer() { + @Override + public Object transform(String docString) { + return DataTable.create(singletonList(singletonList(docString)), StepExpressionFactory.this.tableConverter) + .convert(tableOrDocStringType.resolve(), transpose); + } + }; + return new StepExpression(expression, docStringTransform, tableTransform); + } + + private CucumberException registerTypeInConfiguration(String expressionString, UndefinedParameterTypeException e) { + return new CucumberException(String.format("" + + "Could not create a cucumber expression for '%s'.\n" + + "It appears you did not register parameter type. The details are in the stacktrace below.\n" + + "You can find the documentation here: https://docs.cucumber.io/cucumber/cucumber-expressions/", + expressionString + ), e); + } + + private static final class ResolvedType implements TypeResolver { + + private final Type type; + + private ResolvedType(Type type) { + this.type = type; + } + + @Override + public Type resolve() { + return type; + } + } + +} diff --git a/core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java b/core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java new file mode 100644 index 0000000000..55a59ffd6b --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/TypeRegistry.java @@ -0,0 +1,38 @@ +package io.cucumber.stepexpression; + +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.DataTableTypeRegistry; + +import java.util.Locale; + +public final class TypeRegistry implements cucumber.api.TypeRegistry { + + private final ParameterTypeRegistry parameterTypeRegistry; + + private final DataTableTypeRegistry dataTableTypeRegistry; + + + public TypeRegistry(Locale locale) { + parameterTypeRegistry = new ParameterTypeRegistry(locale); + dataTableTypeRegistry = new DataTableTypeRegistry(locale); + } + + public ParameterTypeRegistry parameterTypeRegistry() { + return parameterTypeRegistry; + } + + public DataTableTypeRegistry dataTableTypeRegistry() { + return dataTableTypeRegistry; + } + + public void defineParameterType(ParameterType parameterType) { + parameterTypeRegistry.defineParameterType(parameterType); + } + + public void defineDataTableType(DataTableType tableType) { + dataTableTypeRegistry.defineDataTableType(tableType); + } + +} diff --git a/core/src/main/java/io/cucumber/stepexpression/TypeResolver.java b/core/src/main/java/io/cucumber/stepexpression/TypeResolver.java new file mode 100644 index 0000000000..088c70ee0d --- /dev/null +++ b/core/src/main/java/io/cucumber/stepexpression/TypeResolver.java @@ -0,0 +1,17 @@ +package io.cucumber.stepexpression; + +import java.lang.reflect.Type; + +/** + * Allows lazy resolution of the type of a data table or doc string. + */ +public interface TypeResolver { + + /** + * A type to data convert the table or doc string to. May not return null. + * + * @return a type + */ + Type resolve(); + +} diff --git a/core/src/test/java/cucumber/runner/RunnerTest.java b/core/src/test/java/cucumber/runner/RunnerTest.java index 002966cec2..f56123b9ef 100644 --- a/core/src/test/java/cucumber/runner/RunnerTest.java +++ b/core/src/test/java/cucumber/runner/RunnerTest.java @@ -1,6 +1,6 @@ package cucumber.runner; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.HookType; import cucumber.api.Scenario; import cucumber.runtime.Backend; diff --git a/core/src/test/java/cucumber/runtime/DummyConverter.java b/core/src/test/java/cucumber/runtime/DummyConverter.java deleted file mode 100644 index 9a6a726525..0000000000 --- a/core/src/test/java/cucumber/runtime/DummyConverter.java +++ /dev/null @@ -1,26 +0,0 @@ -package cucumber.runtime; - -import cucumber.deps.com.thoughtworks.xstream.converters.Converter; -import cucumber.deps.com.thoughtworks.xstream.converters.MarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.converters.UnmarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamReader; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamWriter; - -public class DummyConverter implements Converter { - - @Override - public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext ctx) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext ctx) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public boolean canConvert(Class type) { - return false; - } - -} diff --git a/core/src/test/java/cucumber/runtime/HookOrderTest.java b/core/src/test/java/cucumber/runtime/HookOrderTest.java index c7842fae09..91908d153d 100644 --- a/core/src/test/java/cucumber/runtime/HookOrderTest.java +++ b/core/src/test/java/cucumber/runtime/HookOrderTest.java @@ -1,6 +1,6 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; import cucumber.api.Scenario; import cucumber.runner.Runner; import cucumber.runtime.io.ResourceLoader; diff --git a/core/src/test/java/cucumber/runtime/HookTest.java b/core/src/test/java/cucumber/runtime/HookTest.java index 1cfccf91d5..46487de930 100644 --- a/core/src/test/java/cucumber/runtime/HookTest.java +++ b/core/src/test/java/cucumber/runtime/HookTest.java @@ -15,12 +15,10 @@ import java.util.Collections; import static java.util.Arrays.asList; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyListOf; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.times; public class HookTest { private final static String ENGLISH = "en"; @@ -50,4 +48,6 @@ public void after_hooks_execute_before_objects_are_disposed() throws Throwable { inOrder.verify(hook).execute(Matchers.any()); inOrder.verify(backend).disposeWorld(); } + + } diff --git a/core/src/test/java/cucumber/runtime/JdkPatternArgumentMatcherTest.java b/core/src/test/java/cucumber/runtime/JdkPatternArgumentMatcherTest.java deleted file mode 100644 index c12e123627..0000000000 --- a/core/src/test/java/cucumber/runtime/JdkPatternArgumentMatcherTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package cucumber.runtime; - -import cucumber.api.Argument; -import org.junit.Test; - -import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class JdkPatternArgumentMatcherTest { - @Test - public void shouldDealWithOnlyAscii() throws UnsupportedEncodingException { - assertVariables("Ja (.+) elsker (.+) landet", "Ja vi elsker dette landet", "vi", 3, "dette", 13); - } - - @Test - public void shouldDealWithUnicodeInsideCaptures() throws UnsupportedEncodingException { - assertVariables("Ja (.+) elsker (.+) landet", "Ja vø elsker døtte landet", "vø", 3, "døtte", 13); - } - - @Test - public void shouldDealWithUnicodeOutsideCaptures() throws UnsupportedEncodingException { - assertVariables("Jæ (.+) ålsker (.+) lændet", "Jæ vi ålsker dette lændet", "vi", 3, "dette", 13); - } - - @Test - public void shouldDealWithUnicodeEverywhere() throws UnsupportedEncodingException { - assertVariables("Jæ (.+) ålsker (.+) lændet", "Jæ vø ålsker døtte lændet", "vø", 3, "døtte", 13); - } - - @Test - public void shouldDealWithUnAnchoredPattern() throws UnsupportedEncodingException { - assertVariables("^I wait for (.+) and (.+) seconds", - "I wait for 3 and 4 seconds to be sure", - "3", 11, "4", 17 - ); - } - - @Test - public void shouldDealWithAnchoredPattern() { - JdkPatternArgumentMatcher matcher = new JdkPatternArgumentMatcher(Pattern.compile("^I wait for (.+) seconds$")); - - assertNull(matcher.argumentsFrom("I wait for 30 seconds to be sure")); - assertEquals(1, matcher.argumentsFrom("I wait for 30 seconds").size()); - } - - @Test - public void canHandleVariableNumberOfArguments() { - JdkPatternArgumentMatcher matcher = new JdkPatternArgumentMatcher(Pattern.compile("I wait for (.+) seconds|I wait for some time")); - - List arguments = matcher.argumentsFrom("I wait for 30 seconds to be sure"); - List optionalArguments = matcher.argumentsFrom("I wait for some time"); - - assertEquals(1, arguments.size()); - assertEquals(1, optionalArguments.size()); - assertNull(matcher.argumentsFrom("I wait for some time").get(0).getOffset()); - assertNull(matcher.argumentsFrom("I wait for some time").get(0).getVal()); - } - - @Test - public void canHandleNestedCaptureGroups() throws UnsupportedEncodingException { - assertVariables("the order is placed( and( not yet)? confirmed)?", "the order is placed and not yet confirmed", - " and not yet confirmed", 19, - " not yet", 23); - } - - private void assertVariables(String regex, String string, String v1, Integer pos1, String v2, Integer pos2) throws UnsupportedEncodingException { - List args = new JdkPatternArgumentMatcher(Pattern.compile(regex)).argumentsFrom(string); - assertEquals(2, args.size()); - assertEquals(v1, args.get(0).getVal()); - assertEquals(pos1, args.get(0).getOffset()); - assertEquals(v2, args.get(1).getVal()); - assertEquals(pos2, args.get(1).getOffset()); - } -} diff --git a/core/src/test/java/cucumber/runtime/ParameterInfoTest.java b/core/src/test/java/cucumber/runtime/ParameterInfoTest.java deleted file mode 100644 index e32b3ab9a7..0000000000 --- a/core/src/test/java/cucumber/runtime/ParameterInfoTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package cucumber.runtime; - -import static org.junit.Assert.assertEquals; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.junit.Test; - -import cucumber.api.Delimiter; -import cucumber.api.Format; -import cucumber.api.Transform; -import cucumber.api.Transformer; -import cucumber.runtime.annotations.CustomDelimiter; -import cucumber.runtime.annotations.SampleDateFormat; -import cucumber.runtime.annotations.TransformToFortyTwo; -import cucumber.runtime.xstream.LocalizedXStreams; - -public class ParameterInfoTest { - - private static final LocalizedXStreams.LocalizedXStream US = new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(Locale.US); - private static final LocalizedXStreams.LocalizedXStream FR = new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(Locale.FRANCE); - - public void withInt(int i) { - } - - @Test - public void converts_with_built_in_converter() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("withInt", Integer.TYPE)).get(0); - assertEquals(23, pt.convert("23", US)); - } - - public void withJodaTime(@Transform(JodaTransformer.class) LocalDate date) { - } - - public static class JodaTransformer extends Transformer { - private static DateTimeFormatter FORMATTER = DateTimeFormat.forStyle("S-"); - - @Override - public LocalDate transform(String value) { - return FORMATTER.withLocale(getLocale()).parseLocalDate(value); - } - } - - @Test - public void converts_with_custom_joda_time_transform_and_format() throws NoSuchMethodException { - ParameterInfo parameterInfo = ParameterInfo.fromMethod(getClass().getMethod("withJodaTime", LocalDate.class)).get(0); - LocalDate aslaksBirthday = new LocalDate(1971, 2, 28); - assertEquals(aslaksBirthday, parameterInfo.convert("28/02/1971", FR)); - assertEquals(aslaksBirthday, parameterInfo.convert("02/28/1971", US)); - } - - public void withJodaTimeAndFormat(@Transform(JodaTransformer.class) @Format("S-") LocalDate date) { - } - - @Test - public void converts_with_custom_joda_time_transform() throws NoSuchMethodException { - ParameterInfo parameterInfo = ParameterInfo.fromMethod(getClass().getMethod("withJodaTimeAndFormat", LocalDate.class)).get(0); - LocalDate aslaksBirthday = new LocalDate(1971, 2, 28); - assertEquals(aslaksBirthday, parameterInfo.convert("28/02/1971", FR)); - assertEquals(aslaksBirthday, parameterInfo.convert("02/28/1971", US)); - } - - public void withJodaTimeWithoutTransform(LocalDate date) { - } - - @Test - public void converts_to_joda_time_using_object_ctor_and_default_locale() throws NoSuchMethodException { - ParameterInfo parameterInfo = ParameterInfo.fromMethod(getClass().getMethod("withJodaTimeWithoutTransform", LocalDate.class)).get(0); - LocalDate localDate = new LocalDate("1971"); - assertEquals(localDate, parameterInfo.convert("1971", US)); - } - - public static class FortyTwoTransformer extends Transformer { - @Override - public Integer transform(String value) { - return 42; - } - } - - public void intWithCustomTransform(@Transform(FortyTwoTransformer.class) int n) { - } - - @Test - public void converts_int_with_custom_transform() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("intWithCustomTransform", Integer.TYPE)).get(0); - assertEquals(42, pt.convert("hello", US)); - } - - public void listWithNoDelimiter(List list) { - } - - @Test - public void converts_list_with_default_delimiter() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("listWithNoDelimiter", List.class)).get(0); - assertEquals(Arrays.asList("hello", "world"), pt.convert("hello, world", US)); - assertEquals(Arrays.asList("hello", "world"), pt.convert("hello,world", US)); - } - - public void listWithCustomDelimiter(@Delimiter("\\|") List list) { - } - - @Test - public void converts_list_with_custom_delimiter() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("listWithCustomDelimiter", List.class)).get(0); - assertEquals(Arrays.asList("hello", "world"), pt.convert("hello|world", US)); - } - - public void listWithNoTypeArgument(List list) { - } - - @Test - public void converts_list_with_no_type_argument() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("listWithNoTypeArgument", List.class)).get(0); - assertEquals(Arrays.asList("hello", "world"), pt.convert("hello, world", US)); - } - - public void intWithCustomTransformAnnotation(@TransformToFortyTwo int n) { - } - - @Test - public void converts_int_with_custom_annotation() throws NoSuchMethodException{ - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("intWithCustomTransformAnnotation", Integer.TYPE)).get(0); - assertEquals(42, pt.convert("hello", US)); - } - - public void listWithCustomAnnotationDelimiter(@CustomDelimiter List list) { - } - - @Test - public void converts_list_with_custom_annotation_delimiter() throws NoSuchMethodException { - ParameterInfo pt = ParameterInfo.fromMethod(getClass().getMethod("listWithCustomAnnotationDelimiter", List.class)).get(0); - assertEquals(Arrays.asList("hello", "world"), pt.convert("hello,!,world", US)); - } - - public void withDateAndAnnotationFormat(@SampleDateFormat Date date) { - } - - @Test - public void converts_with_custom_format_annotation() throws NoSuchMethodException, ParseException { - ParameterInfo parameterInfo = ParameterInfo.fromMethod(getClass().getMethod("withDateAndAnnotationFormat", Date.class)).get(0); - Date sampleDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse("1985-02-12T16:05:12"); - assertEquals(sampleDate, parameterInfo.convert("1985-02-12T16:05:12", US)); - } -} diff --git a/core/src/test/java/cucumber/runtime/RuntimeGlueTest.java b/core/src/test/java/cucumber/runtime/RuntimeGlueTest.java index ebb69695c7..b47b7fe0ba 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeGlueTest.java +++ b/core/src/test/java/cucumber/runtime/RuntimeGlueTest.java @@ -1,17 +1,22 @@ package cucumber.runtime; -import cucumber.runtime.xstream.LocalizedXStreams; +import io.cucumber.stepexpression.TypeRegistry; +import io.cucumber.stepexpression.ArgumentMatcher; +import io.cucumber.stepexpression.ExpressionArgumentMatcher; +import io.cucumber.stepexpression.StepExpression; +import io.cucumber.stepexpression.StepExpressionFactory; import gherkin.pickles.Argument; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; + import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Collections; -import java.util.regex.Pattern; +import static java.util.Locale.ENGLISH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -27,11 +32,12 @@ public class RuntimeGlueTest { @Before public void setUp() { - glue = new RuntimeGlue(new LocalizedXStreams(Thread.currentThread().getContextClassLoader())); + glue = new RuntimeGlue(); } @Test public void throws_duplicate_error_on_dupe_stepdefs() { + RuntimeGlue glue = new RuntimeGlue(); StepDefinition a = mock(StepDefinition.class); when(a.getPattern()).thenReturn("hello"); @@ -55,7 +61,6 @@ public void removes_glue_that_is_scenario_scoped() { // But it was too much hassle creating a better test without refactoring RuntimeGlue // and probably some of its immediate collaborators... Aslak. - StepDefinition sd = mock(StepDefinition.class); when(sd.isScenarioScoped()).thenReturn(true); when(sd.getPattern()).thenReturn("pattern"); @@ -87,7 +92,7 @@ public void removes_scenario_scoped_cache_entries() { glue.addStepDefinition(sd); String featurePath = "someFeature.feature"; - String stepText = "pattern1"; + String stepText = "pattern"; PickleStep pickleStep1 = getPickleStep(stepText); assertEquals(sd, glue.stepDefinitionMatch(featurePath, pickleStep1).getStepDefinition()); @@ -168,13 +173,14 @@ private static PickleStep getPickleStep(String text) { } private static StepDefinition getStepDefinitionMockWithPattern(String pattern) { - final JdkPatternArgumentMatcher jdkPatternArgumentMatcher = new JdkPatternArgumentMatcher(Pattern.compile(pattern)); + StepExpression expression = new StepExpressionFactory(new TypeRegistry(ENGLISH)).createExpression(pattern); + final ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); StepDefinition stepDefinition = mock(StepDefinition.class); when(stepDefinition.getPattern()).thenReturn(pattern); when(stepDefinition.matchedArguments(any(PickleStep.class))).then(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) { - return jdkPatternArgumentMatcher.argumentsFrom(invocationOnMock.getArgumentAt(0,PickleStep.class).getText()); + return argumentMatcher.argumentsFrom(invocationOnMock.getArgumentAt(0, PickleStep.class)); } }); return stepDefinition; diff --git a/core/src/test/java/cucumber/runtime/RuntimeOptionsFactoryTest.java b/core/src/test/java/cucumber/runtime/RuntimeOptionsFactoryTest.java index 2d56b61030..2f3a8f4406 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeOptionsFactoryTest.java +++ b/core/src/test/java/cucumber/runtime/RuntimeOptionsFactoryTest.java @@ -3,11 +3,7 @@ import cucumber.api.CucumberOptions; import cucumber.api.Plugin; import cucumber.api.SnippetType; -import cucumber.api.formatter.Formatter; import cucumber.runtime.io.ResourceLoader; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverters; -import cucumber.deps.com.thoughtworks.xstream.converters.basic.LongConverter; import org.junit.Test; import java.util.Iterator; @@ -16,11 +12,9 @@ import static cucumber.runtime.RuntimeOptionsFactory.packageName; import static cucumber.runtime.RuntimeOptionsFactory.packagePath; -import cucumber.runtime.xstream.PatternConverter; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -141,41 +135,6 @@ public void create_with_junit_options() { assertEquals(asList("option1", "option2=value"), runtimeOptions.getJunitOptions()); } - @Test - public void create_with_xstream_converter() { - RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(ClassWithConverter.class); - RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); - - List converters = runtimeOptions.getConverters(); - assertNotNull(converters); - assertEquals(1, converters.size()); - assertEquals(DummyConverter.class, converters.get(0).value()); - } - - @Test - public void create_with_xstream_converters() { - RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(ClassWithConverters.class); - RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); - - List converters = runtimeOptions.getConverters(); - assertNotNull(converters); - assertEquals(2, converters.size()); - assertEquals(DummyConverter.class, converters.get(0).value()); - assertEquals(PatternConverter.class, converters.get(1).value()); - } - - @Test - public void create_with_xstream_converter_from_baseclass() { - RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(SubclassWithConverter.class); - RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); - - List converters = runtimeOptions.getConverters(); - assertNotNull(converters); - assertEquals(2, converters.size()); - assertEquals(LongConverter.class, converters.get(0).value()); - assertEquals(DummyConverter.class, converters.get(1).value()); - } - private void assertPluginExists(List plugins, String pluginName) { boolean found = false; for (Plugin plugin : plugins) { @@ -254,22 +213,4 @@ static class ClassWithJunitOption { // empty } - @XStreamConverter(DummyConverter.class) - static class ClassWithConverter { - // empty - } - - @XStreamConverters({ - @XStreamConverter(DummyConverter.class), - @XStreamConverter(PatternConverter.class) - }) - static class ClassWithConverters { - // empty - } - - @XStreamConverter(LongConverter.class) - static class SubclassWithConverter extends ClassWithConverter { - // empty - } - } diff --git a/core/src/test/java/cucumber/runtime/RuntimeTest.java b/core/src/test/java/cucumber/runtime/RuntimeTest.java index 7596b02300..d1f5ddb73a 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeTest.java +++ b/core/src/test/java/cucumber/runtime/RuntimeTest.java @@ -4,9 +4,10 @@ import cucumber.api.PendingException; import cucumber.api.Result; import cucumber.api.StepDefinitionReporter; +import cucumber.api.TestCase; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.event.TestCaseFinished; import cucumber.api.Scenario; -import cucumber.api.TestCase; import cucumber.runtime.formatter.FormatterSpy; import cucumber.runtime.io.ClasspathResourceLoader; import cucumber.runtime.io.Resource; @@ -23,6 +24,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; + import java.io.PrintStream; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; @@ -31,6 +33,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static cucumber.runtime.TestHelper.feature; @@ -231,10 +234,10 @@ public void should_pass_if_no_features_are_found() throws IOException { } @Test - public void reports_step_definitions_to_plugin() throws IOException, NoSuchMethodException { + public void reports_step_definitions_to_plugin() throws IOException { Runtime runtime = createRuntime("--plugin", "cucumber.runtime.RuntimeTest$StepdefsPrinter"); - StubStepDefinition stepDefinition = new StubStepDefinition(this, getClass().getMethod("reports_step_definitions_to_plugin"), "some pattern"); + StubStepDefinition stepDefinition = new StubStepDefinition( "some pattern", new TypeRegistry(Locale.ENGLISH)); runtime.getGlue().addStepDefinition(stepDefinition); runtime.run(); diff --git a/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java b/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java index aa6577ad15..35ecf194ab 100644 --- a/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java +++ b/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java @@ -1,209 +1,210 @@ package cucumber.runtime; -import cucumber.api.Argument; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter; -import cucumber.runtime.xstream.LocalizedXStreams; +import io.cucumber.stepexpression.TypeRegistry; +import gherkin.pickles.PickleCell; import gherkin.pickles.PickleLocation; +import gherkin.pickles.PickleRow; import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.Transformer; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableCellTransformer; +import io.cucumber.stepexpression.Argument; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static cucumber.runtime.Arguments.createArgument; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static java.util.Locale.ENGLISH; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class StepDefinitionMatchTest { - private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - private static final String ENGLISH = "en"; - @Test - public void converts_numbers() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(1); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(Integer.TYPE, null, null, - null)); - - PickleStep stepWithoutDocStringOrTable = mock(PickleStep.class); - when(stepWithoutDocStringOrTable.getArgument()).thenReturn(Collections.emptyList()); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(0, "5")), stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - verify(stepDefinition).execute(ENGLISH, new Object[]{5}); - } + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private final TypeRegistry typeRegistry = new TypeRegistry(ENGLISH); @Test - public void converts_with_explicit_converter() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(1); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(Thing.class, null, null, - null)); - - PickleStep stepWithoutDocStringOrTable = mock(PickleStep.class); - when(stepWithoutDocStringOrTable.getArgument()).thenReturn(Collections.emptyList()); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(0, "the thing")), stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - verify(stepDefinition).execute(ENGLISH, new Object[]{new Thing("the thing")}); + public void executes_a_step() throws Throwable { + PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); + + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.class); + List arguments = stepDefinition.matchedArguments(step); + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + stepDefinitionMatch.runStep(null, null); } @Test - public void converts_doc_string_with_explicit_converter() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(1); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(Thing.class, null, null, - null)); - - PickleStep stepWithDocString = mock(PickleStep.class); - PickleString docString = new PickleString(mock(PickleLocation.class), "the thing"); - when(stepWithDocString.getArgument()).thenReturn(asList((gherkin.pickles.Argument)docString)); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(new ArrayList(), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - verify(stepDefinition).execute(ENGLISH, new Object[]{new Thing("the thing")}); - } + public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_arguments() throws Throwable { + PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); - @XStreamConverter(ThingConverter.class) - public static class Thing { - public final String name; + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); + List arguments = stepDefinition.matchedArguments(step); - public Thing(String name) { - this.name = name; - } + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Thing thing = (Thing) o; - return name.equals(thing.name); - } - - @Override - public int hashCode() { - return name.hashCode(); - } + expectedException.expectMessage( + "" + + "Step [I have {int} cukes in my belly] is defined with 0 parameters at '{stubbed location with details}'.\n" + + "However, the gherkin step has 1 arguments:\n" + + " * 4\n" + + "Step text: I have 4 cukes in my belly"); + stepDefinitionMatch.runStep(null, null); } - public static class ThingConverter extends AbstractSingleValueConverter { - @Override - public boolean canConvert(Class type) { - return Thing.class.equals(type); - } - - @Override - public Object fromString(String str) { - return new Thing(str); - } + @Test + public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_arguments_with_data_table() throws Throwable { + PickleTable table = new PickleTable( + asList( + new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "A"), new PickleCell(mock(PickleLocation.class), "B"))), + new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "C"), new PickleCell(mock(PickleLocation.class), "D"))) + ) + ); + + PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); + + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry); + List arguments = stepDefinition.matchedArguments(step); + PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + + expectedException.expectMessage( + "" + + "Step [I have {int} cukes in my belly] is defined with 0 parameters at '{stubbed location with details}'.\n" + + "However, the gherkin step has 2 arguments:\n" + + " * 4\n" + + " * Table:\n" + + " | A | B |\n" + + " | C | D |\n" + + "\n" + + "Step text: I have 4 cukes in my belly"); + stepDefinitionMatch.runStep(null, null); } @Test - public void gives_nice_error_message_when_conversion_fails() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(1); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(Thang.class, null, null, - null)); - - PickleStep stepWithoutDocStringOrTable = mock(PickleStep.class); - when(stepWithoutDocStringOrTable.getArgument()).thenReturn(Collections.emptyList()); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(0, "blah")), stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams(classLoader)); - try { - - stepDefinitionMatch.runStep(ENGLISH, null); - fail(); - } catch (CucumberException expected) { - assertEquals( - "Don't know how to convert \"blah\" into cucumber.runtime.StepDefinitionMatchTest$Thang.\n" + - "Try writing your own converter:\n" + - "\n" + - "@cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter(ThangConverter.class)\n" + - "public class Thang {}\n", - expected.getMessage() - ); - } + public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_arguments() throws Throwable { + PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument) mock(PickleTable.class)), asList(mock(PickleLocation.class))); + StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); + List arguments = stepDefinition.matchedArguments(step); + PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + expectedException.expectMessage("" + + "Step [I have {int} cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + + "However, the gherkin step has 2 arguments:\n" + + " * 4\n" + + " * Table:\n" + + "\n" + + "Step text: I have 4 cukes in my belly"); + stepDefinitionMatch.runStep(null, null); } - public static class Thang { + @Test + public void throws_arity_mismatch_exception_when_there_are_more_parameters_and_no_arguments() throws Throwable { + PickleStep step = new PickleStep("I have cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); + StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", typeRegistry, Integer.TYPE, Short.TYPE, List.class); + List arguments = stepDefinition.matchedArguments(step); + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + expectedException.expectMessage("" + + "Step [I have cukes in my belly] is defined with 3 parameters at '{stubbed location with details}'.\n" + + "However, the gherkin step has 0 arguments.\n" + + "Step text: I have cukes in my belly"); + stepDefinitionMatch.runStep(null, null); } @Test - public void can_have_doc_string_as_only_argument() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(1); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(String.class, null, null, - null)); - - PickleStep stepWithDocString = mock(PickleStep.class); - PickleString docString = new PickleString(mock(PickleLocation.class), "HELLO"); - when(stepWithDocString.getArgument()).thenReturn(asList((gherkin.pickles.Argument)docString)); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(new ArrayList(), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - verify(stepDefinition).execute(ENGLISH, new Object[]{"HELLO"}); + public void throws_register_type_in_configuration_exception_when_there_is_no_data_table_type_defined() throws Throwable { + // Empty table maps to null and doesn't trigger a type check. + PickleTable table = new PickleTable(singletonList(new PickleRow(singletonList(new PickleCell(mock(PickleLocation.class), "A"))))); + + PickleStep step = new PickleStep("I have a datatable", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); + StepDefinition stepDefinition = new StubStepDefinition("I have a datatable", typeRegistry, UndefinedDataTableType.class); + List arguments = stepDefinition.matchedArguments(step); + + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + expectedException.expectMessage("" + + "Could not convert arguments for step [I have a datatable] defined at '{stubbed location with details}'.\n" + + "It appears you did not register a data table type. The details are in the stacktrace below."); + stepDefinitionMatch.runStep(null, null); + } @Test - public void can_have_doc_string_as_last_argument_among_many() throws Throwable { - StepDefinition stepDefinition = mock(StepDefinition.class); - when(stepDefinition.getParameterCount()).thenReturn(2); - when(stepDefinition.getParameterType(0, String.class)).thenReturn(new ParameterInfo(Integer.TYPE, null, null, - null)); - when(stepDefinition.getParameterType(1, String.class)).thenReturn(new ParameterInfo(String.class, null, null, - null)); - - PickleStep stepWithDocString = mock(PickleStep.class); - PickleString docString = new PickleString(mock(PickleLocation.class), "HELLO"); - when(stepWithDocString.getArgument()).thenReturn(asList((gherkin.pickles.Argument)docString)); - - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(0, "5")), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - verify(stepDefinition).execute(ENGLISH, new Object[]{5, "HELLO"}); + public void throws_could_not_convert_exception_for_transfomer_and_capture_group_mismatch() throws Throwable { + typeRegistry.defineParameterType(new ParameterType( + "itemQuantity", + "(few|some|lots of) (cukes|gherkins)", + ItemQuantity.class, + new Transformer() { + @Override + public ItemQuantity transform(String s) throws Throwable { + return null; + } + })); + + PickleStep step = new PickleStep("I have some cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); + StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", typeRegistry, ItemQuantity.class); + List arguments = stepDefinition.matchedArguments(step); + + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + expectedException.expectMessage("" + + "Could not convert arguments for step [I have {itemQuantity} in my belly] defined at '{stubbed location with details}'.\n" + + "The details are in the stacktrace below." + ); + stepDefinitionMatch.runStep(null, null); + } @Test - public void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_arguments() throws Throwable { - PickleStep step = new PickleStep("I have 4 cukes in my belly", Collections.emptyList(), asList(mock(PickleLocation.class))); + public void throws_could_not_convert_exception_for_singleton_table_dimension_mismatch() throws Throwable { + PickleTable table = new PickleTable( + asList( + new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "A"), new PickleCell(mock(PickleLocation.class), "B"))), + new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "C"), new PickleCell(mock(PickleLocation.class), "D"))) + ) + ); + + typeRegistry.defineDataTableType(new DataTableType( + ItemQuantity.class, + new TableCellTransformer() { + @Override + public ItemQuantity transform(String s) { + return new ItemQuantity(s); + } + } + + )); + + PickleStep step = new PickleStep("I have some cukes in my belly", singletonList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); + StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", typeRegistry, ItemQuantity.class); + List arguments = stepDefinition.matchedArguments(step); + + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); + expectedException.expectMessage("" + + "Could not convert arguments for step [I have some cukes in my belly] defined at '{stubbed location with details}'.\n" + + "The details are in the stacktrace below."); + stepDefinitionMatch.runStep(null, null); - StepDefinition stepDefinition = new StubStepDefinition(new Object(), Object.class.getMethod("toString"), "some pattern"); - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(7, "4")), stepDefinition, null, step, new LocalizedXStreams(getClass().getClassLoader())); - try { - stepDefinitionMatch.runStep(ENGLISH, null); - fail(); - } catch (CucumberException expected) { - assertEquals("Arity mismatch: Step Definition 'toString' with pattern [some pattern] is declared with 0 parameters. However, the gherkin step has 1 arguments [4]. \n" + - "Step text: I have 4 cukes in my belly", expected.getMessage()); - } } - public static class WithTwoParams { - public void withTwoParams(int anInt, short aShort, List strings) { + private static final class ItemQuantity { + + private final String s; + + public ItemQuantity(String s) { + this.s = s; } - } - @Test - public void throws_arity_mismatch_exception_when_there_are_more_parameters_than_arguments() throws Throwable { - PickleStep step = new PickleStep("I have 4 cukes in my belly", asList((gherkin.pickles.Argument)mock(PickleTable.class)), asList(mock(PickleLocation.class))); - - StepDefinition stepDefinition = new StubStepDefinition(new Object(), WithTwoParams.class.getMethod("withTwoParams", Integer.TYPE, Short.TYPE, List.class), "some pattern"); - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(singletonList(createArgument(7, "4")), stepDefinition, null, step, new LocalizedXStreams(getClass().getClassLoader())); - try { - stepDefinitionMatch.runStep(ENGLISH, null); - fail(); - } catch (CucumberException expected) { - assertEquals("Arity mismatch: Step Definition 'withTwoParams' with pattern [some pattern] is declared with 3 parameters. However, the gherkin step has 2 arguments [4, Table:[]]. \n" + - "Step text: I have 4 cukes in my belly", expected.getMessage()); + @Override + public String toString() { + return s; } } + + private static final class UndefinedDataTableType { + + } } diff --git a/core/src/test/java/cucumber/runtime/StubStepDefinition.java b/core/src/test/java/cucumber/runtime/StubStepDefinition.java index 8ed6a3f1c3..fce143658d 100644 --- a/core/src/test/java/cucumber/runtime/StubStepDefinition.java +++ b/core/src/test/java/cucumber/runtime/StubStepDefinition.java @@ -1,33 +1,41 @@ package cucumber.runtime; -import cucumber.api.Argument; +import io.cucumber.stepexpression.TypeRegistry; +import io.cucumber.stepexpression.Argument; import gherkin.pickles.PickleStep; +import io.cucumber.stepexpression.ArgumentMatcher; +import io.cucumber.stepexpression.ExpressionArgumentMatcher; +import io.cucumber.stepexpression.StepExpression; +import io.cucumber.stepexpression.StepExpressionFactory; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.List; +import static org.junit.Assert.assertEquals; + public class StubStepDefinition implements StepDefinition { - private final Object target; - private final Method method; - private final String pattern; + private final StepExpression expression; private List parameterInfos; - public StubStepDefinition(Object target, Method method, String pattern) { - this.target = target; - this.method = method; - this.pattern = pattern; - this.parameterInfos = ParameterInfo.fromMethod(method); + StubStepDefinition(String pattern, TypeRegistry typeRegistry, Type... types) { + this.parameterInfos = ParameterInfo.fromTypes(types); + if (parameterInfos.isEmpty()) { + this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern); + } else { + ParameterInfo lastParameter = parameterInfos.get(parameterInfos.size() - 1); + this.expression = new StepExpressionFactory(typeRegistry).createExpression(pattern, lastParameter.getType()); + } } @Override public List matchedArguments(PickleStep step) { - throw new UnsupportedOperationException(); + ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); + return argumentMatcher.argumentsFrom(step); } @Override public String getLocation(boolean detail) { - return method.getName(); + return "{stubbed location" + (detail ? " with details" : "") + "}"; } @Override @@ -41,8 +49,11 @@ public ParameterInfo getParameterType(int n, Type argumentType) { } @Override - public void execute(String language, Object[] args) throws Throwable { - Utils.invoke(target, method, 0, args); + public void execute(String language, Object[] args) { + assertEquals(parameterInfos.size(), args.length); + for (int i = 0; i < args.length; i++) { + assertEquals(parameterInfos.get(i).getType(), args[i].getClass()); + } } @Override @@ -52,7 +63,7 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { @Override public String getPattern() { - return pattern; + return expression.getSource(); } @Override diff --git a/core/src/test/java/cucumber/runtime/annotations/CustomDelimiter.java b/core/src/test/java/cucumber/runtime/annotations/CustomDelimiter.java deleted file mode 100644 index 7713b7fe60..0000000000 --- a/core/src/test/java/cucumber/runtime/annotations/CustomDelimiter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import cucumber.api.Delimiter; -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER}) -@Documented -@Delimiter(",!,") -public @interface CustomDelimiter { - -} diff --git a/core/src/test/java/cucumber/runtime/annotations/SampleDateFormat.java b/core/src/test/java/cucumber/runtime/annotations/SampleDateFormat.java deleted file mode 100644 index 70137659fa..0000000000 --- a/core/src/test/java/cucumber/runtime/annotations/SampleDateFormat.java +++ /dev/null @@ -1,16 +0,0 @@ -package cucumber.runtime.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import cucumber.api.Format; -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER}) -@Documented -@Format("yyyy-MM-dd'T'HH:mm:ss") -public @interface SampleDateFormat { - -} diff --git a/core/src/test/java/cucumber/runtime/annotations/TransformToFortyTwo.java b/core/src/test/java/cucumber/runtime/annotations/TransformToFortyTwo.java deleted file mode 100644 index a18096c3fd..0000000000 --- a/core/src/test/java/cucumber/runtime/annotations/TransformToFortyTwo.java +++ /dev/null @@ -1,18 +0,0 @@ -package cucumber.runtime.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import cucumber.api.Transform; -import cucumber.runtime.ParameterInfoTest.FortyTwoTransformer; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER}) -@Documented -@Transform(FortyTwoTransformer.class) -public @interface TransformToFortyTwo { - -} diff --git a/core/src/test/java/cucumber/runtime/autocomplete/StepdefGeneratorTest.java b/core/src/test/java/cucumber/runtime/autocomplete/StepdefGeneratorTest.java deleted file mode 100644 index 1a3639ad92..0000000000 --- a/core/src/test/java/cucumber/runtime/autocomplete/StepdefGeneratorTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package cucumber.runtime.autocomplete; - -import cucumber.api.Argument; -import cucumber.runtime.FeatureBuilder; -import cucumber.runtime.JdkPatternArgumentMatcher; -import cucumber.runtime.ParameterInfo; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.io.Resource; -import cucumber.runtime.model.CucumberFeature; -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; -import gherkin.pickles.PickleStep; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; - -public class StepdefGeneratorTest { - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - @Test - public void generates_code_completion_metadata() throws IOException { - StepdefGenerator meta = new StepdefGenerator(); - - List stepDefs = asList(def("I have (\\d+) cukes in my belly"), def("I have (\\d+) apples in my bowl")); - - List metadata = meta.generate(stepDefs, features()); - String expectedJson = "" + - "[\n" + - " {\n" + - " \"source\": \"I have (\\\\d+) apples in my bowl\",\n" + - " \"flags\": \"\",\n" + - " \"steps\": []\n" + - " },\n" + - " {\n" + - " \"source\": \"I have (\\\\d+) cukes in my belly\",\n" + - " \"flags\": \"\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"name\": \"I have 4 cukes in my belly\",\n" + - " \"args\": [\n" + - " {\n" + - " \"offset\": 7,\n" + - " \"val\": \"4\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"name\": \"I have 42 cukes in my belly\",\n" + - " \"args\": [\n" + - " {\n" + - " \"offset\": 7,\n" + - " \"val\": \"42\"\n" + - " }\n" + - " ]\n" + - " }\n" + - " ]\n" + - " }\n" + - "]"; - assertEquals(GSON.fromJson(expectedJson, new TypeReference>() { - }.getType()), metadata); - } - - private List features() throws IOException { - List features = new ArrayList(); - FeatureBuilder fb = new FeatureBuilder(features); - fb.parse(new Resource() { - @Override - public String getPath() { - return "test.feature"; - } - - @Override - public String getAbsolutePath() { - throw new UnsupportedOperationException(); - } - - @Override - public InputStream getInputStream() { - try { - return new ByteArrayInputStream(("" + - "Feature: Test\n" + - " Scenario: Test\n" + - " Given I have 4 cukes in my belly\n" + - " And I have 3 bananas in my basket\n" + - " Given I have 42 cukes in my belly\n") - .getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Override - public String getClassName(String extension) { - throw new UnsupportedOperationException(); - } - }); - return features; - } - - private StepDefinition def(final String pattern) { - return new StepDefinition() { - Pattern regexp = Pattern.compile(pattern); - - @Override - public List matchedArguments(PickleStep step) { - return new JdkPatternArgumentMatcher(regexp).argumentsFrom(step.getText()); - } - - @Override - public String getLocation(boolean detail) { - throw new UnsupportedOperationException(); - } - - @Override - public Integer getParameterCount() { - return null; - } - - @Override - public ParameterInfo getParameterType(int n, Type argumentType) { - return null; - } - - @Override - public void execute(String language, Object[] args) throws Throwable { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isDefinedAt(StackTraceElement stackTraceElement) { - throw new UnsupportedOperationException(); - } - - @Override - public String getPattern() { - return pattern; - } - - @Override - public boolean isScenarioScoped() { - return false; - } - }; - } - - public abstract static class TypeReference { - private final Type type; - - protected TypeReference() { - Type superclass = getClass().getGenericSuperclass(); - if (superclass instanceof Class) { - throw new RuntimeException("Missing type parameter."); - } - this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; - } - - public Type getType() { - return this.type; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java b/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java index f834a09f5a..10c734d3c8 100755 --- a/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java +++ b/core/src/test/java/cucumber/runtime/formatter/PrettyFormatterTest.java @@ -1,26 +1,27 @@ package cucumber.runtime.formatter; -import cucumber.api.Argument; import cucumber.api.Result; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.formatter.AnsiEscapes; -import cucumber.runtime.Arguments; +import cucumber.runtime.DefinitionArgument; import cucumber.runtime.TestHelper; import cucumber.runtime.model.CucumberFeature; +import io.cucumber.stepexpression.StepExpression; +import io.cucumber.stepexpression.StepExpressionFactory; import org.junit.Test; import org.mockito.stubbing.Answer; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; -import static cucumber.runtime.Arguments.createArgument; import static cucumber.runtime.TestHelper.createWriteHookAction; import static cucumber.runtime.TestHelper.feature; import static cucumber.runtime.TestHelper.result; -import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; @@ -357,7 +358,7 @@ public void should_color_code_steps_according_to_the_result() throws Throwable { String formatterOutput = runFeatureWithPrettyFormatter(feature, stepsToLocation, stepsToResult, monochrome(false)); assertThat(formatterOutput, containsString("" + - " " + AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "first step" + AnsiEscapes.RESET)); + " " + AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "first step" + AnsiEscapes.RESET)); } @Test @@ -374,7 +375,7 @@ public void should_color_code_locations_as_comments() throws Throwable { String formatterOutput = runFeatureWithPrettyFormatter(feature, stepsToLocation, stepsToResult, monochrome(false)); assertThat(formatterOutput, containsString("" + - AnsiEscapes.GREY + "# path/step_definitions.java:3" + AnsiEscapes.RESET + "\n")); + AnsiEscapes.GREY + "# path/step_definitions.java:3" + AnsiEscapes.RESET + "\n")); } @Test @@ -391,52 +392,60 @@ public void should_color_code_error_message_according_to_the_result() throws Thr String formatterOutput = runFeatureWithPrettyFormatter(feature, stepsToLocation, stepsToResult, monochrome(false)); assertThat(formatterOutput, containsString("" + - " " + AnsiEscapes.RED + "the stack trace" + AnsiEscapes.RESET + "\n")); + " " + AnsiEscapes.RED + "the stack trace" + AnsiEscapes.RESET + "\n")); } @Test public void should_mark_subsequent_arguments_in_steps() throws Throwable { Formats formats = new AnsiFormats(); - Argument arg1 = createArgument(5, "arg1"); - Argument arg2 = createArgument(15, "arg2"); - PrettyFormatter prettyFormatter = new PrettyFormatter(null); - String formattedText = prettyFormatter.formatStepText("Given ", "text arg1 text arg2", formats.get("passed"), formats.get("passed_arg"), asList(arg1, arg2)); + TypeRegistry registry = new TypeRegistry(Locale.ENGLISH); + StepExpressionFactory stepExpressionFactory = new StepExpressionFactory(registry); + StepExpression expression = stepExpressionFactory.createExpression("text {string} text {string}"); + + PrettyFormatter prettyFormatter = new PrettyFormatter(null); + String stepText = "text 'arg1' text 'arg2'"; + String formattedText = prettyFormatter.formatStepText("Given ", stepText, formats.get("passed"), formats.get("passed_arg"), DefinitionArgument.createArguments(expression.match(stepText))); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + - AnsiEscapes.GREEN + "text " + AnsiEscapes.RESET + - AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + "arg1" + AnsiEscapes.RESET + - AnsiEscapes.GREEN + " text " + AnsiEscapes.RESET + - AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + "arg2" + AnsiEscapes.RESET)); + AnsiEscapes.GREEN + "text " + AnsiEscapes.RESET + + AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + "'arg1'" + AnsiEscapes.RESET + + AnsiEscapes.GREEN + " text " + AnsiEscapes.RESET + + AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + "'arg2'" + AnsiEscapes.RESET)); } @Test - public void should_mark_nested_argument_as_part_of_full_argument(){ + public void should_mark_nested_argument_as_part_of_full_argument() { Formats formats = new AnsiFormats(); - Argument enclosingArg = createArgument(19, " and not yet confirmed"); - Argument nestedArg = createArgument(23, " not yet "); + + TypeRegistry registry = new TypeRegistry(Locale.ENGLISH); + StepExpressionFactory stepExpressionFactory = new StepExpressionFactory(registry); + StepExpression expression = stepExpressionFactory.createExpression("the order is placed( and (not yet )?confirmed)?"); + PrettyFormatter prettyFormatter = new PrettyFormatter(null); + String stepText = "the order is placed and not yet confirmed"; - String formattedText = prettyFormatter.formatStepText("Given ", "the order is placed and not yet confirmed", formats.get("passed"), formats.get("passed_arg"), asList(enclosingArg, nestedArg)); + String formattedText = prettyFormatter.formatStepText("Given ", stepText, formats.get("passed"), formats.get("passed_arg"), DefinitionArgument.createArguments(expression.match(stepText))); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "the order is placed" + AnsiEscapes.RESET + - AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + " and not yet confirmed" + AnsiEscapes.RESET)); + AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + " and not yet confirmed" + AnsiEscapes.RESET)); } @Test - public void should_mark_nested_arguments_as_part_of_enclosing_argument(){ + public void should_mark_nested_arguments_as_part_of_enclosing_argument() { Formats formats = new AnsiFormats(); - Argument enclosingArg = createArgument(19, " and not yet confirmed"); - Argument nestedArg = createArgument(23, " not yet "); - Argument nestedNestedArg = createArgument(27, "yet "); PrettyFormatter prettyFormatter = new PrettyFormatter(null); + TypeRegistry registry = new TypeRegistry(Locale.ENGLISH); + StepExpressionFactory stepExpressionFactory = new StepExpressionFactory(registry); + StepExpression expression = stepExpressionFactory.createExpression("the order is placed( and (not( yet)? )?confirmed)?"); + String stepText = "the order is placed and not yet confirmed"; + String formattedText = prettyFormatter.formatStepText("Given ", stepText, formats.get("passed"), formats.get("passed_arg"), DefinitionArgument.createArguments(expression.match(stepText))); - String formattedText = prettyFormatter.formatStepText("Given ", "the order is placed and not yet confirmed", formats.get("passed"), formats.get("passed_arg"), asList(enclosingArg, nestedArg, nestedNestedArg)); assertThat(formattedText, equalTo(AnsiEscapes.GREEN + "Given " + AnsiEscapes.RESET + AnsiEscapes.GREEN + "the order is placed" + AnsiEscapes.RESET + - AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + " and not yet confirmed" + AnsiEscapes.RESET)); + AnsiEscapes.GREEN + AnsiEscapes.INTENSITY_BOLD + " and not yet confirmed" + AnsiEscapes.RESET)); } private String runFeatureWithPrettyFormatter(final CucumberFeature feature, final Map stepsToLocation) throws Throwable { diff --git a/core/src/test/java/cucumber/runtime/FeatureBuilderTest.java b/core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java similarity index 96% rename from core/src/test/java/cucumber/runtime/FeatureBuilderTest.java rename to core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java index aad0bdcab0..354fc14bdc 100644 --- a/core/src/test/java/cucumber/runtime/FeatureBuilderTest.java +++ b/core/src/test/java/cucumber/runtime/model/FeatureBuilderTest.java @@ -1,7 +1,6 @@ -package cucumber.runtime; +package cucumber.runtime.model; import cucumber.runtime.io.Resource; -import cucumber.runtime.model.CucumberFeature; import org.junit.Test; import java.io.ByteArrayInputStream; diff --git a/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java b/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java index 83a7b0d2ff..d85ba1d333 100644 --- a/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java +++ b/core/src/test/java/cucumber/runtime/snippets/ArgumentPatternTest.java @@ -7,9 +7,8 @@ import static org.junit.Assert.assertEquals; public class ArgumentPatternTest { - private Class intType = Integer.TYPE; private Pattern singleDigit = Pattern.compile("(\\d)"); - private ArgumentPattern argumentPattern = new ArgumentPattern(singleDigit, intType); + private ArgumentPattern argumentPattern = new ArgumentPattern(singleDigit); @Test public void replacesMatchWithoutEscapedNumberClass() { diff --git a/core/src/test/java/cucumber/runtime/table/CamelCaseStringConverterTest.java b/core/src/test/java/cucumber/runtime/table/CamelCaseStringConverterTest.java deleted file mode 100644 index d25dc78ed6..0000000000 --- a/core/src/test/java/cucumber/runtime/table/CamelCaseStringConverterTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.runtime.CucumberException; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class CamelCaseStringConverterTest { - - private final CamelCaseStringConverter mapper = new CamelCaseStringConverter(); - - @Test - public void testTransformToJavaPropertyName() { - - assertEquals("Transformed Name", "userName", mapper.map("User Name")); - assertEquals("Transformed Name", "birthDate", mapper.map(" Birth Date\t")); - assertEquals("Transformed Name", "email", mapper.map("email")); - } - - @Test(expected = CucumberException.class) - public void testEmptyInputShouldBeRejected() { - assertEquals("", mapper.map(" ")); - } - -} diff --git a/core/src/test/java/cucumber/runtime/table/DataTableTest.java b/core/src/test/java/cucumber/runtime/table/DataTableTest.java deleted file mode 100644 index 90ac382fc3..0000000000 --- a/core/src/test/java/cucumber/runtime/table/DataTableTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.runtime.CucumberException; -import cucumber.runtime.xstream.LocalizedXStreams; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleTable; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotSame; - -public class DataTableTest { - - @Test - public void rawShouldHaveThreeColumnsAndTwoRows() { - List> raw = createSimpleTable().raw(); - assertEquals("Rows size", 2, raw.size()); - for (List list : raw) { - assertEquals("Cols size: " + list, 3, list.size()); - } - } - - @Test - public void transposedRawShouldHaveTwoColumnsAndThreeRows() { - List> raw = createSimpleTable().transpose().raw(); - assertEquals("Rows size", 3, raw.size()); - for (List list : raw) { - assertEquals("Cols size: " + list, 2, list.size()); - } - } - - @Test(expected = CucumberException.class) - public void canNotSupportNonRectangularTablesMissingColumn() { - createTable(asList("one", "four", "seven"), - asList("a1", "a4444"), - asList("b1")).raw(); - } - - @Test(expected = CucumberException.class) - public void canNotSupportNonRectangularTablesExceedingColumn() { - createTable(asList("one", "four", "seven"), - asList("a1", "a4444", "b7777777", "zero")).raw(); - } - - @Test - public void canCreateTableFromListOfListOfString() { - DataTable dataTable = createSimpleTable(); - List> listOfListOfString = dataTable.raw(); - DataTable other = dataTable.toTable(listOfListOfString); - assertEquals("" + - " | one | four | seven |\n" + - " | 4444 | 55555 | 666666 |\n", - other.toString()); - } - - @Test(expected = UnsupportedOperationException.class) - public void raw_row_is_immutable() { - createSimpleTable().raw().remove(0); - } - - @Test(expected = UnsupportedOperationException.class) - public void raw_col_is_immutable() { - createSimpleTable().raw().get(0).remove(0); - } - - @Test(expected = UnsupportedOperationException.class) - public void asMaps_is_immutable() { - List> maps = createSimpleTable().asMaps(String.class, String.class); - maps.remove(0); - } - - @Test(expected = UnsupportedOperationException.class) - public void asMap_is_immutable() { - Map map = createTable(asList("hundred", "100"), asList("thousand", "1000")).asMap(String.class, Long.class); - assertEquals(new Long(1000L), map.get("thousand")); - map.remove("hundred"); - } - - @Test - public void two_identical_tables_are_considered_equal() { - assertEquals(createSimpleTable(), createSimpleTable()); - assertEquals(createSimpleTable().hashCode(), createSimpleTable().hashCode()); - } - - @Test - public void two_identical_transposed_tables_are_considered_equal() { - assertEquals(createSimpleTable().transpose(), createSimpleTable().transpose()); - assertEquals(createSimpleTable().transpose().hashCode(), createSimpleTable().transpose().hashCode()); - } - - @Test - public void two_different_tables_are_considered_non_equal() { - assertFalse(createSimpleTable().equals(createTable(asList("one")))); - assertNotSame(createSimpleTable().hashCode(), createTable(asList("one")).hashCode()); - } - - @Test - public void two_different_transposed_tables_are_considered_non_equal() { - assertFalse(createSimpleTable().transpose().equals(createTable(asList("one")).transpose())); - assertNotSame(createSimpleTable().transpose().hashCode(), createTable(asList("one")).transpose().hashCode()); - } - - public DataTable createSimpleTable() { - return createTable(asList("one", "four", "seven"), asList("4444", "55555", "666666")); - } - - private DataTable createTable(List... rows) { - List simpleRows = new ArrayList(); - for (int i = 0; i < rows.length; i++) { - List cells = new ArrayList(); - for (String cellContent : rows[i]) { - cells.add(new PickleCell(null, cellContent)); - } - simpleRows.add(new PickleRow(cells)); - } - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - LocalizedXStreams.LocalizedXStream xStream = new LocalizedXStreams(classLoader).get(Locale.US); - return new DataTable(new PickleTable(simpleRows), new TableConverter(xStream, null)); - } -} diff --git a/core/src/test/java/cucumber/runtime/table/FromDataTableTest.java b/core/src/test/java/cucumber/runtime/table/FromDataTableTest.java deleted file mode 100755 index 5c7013aa66..0000000000 --- a/core/src/test/java/cucumber/runtime/table/FromDataTableTest.java +++ /dev/null @@ -1,358 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.Argument; -import cucumber.api.DataTable; -import cucumber.api.Format; -import cucumber.api.Transformer; -import cucumber.api.Transpose; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.javabean.JavaBeanConverter; -import cucumber.runtime.StepDefinition; -import cucumber.runtime.PickleStepDefinitionMatch; -import cucumber.runtime.StubStepDefinition; -import cucumber.runtime.xstream.LocalizedXStreams; -import gherkin.pickles.PickleCell; -import gherkin.pickles.PickleLocation; -import gherkin.pickles.PickleRow; -import gherkin.pickles.PickleStep; -import gherkin.pickles.PickleTable; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; - -public class FromDataTableTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private static final List NO_ARGS = emptyList(); - private static final String ENGLISH = "en"; - - public static class StepDefs { - public List listOfPrimitiveContainers; - public List listOfPojos; - public List listOfBeans; - public List listOfUsersWithNameField; - public List> listOfListOfDoubles; - public List> listOfMapsOfStringToDate; - public List> listOfMapsOfStringToObject; - public Map mapOfDoubleToDouble; - - public DataTable dataTable; - - public void listOfPrimitiveContainers(List primitiveContainers) { - this.listOfPrimitiveContainers = primitiveContainers; - } - - public void listOfPojos(@Format("yyyy-MM-dd") List listOfPojos) { - this.listOfPojos = listOfPojos; - } - - public void listOfPojosTransposed(@Transpose @Format("yyyy-MM-dd") List listOfPojos) { - this.listOfPojos = listOfPojos; - } - - public void listOfBeans(@Format("yyyy-MM-dd") List listOfBeans) { - this.listOfBeans = listOfBeans; - } - - public void listOfBeansTransposed(@Transpose @Format("yyyy-MM-dd") List listOfBeans) { - this.listOfBeans = listOfBeans; - } - - public void listOfUsersWithNameField(@Format("yyyy-MM-dd") List listOfUsersWithNameField) { - this.listOfUsersWithNameField = listOfUsersWithNameField; - } - - public void listOfUsersTransposedWithNameField(@Transpose @Format("yyyy-MM-dd") List listOfUsersWithNameField) { - this.listOfUsersWithNameField = listOfUsersWithNameField; - } - - public void listOfListOfDoubles(List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } - - public void listOfListOfDoublesTransposed(@Transpose List> listOfListOfDoubles) { - this.listOfListOfDoubles = listOfListOfDoubles; - } - - public void listOfMapsOfStringToDate(@Format("yyyy-MM-dd") List> listOfMapsOfStringToDate) { - this.listOfMapsOfStringToDate = listOfMapsOfStringToDate; - } - - public void listOfMapsOfStringToObject(List> listOfMapsOfStringToObject) { - this.listOfMapsOfStringToObject = listOfMapsOfStringToObject; - } - - public void plainDataTable(DataTable dataTable) { - this.dataTable = dataTable; - } - - public void listOfMapsOfDateToString(List> mapsOfDateToString) { - } - - public void listOfMaps(List maps) { - } - - public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { - this.mapOfDoubleToDouble = mapOfDoubleToDouble; - } - } - - @Test - public void transforms_to_list_of_pojos() throws Throwable { - Method m = StepDefs.class.getMethod("listOfPojos", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesAndCalWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfPojos.get(0).birthDate); - assertEquals(sidsDeathcal().getTime(), stepDefs.listOfPojos.get(0).deathCal.getTime()); - assertNull(stepDefs.listOfPojos.get(1).deathCal); - } - - @Test - public void transforms_to_list_of_pojos_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("listOfPojosTransposed", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDatesAndCalWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfPojos.get(0).birthDate); - assertEquals(sidsDeathcal().getTime(), stepDefs.listOfPojos.get(0).deathCal.getTime()); - assertNull(stepDefs.listOfPojos.get(1).deathCal); - } - - @Test - public void assigns_null_to_objects_when_empty_except_boolean_special_case() throws Throwable { - Method m = StepDefs.class.getMethod("listOfPrimitiveContainers", List.class); - - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "number"), new PickleCell(mock(PickleLocation.class), "bool"), new PickleCell(mock(PickleLocation.class), "bool2")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1"), new PickleCell(mock(PickleLocation.class), "false"), new PickleCell(mock(PickleLocation.class), "true")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), ""), new PickleCell(mock(PickleLocation.class), ""), new PickleCell(mock(PickleLocation.class), "")))); - PickleTable table = new PickleTable(rows); - - StepDefs stepDefs = runStepDef(m, table); - - assertEquals(new Integer(1), stepDefs.listOfPrimitiveContainers.get(0).number); - assertEquals(new Boolean(false), stepDefs.listOfPrimitiveContainers.get(0).bool); - assertEquals(true, stepDefs.listOfPrimitiveContainers.get(0).bool2); - - assertEquals(null, stepDefs.listOfPrimitiveContainers.get(1).number); - assertEquals(new Boolean(false), stepDefs.listOfPrimitiveContainers.get(1).bool); - assertEquals(false, stepDefs.listOfPrimitiveContainers.get(1).bool2); - } - - @Test - public void transforms_to_list_of_beans() throws Throwable { - Method m = StepDefs.class.getMethod("listOfBeans", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfBeans.get(0).getBirthDate()); - } - - @Test - public void transforms_to_list_of_beans_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("listOfBeansTransposed", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDatesWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfBeans.get(0).getBirthDate()); - } - - @Test - public void converts_table_to_list_of_class_with_special_fields() throws Throwable { - Method m = StepDefs.class.getMethod("listOfUsersWithNameField", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesAndNamesWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfUsersWithNameField.get(0).birthDate); - assertEquals("Sid", stepDefs.listOfUsersWithNameField.get(0).name.first); - assertEquals("Vicious", stepDefs.listOfUsersWithNameField.get(0).name.last); - } - - @Test - public void converts_table_to_list_of_class_with_special_fields_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("listOfUsersTransposedWithNameField", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDatesAndNamesWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfUsersWithNameField.get(0).birthDate); - assertEquals("Sid", stepDefs.listOfUsersWithNameField.get(0).name.first); - assertEquals("Vicious", stepDefs.listOfUsersWithNameField.get(0).name.last); - } - - @Test - public void transforms_to_map_of_double_to_double() throws Throwable { - Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals(Double.valueOf(999.0), stepDefs.mapOfDoubleToDouble.get(1000.0)); - assertEquals(Double.valueOf(-0.5), stepDefs.mapOfDoubleToDouble.get(0.5)); - assertEquals(Double.valueOf(99.5), stepDefs.mapOfDoubleToDouble.get(100.5)); - } - - @Test - public void transforms_to_list_of_single_values() throws Throwable { - Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); - assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); - } - - @Test - public void transforms_to_list_of_single_values_transposed() throws Throwable { - Method m = StepDefs.class.getMethod("listOfListOfDoublesTransposed", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDoublesWithoutHeader())); - assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); - } - - @Test - public void transforms_to_list_of_map_of_string_to_date() throws Throwable { - Method m = StepDefs.class.getMethod("listOfMapsOfStringToDate", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals(sidsBirthday(), stepDefs.listOfMapsOfStringToDate.get(0).get("Birth Date")); - } - - @Test - public void transforms_to_list_of_map_of_string_to_object() throws Throwable { - Method m = StepDefs.class.getMethod("listOfMapsOfStringToObject", List.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals("1957-05-10", stepDefs.listOfMapsOfStringToObject.get(0).get("Birth Date")); - } - - @Test - public void passes_plain_data_table() throws Throwable { - Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); - StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); - assertEquals("1957-05-10", stepDefs.dataTable.raw().get(1).get(0)); - assertEquals("Birth Date", stepDefs.dataTable.raw().get(0).get(0)); - } - - private StepDefs runStepDef(Method method, PickleTable table) throws Throwable { - StepDefs stepDefs = new StepDefs(); - StepDefinition stepDefinition = new StubStepDefinition(stepDefs, method, "some pattern"); - - PickleStep stepWithTable = new PickleStep("something", asList((gherkin.pickles.Argument)table), asList(mock(PickleLocation.class))); - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(NO_ARGS, stepDefinition, "some.feature", stepWithTable, new LocalizedXStreams(classLoader)); - stepDefinitionMatch.runStep(ENGLISH, null); - return stepDefs; - } - - private List listOfDatesWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10")))); - return rows; - } - - private List listOfDatesAndCalWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date"), new PickleCell(mock(PickleLocation.class), "Death Cal")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10"), new PickleCell(mock(PickleLocation.class), "1979-02-02")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), ""), new PickleCell(mock(PickleLocation.class), "")))); - return rows; - } - - private List listOfDatesAndNamesWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date"), new PickleCell(mock(PickleLocation.class), "Name")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10"), new PickleCell(mock(PickleLocation.class), "Sid Vicious")))); - return rows; - } - - private List listOfDoublesWithoutHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "99.5")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "-0.5")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1000"), new PickleCell(mock(PickleLocation.class), "999")))); - return rows; - } - - private List transposedListOfDatesWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date"), new PickleCell(mock(PickleLocation.class), "1957-05-10")))); - return rows; - } - - private List transposedListOfDatesAndCalWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date"), new PickleCell(mock(PickleLocation.class), "1957-05-10"), new PickleCell(mock(PickleLocation.class), "")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Death Cal"), new PickleCell(mock(PickleLocation.class), "1979-02-02"), new PickleCell(mock(PickleLocation.class), "")))); - return rows; - } - - private List transposedListOfDatesAndNamesWithHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date"), new PickleCell(mock(PickleLocation.class), "1957-05-10")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Name"), new PickleCell(mock(PickleLocation.class), "Sid Vicious")))); - return rows; - } - - private List transposedListOfDoublesWithoutHeader() { - List rows = new ArrayList(); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "1000")))); - rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "99.5"), new PickleCell(mock(PickleLocation.class), "-0.5"), new PickleCell(mock(PickleLocation.class), "999")))); - return rows; - } - - private Date sidsBirthday() { - Calendar sidsBirthday = Calendar.getInstance(); - sidsBirthday.set(1957, 4, 10, 0, 0, 0); - sidsBirthday.set(Calendar.MILLISECOND, 0); - return sidsBirthday.getTime(); - } - - private Calendar sidsDeathcal() { - Calendar sidsDeathcal = Calendar.getInstance(); - sidsDeathcal.set(1979, 1, 2, 0, 0, 0); - sidsDeathcal.set(Calendar.MILLISECOND, 0); - return sidsDeathcal; - } - - public static class UserPojo { - private Date birthDate; - private Calendar deathCal; - } - - @XStreamConverter(JavaBeanConverter.class) - public static class UserBean { - private Date birthDateX; - - public Date getBirthDate() { - return this.birthDateX; - } - - public void setBirthDate(Date birthDate) { - this.birthDateX = birthDate; - } - } - - public static class UserWithNameField { - public Name name; - public Date birthDate; - } - - public static class PrimitiveContainer { - public Integer number; - public Boolean bool; - public boolean bool2; - } - - @XStreamConverter(NameConverter.class) - public static class Name { - public String first; - public String last; - } - - public static class NameConverter extends Transformer { - @Override - public Name transform(String value) { - Name name = new Name(); - String[] firstLast = value.split(" "); - name.first = firstLast[0]; - name.last = firstLast[1]; - return name; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/table/TableConverterTest.java b/core/src/test/java/cucumber/runtime/table/TableConverterTest.java deleted file mode 100644 index f89d91c19d..0000000000 --- a/core/src/test/java/cucumber/runtime/table/TableConverterTest.java +++ /dev/null @@ -1,447 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.javabean.JavaBeanConverter; -import cucumber.runtime.CucumberException; -import cucumber.runtime.ParameterInfo; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class TableConverterTest { - - private static final String YYYY_MM_DD = "yyyy-MM-dd"; - private static final ParameterInfo PARAMETER_INFO = new ParameterInfo(null, YYYY_MM_DD, null, null); - - @Test - public void converts_table_of_single_column_to_list_of_integers() { - DataTable table = TableParser.parse("|3|\n|5|\n|6|\n|7|\n", null); - assertEquals(asList(3, 5, 6, 7), table.asList(Integer.class)); - } - - @Test - public void converts_table_of_two_columns_to_map() { - DataTable table = TableParser.parse("|3|c|\n|5|e|\n|6|f|\n", null); - Map expected = new HashMap() {{ - put(3, "c"); - put(5, "e"); - put(6, "f"); - }}; - - assertEquals(expected, table.asMap(Integer.class, String.class)); - } - - public static class WithoutStringConstructor { - public String count; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - WithoutStringConstructor thingie = (WithoutStringConstructor) o; - - if (!count.equals(thingie.count)) return false; - - return true; - } - - @Override - public int hashCode() { - return count.hashCode(); - } - - @Override - public String toString() { - return "Thingie{" + - "count=" + count + - '}'; - } - - public WithoutStringConstructor val(String s) { - count = s; - return this; - } - } - - @Test - public void converts_table_of_single_column_to_list_of_without_string_constructor() { - DataTable table = TableParser.parse("|count|\n|5|\n|6|\n|7|\n", null); - List expected = asList(new WithoutStringConstructor().val("5"), new WithoutStringConstructor().val("6"), new WithoutStringConstructor().val("7")); - assertEquals(expected, table.asList(WithoutStringConstructor.class)); - } - - public static class WithStringConstructor extends WithoutStringConstructor { - public WithStringConstructor(String anything) { - count = anything; - } - } - - @Test - public void converts_table_of_single_column_to_list_of_with_string_constructor() { - DataTable table = TableParser.parse("|count|\n|5|\n|6|\n|7|\n", null); - List expected = asList(new WithStringConstructor("count"), new WithStringConstructor("5"), new WithStringConstructor("6"), new WithStringConstructor("7")); - assertEquals(expected, table.asList(WithStringConstructor.class)); - } - - @Test - public void converts_table_of_several_columns_to_list_of_integers() { - DataTable table = TableParser.parse("|3|5|\n|6|7|\n", null); - List converted = table.asList(Integer.class); - assertEquals(asList(3, 5, 6, 7), converted); - } - - @Test - public void converts_table_to_list_of_list_of_integers_and_back() { - DataTable table = TableParser.parse("|3|5|\n|6|7|\n", null); - List> converted = table.asLists(Integer.class); - assertEquals(asList(asList(3, 5), asList(6, 7)), converted); - assertEquals(" | 3 | 5 |\n | 6 | 7 |\n", table.toTable(converted).toString()); - } - - public static enum Color { - RED, GREEN, BLUE - } - - @Test - public void converts_table_of_single_column_to_enums() { - DataTable table = TableParser.parse("|RED|\n|GREEN|\n", null); - assertEquals(asList(Color.RED, Color.GREEN), table.asList(Color.class)); - } - - @Test - public void converts_table_of_single_column_to_nullable_enums() { - DataTable table = TableParser.parse("|RED|\n||\n", null); - assertEquals(asList(Color.RED, null), table.asList(Color.class)); - } - - @Test - public void converts_to_map_of_enum_to_int() { - DataTable table = TableParser.parse("|RED|BLUE|\n|6|7|\n|8|9|\n", null); - HashMap map1 = new HashMap() {{ - put(Color.RED, 6); - put(Color.BLUE, 7); - }}; - HashMap map2 = new HashMap() {{ - put(Color.RED, 8); - put(Color.BLUE, 9); - }}; - List> converted = table.asMaps(Color.class, Integer.class); - assertEquals(asList(map1, map2), converted); - } - - public static class UserPojo { - private Date birthDate; - private Calendar deathCal; - } - - @Test - public void converts_table_to_list_of_pojo_and_almost_back() { - DataTable table = TableParser.parse("|Birth Date|Death Cal|\n|1957-05-10|1979-02-02|\n", PARAMETER_INFO); - List converted = table.asList(UserPojo.class); - assertEquals(sidsBirthday(), converted.get(0).birthDate); - assertEquals(sidsDeathcal(), converted.get(0).deathCal); - assertEquals(" | birthDate | deathCal |\n | 1957-05-10 | 1979-02-02 |\n", table.toTable(converted).toString()); - } - - @XStreamConverter(JavaBeanConverter.class) - public static class UserBean { - private Date birthDateX; - private Calendar deathCalX; - - public Date getBirthDate() { - return this.birthDateX; - } - - public void setBirthDate(Date birthDate) { - this.birthDateX = birthDate; - } - - public Calendar getDeathCal() { - return deathCalX; - } - - public void setDeathCal(Calendar deathCal) { - this.deathCalX = deathCal; - } - } - - @Test - public void converts_to_list_of_java_bean_and_almost_back() { - DataTable table = TableParser.parse("|Birth Date|Death Cal|\n|1957-05-10|1979-02-02|\n", PARAMETER_INFO); - List converted = table.asList(UserBean.class); - assertEquals(sidsBirthday(), converted.get(0).getBirthDate()); - assertEquals(sidsDeathcal(), converted.get(0).getDeathCal()); - assertEquals(" | birthDate | deathCal |\n | 1957-05-10 | 1979-02-02 |\n", table.toTable(converted).toString()); - } - - public static class BlogBean { - private String author; - private List tags; - private String post; - - public String getPost() { - return post; - } - - public void setPost(String post) { - this.post = post; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - } - - @Test - public void throws_cucumber_exception_for_complex_types() { - BlogBean blog = new BlogBean(); - blog.setAuthor("Tom Scott"); - blog.setTags(asList("Language", "Linguistics", " Mycenaean Greek")); - blog.setPost("Linear B is a syllabic script that was used for writing Mycenaean Greek..."); - try { - DataTable.create(Collections.singletonList(blog)); - fail(); - } catch (CucumberException expected) { - assertEquals("" + - "Don't know how to convert \"cucumber.runtime.table.TableConverterTest$BlogBean.tags\" into a table entry.\n" + - "Either exclude tags from the table by selecting the fields to include:\n" + - "\n" + - "DataTable.create(entries, \"Field\", \"Other Field\")\n" + - "\n" + - "Or try writing your own converter:\n" + - "\n" + - "@cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter(TagsConverter.class)\n" + - "private List tags;\n", - expected.getMessage()); - } - } - - @Test - public void converts_empty_complex_types_and_almost_back() { - DataTable table = TableParser.parse("" + - "|Author |Tags |Post |\n" + - "|Tom Scott| |Linear B is a...|\n", PARAMETER_INFO); - List converted = table.asList(BlogBean.class); - BlogBean blog = converted.get(0); - assertEquals("Tom Scott", blog.getAuthor()); - assertEquals(emptyList(), blog.getTags()); - assertEquals("Linear B is a...", blog.getPost()); - assertEquals("" + - " | author | tags | post |\n" + - " | Tom Scott | | Linear B is a... |\n", - table.toTable(converted).toString()); - } - - public static class AnnotatedBlogBean { - private String author; - @XStreamConverter(TagsConverter.class) - private List tags; - private String post; - - public String getPost() { - return post; - } - - public void setPost(String post) { - this.post = post; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - } - - public static class TagsConverter implements SingleValueConverter { - - @Override - public String toString(Object o) { - return o.toString().replace("[", "").replace("]", ""); - } - - @Override - public Object fromString(String s) { - return asList(s.split(", ")); - } - - @Override - public boolean canConvert(Class type) { - return List.class.isAssignableFrom(type); - } - } - - @Test - public void converts_annotated_complex_types_and_almost_back() { - DataTable table = TableParser.parse("" + - "|Author |Tags |Post |\n" + - "|Tom Scott|Language, Linguistics, Mycenaean Greek|Linear B is a...|\n", PARAMETER_INFO); - List converted = table.asList(AnnotatedBlogBean.class); - AnnotatedBlogBean blog = converted.get(0); - assertEquals("Tom Scott", blog.getAuthor()); - assertEquals(asList("Language", "Linguistics", "Mycenaean Greek"), blog.getTags()); - assertEquals("Linear B is a...", blog.getPost()); - assertEquals("" + - " | author | tags | post |\n" + - " | Tom Scott | Language, Linguistics, Mycenaean Greek | Linear B is a... |\n", - table.toTable(converted).toString()); - } - - @Test - public void converts_to_list_of_map_of_date() { - DataTable table = TableParser.parse("|Birth Date|Death Cal|\n|1957-05-10|1979-02-02|\n", PARAMETER_INFO); - List> converted = table.asMaps(String.class, Date.class); - assertEquals(sidsBirthday(), converted.get(0).get("Birth Date")); - } - - @Test - public void converts_to_list_of_map_of_string() { - DataTable table = TableParser.parse("|Birth Date|Death Cal|\n|1957-05-10|1979-02-02|\n", null); - List> converted = table.asMaps(String.class, String.class); - assertEquals("1957-05-10", converted.get(0).get("Birth Date")); - } - - private Date sidsBirthday() { - Calendar sidsBirthday = Calendar.getInstance(Locale.US); - sidsBirthday.set(1957, 4, 10, 0, 0, 0); - sidsBirthday.set(Calendar.MILLISECOND, 0); - return sidsBirthday.getTime(); - } - - private Calendar sidsDeathcal() { - Calendar sidsDeathcal = Calendar.getInstance(Locale.US); - sidsDeathcal.set(1979, 1, 2, 0, 0, 0); - sidsDeathcal.set(Calendar.MILLISECOND, 0); - return sidsDeathcal; - } - - @Test - public void converts_distinct_tostring_objects_correctly() { - DataTable table = TableParser.parse("|first|second|\n|row1.first|row1.second|\n|row2.first|row2.second|\n", null); - List converted = table.asList(ContainsTwoFromStringableFields.class); - - List expected = Arrays.asList( - new ContainsTwoFromStringableFields(new FirstFromStringable("row1.first"), new SecondFromStringable("row1.second")), - new ContainsTwoFromStringableFields(new FirstFromStringable("row2.first"), new SecondFromStringable("row2.second")) - ); - - assertEquals(expected, converted); - } - - public static class ContainsTwoFromStringableFields { - private FirstFromStringable first; - private SecondFromStringable second; - - public ContainsTwoFromStringableFields(FirstFromStringable first, SecondFromStringable second) { - this.first = first; - this.second = second; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ContainsTwoFromStringableFields that = (ContainsTwoFromStringableFields) o; - - if (first != null ? !first.equals(that.first) : that.first != null) return false; - if (second != null ? !second.equals(that.second) : that.second != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = first != null ? first.hashCode() : 0; - result = 31 * result + (second != null ? second.hashCode() : 0); - return result; - } - } - - public static class FirstFromStringable { - private final String value; - - public FirstFromStringable(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FirstFromStringable that = (FirstFromStringable) o; - - if (value != null ? !value.equals(that.value) : that.value != null) return false; - - return true; - } - - @Override - public int hashCode() { - return value != null ? value.hashCode() : 0; - } - } - - public static class SecondFromStringable { - private final String value; - - public SecondFromStringable(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SecondFromStringable that = (SecondFromStringable) o; - - if (value != null ? !value.equals(that.value) : that.value != null) return false; - - return true; - } - - @Override - public int hashCode() { - return value != null ? value.hashCode() : 0; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/table/TableDifferTest.java b/core/src/test/java/cucumber/runtime/table/TableDifferTest.java deleted file mode 100755 index 4c456e7833..0000000000 --- a/core/src/test/java/cucumber/runtime/table/TableDifferTest.java +++ /dev/null @@ -1,467 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class TableDifferTest { - - private DataTable table() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Joe | joe@email.com | 234 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n"; - return TableParser.parse(source, null); - } - - private DataTable tableWithDuplicate() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Joe | joe@email.com | 234 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Joe | joe@email.com | 234 |\n" + - "| Ni | ni@email.com | 654 |\n" + - "| Ni | ni@email.com | 654 |\n" ; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithTwoConsecutiveRowsDeleted() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Ni | ni@email.com | 654 |\n"; - return TableParser.parse(source, null); - - } - - private DataTable otherTableWithTwoConsecutiveRowsChanged() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Joe | joe@NOSPAM.com | 234 |\n" + - "| Bryan | bryan@NOSPAM.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n"; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithTwoConsecutiveRowsInserted() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Joe | joe@email.com | 234 |\n" + - "| Doe | joe@email.com | 234 |\n" + - "| Foo | schnickens@email.net | 789 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n"; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithDeletedAndInserted() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Doe | joe@email.com | 234 |\n" + - "| Foo | schnickens@email.net | 789 |\n" + - "| Bryan | bryan@email.org | 456 |\n"; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithInsertedAtEnd() { - String source = "" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Joe | joe@email.com | 234 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n" + - "| Doe | joe@email.com | 234 |\n" + - "| Foo | schnickens@email.net | 789 |\n"; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithDifferentOrder() { - String source = "" + - "| Joe | joe@email.com | 234 |\n" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n"; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithDifferentOrderAndDuplicate() { - String source = "" + - "| Joe | joe@email.com | 234 |\n" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n"+ - "| Ni | ni@email.com | 654 |\n" + - "| Joe | joe@email.com | 234 |\n" ; - return TableParser.parse(source, null); - } - - private DataTable otherTableWithDifferentOrderDuplicateAndDeleted() { - String source = "" + - "| Joe | joe@email.com | 234 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n" + - "| Bob | bob.email.com | 555 |\n" + - "| Bryan | bryan@email.org | 456 |\n" + - "| Ni | ni@email.com | 654 |\n" + - "| Joe | joe@email.com | 234 |\n" ; - - return TableParser.parse(source, null); - } - - private DataTable otherTableWithDeletedAndInsertedDifferentOrder() { - String source = "" + - "| Doe | joe@email.com | 234 |\n" + - "| Foo | schnickens@email.net | 789 |\n" + - "| Aslak | aslak@email.com | 123 |\n" + - "| Bryan | bryan@email.org | 456 |\n"; - return TableParser.parse(source, null); - } - - @Test(expected = TableDiffException.class) - public void shouldFindDifferences() { - try { - DataTable otherTable = otherTableWithDeletedAndInserted(); - new TableDiffer(table(), otherTable).calculateDiffs(); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " - | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void shouldFindNewLinesAtEnd() { - try { - new TableDiffer(table(), otherTableWithInsertedAtEnd()).calculateDiffs(); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test - public void considers_same_table_as_equal() { - table().diff(table().raw()); - } - - @Test(expected = TableDiffException.class) - public void should_find_new_lines_at_end_when_using_diff() { - try { - List> other = otherTableWithInsertedAtEnd().raw(); - table().diff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void should_not_fail_with_out_of_memory() { - DataTable expected = TableParser.parse("" + - "| I'm going to work |\n", null); - List> actual = new ArrayList>(); - actual.add(asList("I just woke up")); - actual.add(asList("I'm going to work")); - expected.diff(actual); - } - - @Test(expected = TableDiffException.class) - public void should_diff_when_consecutive_deleted_lines() { - try { - List> other = otherTableWithTwoConsecutiveRowsDeleted().raw(); - table().diff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " - | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void should_diff_with_empty_list() { - try { - List> other = new ArrayList>(); - table().diff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " - | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " - | Bryan | bryan@email.org | 456 |\n" + - " - | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void should_diff_with_empty_table() { - try { - DataTable emptyTable = DataTable.create(Collections.emptyList()); - table().diff(emptyTable); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " - | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " - | Bryan | bryan@email.org | 456 |\n" + - " - | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test - public void empty_list_should_not_diff_with_empty_table() { - List> emptyList = new ArrayList>(); - DataTable emptyTable = DataTable.create(Collections.emptyList()); - assertEquals(emptyTable.raw(), emptyList); - } - - @Test(expected = TableDiffException.class) - public void should_diff_when_consecutive_changed_lines() { - try { - List> other = otherTableWithTwoConsecutiveRowsChanged().raw(); - table().diff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " - | Bryan | bryan@email.org | 456 |\n" + - " + | Joe | joe@NOSPAM.com | 234 |\n" + - " + | Bryan | bryan@NOSPAM.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void should_diff_when_consecutive_inserted_lines() { - try { - List> other = otherTableWithTwoConsecutiveRowsInserted().raw(); - table().diff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void should_return_tables() { - DataTable from = table(); - DataTable to = otherTableWithTwoConsecutiveRowsInserted(); - try { - from.diff(to); - } catch (TableDiffException e) { - String expected = "" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n"; - assertSame(from, e.getFrom()); - assertSame(to, e.getTo()); - assertEquals(expected, e.getDiff().toString()); - throw e; - } - } - - public static class TestPojo { - Integer id; - String givenName; - int decisionCriteria; - - public TestPojo(Integer id, String givenName, int decisionCriteria) { - this.id = id; - this.givenName = givenName; - this.decisionCriteria = decisionCriteria; - } - } - - @Test - public void diff_with_list_of_pojos_and_camelcase_header_mapping() { - String source = "" + - "| id | Given Name |\n" + - "| 1 | me |\n" + - "| 2 | you |\n" + - "| 3 | jdoe |\n"; - - DataTable expected = TableParser.parse(source, null); - - List actual = new ArrayList(); - actual.add(new TestPojo(1, "me", 123)); - actual.add(new TestPojo(2, "you", 222)); - actual.add(new TestPojo(3, "jdoe", 34545)); - expected.diff(actual); - } - - @Test - public void diff_set_with_itself() { - table().unorderedDiff(table()); - } - - @Test - public void diff_set_with_itself_in_different_order() { - DataTable other = otherTableWithDifferentOrder(); - table().unorderedDiff(other); - } - - @Test(expected = TableDiffException.class) - public void diff_set_with_less_lines_in_other() { - DataTable other = otherTableWithTwoConsecutiveRowsDeleted(); - try { - table().unorderedDiff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " - | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void unordered_diff_with_more_lines_in_other() { - DataTable other = otherTableWithTwoConsecutiveRowsInserted(); - try { - table().unorderedDiff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void unordered_diff_with_added_and_deleted_rows_in_other() { - DataTable other = otherTableWithDeletedAndInsertedDifferentOrder(); - try { - table().unorderedDiff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " - | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " - | Ni | ni@email.com | 654 |\n" + - " + | Doe | joe@email.com | 234 |\n" + - " + | Foo | schnickens@email.net | 789 |\n"; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test - public void unordered_diff_with_list_of_pojos_and_camelcase_header_mapping() { - String source = "" + - "| id | Given Name |\n" + - "| 1 | me |\n" + - "| 2 | you |\n" + - "| 3 | jdoe |\n"; - - DataTable expected = TableParser.parse(source, null); - - List actual = new ArrayList(); - actual.add(new TestPojo(2, "you", 222)); - actual.add(new TestPojo(3, "jdoe", 34545)); - actual.add(new TestPojo(1, "me", 123)); - expected.unorderedDiff(actual); - } - - @Test(expected = TableDiffException.class) - public void unordered_diff_with_added_duplicate_in_other() { - DataTable other = otherTableWithDifferentOrderAndDuplicate(); - try { - table().unorderedDiff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " + | Ni | ni@email.com | 654 |\n" + - " + | Joe | joe@email.com | 234 |\n" ; - assertEquals(expected, e.getMessage()); - throw e; - } - } - - @Test(expected = TableDiffException.class) - public void unordered_diff_with_added_duplicate_and_deleted_in_other() { - DataTable other = otherTableWithDifferentOrderDuplicateAndDeleted(); - try { - tableWithDuplicate().unorderedDiff(other); - } catch (TableDiffException e) { - String expected = "" + - "Tables were not identical:\n" + - " - | Aslak | aslak@email.com | 123 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Bryan | bryan@email.org | 456 |\n" + - " | Joe | joe@email.com | 234 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " | Ni | ni@email.com | 654 |\n" + - " + | Bryan | bryan@email.org | 456 |\n" + - " + | Bob | bob.email.com | 555 |\n" + - " + | Bryan | bryan@email.org | 456 |\n" ; - assertEquals(expected, e.getMessage()); - throw e; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/table/TableParser.java b/core/src/test/java/cucumber/runtime/table/TableParser.java deleted file mode 100644 index e21702d41e..0000000000 --- a/core/src/test/java/cucumber/runtime/table/TableParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.runtime.ParameterInfo; -import cucumber.runtime.xstream.LocalizedXStreams; -import gherkin.AstBuilder; -import gherkin.Parser; -import gherkin.ast.GherkinDocument; -import gherkin.pickles.Compiler; -import gherkin.pickles.Pickle; -import gherkin.pickles.PickleTable; - -import java.util.List; -import java.util.Locale; - -public class TableParser { - - private TableParser() { - } - - public static DataTable parse(String source, ParameterInfo parameterInfo) { - String feature = "" + - "Feature:\n" + - " Scenario:\n" + - " Given x\n" + - source; - Parser parser = new Parser(new AstBuilder()); - Compiler compiler = new Compiler(); - List pickles = compiler.compile(parser.parse(feature)); - PickleTable pickleTable = (PickleTable)pickles.get(0).getSteps().get(0).getArgument().get(0); - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return new DataTable(pickleTable, new TableConverter(new LocalizedXStreams(classLoader).get(Locale.US), parameterInfo)); - } -} diff --git a/core/src/test/java/cucumber/runtime/table/ToDataTableTest.java b/core/src/test/java/cucumber/runtime/table/ToDataTableTest.java deleted file mode 100644 index c9e8dbc141..0000000000 --- a/core/src/test/java/cucumber/runtime/table/ToDataTableTest.java +++ /dev/null @@ -1,328 +0,0 @@ -package cucumber.runtime.table; - -import cucumber.api.DataTable; -import cucumber.runtime.CucumberException; -import cucumber.runtime.ParameterInfo; -import cucumber.runtime.xstream.LocalizedXStreams; -import org.junit.Before; -import org.junit.Test; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.Set; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class ToDataTableTest { - private static final String DD_MM_YYYY = "dd/MM/yyyy"; - private static final ParameterInfo PARAMETER_INFO = new ParameterInfo(null, DD_MM_YYYY, null, null); - private TableConverter tc; - - @Before - public void createTableConverterWithDateFormat() { - LocalizedXStreams.LocalizedXStream xStream = new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(Locale.US); - tc = new TableConverter(xStream, new ParameterInfo(null, DD_MM_YYYY, null, null)); - } - - @Test - public void converts_list_of_beans_to_table() { - List users = tc.toList(personTable(), UserPojo.class); - DataTable table = tc.toTable(users); - assertEquals("" + - " | credits | name | birthDate |\n" + - " | 1,000 | Sid Vicious | 10/05/1957 |\n" + - " | 3,000 | Frank Zappa | 21/12/1940 |\n" + - "", table.toString()); - } - - @Test - public void converts_only_selected_fields_of_object_to_table() throws ParseException { - RelationPojo relation = new RelationPojo(); - relation.id = 12; - relation.user = new UserPojo(0); - relation.user.credits = 1000; - relation.user.name = "Tom Scott"; - relation.user.birthDate = new SimpleDateFormat("yyyy-MM-dd").parse("1984-01-01"); - relation.created = new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"); - relation.tags = new HashSet(Arrays.asList("A","B", "C")); - - DataTable table = tc.toTable(singletonList(relation), "id", "created"); - assertEquals("" + - " | id | created |\n" + - " | 12 | 01/01/2000 |\n" + - "", table.toString()); - } - - @Test - public void throws_exception_when_converting_complex_selected_field_to_table() throws ParseException { - RelationPojo relation = new RelationPojo(); - relation.id = 12; - relation.user = new UserPojo(0); - relation.user.credits = 1000; - relation.user.name = "Tom Scott"; - relation.user.birthDate = new SimpleDateFormat("yyyy-MM-dd").parse("1984-01-01"); - relation.created = new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"); - relation.tags = new HashSet(Arrays.asList("A","B", "C")); - - try { - tc.toTable(singletonList(relation), "id", "created", "tags"); - fail(); - } catch (CucumberException expected){ - assertEquals("" + - "Don't know how to convert \"cucumber.runtime.table.ToDataTableTest$RelationPojo.tags\" into a table entry.\n" + - "Either exclude tags from the table by selecting the fields to include:\n" + - "\n" + - "DataTable.create(entries, \"Field\", \"Other Field\")\n" + - "\n" + - "Or try writing your own converter:\n" + - "\n" + - "@cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter(TagsConverter.class)\n" + - "public Set tags;\n", - expected.getMessage()); - } - } - - - @Test - public void converts_list_of_beans_with_null_to_table() { - List users = tc.toList(personTableWithNull(), UserPojo.class); - DataTable table = tc.toTable(users, "name", "birthDate", "credits"); - assertEquals("" + - " | name | birthDate | credits |\n" + - " | Sid Vicious | | 1,000 |\n" + - " | Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", table.toString()); - } - - @Test - public void gives_a_nice_error_message_when_field_is_missing() { - try { - tc.toList(TableParser.parse("" + - "| name | birthDate | crapola |\n" + - "| Sid Vicious | 10/05/1957 | 1,000 |\n" + - "| Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", PARAMETER_INFO), - UserPojo.class); - fail(); - } catch (CucumberException e) { - assertEquals("No such field cucumber.runtime.table.ToDataTableTest$UserPojo.crapola", e.getMessage()); - } - } - - @Test - public void gives_explicit_error_message_on_field_name_missing_in_header() { - try { - tc.toList(TableParser.parse("" + - "| name | |\n" + - "| Sid Vicious | 10/05/1957 |\n" + - "| Frank Zappa | 21/12/1940 |\n" + - "", PARAMETER_INFO), - UserPojo.class); - fail(); - } catch (CucumberException e) { - assertEquals("Field name cannot be empty. Please check the table header.", e.getMessage()); - } - } - - @Test - public void gives_a_nice_error_message_when_primitive_field_is_null() { - try { - tc.toList(TableParser.parse("" + - "| credits |\n" + - "| 5 |\n" + - "| |\n" + - "", PARAMETER_INFO), - PojoWithInt.class - ); - fail(); - } catch (CucumberException e) { - assertEquals("Can't assign null value to one of the primitive fields in cucumber.runtime.table.ToDataTableTest$PojoWithInt. Please use boxed types.", e.getMessage()); - } - } - - @Test - public void gives_a_meaningfull_error_message_when_field_is_repeated() { - try { - tc.toList(TableParser.parse("" + - "| credits | credits |\n" + - "| 5 | 5 |\n" + - "", PARAMETER_INFO), - UserPojo.class - ); - fail(); - } catch (CucumberException e) { - assertEquals("Duplicate field credits", e.getMessage()); - } - } - - @Test - public void converts_list_of_beans_to_table_with_explicit_columns() { - List users = tc.toList(personTable(), UserPojo.class); - DataTable table = tc.toTable(users, "name", "birthDate", "credits"); - assertEquals("" + - " | name | birthDate | credits |\n" + - " | Sid Vicious | 10/05/1957 | 1,000 |\n" + - " | Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", table.toString()); - } - - @Test - public void diffs_round_trip() { - List users = tc.toList(personTable(), UserPojo.class); - personTable().diff(users); - } - - private DataTable personTable() { - return TableParser.parse("" + - "| name | birthDate | credits |\n" + - "| Sid Vicious | 10/05/1957 | 1,000 |\n" + - "| Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", PARAMETER_INFO); - } - - private DataTable personTableWithNull() { - return TableParser.parse("" + - "| name | birthDate | credits |\n" + - "| Sid Vicious | | 1,000 |\n" + - "| Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", PARAMETER_INFO); - } - - @Test - public void converts_list_of_list_of_number_to_table() { - List> lists = asList(asList(0.5, 1.5), asList(99.0, 1000.5)); - DataTable table = tc.toTable(lists); - assertEquals("" + - " | 0.5 | 1.5 |\n" + - " | 99 | 1,000.5 |\n" + - "", table.toString()); - List> actual = tc.toLists(table, Double.class); - assertEquals(lists, actual); - } - - @Test - public void converts_list_of_array_of_string_or_number_to_table_with_number_formatting() { - List arrays = asList( - new Object[]{"name", "birthDate", "credits"}, - new Object[]{"Sid Vicious", "10/05/1957", 1000}, - new Object[]{"Frank Zappa", "21/12/1940", 3000} - ); - DataTable table = tc.toTable(arrays); - assertEquals("" + - " | name | birthDate | credits |\n" + - " | Sid Vicious | 10/05/1957 | 1,000 |\n" + - " | Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", table.toString()); - } - - @Test - public void convert_list_of_maps_to_table() { - Map vicious = new LinkedHashMap(); - vicious.put("name", "Sid Vicious"); - vicious.put("birthDate", "10/05/1957"); - vicious.put("credits", 1000); - Map zappa = new LinkedHashMap(); - zappa.put("name", "Frank Zappa"); - zappa.put("birthDate", "21/12/1940"); - zappa.put("credits", 3000); - List> maps = asList(vicious, zappa); - - assertEquals("" + - " | name | credits | birthDate |\n" + - " | Sid Vicious | 1,000 | 10/05/1957 |\n" + - " | Frank Zappa | 3,000 | 21/12/1940 |\n" + - "", tc.toTable(maps, "name", "credits", "birthDate").toString()); - - assertEquals("" + - " | name | birthDate | credits |\n" + - " | Sid Vicious | 10/05/1957 | 1,000 |\n" + - " | Frank Zappa | 21/12/1940 | 3,000 |\n" + - "", tc.toTable(maps).toString()); - } - - @Test - public void enum_value_should_be_null_when_text_omitted_for_pojo() { - final List actual = tc.toList(TableParser.parse("" + - "| agree | \n" + - "| yes | \n" + - "| | \n" + - "", PARAMETER_INFO), - PojoWithEnum.class - ); - assertEquals("[PojoWithEnum{yes}, PojoWithEnum{null}]", actual.toString()); - } - - @Test - public void mixed_case_enum_members_shall_still_work_even_when_starts_from_lower_case() { - final List actual = tc.toList(TableParser.parse("" + - "| agree | \n" + - "| mayBeMixedCase | \n" + - "", PARAMETER_INFO), - PojoWithEnum.class - ); - assertEquals("[PojoWithEnum{mayBeMixedCase}]", actual.toString()); - } - - @Test - public void enum_value_should_be_null_when_text_omitted_for_plain_enum() { - final List actual = tc.toList(TableParser.parse("" + - "| yes | \n" + - "| | \n" + - "", PARAMETER_INFO), - AnEnum.class - ); - assertEquals("[yes, null]", actual.toString()); - } - - // No setters - public static class RelationPojo { - public Integer id; - public UserPojo user; - public Date created; - public Set tags = new HashSet(); - - public RelationPojo() { - } - } - - // No setters - public static class UserPojo { - public Integer credits; - public String name; - public Date birthDate; - - public UserPojo(int foo) { - } - } - - public static class PojoWithInt { - public int credits; - } - - public enum AnEnum { - yes, no, mayBeMixedCase - } - - public static class PojoWithEnum { - public AnEnum agree; - - public PojoWithEnum(AnEnum agree) { - this.agree = agree; - } - - @Override - public String toString() { - return "PojoWithEnum{" + agree + '}'; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/xstream/ConvertersTest.java b/core/src/test/java/cucumber/runtime/xstream/ConvertersTest.java deleted file mode 100644 index ec88ea622e..0000000000 --- a/core/src/test/java/cucumber/runtime/xstream/ConvertersTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.ConverterLookup; -import cucumber.deps.com.thoughtworks.xstream.converters.SingleValueConverter; -import org.junit.Before; -import org.junit.Test; - -import java.math.BigDecimal; -import java.util.Locale; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class ConvertersTest { - private ConverterLookup en; - private ConverterLookup no; - - @Before - public void setUp() throws Exception { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - LocalizedXStreams transformers = new LocalizedXStreams(classLoader); - en = transformers.get(Locale.US).getConverterLookup(); - no = transformers.get(new Locale("no")).getConverterLookup(); - } - - @Test - public void shouldTransformToTheRightType() { - assertTrue((Boolean) ((SingleValueConverter) en.lookupConverterForType(Boolean.class)).fromString("true")); - assertTrue((Boolean) ((SingleValueConverter) en.lookupConverterForType(Boolean.TYPE)).fromString("true")); - assertEquals(3000.15f, (Float) ((SingleValueConverter) en.lookupConverterForType(Float.class)).fromString("3000.15"), 0.000001); - assertEquals(3000.15f, (Float) ((SingleValueConverter) en.lookupConverterForType(Float.TYPE)).fromString("3000.15"), 0.000001); - assertEquals(new BigDecimal("3000.15"), ((SingleValueConverter) en.lookupConverterForType(BigDecimal.class)).fromString("3000.15")); - - assertEquals(3000.15f, (Float) ((SingleValueConverter) no.lookupConverterForType(Float.TYPE)).fromString("3000,15"), 0.000001); - } - - @Test - public void shouldTransformPatternWithFlags() { - Pattern expected = Pattern.compile("hello", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); - Pattern actual = (Pattern) ((SingleValueConverter) en.lookupConverterForType(Pattern.class)).fromString("/hello/im"); - assertEquals(expected.pattern(), actual.pattern()); - assertEquals(expected.flags(), actual.flags()); - } - - @Test - public void shouldTransformPatternWithoutFlags() { - Pattern expected = Pattern.compile("hello"); - Pattern actual = (Pattern) ((SingleValueConverter) en.lookupConverterForType(Pattern.class)).fromString("hello"); - assertEquals(expected.pattern(), actual.pattern()); - assertEquals(expected.flags(), actual.flags()); - } - - @Test - public void shouldIncludeSlashesInPatternWhenThereAreNoFlags() { - Pattern expected = Pattern.compile("/hello/"); - Pattern actual = (Pattern) ((SingleValueConverter) en.lookupConverterForType(Pattern.class)).fromString("/hello/"); - assertEquals(expected.pattern(), actual.pattern()); - assertEquals(expected.flags(), actual.flags()); - } - - @Test - public void shouldTransformToTypeWithStringCtor() { - SingleValueConverter c = ((DynamicClassBasedSingleValueConverter) en.lookupConverterForType(MyClass.class)).converterForClass(MyClass.class); - assertEquals("X", ((MyClass) c.fromString("X")).s); - } - - @Test - public void shouldTransformToTypeWithObjectCtor() { - SingleValueConverter c = ((DynamicClassBasedSingleValueConverter) en.lookupConverterForType(MyOtherClass.class)).converterForClass(MyOtherClass.class); - assertEquals("X", ((MyOtherClass) c.fromString("X")).o); - } - - public static class MyClass { - public final String s; - - public MyClass(String s) { - this.s = s; - } - } - - public static class MyOtherClass { - public final Object o; - - public MyOtherClass(Object o) { - this.o = o; - } - } -} diff --git a/core/src/test/java/cucumber/runtime/xstream/ExternalConverterTest.java b/core/src/test/java/cucumber/runtime/xstream/ExternalConverterTest.java deleted file mode 100644 index 7c1fcbb232..0000000000 --- a/core/src/test/java/cucumber/runtime/xstream/ExternalConverterTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; -import cucumber.deps.com.thoughtworks.xstream.converters.Converter; -import cucumber.deps.com.thoughtworks.xstream.converters.ConverterLookup; -import cucumber.deps.com.thoughtworks.xstream.converters.MarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.converters.UnmarshallingContext; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamReader; -import cucumber.deps.com.thoughtworks.xstream.io.HierarchicalStreamWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import static org.junit.Assert.assertTrue; -import org.junit.Before; -import org.junit.Test; - -public class ExternalConverterTest { - - private List extraConverters; - private ClassLoader classLoader; - - @Before - public void setUp() throws Exception { - extraConverters = new ArrayList(); - classLoader = Thread.currentThread().getContextClassLoader(); - } - - @Test - public void shouldUseExtraConverter() { - extraConverters.add(Registration.class.getAnnotation(XStreamConverter.class)); - LocalizedXStreams transformers = new LocalizedXStreams(classLoader, extraConverters); - - ConverterLookup lookup = transformers.get(Locale.US).getConverterLookup(); - Converter c = lookup.lookupConverterForType(MyClass.class); - assertTrue(c instanceof AlwaysConverter); - } - - @XStreamConverter(AlwaysConverter.class) - public static class Registration { - } - - public static class AlwaysConverter implements Converter { - - @Override - public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext ctx) { - throw new UnsupportedOperationException("DUMMY MARSHAL"); - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext ctx) { - throw new UnsupportedOperationException("DUMMY UNMARSHAL"); - } - - @Override - public boolean canConvert(Class type) { - return true; - } - - } - - public static class MyClass { - public final String s; - - public MyClass(String s) { - this.s = s; - } - } - -} diff --git a/core/src/test/java/cucumber/runtime/xstream/StandardConvertersTest.java b/core/src/test/java/cucumber/runtime/xstream/StandardConvertersTest.java deleted file mode 100644 index 0eff2ed013..0000000000 --- a/core/src/test/java/cucumber/runtime/xstream/StandardConvertersTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package cucumber.runtime.xstream; - -import cucumber.deps.com.thoughtworks.xstream.converters.ConversionException; -import org.junit.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class StandardConvertersTest { - - @Test - public void shouldThrowInformativeErrorMessageWhenTransformationFails() { - try { - new IntegerConverter(new Locale("pt")).fromString("hello"); - fail(); - } catch (ConversionException e) { - assertEquals("Couldn't convert \"hello\" to an instance of: [class java.lang.Integer, int]", e.getShortMessage()); - } - } - - @Test - public void shouldConvertToNullWhenArgumentIsNull() { - assertEquals(null, new IntegerConverter(Locale.US).fromString(null)); - } - - @Test - public void shouldTransformBigDecimal() { - BigDecimal englishBigDecimal = new BigDecimalConverter(Locale.US).transform("300.15"); - BigDecimal englishBigDecimal2 = new BigDecimalConverter(Locale.US).transform("30000000.15"); - BigDecimal englishInteger = new BigDecimalConverter(Locale.US).transform("300.15"); - BigDecimal frenchBigDecimal = new BigDecimalConverter(Locale.FRANCE).transform("300.0"); - assertEquals(new BigDecimal("300.15"), englishBigDecimal); - assertEquals(new BigDecimal("30000000.15"), englishBigDecimal2); - assertEquals(new BigDecimal("300.15"), englishInteger); - assertEquals(new BigDecimal("300.0"), frenchBigDecimal); - } - - @Test - public void shouldTransformDate() { - assertEquals(getDateToTest(), new DateConverter(Locale.US).fromString("11/29/2011")); - assertEquals(getDateToTest(), new DateConverter(Locale.FRANCE).fromString("29/11/2011")); - } - - @Test(expected = ConversionException.class) - public void shouldThrowConversionExceptionWhenConvertingInvalidDate() { - assertEquals(getDateToTest(), new DateConverter(Locale.US).fromString("29/11/2011")); - } - - private Date getDateToTest() { - Calendar calendar = Calendar.getInstance(Locale.US); - calendar.set(2011, 10, 29, 0, 0, 0); - calendar.set(Calendar.MILLISECOND, 0); - return calendar.getTime(); - } - - @Test - public void shouldTransformIntegers() { - Integer expected = 1000; - assertEquals(expected, new IntegerConverter(Locale.US).fromString("1000")); - assertEquals(expected, new IntegerConverter(Locale.US).fromString("1,000")); - assertEquals(expected, new IntegerConverter(new Locale("pt")).fromString("1.000")); - } - - @Test - public void shouldTransformDoubles() { - Double expected = 3000.15; - assertEquals(expected, new DoubleConverter(Locale.US).fromString("3000.15")); - assertEquals(expected, new DoubleConverter(Locale.US).fromString("3,000.15")); - assertEquals(expected, new DoubleConverter(new Locale("pt")).fromString("3.000,15")); - assertEquals(expected, new DoubleConverter(Locale.FRANCE).fromString("3000,15")); - assertEquals(null, new DoubleConverter(Locale.FRANCE).fromString("")); - } - - @Test - public void shouldTransformLongs() { - Long expected = 8589934592L; - assertEquals(expected, new LongConverter(Locale.US).fromString("8589934592")); - assertEquals(expected, new LongConverter(Locale.US).fromString("8,589,934,592")); - } - - @Test - public void shouldTransformShorts() { - Short expected = (short) 32767; - Short expected2 = (short) -32768; - assertEquals(expected, new ShortConverter(Locale.US).fromString("32767")); - assertEquals(expected, new ShortConverter(Locale.US).fromString("32,767")); - assertEquals(expected2, new ShortConverter(Locale.US).fromString("-32,768")); - } - - @Test - public void shouldTransformBytes() { - Byte expected = (byte) 127; - assertEquals(expected, new ByteConverter(Locale.US).fromString("127")); - assertEquals(expected, new ByteConverter(Locale.US).fromString("127")); - } - - @Test - public void shouldTransformFloats() { - Float expected = 3000.15f; - assertEquals(expected, new FloatConverter(Locale.US).fromString("3000.15")); - assertEquals(expected, new FloatConverter(Locale.US).fromString("3,000.15")); - } - - @Test - public void shouldTransformBigInteger() { - BigInteger expected = BigInteger.valueOf(8589934592L); - assertEquals(expected, new BigIntegerConverter(Locale.US).fromString("8589934592")); - assertEquals(expected, new BigIntegerConverter(Locale.US).fromString("8,589,934,592")); - } - - @Test - public void shouldTransformEnums() { - EnumConverter enumConverter = new EnumConverter(Locale.US, Color.class); - assertEquals(Color.GREEN, enumConverter.fromString("GREEN")); - assertEquals(Color.GREEN, enumConverter.fromString("Green")); - assertEquals(Color.GREEN, enumConverter.fromString("GrEeN")); - assertEquals(Color.RED, enumConverter.fromString("red")); - } - - @Test - public void shouldListAllowedEnumsWhenConversionFails() { - EnumConverter enumConverter = new EnumConverter(Locale.US, Color.class); - try { - enumConverter.fromString("YELLOW"); - fail(); - } catch (ConversionException expected) { - String expectedMessage = "Couldn't convert YELLOW to cucumber.runtime.xstream.StandardConvertersTest$Color. Legal values are [RED, GREEN, BLUE]"; - if (!expected.getMessage().startsWith(expectedMessage)) { - fail("'" + expected.getMessage() + "' didn't start with '" + expectedMessage + "'"); - } - } - } - - @Test - public void shouldTransformList() { - ListConverter listConverter = new ListConverter(",", new EnumConverter(Locale.US, Color.class)); - assertEquals(Arrays.asList(Color.GREEN, Color.RED, Color.GREEN), listConverter.fromString("green,red,green")); - } - - public static enum Color { - RED, GREEN, BLUE - } -} diff --git a/core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java b/core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java new file mode 100644 index 0000000000..c488d5c173 --- /dev/null +++ b/core/src/test/java/io/cucumber/stepexpression/StepExpressionFactoryTest.java @@ -0,0 +1,113 @@ +package io.cucumber.stepexpression; + +import io.cucumber.datatable.DataTable; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableEntryTransformer; +import io.cucumber.datatable.TableTransformer; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class StepExpressionFactoryTest { + static class Ingredient { + String name; + Integer amount; + String unit; + + Ingredient() { + } + } + + private final TypeRegistry registry = new TypeRegistry(Locale.ENGLISH); + private final List> table = asList(asList("name", "amount", "unit"), asList("chocolate", "2", "tbsp")); + private final List> tableTransposed = asList(asList("name", "chocolate"), asList("amount", "2"), asList("unit", "tbsp")); + + + private TableEntryTransformer listBeanMapper(final TypeRegistry registry) { + //Just pretend this is a bean mapper. + return new TableEntryTransformer() { + + @Override + public Ingredient transform(Map tableRow) { + Ingredient bean = new Ingredient(); + bean.amount = Integer.valueOf(tableRow.get("amount")); + bean.name = tableRow.get("name"); + bean.unit = tableRow.get("unit"); + return bean; + } + }; + } + + + private TableTransformer beanMapper(final TypeRegistry registry) { + return new TableTransformer() { + @Override + public Ingredient transform(DataTable table) throws Throwable { + Map tableRow = table.transpose().asMaps().get(0); + return listBeanMapper(registry).transform(tableRow); + } + }; + } + + + @Test + public void table_expression_with_type_creates_table_from_table() { + + StepExpression expression = new StepExpressionFactory(registry).createExpression("Given some stuff:", DataTable.class); + + + List match = expression.match("Given some stuff:", table); + + DataTable dataTable = (DataTable) match.get(0).getValue(); + assertEquals(table, dataTable.cells()); + } + + @Test + public void table_expression_with_type_creates_single_ingredients_from_table() { + + registry.defineDataTableType(new DataTableType(Ingredient.class, beanMapper(registry))); + StepExpression expression = new StepExpressionFactory(registry).createExpression("Given some stuff:", Ingredient.class); + List match = expression.match("Given some stuff:", tableTransposed); + + + Ingredient ingredient = (Ingredient) match.get(0).getValue(); + assertEquals(ingredient.name, "chocolate"); + } + + @Test + public void table_expression_with_list_type_creates_list_of_ingredients_from_table() { + + registry.defineDataTableType(new DataTableType(Ingredient.class, listBeanMapper(registry))); + + StepExpression expression = new StepExpressionFactory(registry).createExpression("Given some stuff:", getTypeFromStepDefinition()); + List match = expression.match("Given some stuff:", table); + + List ingredients = (List) match.get(0).getValue(); + Ingredient ingredient = ingredients.get(0); + assertEquals(ingredient.name, "chocolate"); + } + + private Type getTypeFromStepDefinition() { + for (Method method : this.getClass().getMethods()) { + if (method.getName().equals("fake_step_definition")) { + return method.getGenericParameterTypes()[0]; + } + } + throw new IllegalStateException(); + } + + + @SuppressWarnings("unused") + public void fake_step_definition(List ingredients) { + + } + + +} diff --git a/core/src/test/java/io/cucumber/stepexpression/TableParser.java b/core/src/test/java/io/cucumber/stepexpression/TableParser.java new file mode 100644 index 0000000000..028232ad47 --- /dev/null +++ b/core/src/test/java/io/cucumber/stepexpression/TableParser.java @@ -0,0 +1,40 @@ +package io.cucumber.stepexpression; + +import io.cucumber.datatable.DataTable; +import io.cucumber.datatable.DataTable.TableConverter; +import gherkin.AstBuilder; +import gherkin.Parser; +import gherkin.ast.GherkinDocument; +import gherkin.pickles.Compiler; +import gherkin.pickles.Pickle; +import gherkin.pickles.PickleTable; + +import java.util.List; + +import static io.cucumber.stepexpression.PickleTableConverter.toTable; + +public class TableParser { + + private TableParser() { + } + + public static DataTable parse(String source) { + return parse(source, null); + } + + public static DataTable parse(String source, TableConverter converter) { + String feature = "" + + "Feature:\n" + + " Scenario:\n" + + " Given x\n" + + source; + Parser parser = new Parser(new AstBuilder()); + Compiler compiler = new Compiler(); + List pickles = compiler.compile(parser.parse(feature)); + PickleTable pickleTable = (PickleTable) pickles.get(0).getSteps().get(0).getArgument().get(0); + if (converter == null) { + return DataTable.create(toTable(pickleTable)); + } + return DataTable.create(toTable(pickleTable), converter); + } +} diff --git a/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java b/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java index 01ad12d658..c1ac4de48c 100644 --- a/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java +++ b/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java @@ -73,12 +73,12 @@ private Activity getActivity() { return rule.getActivity(); } - @Given("^I have a CalculatorActivity$") + @Given("I have a CalculatorActivity") public void I_have_a_CalculatorActivity() { assertNotNull(getActivity()); } - @When("^I press (\\d)$") + @When("I press {digit}") public void I_press_d(final int d) { switch (d) { case 0: @@ -114,7 +114,7 @@ public void I_press_d(final int d) { } } - @When("^I press ([+–x\\/=])$") + @When("I press {operator}") public void I_press_op(final char op) { switch (op) { case '+': @@ -135,7 +135,7 @@ public void I_press_op(final char op) { } } - @Then("^I should see (\\S+) on the display$") + @Then("I should see {word} on the display") public void I_should_see_s_on_the_display(final String s) { onView(withId(R.id.txt_calc_display)).check(matches(withText(s))); } diff --git a/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/ParameterTypes.java b/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/ParameterTypes.java new file mode 100644 index 0000000000..9cf651323e --- /dev/null +++ b/examples/android/android-studio/Cukeulator/app/src/androidTest/java/cucumber/cukeulator/test/ParameterTypes.java @@ -0,0 +1,44 @@ +package cucumber.cukeulator.test; + +import cucumber.api.Configuration; +import io.cucumber.cucumberexpressions.Function; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import io.cucumber.cucumberexpressions.SingleTransformer; + +import static java.text.DateFormat.getDateInstance; +import static java.util.Locale.ENGLISH; + +public class ParameterTypes implements Configuration { + + @Override + public ParameterTypeRegistry createParameterTypeRegistry() { + ParameterTypeRegistry typeRegistry = new ParameterTypeRegistry(ENGLISH); + typeRegistry.defineParameterType(new ParameterType( + "digit", + "[0-9]", + Integer.class, + new SingleTransformer(new Function() { + @Override + public Integer apply(String text) { + return Integer.parseInt(text); + } + }) + )); + + typeRegistry.defineParameterType(new ParameterType( + "operator", + "[+–x\\/=]", + Character.class, + new SingleTransformer(new Function() { + @Override + public Character apply(String text) { + return text.charAt(0); + } + }) + )); + + + return typeRegistry; + } +} diff --git a/examples/android/android-test/src/main/java/cucumber/example/android/test/CucumberActivitySteps.java b/examples/android/android-test/src/main/java/cucumber/example/android/test/CucumberActivitySteps.java index 0088875b66..3f0974a2bd 100644 --- a/examples/android/android-test/src/main/java/cucumber/example/android/test/CucumberActivitySteps.java +++ b/examples/android/android-test/src/main/java/cucumber/example/android/test/CucumberActivitySteps.java @@ -51,17 +51,17 @@ public void after() { assertEquals(3, steps); } - @Given("^I have a test$") + @Given("I have a test") public void I_have_a_test() { assertEquals(1, ++steps); } - @When("^I test$") + @When("I test") public void I_test() { assertEquals(2, ++steps); } - @Then("^I succeed$") + @Then("I succeed") public void I_succeed() { assertEquals(3, ++steps); } diff --git a/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/CalculatorActivitySteps.java b/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/CalculatorActivitySteps.java index a0969faa3f..ea027ed3f3 100644 --- a/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/CalculatorActivitySteps.java +++ b/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/CalculatorActivitySteps.java @@ -35,12 +35,12 @@ public CalculatorActivitySteps(SomeDependency dependency) { assertNotNull(dependency); } - @Given("^I have a CalculatorActivity$") + @Given("I have a CalculatorActivity") public void I_have_a_CalculatorActivity() { assertNotNull(getActivity()); } - @When("^I press (\\d)$") + @When("I press {digit}") public void I_press_d(int d) { CalculatorActivity activity = getActivity(); @@ -78,7 +78,7 @@ public void I_press_d(int d) { } } - @When("^I press ([+–x\\/=])$") + @When("I press {operator}") public void I_press_op(char op) { CalculatorActivity activity = getActivity(); @@ -101,7 +101,7 @@ public void I_press_op(char op) { } } - @Then("^I should see (\\S+) on the display$") + @Then("I should see {word} on the display") public void I_should_see_s_on_the_display(String s) { TextView display = (TextView) getActivity().findViewById(R.id.txt_calc_display); String displayed_result = display.getText().toString(); diff --git a/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/TypeRegistryConfiguration.java b/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..55bf6403a8 --- /dev/null +++ b/examples/android/cukeulator-test/src/main/java/cucumber/example/android/cukeulator/test/TypeRegistryConfiguration.java @@ -0,0 +1,45 @@ +package cucumber.example.android.cukeulator.test; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.Transformer; + +import java.util.Locale; + +import static java.util.Locale.ENGLISH; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineParameterType(new ParameterType( + "digit", + "[0-9]", + Integer.class, + new Transformer() { + @Override + public Integer transform(String s) throws Throwable { + return Integer.parseInt(s); + } + }) + ); + + typeRegistry.defineParameterType(new ParameterType( + "operator", + "[+–x\\/=]", + Character.class, + new Transformer() { + @Override + public Character transform(String s) throws Throwable { + return s.charAt(0); + } + }) + ); + } +} diff --git a/examples/java-calculator-testng/pom.xml b/examples/java-calculator-testng/pom.xml index 8b461e739d..193a7d40ae 100644 --- a/examples/java-calculator-testng/pom.xml +++ b/examples/java-calculator-testng/pom.xml @@ -23,4 +23,18 @@ test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + diff --git a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java index 9980b532b3..e2ed09ce10 100644 --- a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java +++ b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java @@ -1,6 +1,5 @@ package cucumber.examples.java.calculator; -import cucumber.api.Format; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; @@ -13,25 +12,17 @@ public class DateStepdefs { private String result; private DateCalculator calculator; - @Given("^today is (.+)$") - public void today_is(@Format("yyyy-MM-dd") Date date) { + @Given("today is {iso-date}") + public void today_is(Date date) { calculator = new DateCalculator(date); } - /** - * We don't need to use @Format here, since the date string in the step - * conforms to SimpleDateFormat.SHORT. Cucumber has built-in support for - * SimpleDateFormat.SHORT, SimpleDateFormat.MEDIUM, - * SimpleDateFormat.LONG and SimpleDateFormat.FULL. - * - * @see SimpleDateFormat - */ - @When("^I ask if (.+) is in the past$") + @When("I ask if {date} is in the past") public void I_ask_if_date_is_in_the_past(Date date) { result = calculator.isDateInThePast(date); } - @Then("^the result should be (.+)$") + @Then("the result should be (.+)") public void the_result_should_be(String expectedResult) { assertEquals(expectedResult, result); } diff --git a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java new file mode 100644 index 0000000000..66a69e5d0f --- /dev/null +++ b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java @@ -0,0 +1,62 @@ +package cucumber.examples.java.calculator; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import cucumber.examples.java.calculator.ShoppingStepdefs.Price; +import io.cucumber.datatable.DataTableType; +import cucumber.examples.java.calculator.ShoppingStepdefs.Grocery; +import io.cucumber.cucumberexpressions.ParameterType; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + +import static cucumber.examples.java.calculator.RpnCalculatorStepdefs.Entry; +import static java.text.DateFormat.MEDIUM; +import static java.text.DateFormat.getDateInstance; +import static java.util.Locale.ENGLISH; + +public class ParameterTypes implements TypeRegistryConfigurer { + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineParameterType(new ParameterType<>( + "date", + "((.*) \\d{1,2}, \\d{4})", + Date.class, + (String s) -> getDateInstance(MEDIUM, ENGLISH).parse(s) + ) + ); + + typeRegistry.defineParameterType(new ParameterType<>( + "iso-date", + "\\d{4}-\\d{2}-\\d{2}", + Date.class, + (String s) -> new SimpleDateFormat("yyyy-mm-dd").parse(s) + )); + + typeRegistry.defineDataTableType(new DataTableType( + Entry.class, + (Map row) -> new Entry( + Integer.valueOf(row.get("first")), + Integer.valueOf(row.get("second")), + row.get("operation") + ) + )); + + typeRegistry.defineDataTableType(new DataTableType( + Grocery.class, + (Map row) -> new Grocery( + row.get("name"), + Price.fromString(row.get("price")) + ) + )); + + } + + @Override + public Locale locale() { + return ENGLISH; + } +} diff --git a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java index dd7c805878..aeadb38019 100644 --- a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java +++ b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java @@ -14,24 +14,24 @@ public class RpnCalculatorStepdefs { private RpnCalculator calc; - @Given("^a calculator I just turned on$") + @Given("a calculator I just turned on") public void a_calculator_I_just_turned_on() { calc = new RpnCalculator(); } - @When("^I add (\\d+) and (\\d+)$") + @When("I add {int} and {int}") public void adding(int arg1, int arg2) { calc.push(arg1); calc.push(arg2); calc.push("+"); } - @Given("^I press (.+)$") + @Given("I press (.+)") public void I_press(String what) { calc.push(what); } - @Then("^the result is (\\d+)$") + @Then("the result is {int}") public void the_result_is(double expected) { assertEquals(expected, calc.value()); } @@ -46,7 +46,7 @@ public void after(Scenario scenario) { // result.write("HELLLLOO"); } - @Given("^the previous entries:$") + @Given("the previous entries:") public void thePreviousEntries(List entries) { for (Entry entry : entries) { calc.push(entry.first); @@ -55,9 +55,15 @@ public void thePreviousEntries(List entries) { } } - public class Entry { - Integer first; - Integer second; - String operation; + static final class Entry { + private final Integer first; + private final Integer second; + private final String operation; + + Entry(Integer first, Integer second, String operation) { + this.first = first; + this.second = second; + this.operation = operation; + } } } diff --git a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java index 6d38e76375..4b20c10899 100644 --- a/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java +++ b/examples/java-calculator-testng/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java @@ -1,10 +1,8 @@ package cucumber.examples.java.calculator; -import cucumber.api.Transformer; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; import java.util.List; @@ -13,7 +11,7 @@ public class ShoppingStepdefs { private RpnCalculator calc = new RpnCalculator(); - @Given("^the following groceries:$") + @Given("the following groceries:") public void the_following_groceries(List groceries) { for (Grocery grocery : groceries) { calc.push(grocery.price.value); @@ -21,39 +19,37 @@ public void the_following_groceries(List groceries) { } } - @When("^I pay (\\d+)$") + @When("I pay {int}") public void i_pay(int amount) { calc.push(amount); calc.push("-"); } - @Then("^my change should be (\\d+)$") + @Then("my change should be {int}") public void my_change_should_be_(int change) { assertEquals(-calc.value().intValue(), change); } - public static class Grocery { - public String name; - @XStreamConverter(Price.Converter.class) - public Price price; + static class Grocery { + private String name; + private Price price; - public Grocery() { - super(); + Grocery(String name, Price price) { + this.name = name; + this.price = price; } } - public static class Price { - public int value; + static final class Price { + private int value; - public Price(int value) { + Price(int value) { this.value = value; } - public static class Converter extends Transformer { - @Override - public Price transform(String value) { - return new Price(Integer.parseInt(value)); - } + static Price fromString(String value) { + return new Price(Integer.parseInt(value)); } + } } diff --git a/examples/java-calculator/pom.xml b/examples/java-calculator/pom.xml index 542e3699b7..6fb19975bb 100644 --- a/examples/java-calculator/pom.xml +++ b/examples/java-calculator/pom.xml @@ -12,7 +12,7 @@ Examples: Java Calculator - + io.cucumber cucumber-java test @@ -28,4 +28,17 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + diff --git a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java index bebce6c231..9e162b934a 100644 --- a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java +++ b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/DateStepdefs.java @@ -1,6 +1,5 @@ package cucumber.examples.java.calculator; -import cucumber.api.Format; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; @@ -13,25 +12,17 @@ public class DateStepdefs { private String result; private DateCalculator calculator; - @Given("^today is (.+)$") - public void today_is(@Format("yyyy-MM-dd") Date date) { + @Given("today is {iso-date}") + public void today_is(Date date) { calculator = new DateCalculator(date); } - /** - * We don't need to use @Format here, since the date string in the step - * conforms to SimpleDateFormat.SHORT. Cucumber has built-in support for - * SimpleDateFormat.SHORT, SimpleDateFormat.MEDIUM, - * SimpleDateFormat.LONG and SimpleDateFormat.FULL. - * - * @see SimpleDateFormat - */ - @When("^I ask if (.+) is in the past$") + @When("I ask if {date} is in the past") public void I_ask_if_date_is_in_the_past(Date date) { result = calculator.isDateInThePast(date); } - @Then("^the result should be (.+)$") + @Then("the result should be (.+)") public void the_result_should_be(String expectedResult) { assertEquals(expectedResult, result); } diff --git a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java new file mode 100644 index 0000000000..63c5d0011c --- /dev/null +++ b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ParameterTypes.java @@ -0,0 +1,60 @@ +package cucumber.examples.java.calculator; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.datatable.DataTableType; +import cucumber.examples.java.calculator.RpnCalculatorStepdefs.Entry; +import cucumber.examples.java.calculator.ShoppingStepdefs.Grocery; +import io.cucumber.cucumberexpressions.ParameterType; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + +import static java.text.DateFormat.MEDIUM; +import static java.text.DateFormat.getDateInstance; +import static java.util.Locale.ENGLISH; + +public class ParameterTypes implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineParameterType(new ParameterType<>( + "date", + "((.*) \\d{1,2}, \\d{4})", + Date.class, + (String s) -> getDateInstance(MEDIUM, ENGLISH).parse(s) + ) + ); + + typeRegistry.defineParameterType(new ParameterType<>( + "iso-date", + "\\d{4}-\\d{2}-\\d{2}", + Date.class, + (String s) -> new SimpleDateFormat("yyyy-mm-dd").parse(s) + )); + + typeRegistry.defineDataTableType(new DataTableType( + Entry.class, + (Map row) -> new Entry( + Integer.valueOf(row.get("first")), + Integer.valueOf(row.get("second")), + row.get("operation") + ) + )); + + typeRegistry.defineDataTableType(new DataTableType( + Grocery.class, + (Map row) -> new Grocery( + row.get("name"), + ShoppingStepdefs.Price.fromString(row.get("price")) + ) + )); + } +} diff --git a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java index 25f1fa8d0e..c69beb7fd8 100644 --- a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java +++ b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java @@ -14,24 +14,24 @@ public class RpnCalculatorStepdefs { private RpnCalculator calc; - @Given("^a calculator I just turned on$") + @Given("a calculator I just turned on") public void a_calculator_I_just_turned_on() { calc = new RpnCalculator(); } - @When("^I add (\\d+) and (\\d+)$") + @When("I add {int} and {int}") public void adding(int arg1, int arg2) { calc.push(arg1); calc.push(arg2); calc.push("+"); } - @Given("^I press (.+)$") + @Given("I press (.+)") public void I_press(String what) { calc.push(what); } - @Then("^the result is (\\d+)$") + @Then("the result is {int}") public void the_result_is(double expected) { assertEquals(expected, calc.value()); } @@ -46,7 +46,7 @@ public void after(Scenario scenario) { // result.write("HELLLLOO"); } - @Given("^the previous entries:$") + @Given("the previous entries:") public void thePreviousEntries(List entries) { for (Entry entry : entries) { calc.push(entry.first); @@ -55,9 +55,15 @@ public void thePreviousEntries(List entries) { } } - public class Entry { - Integer first; - Integer second; - String operation; + static final class Entry { + private final Integer first; + private final Integer second; + private final String operation; + + Entry(Integer first, Integer second, String operation) { + this.first = first; + this.second = second; + this.operation = operation; + } } } diff --git a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RunCukesTest.java b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RunCukesTest.java index be1a9a65cf..2834683088 100644 --- a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RunCukesTest.java +++ b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/RunCukesTest.java @@ -5,6 +5,6 @@ import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = "json:target/cucumber-report.json") +@CucumberOptions(plugin = {"progress", "json:target/cucumber-report.json"}) public class RunCukesTest { } diff --git a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java index a2fb69fdb5..60e87c1a67 100644 --- a/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java +++ b/examples/java-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java @@ -1,10 +1,8 @@ package cucumber.examples.java.calculator; -import cucumber.api.Transformer; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; import java.util.List; @@ -13,7 +11,7 @@ public class ShoppingStepdefs { private RpnCalculator calc = new RpnCalculator(); - @Given("^the following groceries:$") + @Given("the following groceries:") public void the_following_groceries(List groceries) { for (Grocery grocery : groceries) { calc.push(grocery.price.value); @@ -21,39 +19,37 @@ public void the_following_groceries(List groceries) { } } - @When("^I pay (\\d+)$") + @When("I pay {int}") public void i_pay(int amount) { calc.push(amount); calc.push("-"); } - @Then("^my change should be (\\d+)$") + @Then("my change should be {int}") public void my_change_should_be_(int change) { assertEquals(-calc.value().intValue(), change); } - public static class Grocery { - public String name; - @XStreamConverter(Price.Converter.class) - public Price price; + static class Grocery { + private String name; + private Price price; - public Grocery() { - super(); + Grocery(String name, Price price) { + this.name = name; + this.price = price; } } - public static class Price { - public int value; + static final class Price { + private int value; - public Price(int value) { + Price(int value) { this.value = value; } - public static class Converter extends Transformer { - @Override - public Price transform(String value) { - return new Price(Integer.parseInt(value)); - } + static Price fromString(String value) { + return new Price(Integer.parseInt(value)); } + } } diff --git a/examples/java-gradle/src/test/java/gradle/cucumber/BasicStepdefs.java b/examples/java-gradle/src/test/java/gradle/cucumber/BasicStepdefs.java index d5fef25efa..be7bef22a8 100644 --- a/examples/java-gradle/src/test/java/gradle/cucumber/BasicStepdefs.java +++ b/examples/java-gradle/src/test/java/gradle/cucumber/BasicStepdefs.java @@ -4,7 +4,7 @@ public class BasicStepdefs { - @When("^I run a failing step") + @When("I run a failing step") public void I_run_a_failing_step() throws Throwable { new Production().doWork(); } diff --git a/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/NavigationStepdefs.java b/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/NavigationStepdefs.java index dc8bd7d4fa..3a27d44bed 100644 --- a/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/NavigationStepdefs.java +++ b/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/NavigationStepdefs.java @@ -8,7 +8,7 @@ public class NavigationStepdefs implements En { public NavigationStepdefs(SharedDriver webDriver) { - Given("^I am on the front page$", () -> { + Given("I am on the front page", () -> { webDriver.get("http://localhost:" + ServerHooks.PORT); // The input fields won't be enabled until the WebSocket has established diff --git a/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/TemperatureStepdefs.java b/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/TemperatureStepdefs.java index ecf99d637c..b7f491f662 100644 --- a/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/TemperatureStepdefs.java +++ b/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/TemperatureStepdefs.java @@ -9,10 +9,10 @@ public class TemperatureStepdefs implements En { public TemperatureStepdefs(SharedDriver webDriver) { - When("^I enter (.+) (celsius|fahrenheit)$", (Double value, String unit) -> + When("I enter {double} celsius|fahrenheit", (Double value, String unit) -> webDriver.findElement(By.id(unit)).sendKeys(String.valueOf(value))); - Then("^I should see (.+) (celsius|fahrenheit)$", (Double value, String unit) -> + Then("I should see {double} celsius|fahrenheit", (Double value, String unit) -> assertEquals(String.valueOf(value), webDriver.findElement(By.id(unit)).getAttribute("value"))); } } diff --git a/examples/java-wicket/java-wicket-test/src/test/java/cucumber/examples/java/wicket/steps/RentStepdefs.java b/examples/java-wicket/java-wicket-test/src/test/java/cucumber/examples/java/wicket/steps/RentStepdefs.java index 14d47cae2d..7d9d933118 100644 --- a/examples/java-wicket/java-wicket-test/src/test/java/cucumber/examples/java/wicket/steps/RentStepdefs.java +++ b/examples/java-wicket/java-wicket-test/src/test/java/cucumber/examples/java/wicket/steps/RentStepdefs.java @@ -10,17 +10,17 @@ public class RentStepdefs { private RentACarSupport rentACarSupport = new RentACarSupport(); - @Given("^there are (\\d+) cars available for rental$") + @Given("there are {int} cars available for rental") public void there_are_cars_available_for_rental(int availableCars) throws Throwable { rentACarSupport.createCars(availableCars); } - @When("^I rent one$") + @When("I rent one") public void rent_one_car() throws Throwable { rentACarSupport.rentACar(); } - @Then("^there will only be (\\d+) cars available for rental$") + @Then("there will only be {int} cars available for rental") public void there_will_be_less_cars_available_for_rental(int expectedAvailableCars) throws Throwable { int actualAvailableCars = rentACarSupport.getAvailableNumberOfCars(); assertThat(actualAvailableCars, is(expectedAvailableCars)); diff --git a/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java index c01081924c..00f3417006 100644 --- a/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java +++ b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/RpnCalculatorStepdefs.java @@ -1,7 +1,7 @@ package cucumber.examples.java.calculator; -import cucumber.api.DataTable; import cucumber.api.Scenario; +import io.cucumber.datatable.DataTable; import cucumber.api.java8.En; import java.util.List; @@ -12,20 +12,22 @@ public class RpnCalculatorStepdefs implements En { private RpnCalculator calc; public RpnCalculatorStepdefs() { - Given("^a calculator I just turned on$", () -> { + Given("a calculator I just turned on", () -> { calc = new RpnCalculator(); }); - When("^I add (\\d+) and (\\d+)$", (Integer arg1, Integer arg2) -> { + When("I add {int} and {int}", (Integer arg1, Integer arg2) -> { calc.push(arg1); calc.push(arg2); calc.push("+"); }); - Given("^I press (.+)$", (String what) -> calc.push(what)); + Given("I press (.+)", (String what) -> calc.push(what)); - Then("^the result is (\\d+)$", (Double expected) -> assertEquals(expected, calc.value())); + Then("the result is {double}", (Integer expected) -> assertEquals(expected, calc.value())); + + Then("the result is {int}", (Integer expected) -> assertEquals(expected.doubleValue(), calc.value())); Before(new String[]{"not @foo"}, (Scenario scenario) -> { @@ -37,7 +39,7 @@ public RpnCalculatorStepdefs() { }); - Given("^the previous entries:$", (DataTable dataTable) -> { + Given("the previous entries:", (DataTable dataTable) -> { List entries = dataTable.asList(Entry.class); for (Entry entry : entries) { calc.push(entry.first); @@ -48,9 +50,15 @@ public RpnCalculatorStepdefs() { } - public class Entry { - Integer first; - Integer second; - String operation; + static final class Entry { + private final Integer first; + private final Integer second; + private final String operation; + + Entry(Integer first, Integer second, String operation) { + this.first = first; + this.second = second; + this.operation = operation; + } } } diff --git a/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java index bf6cdd7cd7..c49d4c3fc9 100644 --- a/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java +++ b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/ShoppingStepdefs.java @@ -1,10 +1,7 @@ package cucumber.examples.java.calculator; -import cucumber.api.DataTable; -import cucumber.api.Transformer; +import io.cucumber.datatable.DataTable; import cucumber.api.java8.En; -import cucumber.deps.com.thoughtworks.xstream.annotations.XStreamConverter; - import java.util.List; @@ -17,7 +14,7 @@ public class ShoppingStepdefs implements En { public ShoppingStepdefs() { - Given("^the following groceries:$", (DataTable dataTable) -> { + Given("the following groceries:", (DataTable dataTable) -> { List groceries = dataTable.asList(Grocery.class); for (Grocery grocery : groceries) { calc.push(grocery.price.value); @@ -25,39 +22,36 @@ public ShoppingStepdefs() { } }); - When("^I pay (\\d+)$", (Integer amount) -> { + When("I pay {int}", (Integer amount) -> { calc.push(amount); calc.push("-"); }); - Then("^my change should be (\\d+)$", (Integer change) -> { + Then("my change should be {int}", (Integer change) -> { assertEquals(-calc.value().intValue(), change.intValue()); }); } - static class Grocery { - public String name; - @XStreamConverter(Price.Converter.class) - public Price price; + private String name; + private Price price; - public Grocery() { - super(); + Grocery(String name, Price price) { + this.name = name; + this.price = price; } } - static class Price { - public int value; + static final class Price { + private int value; - public Price(int value) { + Price(int value) { this.value = value; } - public static class Converter extends Transformer { - @Override - public Price transform(String value) { - return new Price(Integer.parseInt(value)); - } + static Price fromString(String value) { + return new Price(Integer.parseInt(value)); } + } } diff --git a/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/TypeRegistryConfiguration.java b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..6f277ef1a3 --- /dev/null +++ b/examples/java8-calculator/src/test/java/cucumber/examples/java/calculator/TypeRegistryConfiguration.java @@ -0,0 +1,40 @@ +package cucumber.examples.java.calculator; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.datatable.DataTableType; +import cucumber.examples.java.calculator.RpnCalculatorStepdefs.Entry; +import cucumber.examples.java.calculator.ShoppingStepdefs.Grocery; + +import java.util.Locale; +import java.util.Map; + +import static java.util.Locale.ENGLISH; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineDataTableType(new DataTableType( + Entry.class, + (Map row) -> new Entry( + Integer.valueOf(row.get("first")), + Integer.valueOf(row.get("second")), + row.get("operation") + ) + )); + + typeRegistry.defineDataTableType(new DataTableType( + Grocery.class, + (Map row) -> new Grocery( + row.get("name"), + ShoppingStepdefs.Price.fromString(row.get("price")) + ) + )); + } +} diff --git a/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorSteps.java b/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorSteps.java index 7ec8397e8c..2dc024c9a1 100644 --- a/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorSteps.java +++ b/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorSteps.java @@ -22,22 +22,22 @@ public void before() { result = 0; } - @When("^I set a to (\\d+)$") + @When("I set a to {int}") public void i_set_a_to(int v) throws Throwable { a = v; } - @When("^I set b to (\\d+)$") + @When("I set b to {int}") public void i_set_b_to(int v) throws Throwable { b = v; } - @When("^I call the calculator service$") + @When("I call the calculator service") public void i_call_the_calculator_service() throws Throwable { result = calculatorService.add(a, b); } - @When("^the result is (\\d+)$") + @When("the result is {int}") public void the_result_is(int expected) throws Throwable { assertEquals(expected, result); } diff --git a/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorTest.java b/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorTest.java index becb5d64da..e2a7fbe797 100644 --- a/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorTest.java +++ b/examples/pax-exam/calculator-test/src/test/java/cucumber/examples/java/paxexam/test/CalculatorTest.java @@ -7,9 +7,11 @@ import static org.ops4j.pax.exam.CoreOptions.options; import java.util.Collections; +import java.util.Locale; import javax.inject.Inject; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.ObjectFactory; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,7 +56,9 @@ public Option[] config() { mavenBundle("io.cucumber", "gherkin"), mavenBundle("io.cucumber", "tag-expressions"), - mavenBundle("io.cucumber", "cucumber-jvm-deps"), + mavenBundle("io.cucumber", "datatable"), + mavenBundle("io.cucumber", "datatable-dependencies"), + mavenBundle("io.cucumber", "cucumber-expressions"), mavenBundle("io.cucumber", "cucumber-core"), mavenBundle("io.cucumber", "cucumber-java"), mavenBundle("io.cucumber", "cucumber-osgi"), @@ -75,7 +79,8 @@ public void cucumber() throws Exception { final ClassLoader classLoader = Runtime.class.getClassLoader(); final ObjectFactory objectFactory = new PaxExamObjectFactory(injector); final ClassFinder classFinder = new OsgiClassFinder(bundleContext); - final Backend backend = new JavaBackend(objectFactory, classFinder); + TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + final Backend backend = new JavaBackend(objectFactory, classFinder, typeRegistry); final RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(getClass()); final RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); diff --git a/examples/pom.xml b/examples/pom.xml index 15cd824eef..565b2bed84 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -16,9 +16,9 @@ java-calculator java8-calculator java-calculator-testng - pax-exam java-wicket java-webbit-websockets-selenium + pax-exam diff --git a/examples/spring-txn/pom.xml b/examples/spring-txn/pom.xml index a6f3420f21..15c05d666c 100644 --- a/examples/spring-txn/pom.xml +++ b/examples/spring-txn/pom.xml @@ -82,11 +82,6 @@ logback-classic - - io.cucumber - cucumber-jvm-deps - test - io.cucumber cucumber-spring diff --git a/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/SeeMessagesStepdefs.java b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/SeeMessagesStepdefs.java index 61072aa55b..6536071920 100644 --- a/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/SeeMessagesStepdefs.java +++ b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/SeeMessagesStepdefs.java @@ -28,24 +28,24 @@ public class SeeMessagesStepdefs { private ResultActions resultActions; - @Given("^there is a User$") + @Given("there is a User") public void there_is_a_User() { user = userRepository.save(new User("John Doe")); } - @Given("^the User has posted the message \"([^\"]*)\"$") + @Given("the User has posted the message {string}") public void the_User_has_posted_the_message(String content) { messageRepository.save(new Message(user, content)); } - @When("^I visit the page for the User$") + @When("I visit the page for the User") public void I_visit_the_page_for_the_User() throws Exception { resultActions = mockMvc .perform(get("/users/" + user.getId())) .andExpect(status().isOk()); } - @Then("^I should see \"([^\"]*)\"$") + @Then("I should see {string}") public void I_should_see(String content) throws Exception { resultActions.andExpect(content().string(containsString(content))); } diff --git a/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/TypeRegistryConfiguration.java b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..607b8acc08 --- /dev/null +++ b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/TypeRegistryConfiguration.java @@ -0,0 +1,34 @@ +package cucumber.examples.spring.txn; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableEntryTransformer; + +import java.util.Locale; +import java.util.Map; + +import static java.util.Locale.ENGLISH; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineDataTableType(new DataTableType( + Message.class, + new TableEntryTransformer() { + @Override + public Message transform(Map map) { + Message message = new Message(); + message.setContent(map.get("content")); + return message; + } + })); + + } +} diff --git a/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/UserStepdefs.java b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/UserStepdefs.java index 6d6270df71..ac3b73d9b1 100644 --- a/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/UserStepdefs.java +++ b/examples/spring-txn/src/test/java/cucumber/examples/spring/txn/UserStepdefs.java @@ -25,7 +25,7 @@ public void thereIsAuser() { userRepository.save(user); } - @Given("^a User has posted the following messages:$") + @Given("a User has posted the following messages:") public void a_User_has_posted_the_following_messages(List messages) throws Throwable { thereIsAuser(); for (Message m : messages) { diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java index 1f4c6a1402..14fbbb3a5d 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java +++ b/guice/src/test/java/cucumber/runtime/java/guice/integration/HelloWorldSteps.java @@ -5,7 +5,7 @@ @ScenarioScoped public class HelloWorldSteps { - @Given("^I have (\\d+) cukes in my belly$") + @Given("I have {int} cukes in my belly") public void I_have_cukes_in_my_belly(int n) { n++; } diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java index 4e8985394a..93dba7fe0a 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java +++ b/guice/src/test/java/cucumber/runtime/java/guice/integration/ScenarioScopedSteps.java @@ -28,30 +28,30 @@ public ScenarioScopedSteps(Provider scenarioScopedObjectPr this.scenarioScopedObjectProvider = scenarioScopedObjectProvider; } - @Given("^a scenario scope instance has been provided in this scenario$") + @Given("a scenario scope instance has been provided in this scenario") public void a_scenario_scope_instance_has_been_provided_in_this_scenario() throws Throwable { OBJECTS.clear(); provide(); } - @When("^another scenario scope instance is provided$") + @When("another scenario scope instance is provided") public void another_scenario_scope_instance_is_provided() throws Throwable { provide(); } - @Then("^all three provided instances are the same instance$") + @Then("all three provided instances are the same instance") public void all_three_provided_instances_are_the_same_instance() throws Throwable { assertThat("Expected test scenario to provide three objects.", OBJECTS.size(), equalTo(3)); assertThat(OBJECTS, elementsAreAllEqual()); } - @Given("^a scenario scope instance was provided in the previous scenario$") + @Given("a scenario scope instance was provided in the previous scenario") public void a_scenario_scope_instance_was_provided_in_the_previous_scenario() throws Throwable { // we only need one instance from the previous scenario removeAllExceptFirstElement(OBJECTS); } - @Then("^the two provided instances are different$") + @Then("the two provided instances are different") public void the_two_provided_instances_are_different() throws Throwable { assertThat("Expected test scenario to provide two objects.", OBJECTS.size(), equalTo(2)); assertThat(OBJECTS, elementsAreAllUnique()); diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java index d0177e1bbb..ad4c324fef 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java +++ b/guice/src/test/java/cucumber/runtime/java/guice/integration/SingletonScopedSteps.java @@ -27,30 +27,30 @@ public SingletonScopedSteps(Provider singletonObjectProvider) { this.singletonObjectProvider = singletonObjectProvider; } - @Given("^a singleton scope instance has been provided in this scenario$") + @Given("a singleton scope instance has been provided in this scenario") public void a_singleton_scope_instance_has_been_provided_in_this_scenario() throws Throwable { OBJECTS.clear(); provide(); } - @When("^another singleton scope instance is provided$") + @When("another singleton scope instance is provided") public void another_singleton_scope_instance_is_provided() throws Throwable { provide(); } - @Then("^all three provided instances are the same singleton instance$") + @Then("all three provided instances are the same singleton instance") public void all_three_provided_instances_are_the_same_singleton_instance() throws Throwable { assertThat("Expected test scenario to provide three objects.", OBJECTS.size(), equalTo(3)); assertThat(OBJECTS, elementsAreAllEqual()); } - @Given("^a singleton scope instance was provided in the previous scenario$") + @Given("a singleton scope instance was provided in the previous scenario") public void a_singleton_scope_instance_was_provided_in_the_previous_scenario() throws Throwable { // we only need one instance from the previous scenario removeAllExceptFirstElement(OBJECTS); } - @Then("^the two provided instances are the same instance$") + @Then("the two provided instances are the same instance") public void the_two_provided_instances_are_the_same_instance() throws Throwable { assertThat("Expected test scenario to provide two objects.", OBJECTS.size(), equalTo(2)); assertThat(OBJECTS, elementsAreAllEqual()); diff --git a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java b/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java index cb08a0a394..1909f4855e 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java +++ b/guice/src/test/java/cucumber/runtime/java/guice/integration/UnScopedSteps.java @@ -23,18 +23,18 @@ public UnScopedSteps(Provider unScopedObjectProvider) { this.unScopedObjectProvider = unScopedObjectProvider; } - @Given("^an un-scoped instance has been provided in this scenario$") + @Given("an un-scoped instance has been provided in this scenario") public void an_un_scoped_instance_has_been_provided_in_this_scenario() throws Throwable { OBJECTS.clear(); provide(); } - @When("^another un-scoped instance is provided$") + @When("another un-scoped instance is provided") public void another_un_scoped_instance_is_provided() throws Throwable { provide(); } - @Then("^all three provided instances are unique instances$") + @Then("all three provided instances are unique instances") public void all_three_provided_instances_are_unique_instances() throws Throwable { assertThat("Expected test scenario to provide three objects.", OBJECTS.size(), equalTo(3)); assertThat(OBJECTS, elementsAreAllUnique()); diff --git a/java/pom.xml b/java/pom.xml index 9de32c8b10..8648d9c6eb 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -153,7 +153,7 @@ GherkinDialectProvider.DIALECTS.keySet().each { language -> - cucumber.* + * diff --git a/java/src/main/code_generator/I18n.java.txt b/java/src/main/code_generator/I18n.java.txt index c294fe1e7d..119b091ec3 100644 --- a/java/src/main/code_generator/I18n.java.txt +++ b/java/src/main/code_generator/I18n.java.txt @@ -8,13 +8,30 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.Documented; +/** + * To execute steps in a feature file the steps must be + * connected to executable code. This can be done by annotating + * a method with a cucumber or regular expression. + *

+ * The parameters extracted from the step by the expression + * along with the data table or doc string argument are provided as + * arguments to the method. + *

+ * The types of the parameters are determined by the cucumber or + * regular expression. + *

+ * The type of the data table or doc string argument is determined + * by the argument name value. When none is provided cucumber will + * attempt to transform the data table or doc string to the type + * of the last argument. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @StepDefAnnotation @Documented public @interface ${kw} { /** - * @return a regular expression + * @return a cucumber or regular expression */ String value(); @@ -22,5 +39,6 @@ public @interface ${kw} { * @return max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. */ long timeout() default 0; + } diff --git a/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java b/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java index aa6ff907f2..82fb4fe44a 100644 --- a/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java +++ b/java/src/main/java/cucumber/runtime/java/AbstractJavaSnippet.java @@ -2,45 +2,48 @@ import cucumber.runtime.snippets.Snippet; -import java.util.List; +import java.lang.reflect.Type; +import java.util.Map; -public abstract class AbstractJavaSnippet implements Snippet { +abstract class AbstractJavaSnippet implements Snippet { @Override - public String arguments(List> argumentTypes) { + public final String arguments(Map arguments) { StringBuilder sb = new StringBuilder(); - int n = 1; - for (Class argType : argumentTypes) { - if (n > 1) { + boolean first = true; + for (Map.Entry argType : arguments.entrySet()) { + if (first) { + first = false; + } else { sb.append(", "); } - sb.append(getArgType(argType)).append(" ").append("arg").append(n++); + sb.append(getArgType(argType.getValue())).append(" ").append(argType.getKey()); } return sb.toString(); } - protected abstract String getArgType(Class argType); - - @Override - public String tableHint() { - return " // For automatic transformation, change DataTable to one of\n" + - " // List, List>, List> or Map.\n" + - " // E,K,V must be a scalar (String, Integer, Date, enum etc).\n" + - " // Field names for YourType must match the column names in \n" + - " // your feature file (except for spaces and capitalization).\n"; - } + private String getArgType(Type argType) { + if (argType instanceof Class) { + Class cType = (Class) argType; + return cType.getSimpleName(); + } - @Override - public String namedGroupStart() { - return null; + // Got a better idea? Send a PR. + return argType.toString(); } @Override - public String namedGroupEnd() { - return null; + public final String tableHint() { + return "" + + " // For automatic transformation, change DataTable to one of\n" + + " // List, List>, List>, Map or\n" + + " // Map>. E,K,V must be a String, Integer, Float,\n" + + " // Double, Byte Short, Long, BigInteger or BigDecimal.\n" + + " //\n" + + " // For other transformations you can register a DataTableType.\n"; //TODO: Add doc URL } @Override - public String escapePattern(String pattern) { + public final String escapePattern(String pattern) { return pattern.replace("\\", "\\\\").replace("\"", "\\\""); } } diff --git a/java/src/main/java/cucumber/runtime/java/Function.java b/java/src/main/java/cucumber/runtime/java/Function.java new file mode 100644 index 0000000000..fe6ea885e2 --- /dev/null +++ b/java/src/main/java/cucumber/runtime/java/Function.java @@ -0,0 +1,7 @@ +package cucumber.runtime.java; + +public interface Function { + + R apply(T t); + +} diff --git a/java/src/main/java/cucumber/runtime/java/Java8Snippet.java b/java/src/main/java/cucumber/runtime/java/Java8Snippet.java index 10ecd1ac07..cb2aad3dcd 100644 --- a/java/src/main/java/cucumber/runtime/java/Java8Snippet.java +++ b/java/src/main/java/cucumber/runtime/java/Java8Snippet.java @@ -1,34 +1,13 @@ package cucumber.runtime.java; -import java.util.HashMap; -import java.util.Map; - -class Java8Snippet extends AbstractJavaSnippet { - private static final Map, Class> PRIMITIVES_TO_WRAPPERS = new HashMap, Class>() {{ - put(boolean.class, Boolean.class); - put(byte.class, Byte.class); - put(char.class, Character.class); - put(double.class, Double.class); - put(float.class, Float.class); - put(int.class, Integer.class); - put(long.class, Long.class); - put(short.class, Short.class); - put(void.class, Void.class); - }}; - - @Override - protected String getArgType(Class argType) { - if (argType.isPrimitive()) { - return PRIMITIVES_TO_WRAPPERS.get(argType).getSimpleName(); - } - return argType.getSimpleName(); - } +final class Java8Snippet extends AbstractJavaSnippet { @Override public String template() { - return "{0}(\"{1}\", ({3}) -> '{'\n" + - " // {4}\n" + - "{5} throw new PendingException();\n" + - "'}');\n"; + return "" + + "{0}(\"{1}\", ({3}) -> '{'\n" + + " // {4}\n" + + "{5} throw new PendingException();\n" + + "'}');\n"; } } diff --git a/java/src/main/java/cucumber/runtime/java/JavaBackend.java b/java/src/main/java/cucumber/runtime/java/JavaBackend.java index f4ecde59f3..52492d752e 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaBackend.java +++ b/java/src/main/java/cucumber/runtime/java/JavaBackend.java @@ -4,6 +4,7 @@ import static cucumber.runtime.java.ObjectFactoryLoader.loadObjectFactory; import static java.lang.Thread.currentThread; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.After; import cucumber.api.java.AfterStep; import cucumber.api.java.Before; @@ -20,7 +21,6 @@ import cucumber.runtime.StepDefinition; import cucumber.runtime.UnreportedStepExecutor; import cucumber.runtime.Utils; -import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.io.ResourceLoaderClassFinder; import cucumber.runtime.snippets.FunctionNameGenerator; @@ -33,10 +33,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.regex.Pattern; public class JavaBackend implements Backend, LambdaGlueRegistry { - private final SnippetGenerator snippetGenerator = new SnippetGenerator(createSnippet()); + + private final SnippetGenerator snippetGenerator; + private final TypeRegistry typeRegistry; private Snippet createSnippet() { ClassLoader classLoader = currentThread().getContextClassLoader(); @@ -60,18 +61,20 @@ private Snippet createSnippet() { * * @param resourceLoader */ - public JavaBackend(ResourceLoader resourceLoader) { - this(new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader())); + public JavaBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { + this(new ResourceLoaderClassFinder(resourceLoader, currentThread().getContextClassLoader()), typeRegistry); } - private JavaBackend(ClassFinder classFinder) { - this(loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName())), classFinder); + private JavaBackend(ClassFinder classFinder, TypeRegistry typeRegistry) { + this(loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName())), classFinder, typeRegistry); } - public JavaBackend(ObjectFactory objectFactory, ClassFinder classFinder) { + public JavaBackend(ObjectFactory objectFactory, ClassFinder classFinder, TypeRegistry typeRegistry) { this.classFinder = classFinder; this.objectFactory = objectFactory; this.methodScanner = new MethodScanner(classFinder); + this.snippetGenerator = new SnippetGenerator(createSnippet(), typeRegistry.parameterTypeRegistry()); + this.typeRegistry = typeRegistry; } @Override @@ -143,9 +146,15 @@ public String getSnippet(PickleStep step, String keyword, FunctionNameGenerator void addStepDefinition(Annotation annotation, Method method) { try { if (objectFactory.addClass(method.getDeclaringClass())) { - glue.addStepDefinition(new JavaStepDefinition(method, pattern(annotation), timeoutMillis(annotation), objectFactory)); + glue.addStepDefinition( + new JavaStepDefinition( + method, + expression(annotation), + timeoutMillis(annotation), + objectFactory, + typeRegistry)); } - } catch (DuplicateStepDefinitionException e) { + } catch (CucumberException e) { throw e; } catch (Throwable e) { throw new CucumberException(e); @@ -153,8 +162,8 @@ void addStepDefinition(Annotation annotation, Method method) { } @Override - public void addStepDefinition(StepDefinition stepDefinition) { - glue.addStepDefinition(stepDefinition); + public void addStepDefinition(Function stepDefinitionFunction) { + glue.addStepDefinition(stepDefinitionFunction.apply(typeRegistry)); } void addHook(Annotation annotation, Method method) { @@ -200,10 +209,10 @@ public void addBeforeStepHookDefinition(HookDefinition beforeStepHook) { } - private Pattern pattern(Annotation annotation) throws Throwable { - Method regexpMethod = annotation.getClass().getMethod("value"); - String regexpString = (String) Utils.invoke(annotation, regexpMethod, 0); - return Pattern.compile(regexpString); + + private String expression(Annotation annotation) throws Throwable { + Method expressionMethod = annotation.getClass().getMethod("value"); + return (String) Utils.invoke(annotation, expressionMethod, 0); } private long timeoutMillis(Annotation annotation) throws Throwable { diff --git a/java/src/main/java/cucumber/runtime/java/JavaSnippet.java b/java/src/main/java/cucumber/runtime/java/JavaSnippet.java index 4c63eaaf81..37adf13230 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaSnippet.java +++ b/java/src/main/java/cucumber/runtime/java/JavaSnippet.java @@ -1,18 +1,14 @@ package cucumber.runtime.java; -class JavaSnippet extends AbstractJavaSnippet { - - @Override - protected String getArgType(Class argType) { - return argType.getSimpleName(); - } +final class JavaSnippet extends AbstractJavaSnippet { @Override public String template() { - return "@{0}(\"{1}\")\n" + - "public void {2}({3}) '{'\n" + - " // {4}\n" + - "{5} throw new PendingException();\n" + - "'}'\n"; + return "" + + "@{0}(\"{1}\")\n" + + "public void {2}({3}) '{'\n" + + " // {4}\n" + + "{5} throw new PendingException();\n" + + "'}'\n"; } } diff --git a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java index 24e9dfc381..a708c72e72 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java +++ b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java @@ -1,36 +1,49 @@ package cucumber.runtime.java; -import cucumber.api.Argument; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.ObjectFactory; -import cucumber.runtime.JdkPatternArgumentMatcher; +import io.cucumber.stepexpression.Argument; +import io.cucumber.stepexpression.ArgumentMatcher; +import io.cucumber.stepexpression.ExpressionArgumentMatcher; import cucumber.runtime.MethodFormat; import cucumber.runtime.ParameterInfo; import cucumber.runtime.StepDefinition; +import io.cucumber.stepexpression.StepExpression; +import io.cucumber.stepexpression.StepExpressionFactory; import cucumber.runtime.Utils; import gherkin.pickles.PickleStep; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.List; -import java.util.regex.Pattern; class JavaStepDefinition implements StepDefinition { private final Method method; - private final Pattern pattern; + private final StepExpression expression; private final long timeoutMillis; private final ObjectFactory objectFactory; - private final JdkPatternArgumentMatcher argumentMatcher; private final List parameterInfos; - public JavaStepDefinition(Method method, Pattern pattern, long timeoutMillis, ObjectFactory objectFactory) { + JavaStepDefinition(Method method, + String expression, + long timeoutMillis, + ObjectFactory objectFactory, + TypeRegistry typeRegistry) { this.method = method; - this.pattern = pattern; this.timeoutMillis = timeoutMillis; this.objectFactory = objectFactory; - - this.argumentMatcher = new JdkPatternArgumentMatcher(pattern); this.parameterInfos = ParameterInfo.fromMethod(method); + this.expression = createExpression(expression, typeRegistry); + } + + private StepExpression createExpression(String expression, TypeRegistry typeRegistry) { + if (parameterInfos.isEmpty()) { + return new StepExpressionFactory(typeRegistry).createExpression(expression); + } else { + ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); + return new StepExpressionFactory(typeRegistry).createExpression(expression, parameterInfo.getType(), parameterInfo.isTransposed()); + } } public void execute(String language, Object[] args) throws Throwable { @@ -38,7 +51,8 @@ public void execute(String language, Object[] args) throws Throwable { } public List matchedArguments(PickleStep step) { - return argumentMatcher.argumentsFrom(step.getText()); + ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); + return argumentMatcher.argumentsFrom(step); } public String getLocation(boolean detail) { @@ -62,7 +76,7 @@ public boolean isDefinedAt(StackTraceElement e) { @Override public String getPattern() { - return pattern.pattern(); + return expression.getSource(); } @Override diff --git a/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java b/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java index 2aa43074b2..b720d6105e 100644 --- a/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java +++ b/java/src/main/java/cucumber/runtime/java/LambdaGlueRegistry.java @@ -1,12 +1,13 @@ package cucumber.runtime.java; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.runtime.HookDefinition; import cucumber.runtime.StepDefinition; public interface LambdaGlueRegistry { ThreadLocal INSTANCE = new ThreadLocal(); - void addStepDefinition(StepDefinition stepDefinition); + void addStepDefinition(Function stepDefinition); void addBeforeStepHookDefinition(HookDefinition beforeStepHook); diff --git a/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java b/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java index 5b7bd3ff90..f4fe0ac325 100644 --- a/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java +++ b/java/src/main/java/cucumber/runtime/java/ObjectFactoryLoader.java @@ -7,6 +7,8 @@ import cucumber.runtime.Reflections; import cucumber.runtime.TooManyInstancesException; +import static java.util.Arrays.asList; + public class ObjectFactoryLoader { private ObjectFactoryLoader() { } @@ -28,7 +30,7 @@ public static ObjectFactory loadObjectFactory(ClassFinder classFinder, String ob Class objectFactoryClass = (Class) classFinder.loadClass(objectFactoryClassName); objectFactory = reflections.newInstance(new Class[0], new Object[0], objectFactoryClass); } else { - objectFactory = reflections.instantiateExactlyOneSubclass(ObjectFactory.class, "cucumber.runtime", new Class[0], new Object[0]); + objectFactory = reflections.instantiateExactlyOneSubclass(ObjectFactory.class, asList("cucumber.runtime"), new Class[0], new Object[0], null); } } catch (TooManyInstancesException e) { System.out.println(e.getMessage()); diff --git a/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java b/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java index e641c9fa50..17252f28df 100644 --- a/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java +++ b/java/src/test/java/cucumber/runtime/java/Java8SnippetTest.java @@ -4,9 +4,11 @@ import gherkin.pickles.Argument; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleStep; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; import org.junit.Test; import java.util.Collections; +import java.util.Locale; import static org.junit.Assert.assertEquals; @@ -16,7 +18,7 @@ public class Java8SnippetTest { @Test public void generatesPlainSnippet() { String expected = "" + - "Given(\"^I have (\\\\d+) cukes in my \\\"([^\\\"]*)\\\" belly$\", (Integer arg1, String arg2) -> {\n" + + "Given(\"I have {int} cukes in my {string} belly\", (Integer int1, String string) -> {\n" + " // Write code here that turns the phrase above into concrete actions\n" + " throw new PendingException();\n" + "});\n"; @@ -26,6 +28,6 @@ public void generatesPlainSnippet() { private String snippetFor(String name) { PickleStep step = new PickleStep(name, Collections.emptyList(), Collections.emptyList()); - return new SnippetGenerator(new Java8Snippet()).getSnippet(step, GIVEN_KEYWORD, null); + return new SnippetGenerator(new Java8Snippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, null); } } \ No newline at end of file diff --git a/java/src/test/java/cucumber/runtime/java/JavaBackendTest.java b/java/src/test/java/cucumber/runtime/java/JavaBackendTest.java index 4efe199aed..8584789740 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaBackendTest.java +++ b/java/src/test/java/cucumber/runtime/java/JavaBackendTest.java @@ -1,6 +1,7 @@ package cucumber.runtime.java; import cucumber.api.StepDefinitionReporter; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.ObjectFactory; import cucumber.runtime.CucumberException; import cucumber.runtime.Glue; @@ -17,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; @@ -33,7 +35,8 @@ public void createBackend(){ ResourceLoader resourceLoader = new MultiLoader(classLoader); ResourceLoaderClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); this.factory = new DefaultJavaObjectFactory(); - this.backend = new JavaBackend(factory, classFinder); + TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + this.backend = new JavaBackend(factory, classFinder, typeRegistry); } @Test diff --git a/java/src/test/java/cucumber/runtime/java/JavaHookTest.java b/java/src/test/java/cucumber/runtime/java/JavaHookTest.java index 5254bda375..67f73f2273 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaHookTest.java +++ b/java/src/test/java/cucumber/runtime/java/JavaHookTest.java @@ -1,6 +1,7 @@ package cucumber.runtime.java; import cucumber.api.Scenario; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.After; import cucumber.api.java.AfterStep; import cucumber.api.java.Before; @@ -13,13 +14,13 @@ import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.io.ResourceLoaderClassFinder; -import cucumber.runtime.xstream.LocalizedXStreams; import gherkin.pickles.PickleLocation; import gherkin.pickles.PickleTag; import org.junit.Test; import java.lang.reflect.Method; import java.util.Collections; +import java.util.Locale; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; @@ -59,10 +60,9 @@ public void createBackendAndLoadNoGlue() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ResourceLoader resourceLoader = new MultiLoader(classLoader); ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); - this.backend = new JavaBackend(objectFactory, classFinder); - - LocalizedXStreams localizedXStreams = new LocalizedXStreams(classLoader); - this.glue = new RuntimeGlue(localizedXStreams); + TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + this.backend = new JavaBackend(objectFactory, classFinder, typeRegistry); + this.glue = new RuntimeGlue(); backend.loadGlue(glue, Collections.emptyList()); } diff --git a/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java b/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java index d6cbd80a3a..8cf1de64d1 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java +++ b/java/src/test/java/cucumber/runtime/java/JavaSnippetTest.java @@ -10,12 +10,20 @@ import gherkin.pickles.PickleStep; import gherkin.pickles.PickleString; import gherkin.pickles.PickleTable; +import io.cucumber.cucumberexpressions.CaptureGroupTransformer; +import io.cucumber.cucumberexpressions.ParameterType; +import io.cucumber.cucumberexpressions.ParameterTypeRegistry; +import io.cucumber.cucumberexpressions.Transformer; +import io.cucumber.cucumberexpressions.TypeReference; import org.junit.Ignore; import org.junit.Test; import java.util.Collections; +import java.util.List; +import java.util.Locale; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; public class JavaSnippetTest { @@ -26,166 +34,302 @@ public class JavaSnippetTest { @Test public void generatesPlainSnippet() { String expected = "" + - "@Given(\"^I have (\\\\d+) cukes in my \\\"([^\\\"]*)\\\" belly$\")\n" + - "public void i_have_cukes_in_my_belly(int arg1, String arg2) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have {int} cukes in my {string} belly\")\n" + + "public void i_have_cukes_in_my_belly(Integer int1, String string) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("I have 4 cukes in my \"big\" belly")); } @Test - public void generatesCopyPasteReadyStepSnippetForNumberParameters() throws Exception { + public void generatesPlainSnippetUsingCustomParameterTypes() { + ParameterType customParameterType = new ParameterType( + "size", + "small|medium|large", + Size.class, + new CaptureGroupTransformer() { + @Override + public Size transform(String... strings) { + return null; + } + }, true, + false); + + String expected = "" + + "@Given(\"I have {double} cukes in my {size} belly\")\n" + + "public void i_have_cukes_in_my_belly(Double double1, Size size) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; + assertEquals(expected, snippetFor("I have 4.2 cukes in my large belly", customParameterType)); + } + + @Test + public void generatesPlainSnippetUsingComplexParameterTypes() { + ParameterType> customParameterType = new ParameterType>( + "sizes", + singletonList("(small|medium|large)(( and |, )(small|medium|large))*"), + new TypeReference>() { + }.getType(), + new CaptureGroupTransformer>() { + @Override + public List transform(String... strings) { + return null; + } + }, + true, + false); + + String expected = "" + + "@Given(\"I have {sizes} bellies\")\n" + + "public void i_have_bellies(java.util.List sizes) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; + assertEquals(expected, snippetFor("I have large and small bellies", customParameterType)); + } + + @Test + public void generatesCopyPasteReadyStepSnippetForNumberParameters() { String expected = "" + - "@Given(\"^before (\\\\d+) after$\")\n" + - "public void before_after(int arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; - String snippet = snippetFor("before 5 after"); - assertEquals(expected, snippet); + "@Given(\"before {int} after\")\n" + + "public void before_after(Integer int1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; + assertEquals(expected, snippetFor("before 5 after")); } @Test public void generatesCopyPasteReadySnippetWhenStepHasIllegalJavaIdentifierChars() { String expected = "" + - "@Given(\"^I have (\\\\d+) cukes in: my \\\"([^\\\"]*)\\\" red-belly!$\")\n" + - "public void i_have_cukes_in_my_red_belly(int arg1, String arg2) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have {int} cukes in: my {string} red-belly!\")\n" + + "public void i_have_cukes_in_my_red_belly(Integer int1, String string) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("I have 4 cukes in: my \"big\" red-belly!")); } @Test public void generatesCopyPasteReadySnippetWhenStepHasIntegersInsideStringParameter() { String expected = "" + - "@Given(\"^the DI system receives a message saying \\\"([^\\\"]*)\\\"$\")\n" + - "public void the_DI_system_receives_a_message_saying(String arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"the DI system receives a message saying {string}\")\n" + + "public void the_DI_system_receives_a_message_saying(String string) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("the DI system receives a message saying \"{ dataIngestion: { feeds: [ feed: { merchantId: 666, feedId: 1, feedFileLocation: feed.csv } ] }\"")); } @Test - public void generatesSnippetWithEscapedDollarSigns() { + public void generatesSnippetWithDollarSigns() { String expected = "" + - "@Given(\"^I have \\\\$(\\\\d+)$\")\n" + - "public void i_have_$(int arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have ${int}\")\n" + + "public void i_have_$(Integer int1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("I have $5")); } @Test - public void generatesSnippetWithEscapedQuestionMarks() { + public void generatesSnippetWithQuestionMarks() { String expected = "" + - "@Given(\"^is there an error\\\\?:$\")\n" + - "public void is_there_an_error() {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"is there an error?:\")\n" + + "public void is_there_an_error() {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("is there an error?:")); } @Test - public void generatesSnippetWithLotsOfEscapes() { + public void generatesSnippetWithLotsOfNonIdentifierCharacters() { String expected = "" + - "@Given(\"^\\\\^\\\\(\\\\[a-z\\\\]\\\\*\\\\)\\\\?\\\\.\\\\+\\\\$$\")\n" + - "public void a_z_$() {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; - assertEquals(expected, snippetFor("^([a-z]*)?.+$")); + "@Given(\"\\\\([a-z]*)?.+\")\n" + + "public void a_z() {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; + assertEquals(expected, snippetFor("([a-z]*)?.+")); } @Test - public void generatesSnippetWithEscapedParentheses() { + public void generatesSnippetWithParentheses() { String expected = "" + - "@Given(\"^I have (\\\\d+) cukes \\\\(maybe more\\\\)$\")\n" + - "public void i_have_cukes_maybe_more(int arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have {int} cukes \\\\(maybe more)\")\n" + + "public void i_have_cukes_maybe_more(Integer int1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("I have 5 cukes (maybe more)")); } @Test - public void generatesSnippetWithEscapedBrackets() { + public void generatesSnippetWithBrackets() { String expected = "" + - "@Given(\"^I have (\\\\d+) cukes \\\\[maybe more\\\\]$\")\n" + - "public void i_have_cukes_maybe_more(int arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have {int} cukes [maybe more]\")\n" + + "public void i_have_cukes_maybe_more(Integer int1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("I have 5 cukes [maybe more]")); } @Test public void generatesSnippetWithDocString() { String expected = "" + - "@Given(\"^I have:$\")\n" + - "public void i_have(String arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have:\")\n" + + "public void i_have(String docString) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetForDocString("I have:", new PickleString(null, "hello"))); } + @Test + public void generatesSnippetWithMultipleArgumentsNamedDocString() { + ParameterType customParameterType = new ParameterType( + "docString", + "\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"", + String.class, + new CaptureGroupTransformer() { + @Override + public String transform(String... strings) { + return null; + } + }, + true, + false); + + String expected = "" + + "@Given(\"I have a {docString}:\")\n" + + "public void i_have_a(String docString, String docString1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; + assertEquals(expected, snippetForDocString("I have a \"Documentation String\":", new PickleString(null, "hello"), customParameterType)); + } + @Test @Ignore public void recognisesWordWithNumbers() { String expected = "" + - "@Given(\"^Then it responds ([^\\\"]*)$\")\n" + - "public void Then_it_responds(String arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - "}\n"; + "@Given(\"Then it responds ([\\\"]*)\")\n" + + "public void Then_it_responds(String arg1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + "}\n"; assertEquals(expected, snippetFor("Then it responds UTF-8")); } @Test public void generatesSnippetWithDataTable() { String expected = "" + - "@Given(\"^I have:$\")\n" + - "public void i_have(DataTable arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " // For automatic transformation, change DataTable to one of\n" + - " // List, List>, List> or Map.\n" + - " // E,K,V must be a scalar (String, Integer, Date, enum etc).\n" + - " // Field names for YourType must match the column names in \n" + - " // your feature file (except for spaces and capitalization).\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"I have:\")\n" + + "public void i_have(DataTable dataTable) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " // For automatic transformation, change DataTable to one of\n" + + " // List, List>, List>, Map or\n" + + " // Map>. E,K,V must be a String, Integer, Float,\n" + + " // Double, Byte Short, Long, BigInteger or BigDecimal.\n" + + " //\n" + + " // For other transformations you can register a DataTableType.\n" + + " throw new PendingException();\n" + + "}\n"; PickleTable dataTable = new PickleTable(asList(new PickleRow(asList(new PickleCell(null, "col1"))))); assertEquals(expected, snippetForDataTable("I have:", dataTable)); } + + @Test + public void generatesSnippetWithMultipleArgumentsNamedDataTable() { + ParameterType customParameterType = new ParameterType( + "dataTable", + "\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"", + String.class, + new CaptureGroupTransformer() { + @Override + public String transform(String... strings) { + return null; + } + }, + true, + false); + + String expected = "" + + "@Given(\"I have in table {dataTable}:\")\n" + + "public void i_have_in_table(String dataTable, DataTable dataTable1) {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " // For automatic transformation, change DataTable to one of\n" + + " // List, List>, List>, Map or\n" + + " // Map>. E,K,V must be a String, Integer, Float,\n" + + " // Double, Byte Short, Long, BigInteger or BigDecimal.\n" + + " //\n" + + " // For other transformations you can register a DataTableType.\n" + +// " //\n" + +// " // See: TODO URL\n" + + " throw new PendingException();\n" + + "}\n"; + PickleTable dataTable = new PickleTable(asList(new PickleRow(asList(new PickleCell(null, "col1"))))); + assertEquals(expected, snippetForDataTable("I have in table \"M6\":", dataTable, customParameterType)); + } + @Test public void generateSnippetWithOutlineParam() { String expected = "" + - "@Given(\"^Then it responds (.*)$\")\n" + - "public void then_it_responds(String arg1) {\n" + - " // Write code here that turns the phrase above into concrete actions\n" + - " throw new PendingException();\n" + - "}\n"; + "@Given(\"Then it responds \")\n" + + "public void then_it_responds_param() {\n" + + " // Write code here that turns the phrase above into concrete actions\n" + + " throw new PendingException();\n" + + "}\n"; assertEquals(expected, snippetFor("Then it responds ")); } private String snippetFor(String name) { PickleStep step = new PickleStep(name, Collections.emptyList(), Collections.emptyList()); - return new SnippetGenerator(new JavaSnippet()).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + return new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + } + + + private String snippetFor(String name, ParameterType parameterType) { + PickleStep step = new PickleStep(name, Collections.emptyList(), Collections.emptyList()); + ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); + parameterTypeRegistry.defineParameterType(parameterType); + return new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); } private String snippetForDocString(String name, PickleString docString) { - PickleStep step = new PickleStep(name, asList((Argument)docString), Collections.emptyList()); - return new SnippetGenerator(new JavaSnippet()).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + PickleStep step = new PickleStep(name, asList((Argument) docString), Collections.emptyList()); + return new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + } + + + private String snippetForDocString(String name, PickleString docString, ParameterType parameterType) { + PickleStep step = new PickleStep(name, asList((Argument) docString), Collections.emptyList()); + ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); + parameterTypeRegistry.defineParameterType(parameterType); + return new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); } + private String snippetForDataTable(String name, PickleTable dataTable) { - PickleStep step = new PickleStep(name, asList((Argument)dataTable), Collections.emptyList()); - return new SnippetGenerator(new JavaSnippet()).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + PickleStep step = new PickleStep(name, asList((Argument) dataTable), Collections.emptyList()); + return new SnippetGenerator(new JavaSnippet(), new ParameterTypeRegistry(Locale.ENGLISH)).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); } + + + private String snippetForDataTable(String name, PickleTable dataTable, ParameterType parameterType) { + PickleStep step = new PickleStep(name, asList((Argument) dataTable), Collections.emptyList()); + ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); + parameterTypeRegistry.defineParameterType(parameterType); + return new SnippetGenerator(new JavaSnippet(), parameterTypeRegistry).getSnippet(step, GIVEN_KEYWORD, functionNameGenerator); + } + + private static class Size { + // Dummy. Makes the test readable + } + } \ No newline at end of file diff --git a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java b/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java index 4060352812..71059056e5 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java +++ b/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java @@ -1,6 +1,7 @@ package cucumber.runtime.java; import cucumber.api.Result; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.event.EventHandler; import cucumber.api.event.TestStepFinished; import cucumber.api.java.ObjectFactory; @@ -24,6 +25,7 @@ import java.lang.reflect.Method; import java.util.Collections; +import java.util.Locale; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; @@ -58,7 +60,8 @@ public void createBackendAndLoadNoGlue() { ResourceLoader resourceLoader = new MultiLoader(classLoader); ResourceLoaderClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); ObjectFactory factory = new SingletonFactory(defs); - this.backend = new JavaBackend(factory, classFinder); + TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + this.backend = new JavaBackend(factory, classFinder, typeRegistry); RuntimeOptions runtimeOptions = new RuntimeOptions(""); this.runtime = new Runtime(new ClasspathResourceLoader(classLoader), classLoader, asList(backend), runtimeOptions); diff --git a/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java b/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java new file mode 100755 index 0000000000..17d64f2378 --- /dev/null +++ b/java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTransposeTest.java @@ -0,0 +1,152 @@ +package cucumber.runtime.java; + +import cucumber.api.Transpose; +import io.cucumber.stepexpression.TypeRegistry; +import cucumber.runtime.PickleStepDefinitionMatch; +import io.cucumber.datatable.DataTable; +import cucumber.api.java.ObjectFactory; +import cucumber.runtime.StepDefinition; +import cucumber.runtime.StepDefinitionMatch; +import io.cucumber.stepexpression.Argument; +import gherkin.pickles.PickleCell; +import gherkin.pickles.PickleLocation; +import gherkin.pickles.PickleRow; +import gherkin.pickles.PickleStep; +import gherkin.pickles.PickleTable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class JavaStepDefinitionTransposeTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String ENGLISH = "en"; + private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + + public static class StepDefs { + public List> listOfListOfDoubles; + public Map mapOfDoubleToDouble; + + public DataTable dataTable; + private Map> mapOfDoubleToListOfDouble; + + + public void listOfListOfDoubles(List> listOfListOfDoubles) { + this.listOfListOfDoubles = listOfListOfDoubles; + } + + public void listOfListOfDoublesTransposed(@Transpose List> listOfListOfDoubles) { + this.listOfListOfDoubles = listOfListOfDoubles; + } + + public void plainDataTable(DataTable dataTable) { + this.dataTable = dataTable; + } + + public void transposedDataTable(@Transpose DataTable dataTable) { + this.dataTable = dataTable; + } + + public void mapOfDoubleToDouble(Map mapOfDoubleToDouble) { + this.mapOfDoubleToDouble = mapOfDoubleToDouble; + } + + public void transposedMapOfDoubleToListOfDouble(@Transpose Map> mapOfDoubleToListOfDouble) { + this.mapOfDoubleToListOfDouble = mapOfDoubleToListOfDouble; + } + } + + @Test + public void transforms_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("mapOfDoubleToDouble", Map.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); + assertEquals(Double.valueOf(999.0), stepDefs.mapOfDoubleToDouble.get(1000.0)); + assertEquals(Double.valueOf(-0.5), stepDefs.mapOfDoubleToDouble.get(0.5)); + assertEquals(Double.valueOf(99.5), stepDefs.mapOfDoubleToDouble.get(100.5)); + } + + @Test + public void transforms_transposed_to_map_of_double_to_double() throws Throwable { + Method m = StepDefs.class.getMethod("transposedMapOfDoubleToListOfDouble", Map.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); + assertEquals(asList(0.5, 1000.0), stepDefs.mapOfDoubleToListOfDouble.get(100.5)); + } + + @Test + public void transforms_to_list_of_single_values() throws Throwable { + Method m = StepDefs.class.getMethod("listOfListOfDoubles", List.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); + } + + @Test + public void transforms_to_list_of_single_values_transposed() throws Throwable { + Method m = StepDefs.class.getMethod("listOfListOfDoublesTransposed", List.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(transposedListOfDoublesWithoutHeader())); + assertEquals("[[100.5, 99.5], [0.5, -0.5], [1000.0, 999.0]]", stepDefs.listOfListOfDoubles.toString()); + } + + @Test + public void passes_plain_data_table() throws Throwable { + Method m = StepDefs.class.getMethod("plainDataTable", DataTable.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.dataTable.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.dataTable.cell(1, 0)); + } + + @Test + public void passes_transposed_data_table() throws Throwable { + Method m = StepDefs.class.getMethod("transposedDataTable", DataTable.class); + StepDefs stepDefs = runStepDef(m, new PickleTable(listOfDatesWithHeader())); + assertEquals("Birth Date", stepDefs.dataTable.cell(0, 0)); + assertEquals("1957-05-10", stepDefs.dataTable.cell(0, 1)); + } + + private StepDefs runStepDef(Method method, PickleTable table) throws Throwable { + StepDefs stepDefs = new StepDefs(); + ObjectFactory objectFactory = new SingletonFactory(stepDefs); + + StepDefinition stepDefinition = new JavaStepDefinition(method, "some text", 0, objectFactory, typeRegistry); + PickleStep stepWithTable = new PickleStep("some text", asList((gherkin.pickles.Argument) table), asList(mock(PickleLocation.class))); + List arguments = stepDefinition.matchedArguments(stepWithTable); + + StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, "some.feature", stepWithTable); + stepDefinitionMatch.runStep(ENGLISH, null); + return stepDefs; + } + + private List listOfDatesWithHeader() { + List rows = new ArrayList(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "Birth Date")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1957-05-10")))); + return rows; + } + + private List listOfDoublesWithoutHeader() { + List rows = new ArrayList(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "99.5")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "-0.5")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "1000"), new PickleCell(mock(PickleLocation.class), "999")))); + return rows; + } + + private List transposedListOfDoublesWithoutHeader() { + List rows = new ArrayList(); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "100.5"), new PickleCell(mock(PickleLocation.class), "0.5"), new PickleCell(mock(PickleLocation.class), "1000")))); + rows.add(new PickleRow(asList(new PickleCell(mock(PickleLocation.class), "99.5"), new PickleCell(mock(PickleLocation.class), "-0.5"), new PickleCell(mock(PickleLocation.class), "999")))); + return rows; + } + + +} diff --git a/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java b/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java index 9d0fb97622..66fb6dc2e8 100644 --- a/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java +++ b/java/src/test/java/cucumber/runtime/java/MethodScannerTest.java @@ -1,5 +1,6 @@ package cucumber.runtime.java; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java.ObjectFactory; import cucumber.runtime.CucumberException; import cucumber.runtime.Glue; @@ -11,6 +12,8 @@ import org.mockito.Mockito; import org.mockito.internal.util.reflection.Whitebox; +import java.util.Locale; + import static java.lang.Thread.currentThread; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -28,7 +31,8 @@ public void createBackend(){ ResourceLoader resourceLoader = new MultiLoader(classLoader); this.classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); this.factory = Mockito.mock(ObjectFactory.class); - this.backend = new JavaBackend(factory, classFinder); + TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + this.backend = new JavaBackend(factory, classFinder, typeRegistry); } @Test diff --git a/java/src/test/java/cucumber/runtime/java/test/Authors.java b/java/src/test/java/cucumber/runtime/java/test/Authors.java new file mode 100644 index 0000000000..5cb9d2e364 --- /dev/null +++ b/java/src/test/java/cucumber/runtime/java/test/Authors.java @@ -0,0 +1,75 @@ +package cucumber.runtime.java.test; + +import cucumber.api.Transpose; +import cucumber.api.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/cucumber/runtime/java/test/ScenarioStepDefs.java b/java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java index b7162d03af..b4585e4321 100644 --- a/java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java +++ b/java/src/test/java/cucumber/runtime/java/test/ScenarioStepDefs.java @@ -17,17 +17,17 @@ public void get_scenario_name(Scenario scenario) { scenarioName = scenario.getName(); } - @Given("^I am running a scenario$") + @Given("I am running a scenario") public void i_am_running_a_scenario() { } - @When("^I try to get the scenario name$") + @When("I try to get the scenario name") public void i_try_to_get_the_scenario_name() { } - @Then("^The scenario name is \"([^\"]*)\"$") + @Then("The scenario name is {string}") public void the_scenario_name_is(String scenarioName) { assertEquals(this.scenarioName, scenarioName); } diff --git a/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java b/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java index 16a0445480..a51e4e94ec 100644 --- a/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java +++ b/java/src/test/java/cucumber/runtime/java/test/Stepdefs.java @@ -5,11 +5,11 @@ import java.util.List; public class Stepdefs { - @Given("^I have (\\d+) cukes in the belly$") + @Given("I have {int} cukes in the belly") public void I_have_cukes_in_the_belly(int arg1) { } - @Given("^this data table:$") + @Given("this data table:") public void this_data_table(List people) throws Throwable { } diff --git a/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java b/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java index 425495595c..60ff4934e5 100644 --- a/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java +++ b/java/src/test/java/cucumber/runtime/java/test/SubstitutionStepdefs.java @@ -19,18 +19,18 @@ public class SubstitutionStepdefs { private String role; private String details; - @Given("^I have a user account with my name \"([^\"]*)\"$") + @Given("I have a user account with my name {string}") public void I_have_a_user_account_with_my_name(String name) throws Throwable { this.name = name; } - @When("^an Admin grants me (.+) rights$") + @When("an Admin grants me {word} rights") public void an_Admin_grants_me_role_rights(String role) throws Throwable { this.role = role; this.details = ROLES.get(role); } - @Then("^I should receive an email with the body:$") + @Then("I should receive an email with the body:") public void I_should_receive_an_email_with_the_body(String body) throws Throwable { String expected = String.format("Dear %s,\n" + "You have been granted %s rights. You are %s. Please be responsible.\n" + diff --git a/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java b/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..783d5b88e8 --- /dev/null +++ b/java/src/test/java/cucumber/runtime/java/test/TypeRegistryConfiguration.java @@ -0,0 +1,64 @@ +package cucumber.runtime.java.test; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.datatable.DataTable; +import io.cucumber.datatable.DataTableType; +import io.cucumber.datatable.TableEntryTransformer; +import cucumber.runtime.java.test.Authors.Author; +import cucumber.runtime.java.test.Stepdefs.Person; +import io.cucumber.datatable.TableTransformer; + +import java.util.Locale; +import java.util.Map; + +import static java.util.Locale.ENGLISH; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + + private final TableEntryTransformer personEntryTransformer = new TableEntryTransformer() { + @Override + public Person transform(Map tableEntry) { + Person person = new 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; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineDataTableType(new DataTableType( + Author.class, + authorEntryTransformer)); + + typeRegistry.defineDataTableType(new DataTableType( + Author.class, + singleAuthorTransformer)); + + typeRegistry.defineDataTableType(new DataTableType( + Person.class, + personEntryTransformer)); + } +} diff --git a/java/src/test/resources/cucumber/runtime/java/test/authors.feature b/java/src/test/resources/cucumber/runtime/java/test/authors.feature new file mode 100644 index 0000000000..7b2c57d8d0 --- /dev/null +++ b/java/src/test/resources/cucumber/runtime/java/test/authors.feature @@ -0,0 +1,22 @@ +Feature: Authors and tables + + Scenario: Some authors and tables + Given a list of authors in a table + | firstName | lastName | birthDate | + | Annie M. G. | Schmidt | 1911-03-20 | + | Roald | Dahl | 1916-09-13 | + + Given a list of authors in a transposed table + | firstName | Annie M. G. | Roald | + | lastName | Schmidt | Dahl | + | birthDate | 1911-03-20 | 1916-09-13 | + + Given a single author in a table + | firstName | lastName | birthDate | + | Annie M. G. | Schmidt | 1911-03-20 | + + Given a single author in a transposed table + | firstName | Annie M. G. | + | lastName | Schmidt | + | birthDate | 1911-03-20 | + diff --git a/java8/src/main/code_generator/I18n.java8.txt b/java8/src/main/code_generator/I18n.java8.txt index bb7493a29f..974ca00998 100644 --- a/java8/src/main/code_generator/I18n.java8.txt +++ b/java8/src/main/code_generator/I18n.java8.txt @@ -1,38 +1,92 @@ package cucumber.api.java8; +import cucumber.api.java8.StepdefBody.A0; +import cucumber.api.java8.StepdefBody.A1; +import cucumber.api.java8.StepdefBody.A2; +import cucumber.api.java8.StepdefBody.A3; +import cucumber.api.java8.StepdefBody.A4; +import cucumber.api.java8.StepdefBody.A5; +import cucumber.api.java8.StepdefBody.A6; +import cucumber.api.java8.StepdefBody.A7; +import cucumber.api.java8.StepdefBody.A8; +import cucumber.api.java8.StepdefBody.A9; + import cucumber.runtime.java.LambdaGlueRegistry; import cucumber.runtime.java8.Java8StepDefinition; import cucumber.runtime.java8.LambdaGlueBase; +/** + * To execute steps in a feature file the steps must be + * connected to executable code. This can be done by + * implementing this interface. + *

+ * The parameters extracted from the step by the expression + * along with the data table or doc string argument are provided as + * arguments to the lambda expression. + *

+ * The types of the parameters are determined by the cucumber or + * regular expression. + *

+ * The type of the data table or doc string argument is determined + * by the argument name value. When none is provided cucumber will + * attempt to transform the data table or doc string to the the + * type of last argument. + */ public interface ${className} extends LambdaGlueBase { <% i18n.stepKeywords.findAll { !it.contains('*') && !it.matches("^\\d.*") }.sort().unique().each { kw -> %> - default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(final String regexp, final StepdefBody.A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition( - new Java8StepDefinition(regexp, 0, StepdefBody.A0.class, body) + /** + * Creates a new step definition. + * + * @param expression the cucumber expression + * @param body a lambda expression with no parameters + */ + default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A0 body) { + LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> + Java8StepDefinition.create(expression, A0.class, body, typeRegistry) ); } - default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(final String regexp, final long timeoutMillis, final StepdefBody.A0 body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition( - new Java8StepDefinition(regexp, timeoutMillis, StepdefBody.A0.class, body) + /** + * Creates a new step definition. + * + * @param expression the cucumber expression + * @param timeoutMillis max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. + * @param body a lambda expression with no parameters + */ + default void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A0 body) { + LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> + Java8StepDefinition.create(expression, timeoutMillis, A0.class, body, typeRegistry) ); } + <% (1..9).each { arity -> def ts = (1..arity).collect { n -> "T"+n } - def genericSignature = ts.join(",") %> - - default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(final String regexp, final StepdefBody.A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition( - new Java8StepDefinition(regexp, 0, StepdefBody.A${arity}.class, body) + def genericSignature = ts.join(",") %> + /** + * Creates a new step definition. + * + * @param expression the cucumber expression + * @param body a lambda expression with ${arity} parameters + */ + default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, A${arity}<${genericSignature}> body) { + LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> + Java8StepDefinition.create(expression, A${arity}.class, body, typeRegistry) ); - } - default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(final String regexp, final long timeoutMillis, final StepdefBody.A${arity}<${genericSignature}> body) { - LambdaGlueRegistry.INSTANCE.get().addStepDefinition( - new Java8StepDefinition(regexp, timeoutMillis, StepdefBody.A${arity}.class, body) + /** + * Creates a new step definition. + * + * @param expression the cucumber expression + * @param timeoutMillis max amount of milliseconds this is allowed to run for. 0 (default) means no restriction. + * @param body a lambda expression with ${arity} parameters + */ + default <${genericSignature}> void ${java.text.Normalizer.normalize(kw.replaceAll("[\\s',!]", ""), java.text.Normalizer.Form.NFC)}(String expression, long timeoutMillis, A${arity}<${genericSignature}> body) { + LambdaGlueRegistry.INSTANCE.get().addStepDefinition((typeRegistry) -> + Java8StepDefinition.create(expression, timeoutMillis, A${arity}.class, body, typeRegistry) ); } + <% } %> <% } %> } diff --git a/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java b/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java index 21d6d04a9a..ec38618826 100644 --- a/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java +++ b/java8/src/main/java/cucumber/runtime/java8/Java8StepDefinition.java @@ -1,80 +1,87 @@ package cucumber.runtime.java8; import static cucumber.runtime.ParameterInfo.fromTypes; +import static java.lang.String.format; import static net.jodah.typetools.TypeResolver.resolveRawArguments; -import cucumber.api.Argument; +import io.cucumber.stepexpression.Argument; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java8.StepdefBody; +import io.cucumber.stepexpression.ArgumentMatcher; import cucumber.runtime.CucumberException; -import cucumber.runtime.JdkPatternArgumentMatcher; +import io.cucumber.stepexpression.ExpressionArgumentMatcher; import cucumber.runtime.ParameterInfo; import cucumber.runtime.StepDefinition; +import io.cucumber.stepexpression.StepExpression; +import io.cucumber.stepexpression.StepExpressionFactory; import cucumber.runtime.Utils; import gherkin.pickles.PickleStep; +import io.cucumber.stepexpression.TypeResolver; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.regex.Pattern; public class Java8StepDefinition implements StepDefinition { - private final Pattern pattern; + public static Java8StepDefinition create( + String expression, Class bodyClass, T body, TypeRegistry typeRegistry) { + return new Java8StepDefinition(expression, 0, bodyClass, body, typeRegistry); + } + + public static StepDefinition create( + String expression, long timeoutMillis, Class bodyClass, T body, TypeRegistry typeRegistry) { + return new Java8StepDefinition(expression, timeoutMillis, bodyClass, body, typeRegistry); + } + private final long timeoutMillis; private final StepdefBody body; - private final JdkPatternArgumentMatcher argumentMatcher; + private final StepExpression expression; private final StackTraceElement location; private final List parameterInfos; private final Method method; - public Java8StepDefinition(String pattern, long timeoutMillis, Class bodyClass, T body) { - this.pattern = Pattern.compile(pattern); + private Java8StepDefinition(String expression, + long timeoutMillis, + Class bodyClass, + T body, + TypeRegistry typeRegistry) { this.timeoutMillis = timeoutMillis; this.body = body; - this.argumentMatcher = new JdkPatternArgumentMatcher(this.pattern); - this.location = new Exception().getStackTrace()[2]; + this.location = new Exception().getStackTrace()[5]; this.method = getAcceptMethod(body.getClass()); - try { - Class[] arguments = resolveRawArguments(bodyClass, body.getClass()); - this.parameterInfos = fromTypes(verifyNotListOrMap(arguments)); - } catch (CucumberException e){ - throw e; - } catch (Exception e) { - throw new CucumberException(e); + this.parameterInfos = fromTypes(resolveRawArguments(bodyClass, body.getClass())); + this.expression = createExpression(expression, typeRegistry); + } + + private StepExpression createExpression(String expression, TypeRegistry typeRegistry) { + if (parameterInfos.isEmpty()) { + return new StepExpressionFactory(typeRegistry).createExpression(expression); + } else { + ParameterInfo parameterInfo = parameterInfos.get(parameterInfos.size() - 1); + return new StepExpressionFactory(typeRegistry).createExpression(expression, new LambdaTypeResolver(parameterInfo)); } } private Method getAcceptMethod(Class bodyClass) { - List acceptMethods = new ArrayList(); + List acceptMethods = new ArrayList<>(); for (Method method : bodyClass.getDeclaredMethods()) { if (!method.isBridge() && !method.isSynthetic() && "accept".equals(method.getName())) { acceptMethods.add(method); } } if (acceptMethods.size() != 1) { - throw new IllegalStateException(String.format("Expected single 'accept' method on body class, found " + - "'%s'", acceptMethods)); + throw new IllegalStateException(format( + "Expected single 'accept' method on body class, found '%s'", acceptMethods)); } return acceptMethods.get(0); } - private Type[] verifyNotListOrMap(Type[] argumentTypes) { - for (Type argumentType : argumentTypes) { - if (argumentType instanceof Class) { - Class argumentClass = (Class) argumentType; - if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { - throw withLocation(new CucumberException("Can't use " + argumentClass.getName() + " in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps")); - } - } - } - return argumentTypes; - } - private CucumberException withLocation(CucumberException exception) { exception.setStackTrace(new StackTraceElement[]{this.location}); return exception; @@ -82,7 +89,8 @@ private CucumberException withLocation(CucumberException exception) { @Override public List matchedArguments(PickleStep step) { - return argumentMatcher.argumentsFrom(step.getText()); + ArgumentMatcher argumentMatcher = new ExpressionArgumentMatcher(expression); + return argumentMatcher.argumentsFrom(step); } @Override @@ -112,11 +120,41 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { @Override public String getPattern() { - return pattern.pattern(); + return expression.getSource(); } @Override public boolean isScenarioScoped() { return true; } + + private final class LambdaTypeResolver implements TypeResolver { + + + private final ParameterInfo parameterInfo; + + LambdaTypeResolver(ParameterInfo parameterInfo) { + this.parameterInfo = parameterInfo; + } + + @Override + public Type resolve() { + return requireNonMapOrListType(parameterInfo.getType()); + } + + private Type requireNonMapOrListType(Type argumentType) { + if (argumentType instanceof Class) { + Class argumentClass = (Class) argumentType; + if (List.class.isAssignableFrom(argumentClass) || Map.class.isAssignableFrom(argumentClass)) { + throw withLocation( + new CucumberException( + format("Can't use %s in lambda step definition \"%s\". " + + "Declare a DataTable argument instead and convert " + + "manually with asList/asLists/asMap/asMaps", + argumentClass.getName(), expression.getSource()))); + } + } + return argumentType; + } + } } diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java b/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java index 83ec5fe0aa..0a38d0c0fb 100644 --- a/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java +++ b/java8/src/test/java/cucumber/runtime/java8/Java8AnonInnerClassStepDefinitionTest.java @@ -1,49 +1,30 @@ package cucumber.runtime.java8; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java8.StepdefBody; -import cucumber.runtime.CucumberException; -import org.junit.Ignore; import org.junit.Test; import java.util.List; +import java.util.Locale; public class Java8AnonInnerClassStepDefinitionTest { + private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + @Test - public void should_calculate_parameters_count_from_body_with_one_param() throws Exception { - Java8StepDefinition java8StepDefinition = new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, oneParamStep()); + public void should_calculate_parameters_count_from_body_with_one_param() { + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, oneParamStep(), typeRegistry); assertEquals(Integer.valueOf(1), java8StepDefinition.getParameterCount()); } @Test - public void should_calculate_parameters_count_from_body_with_two_params() throws Exception { - Java8StepDefinition java8StepDefinition = new Java8StepDefinition("^I have some step $", 0, StepdefBody.A2.class, twoParamStep()); + public void should_calculate_parameters_count_from_body_with_two_params() { + Java8StepDefinition java8StepDefinition = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, twoParamStep(), typeRegistry); assertEquals(Integer.valueOf(2), java8StepDefinition.getParameterCount()); } - @Test - public void should_fail_for_param_with_non_generic_list() throws Exception { - try { - new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, nonGenericListStep()); - fail(); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } - } - - @Test - public void should_fail_for_param_with_generic_list() throws Exception { - try { - new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, genericListStep()); - fail(); - } catch (CucumberException expected) { - assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); - } - } - private StepdefBody.A1 oneParamStep() { return new StepdefBody.A1() { @Override diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java b/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java index e24b60c940..c15e5e5feb 100644 --- a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java +++ b/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java @@ -1,13 +1,17 @@ package cucumber.runtime.java8; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.runtime.HookDefinition; import cucumber.runtime.StepDefinition; +import cucumber.runtime.java.Function; import cucumber.runtime.java.LambdaGlueRegistry; import org.hamcrest.CustomTypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import static java.util.Locale.ENGLISH; + public class Java8LambdaStepDefinitionMarksCorrectStackElementTest { @Rule @@ -43,8 +47,8 @@ private class MyLambdaGlueRegistry implements LambdaGlueRegistry { private StepDefinition stepDefinition; @Override - public void addStepDefinition(StepDefinition stepDefinition) { - this.stepDefinition = stepDefinition; + public void addStepDefinition(Function stepDefinitionFunction) { + stepDefinition = stepDefinitionFunction.apply(new TypeRegistry(ENGLISH)); } @Override diff --git a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java b/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java index db8d653f99..82c96d01cc 100644 --- a/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java +++ b/java8/src/test/java/cucumber/runtime/java8/Java8LambdaStepDefinitionTest.java @@ -2,47 +2,51 @@ import static org.junit.Assert.assertEquals; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java8.StepdefBody; import cucumber.runtime.CucumberException; import org.junit.Test; import java.util.List; +import java.util.Locale; public class Java8LambdaStepDefinitionTest { + private final TypeRegistry typeRegistry = new TypeRegistry(Locale.ENGLISH); + @Test - public void should_calculate_parameters_count_from_body_with_one_param() throws Exception { + public void should_calculate_parameters_count_from_body_with_one_param() { StepdefBody.A1 body = p1 -> { }; - Java8StepDefinition def = new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, body); + Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); assertEquals(Integer.valueOf(1), def.getParameterCount()); } @Test - public void should_calculate_parameters_count_from_body_with_two_params() throws Exception { + public void should_calculate_parameters_count_from_body_with_two_params() { StepdefBody.A2 body = (p1, p2) -> { }; - Java8StepDefinition def = new Java8StepDefinition("^I have some step $", 0, StepdefBody.A2.class, body); + Java8StepDefinition def = Java8StepDefinition.create("I have some step", StepdefBody.A2.class, body, typeRegistry); assertEquals(Integer.valueOf(2), def.getParameterCount()); } @Test - public void should_fail_for_param_with_non_generic_list() throws Exception { + public void should_fail_for_param_with_non_generic_list() { try { StepdefBody.A1 body = p1 -> { }; - new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, body); + Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); } catch (CucumberException expected) { assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); } } @Test - public void should_fail_for_param_with_generic_list() throws Exception { + public void should_fail_for_param_with_generic_list() { try { StepdefBody.A1> body = p1 -> { }; - new Java8StepDefinition("^I have (\\d) some step (.*)$", 0, StepdefBody.A1.class, body); + Java8StepDefinition.create("I have some step", StepdefBody.A1.class, body, typeRegistry); } catch (CucumberException expected) { assertEquals("Can't use java.util.List in lambda step definition. Declare a DataTable argument instead and convert manually with asList/asLists/asMap/asMaps", expected.getMessage()); } diff --git a/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java b/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java index 7025c13dfa..d302f2c90d 100644 --- a/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java +++ b/java8/src/test/java/cucumber/runtime/java8/test/AnonInnerClassStepdefs.java @@ -2,23 +2,30 @@ import static org.junit.Assert.assertEquals; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.api.java8.GlueBase; import cucumber.api.java8.StepdefBody; +import cucumber.runtime.StepDefinition; +import cucumber.runtime.java.Function; import cucumber.runtime.java.JavaBackend; import cucumber.runtime.java8.Java8StepDefinition; public class AnonInnerClassStepdefs implements GlueBase { public AnonInnerClassStepdefs() { - JavaBackend.INSTANCE.get().addStepDefinition( - new Java8StepDefinition( - "^I have (\\d+) java7 beans in my (.*)", 0, StepdefBody.A2.class, - new StepdefBody.A2() { - @Override - public void accept(Integer cukes, String what) throws Throwable { - assertEquals(42, cukes.intValue()); - assertEquals("belly", what); - } - })); + JavaBackend.INSTANCE.get().addStepDefinition(new Function() { + @Override + public StepDefinition apply(TypeRegistry typeRegistry) { + return Java8StepDefinition.create( + "I have {int} java7 beans in my {word}", StepdefBody.A2.class, + new StepdefBody.A2() { + @Override + public void accept(Integer cukes, String what) throws Throwable { + assertEquals(42, cukes.intValue()); + assertEquals("belly", what); + } + }, typeRegistry); + } + }); } } diff --git a/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java b/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java index 61685ac97d..e702d80b28 100644 --- a/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java +++ b/java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java @@ -5,8 +5,8 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import cucumber.api.DataTable; import cucumber.api.Scenario; +import io.cucumber.datatable.DataTable; import cucumber.api.java8.En; import java.util.List; @@ -45,35 +45,35 @@ public LambdaStepdefs() { Before(this::hookWithArgs); - Given("^this data table:$", (DataTable peopleTable) -> { + Given("this data table:", (DataTable peopleTable) -> { List people = peopleTable.asList(Person.class); assertEquals("Hellesøy", people.get(0).last); }); Integer alreadyHadThisManyCukes = 1; - Given("^I have (\\d+) cukes in my belly$", (Long n) -> { + Given("I have {long} cukes in my belly", (Long n) -> { assertEquals((Integer) 1, alreadyHadThisManyCukes); assertEquals((Long) 42L, n); }); String localState = "hello"; - Then("^I really have (\\d+) cukes in my belly", (Integer i) -> { + Then("I really have {int} cukes in my belly", (Integer i) -> { assertEquals((Integer) 42, i); assertEquals("hello", localState); }); - Given("^A statement with a simple match$", () -> { + Given("A statement with a simple match", () -> { assertTrue(true); }); int localInt = 1; - Given("^A statement with a scoped argument$", () -> { + Given("A statement with a scoped argument", () -> { assertEquals(2, localInt + 1); assertEquals(42, outside + 1); }); - Given("^I will give you (\\d+) and ([\\d\\.]+) and (\\w+) and (\\d+)$", (Integer a, Float b, String c, + Given("I will give you {int} and {float} and {string} and {int}", (Integer a, Float b, String c, Integer d) -> { assertEquals((Integer) 1, a); @@ -82,13 +82,13 @@ public LambdaStepdefs() { assertEquals((Integer) 4, d); }); - Given("^A method reference that declares an exception$", this::methodThatDeclaresException); - Given("^A method reference with an argument (\\d+)$", this::methodWithAnArgument); - Given("^A method reference with an int argument (\\d+)$", this::methodWithAnIntArgument); - Given("^A constructor reference with an argument (.*)$", Contact::new); - Given("^A static method reference with an argument (\\d+)$", LambdaStepdefs::staticMethodWithAnArgument); - Given("^A method reference to an arbitrary object of a particular type (\\d+)$", Contact::call); - Given("^A method reference to an arbitrary object of a particular type (.*) with argument (.*)$", Contact::update); + Given("A method reference that declares an exception$", this::methodThatDeclaresException); + Given("A method reference with an argument {int}", this::methodWithAnArgument); + Given("A method reference with an int argument {int}", this::methodWithAnIntArgument); + Given("A constructor reference with an argument {string}", Contact::new); + Given("A static method reference with an argument {int}", LambdaStepdefs::staticMethodWithAnArgument); + Given("A method reference to an arbitrary object of a particular type {string}", Contact::call); + Given("A method reference to an arbitrary object of a particular type {string} with argument {string}", Contact::update); } private void methodThatDeclaresException() throws Throwable { diff --git a/java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java b/java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java new file mode 100644 index 0000000000..871f8669f3 --- /dev/null +++ b/java8/src/test/java/cucumber/runtime/java8/test/TypeRegistryConfiguration.java @@ -0,0 +1,31 @@ +package cucumber.runtime.java8.test; + +import cucumber.api.TypeRegistryConfigurer; +import cucumber.api.TypeRegistry; +import io.cucumber.datatable.DataTableType; +import cucumber.runtime.java8.test.LambdaStepdefs.Person; + +import java.util.Locale; +import java.util.Map; + +import static java.util.Locale.ENGLISH; + +public class TypeRegistryConfiguration implements TypeRegistryConfigurer { + + @Override + public Locale locale() { + return ENGLISH; + } + + @Override + public void configureTypeRegistry(TypeRegistry typeRegistry) { + typeRegistry.defineDataTableType(new DataTableType( + Person.class, + (Map map) -> { + Person person = new Person(); + person.first = map.get("first"); + person.last = map.get("last"); + return person; + })); + } +} diff --git a/java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature b/java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature index 3c20a7674a..d63b26b277 100644 --- a/java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature +++ b/java8/src/test/resources/cucumber/runtime/java8/test/lambda-step-definitions.feature @@ -26,6 +26,7 @@ Feature: Java8 Given A method reference with an argument 42 Given A method reference with an int argument 42 Given A static method reference with an argument 42 - Given A constructor reference with an argument 42 - Given A method reference to an arbitrary object of a particular type 42 - Given A method reference to an arbitrary object of a particular type 42 with argument 314 + Given A constructor reference with an argument "42" + #TODO: Add transfomer to create Contact object +# Given A method reference to an arbitrary object of a particular type "42" +# Given A method reference to an arbitrary object of a particular type "42" with argument "314" diff --git a/junit/src/test/java/cucumber/runtime/stub/StubBackend.java b/junit/src/test/java/cucumber/runtime/stub/StubBackend.java index c9f1c649c3..338b8383e3 100644 --- a/junit/src/test/java/cucumber/runtime/stub/StubBackend.java +++ b/junit/src/test/java/cucumber/runtime/stub/StubBackend.java @@ -1,5 +1,6 @@ package cucumber.runtime.stub; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.runtime.Backend; import cucumber.runtime.Glue; import cucumber.runtime.UnreportedStepExecutor; @@ -12,8 +13,9 @@ /** * We need an implementation of Backend to prevent Runtime from blowing up. */ +@SuppressWarnings("unused") public class StubBackend implements Backend { - public StubBackend(ResourceLoader resourceLoader) { + public StubBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { } diff --git a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt b/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt index 77f3b2d25a..18688a8551 100644 --- a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt +++ b/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/LambdaStepdefs.kt @@ -1,7 +1,7 @@ package cucumber.runtime.kotlin.test; -import cucumber.api.DataTable import cucumber.api.Scenario +import io.cucumber.datatable.DataTable import cucumber.api.java8.En import org.junit.Assert.* @@ -30,32 +30,32 @@ class LambdaStepdefs : En { lastInstance = this } - Given("^this data table:$") { peopleTable: DataTable -> - val people = peopleTable.asList(Person::class.java) + Given("this data table:") { peopleTable: DataTable -> + val people : List = peopleTable.asList(Person::class.java) assertEquals("Aslak", people[0].first) assertEquals("Hellesøy", people[0].last) } val alreadyHadThisManyCukes = 1 - Given("^I have (\\d+) cukes in my belly$") { n: Long -> + Given("I have {long} cukes in my belly") { n: Long -> assertEquals(1, alreadyHadThisManyCukes) assertEquals(42L, n) } val localState = "hello" - Then("^I really have (\\d+) cukes in my belly") { i: Int -> + Then("I really have {int} cukes in my belly") { i: Int -> assertEquals(42, i) assertEquals("hello", localState) } - Given("^A statement with a body expression$") { assertTrue(true) } + Given("A statement with a body expression$") { assertTrue(true) } - Given("^A statement with a simple match$", { -> assertTrue(true) }) + Given("A statement with a simple match$", { -> assertTrue(true) }) val localInt = 1 - Given("^A statement with a scoped argument$", { assertEquals(2, localInt + 1) }) + Given("A statement with a scoped argument$", { assertEquals(2, localInt + 1) }) - Given("^I will give you (\\d+) and ([\\d\\.]+) and (\\w+) and (\\d+)$") { a: Int, b: Float, c: String, d: Int -> + Given("I will give you {int} and {float} and {word} and {int}") { a: Int, b: Float, c: String, d: Int -> assertEquals(1, a) assertEquals(2.2f, b) assertEquals("three", c) @@ -64,7 +64,7 @@ class LambdaStepdefs : En { } class Person { - internal var first: String? = null - internal var last: String? = null + var first: String? = null + var last: String? = null } } diff --git a/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt b/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt new file mode 100644 index 0000000000..f0957c7d44 --- /dev/null +++ b/kotlin-java8/src/test/kotlin/cucumber/runtime/kotlin/test/TypeRegistryConfiguration.kt @@ -0,0 +1,27 @@ +package cucumber.runtime.kotlin.test + +import cucumber.api.TypeRegistryConfigurer +import cucumber.api.TypeRegistry +import io.cucumber.datatable.DataTableType +import io.cucumber.datatable.TableEntryTransformer +import java.util.Locale +import java.util.Locale.ENGLISH + +class TypeRegistryConfiguration : TypeRegistryConfigurer { + + override fun locale(): Locale { + return ENGLISH + } + + override fun configureTypeRegistry(typeRegistry: TypeRegistry) { + typeRegistry.defineDataTableType(DataTableType( + LambdaStepdefs.Person::class.java, + TableEntryTransformer + { map: Map -> + val person = LambdaStepdefs.Person() + person.first = map.get("first") + person.last = map.get("last") + person + })) + } +} diff --git a/needle/src/test/java/cucumber/runtime/java/needle/test/AtmWithdrawalSteps.java b/needle/src/test/java/cucumber/runtime/java/needle/test/AtmWithdrawalSteps.java index a91688e5b3..674cbc646d 100755 --- a/needle/src/test/java/cucumber/runtime/java/needle/test/AtmWithdrawalSteps.java +++ b/needle/src/test/java/cucumber/runtime/java/needle/test/AtmWithdrawalSteps.java @@ -60,7 +60,7 @@ public Set> get() { @ObjectUnderTest(implementation = AtmServiceBean.class) private AtmService atmService; - @Given("^I have (\\d+) EUR in my account$") + @Given("I have {int} EUR in my account") public void I_have_EUR_in_my_account(final int account) throws Throwable { assertNotNull(atmService); when(bicGetter.getBic()).thenReturn(BIC); @@ -71,12 +71,12 @@ public void I_have_EUR_in_my_account(final int account) throws Throwable { assert (atmService.getAmount() == account); } - @When("^I withdraw (\\d+) EUR$") + @When("I withdraw {int} EUR") public void I_withdraw_EUR(final int amount) throws Throwable { atmService.withdraw(amount); } - @Then("^I have (\\d+) EUR remaining.$") + @Then("I have {int} EUR remaining.") public void I_have_EUR_remaining(final int remaining) throws Throwable { atmService.getAmount(); assertThat(atmService.getAmount(), Is.is(remaining)); diff --git a/needle/src/test/java/cucumber/runtime/java/needle/test/MoreSteps.java b/needle/src/test/java/cucumber/runtime/java/needle/test/MoreSteps.java index 0b82cf0171..dbe9f27171 100644 --- a/needle/src/test/java/cucumber/runtime/java/needle/test/MoreSteps.java +++ b/needle/src/test/java/cucumber/runtime/java/needle/test/MoreSteps.java @@ -28,7 +28,7 @@ public void checkInjectionWorked() { assertTrue("Got a mock injected instead of the real instance.", atmWithdrawalSteps.isThisReallyYouOrJustAMock()); } - @Given("^i call a step that i don't really need$") + @Given("i call a step that i don't really need") public void this_step_is_here_only_to_have_the_class_instantiated() { } diff --git a/openejb/src/test/java/cucumber/runtime/java/openejb/BellyStepdefs.java b/openejb/src/test/java/cucumber/runtime/java/openejb/BellyStepdefs.java index d0ec3287b1..5d28c77245 100644 --- a/openejb/src/test/java/cucumber/runtime/java/openejb/BellyStepdefs.java +++ b/openejb/src/test/java/cucumber/runtime/java/openejb/BellyStepdefs.java @@ -12,12 +12,12 @@ public class BellyStepdefs { @Inject private Belly belly; - @Given("^I have (\\d+) cukes in my belly") + @Given("I have {int} cukes in my belly") public void haveCukes(int n) { belly.setCukes(n); } - @Then("^there are (\\d+) cukes in my belly") + @Then("there are {int} cukes in my belly") public void checkCukes(int n) { assertEquals(n, belly.getCukes()); } diff --git a/osgi/src/main/java/cucumber/java/runtime/osgi/OsgiClassFinder.java b/osgi/src/main/java/cucumber/java/runtime/osgi/OsgiClassFinder.java index c7440c11e5..40582df937 100644 --- a/osgi/src/main/java/cucumber/java/runtime/osgi/OsgiClassFinder.java +++ b/osgi/src/main/java/cucumber/java/runtime/osgi/OsgiClassFinder.java @@ -25,6 +25,8 @@ public OsgiClassFinder(BundleContext bundleContext) { @Override public Collection> getDescendants(Class parentType, String packageName) { + + if (LOGGER.isDebugEnabled()) LOGGER.debug("Looking for sub classes of " + parentType.getName() + " in '" + packageName + "' package"); diff --git a/picocontainer/pom.xml b/picocontainer/pom.xml index 9b2fde77d2..8528bebb9d 100644 --- a/picocontainer/pom.xml +++ b/picocontainer/pom.xml @@ -16,10 +16,6 @@ io.cucumber cucumber-java - - io.cucumber - cucumber-jvm-deps - io.cucumber gherkin diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java deleted file mode 100644 index 04b7c32667..0000000000 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java +++ /dev/null @@ -1,50 +0,0 @@ -package cucumber.runtime.java.picocontainer; - -import cucumber.api.Format; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; - -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - -import static org.junit.Assert.assertEquals; - -public class DatesSteps { - private Date date; - - @Given("^the ISO date is (.+)$") - public void the_iso_date_is(@Format("yyyy-MM-dd'T'HH:mm:ss") Date date) { - this.date = date; - } - - @Given("^the simple date is (.+)$") - public void the_simple_date_is(@Format("yyyy/MM/dd") Date date) { - this.date = date; - } - - @Given("^the ISO date with timezone is (.+$)") - public void the_ISO_date_with_timezone_is(@Format("yyyy-MM-dd'T'HH:mm:ss, z") Date date) { - this.date = date; - } - - - @Then("^the date should be viewed in (.+) as (\\d+), (\\d+), (\\d+), (\\d+), (\\d+), (\\d+)$") - public void the_date_should_be_decomposed_as(String timeZone, int year, int month, int day, int hours, - int minutes, int seconds) { - Calendar cal; - if (timeZone.equals("default")) { - cal = Calendar.getInstance(TimeZone.getDefault()); - } else { - cal = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); - } - cal.setLenient(false); - cal.setTime(date); - assertEquals(year, cal.get(Calendar.YEAR)); - assertEquals(month, cal.get(Calendar.MONTH) + 1); //calendar month are 0 based - assertEquals(day, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(hours, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(minutes, cal.get(Calendar.MINUTE)); - assertEquals(seconds, cal.get(Calendar.SECOND)); - } -} diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/EnumsSteps.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/EnumsSteps.java deleted file mode 100644 index a328490bbd..0000000000 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/EnumsSteps.java +++ /dev/null @@ -1,38 +0,0 @@ -package cucumber.runtime.java.picocontainer; - -import cucumber.api.java.Before; -import cucumber.api.java.en.Given; -import cucumber.api.java.en.Then; -import cucumber.api.java.en.When; - -public class EnumsSteps { - - private Color color; - - @Before() - public void clearColor() { - this.color = null; - } - - @Given("^I want to recognize colors as enums$") - public void I_want_to_recognize_colors_as_enums() { - } - - @When("^i use the (.*) in a step$") - public void i_use_the_color_in_a_step(Color color) { - } - - @Then("^it should be recognized as enum$") - public void it_should_be_recognized_as_enum() { - } - - - public static class ColorRecognized { - public Color color; - public boolean recognized; - } - - public static enum Color { - RED, BLUE, GREEN - } -} diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/RunCukesTest.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/RunCukesTest.java index eb9a1b747f..cf2aba1dd6 100644 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/RunCukesTest.java +++ b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/RunCukesTest.java @@ -3,6 +3,7 @@ import cucumber.api.junit.Cucumber; import org.junit.runner.RunWith; + @RunWith(Cucumber.class) public class RunCukesTest { } diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/SanityTest.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/SanityTest.java index d17f4e38da..d6dc669aa3 100644 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/SanityTest.java +++ b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/SanityTest.java @@ -1,6 +1,7 @@ package cucumber.runtime.java.picocontainer; import cucumber.runtime.junit.SanityChecker; +import org.junit.Ignore; import org.junit.Test; public class SanityTest { diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/StepDefs.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/StepDefs.java index e282499f85..49302c392e 100644 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/StepDefs.java +++ b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/StepDefs.java @@ -38,12 +38,12 @@ public void after() { assert !belly.isDisposed(); } - @Given("^I have (\\d+) (.*) in my belly$") + @Given("I have {int} {word} in my belly") public void I_have_n_things_in_my_belly(int n, String what) { belly.setContents(Collections.nCopies(n, what)); } - @Given("^I have this in my basket:$") + @Given("I have this in my basket:") public void I_have_this_in_my_basket(List> stuff) { } @@ -52,22 +52,22 @@ public void throw_pending() { throw new PendingException("This should not fail (seeing this output is ok)"); } - @Then("^there are (\\d+) cukes in my belly") + @Then("there are {int} cukes in my belly") public void checkCukes(int n) { assertEquals(belly.getContents(), Collections.nCopies(n, "cukes")); } - @Then("^the (.*) contains (.*)") + @Then("the {word} contains {word}") public void containerContainsIngredient(String container, String ingredient) throws InterruptedException { assertEquals("glass", container); } - @Then("^I add (.*)") + @Then("I add {word}") public void addLiquid(String liquid) throws InterruptedException { assertEquals("milk", liquid); } - @Then("^I should be (.*)$") + @Then("I should be {word}") public void I_should_be(String mood) { assertEquals("happy", mood); } diff --git a/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/dates.feature b/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/dates.feature deleted file mode 100644 index c41adc737b..0000000000 --- a/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/dates.feature +++ /dev/null @@ -1,26 +0,0 @@ -Feature: Dates - - Scenario Outline: Parsing dates with simple a format (yyyy/MM/dd) - Given the simple date is - Then the date should be viewed in as , , , , , - - Examples: - | input date | year | month | day | hours | minutes | seconds | timezone | - | 2012/03/01 | 2012 | 3 | 1 | 0 | 0 | 0 | default | - - Scenario Outline: Parsing dates with an ISO format (yyyy-MM-dd'T'HH:mm:ss) - Given the ISO date is - Then the date should be viewed in as , , , , , - - Examples: - | input date | timezone | year | month | day | hours | minutes | seconds | - | 2012-03-01T06:54:14 | default | 2012 | 3 | 1 | 6 | 54 | 14 | - - Scenario Outline: Parsing dates with a format including timezone (yyyy-MM-dd'T'HH:mm:ss, z) - Given the ISO date with timezone is - Then the date should be viewed in as , , , , , - - Examples: - | input date | timezone | year | month | day | hours | minutes | seconds | - | 2008-12-31T23:59:59, PST | PST | 2008 | 12 | 31 | 23 | 59 | 59 | - | 2008-12-31T23:59:59, PST | UTC | 2009 | 1 | 1 | 7 | 59 | 59 | diff --git a/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/enums.feature b/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/enums.feature deleted file mode 100644 index 2618248565..0000000000 --- a/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/enums.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Java Enums - - Background: What we want - Given I want to recognize colors as enums - - Scenario Outline: color should be recognized as an enum - When i use the in a step - Then it should be recognized as enum - - Examples: - | color | - | Red | - | red | - | RED | - | ReD | - diff --git a/pom.xml b/pom.xml index e71c7e5641..68f07e5db6 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,6 @@ 8.1.12.v20130726 1.6 - 2.9.9 6.0.0 4.3.0 @@ -89,6 +88,8 @@ 5.6.6 1.1.1 0.5.0 + 5.0.17 + 1.0.3 @@ -106,21 +107,6 @@ - - io.cucumber - cucumber-jvm-deps - 1.0.6 - - - com.thoughtworks.xstream - xstream - - - com.googlecode.java-diff-utils - diffutils - - - io.cucumber gherkin @@ -137,6 +123,16 @@ tag-expressions ${tag-expressions.version} + + io.cucumber + cucumber-expressions + ${cucumber-expressions.version} + + + io.cucumber + datatable + ${datatable.version} + io.cucumber cucumber-core @@ -483,11 +479,6 @@ xmlunit ${xmlunit.version} - - joda-time - joda-time - ${joda-time.version} - org.osgi @@ -686,16 +677,6 @@ org.apache.maven.plugins maven-clean-plugin 3.0.0 - - - - . - - **/*.ser - - - - @@ -981,12 +962,5 @@ - - - org.apache.maven.wagon - wagon-ssh - 2.10 - - diff --git a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/AnotherStepDef.java b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/AnotherStepDef.java index 9b18cbce0a..473086a530 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/AnotherStepDef.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/AnotherStepDef.java @@ -10,7 +10,7 @@ public class AnotherStepDef { @Autowired OneStepDef oneStepDef; - @Then("^I can read (\\d+) cucumbers from the other step def class$") + @Then("I can read {int} cucumbers from the other step def class") public void i_can_read_cucumbers_from_the_other_step_def_class(int arg1) throws Throwable { assertEquals(arg1, oneStepDef.cucumbers); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/OneStepDef.java b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/OneStepDef.java index 248e7d9dae..0dcb460b2d 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/OneStepDef.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/OneStepDef.java @@ -15,12 +15,12 @@ public ThirdStepDef getThirdStepDef() { return thirdStepDef; } - @Given("^the StepDef injection works$") + @Given("the StepDef injection works") public void the_StepDef_injection_works() throws Throwable { // blank } - @When("^I assign the \"cucumbers\" attribute to (\\d+) in one step def class$") + @When("I assign the \"cucumbers\" attribute to {int} in one step def class") public void i_assign_the_cucumbers_attribute_to_in_one_step_def_class(int arg1) throws Throwable { cucumbers = arg1; thirdStepDef.cucumbers = arg1; diff --git a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/ThirdStepDef.java b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/ThirdStepDef.java index 6a1da7ae85..d0cfdce7ff 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/ThirdStepDef.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/ThirdStepDef.java @@ -7,7 +7,7 @@ public class ThirdStepDef { int cucumbers; - @Then("^(\\d+) have been pushed to a third step def class$") + @Then("{int} have been pushed to a third step def class") public void have_been_pushed_to_a_third_step_def_class(int arg1) throws Throwable { assertEquals(arg1, cucumbers); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/TransactionStepDefs.java b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/TransactionStepDefs.java index d06b332c7e..849f3bce7c 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/commonglue/TransactionStepDefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/commonglue/TransactionStepDefs.java @@ -11,12 +11,12 @@ public class TransactionStepDefs { - @Given("^a feature with the @txn annotation$") + @Given("a feature with the @txn annotation") public void a_feature_with_the_txn_annotation() throws Throwable { // blank } - @Then("^the scenarios shall execute within a transaction$") + @Then("the scenarios shall execute within a transaction") public void the_scenarios_shall_execute_within_a_transaction() throws Throwable { assertTrue("No transaction is active", TransactionSynchronizationManager.isActualTransactionActive()); diff --git a/spring/src/test/java/cucumber/runtime/java/spring/contextconfig/BellyStepdefs.java b/spring/src/test/java/cucumber/runtime/java/spring/contextconfig/BellyStepdefs.java index 2629b0e186..52e1a2e1ab 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/contextconfig/BellyStepdefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/contextconfig/BellyStepdefs.java @@ -21,12 +21,12 @@ public BellyBean getBellyBean() { return bellyBean; } - @Then("^I have belly$") + @Then("I have belly") public void I_have_belly() throws Throwable { assertNotNull(belly); } - @Then("^I have belly bean$") + @Then("I have belly bean") public void I_have_belly_bean() throws Throwable { assertNotNull(bellyBean); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java b/spring/src/test/java/cucumber/runtime/java/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java index 052d0b30d8..72b56c1505 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/dirtiescontextconfig/DirtiesContextBellyStepDefs.java @@ -20,24 +20,24 @@ public class DirtiesContextBellyStepDefs { @Autowired private BellyBean bellyBean; - @Then("^there are (\\d+) dirty cukes in my belly") + @Then("there are {int} dirty cukes in my belly") public void checkCukes(final int n) { assertEquals(n, belly.getCukes()); } - @Given("^I have (\\d+) dirty cukes in my belly") + @Given("I have {int} dirty cukes in my belly") public void haveCukes(final int n) { assertEquals(0, belly.getCukes()); belly.setCukes(n); } - @Given("^I have (\\d+) dirty beans in my belly$") + @Given("I have {int} dirty beans in my belly") public void I_have_beans_in_my_belly(int n) { assertEquals(0, bellyBean.getCukes()); bellyBean.setCukes(n); } - @Then("^there are (\\d+) dirty beans in my belly$") + @Then("there are {int} dirty beans in my belly") public void there_are_beans_in_my_belly(int n) { assertEquals(n, bellyBean.getCukes()); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java b/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java index 1ccfbcd104..eb2e416f18 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/dirties/DirtiesContextBellyMetaStepDefs.java @@ -17,24 +17,24 @@ public class DirtiesContextBellyMetaStepDefs { @Autowired private BellyBean bellyBean; - @Then("^there are (\\d+) dirty meta cukes in my belly") + @Then("there are {int} dirty meta cukes in my belly") public void checkCukes(final int n) { assertEquals(n, belly.getCukes()); } - @Given("^I have (\\d+) dirty meta cukes in my belly") + @Given("I have {int} dirty meta cukes in my belly") public void haveCukes(final int n) { assertEquals(0, belly.getCukes()); belly.setCukes(n); } - @Given("^I have (\\d+) dirty meta beans in my belly$") + @Given("I have {int} dirty meta beans in my belly") public void I_have_beans_in_my_belly(int n) { assertEquals(0, bellyBean.getCukes()); bellyBean.setCukes(n); } - @Then("^there are (\\d+) dirty meta beans in my belly$") + @Then("there are {int} dirty meta beans in my belly") public void there_are_beans_in_my_belly(int n) { assertEquals(n, bellyBean.getCukes()); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/general/BellyMetaStepdefs.java b/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/general/BellyMetaStepdefs.java index 256e4ff56a..74b7916e0c 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/general/BellyMetaStepdefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/metaconfig/general/BellyMetaStepdefs.java @@ -20,12 +20,12 @@ public BellyBean getBellyBean() { return bellyBean; } - @Then("^I have a meta belly$") + @Then("I have a meta belly") public void I_have_belly() throws Throwable { assertNotNull(belly); } - @Then("^I have a meta belly bean$") + @Then("I have a meta belly bean") public void I_have_belly_bean() throws Throwable { assertNotNull(bellyBean); } diff --git a/spring/src/test/java/cucumber/runtime/java/spring/threading/ThreadingStepDefs.java b/spring/src/test/java/cucumber/runtime/java/spring/threading/ThreadingStepDefs.java index 2b6251b822..ad38f9dd90 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/threading/ThreadingStepDefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/threading/ThreadingStepDefs.java @@ -24,17 +24,17 @@ public class ThreadingStepDefs { private static final CountDownLatch latch = new CountDownLatch(2); - @Given("^I am a step definition$") + @Given("I am a step definition") public void iAmAStepDefinition() throws Throwable { map.put(currentThread(), this); } - @When("^when executed in parallel$") + @When("when executed in parallel") public void whenExecutedInParallel() throws Throwable { latch.await(1, TimeUnit.SECONDS); } - @Then("^I should not be shared between threads$") + @Then("I should not be shared between threads") public void iShouldNotBeSharedBetweenThreads() throws Throwable { for (Map.Entry entries : map.entrySet()) { if (entries.getKey().equals(currentThread())) { diff --git a/spring/src/test/java/cucumber/runtime/java/spring/webappconfig/SpringInjectionStepDefs.java b/spring/src/test/java/cucumber/runtime/java/spring/webappconfig/SpringInjectionStepDefs.java index 94c6baac7f..7a7b41ce5a 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/webappconfig/SpringInjectionStepDefs.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/webappconfig/SpringInjectionStepDefs.java @@ -24,18 +24,18 @@ public class SpringInjectionStepDefs { private ResultActions callUrl; - @Given("^I have the web context set$") + @Given("I have the web context set") public void I_have_the_web_context_set() throws Throwable { assertNotNull(wac); } - @When("^I call the url \"([^\"]*)\"$") + @When("I call the url {string}") public void I_call_the_url(String url) throws Throwable { MockMvc mock = MockMvcBuilders.webAppContextSetup(wac).build(); callUrl = mock.perform(get(url)); } - @Then("^it should return (\\d+)$") + @Then("it should return {int}") public void it_should_return(int httpCode) throws Throwable { callUrl.andExpect(status().is(httpCode)); } diff --git a/testng/src/test/java/cucumber/runtime/stub/StubBackend.java b/testng/src/test/java/cucumber/runtime/stub/StubBackend.java index c9f1c649c3..338b8383e3 100644 --- a/testng/src/test/java/cucumber/runtime/stub/StubBackend.java +++ b/testng/src/test/java/cucumber/runtime/stub/StubBackend.java @@ -1,5 +1,6 @@ package cucumber.runtime.stub; +import io.cucumber.stepexpression.TypeRegistry; import cucumber.runtime.Backend; import cucumber.runtime.Glue; import cucumber.runtime.UnreportedStepExecutor; @@ -12,8 +13,9 @@ /** * We need an implementation of Backend to prevent Runtime from blowing up. */ +@SuppressWarnings("unused") public class StubBackend implements Backend { - public StubBackend(ResourceLoader resourceLoader) { + public StubBackend(ResourceLoader resourceLoader, TypeRegistry typeRegistry) { } diff --git a/weld/src/test/java/cucumber/runtime/java/weld/BellyStepdefs.java b/weld/src/test/java/cucumber/runtime/java/weld/BellyStepdefs.java index 9516e496a4..1817927e5e 100644 --- a/weld/src/test/java/cucumber/runtime/java/weld/BellyStepdefs.java +++ b/weld/src/test/java/cucumber/runtime/java/weld/BellyStepdefs.java @@ -17,13 +17,13 @@ public class BellyStepdefs { private boolean inTheBelly = false; - @Given("^I have (\\d+) cukes in my belly") + @Given("I have {int} cukes in my belly") public void haveCukes(int n) { belly.setCukes(n); inTheBelly = true; } - @Then("^there are (\\d+) cukes in my belly") + @Then("there are {int} cukes in my belly") public void checkCukes(int n) { assertEquals(n, belly.getCukes()); assertTrue(inTheBelly);